diff --git a/.gitattributes b/.gitattributes index 6e3cae0f5..e58996311 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,6 @@ *.odin linguist-language=Odin +* text=auto + +# These files must always have *nix line-endings +Makefile text eol=lf +*.sh text eol=lf \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aacaf427d..53600b258 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,106 +2,162 @@ name: CI on: [push, pull_request, workflow_dispatch] jobs: - build_linux: + build_netbsd: + name: NetBSD Build, Check, and Test + runs-on: ubuntu-latest + env: + PKGSRC_BRANCH: 2024Q3 + 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/amd64/$(uname -r | cut -d_ -f1)_${PKGSRC_BRANCH}/All" /usr/sbin/pkg_add pkgin + pkgin -y in gmake git bash python311 llvm clang + ln -s /usr/pkg/bin/python3.11 /usr/bin/python3 + run: | + 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:netbsd_amd64 + ./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_arm64 + ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + ./odin test tests/core/speed.odin -file -all-packages -vet -strict-style -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + (cd tests/issues; ./run.sh) + ./odin check tests/benchmark -vet -strict-style -no-entry-point + + build_freebsd: + name: FreeBSD Build, Check, and Test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - name: Download LLVM, botan - run: sudo apt-get install llvm-11 clang-11 libbotan-2-dev botan - - name: build odin + - 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 -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + ./odin test tests/core/speed.odin -file -all-packages -vet -strict-style -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + (cd tests/issues; ./run.sh) + ./odin check tests/benchmark -vet -strict-style -no-entry-point + 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: Download LLVM (MacOS Intel) + if: matrix.os == 'macos-13' + run: | + brew install llvm@17 lua@5.4 + echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH + + - name: Download LLVM (MacOS ARM) + if: matrix.os == 'macos-14' + run: | + brew install llvm@17 wasmtime lua@5.4 + echo "/opt/homebrew/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: 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 - run: | - cd tests/core - make - timeout-minutes: 10 + - name: Normal Core library tests + run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + - name: Optimized Core library tests + run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true - name: Vendor library tests + run: ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + - name: Internals tests + run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + - name: GitHub Issue tests run: | - cd tests/vendor - make - timeout-minutes: 10 - - name: Odin internals tests - run: | - cd tests/internal - make - timeout-minutes: 10 + cd tests/issues + ./run.sh + + - name: Check benchmarks + run: ./odin check tests/benchmark -vet -strict-style -no-entry-point - 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 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: - runs-on: macos-latest - steps: - - uses: actions/checkout@v1 - - name: Download LLVM, botan and setup PATH - run: | - brew install llvm@13 botan - echo "/usr/local/opt/llvm@13/bin" >> $GITHUB_PATH - TMP_PATH=$(xcrun --show-sdk-path)/user/include - echo "CPATH=$TMP_PATH" >> $GITHUB_ENV - - 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: Odin internals tests - run: | - cd tests/internal - make - timeout-minutes: 10 - - name: Odin check examples/all for Darwin arm64 - run: ./odin check examples/all -vet -strict-style -target:darwin_arm64 - timeout-minutes: 10 + 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 -target:linux_arm64 - timeout-minutes: 10 + 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: | + ./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: | @@ -109,72 +165,118 @@ 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 + odin run examples/demo -debug -vet -strict-style -disallow-do - name: Odin check examples/all shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat - odin check examples/all -strict-style - timeout-minutes: 10 + odin check examples/all -vet -strict-style -disallow-do - name: Core library tests shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat - cd tests\core - call build.bat - timeout-minutes: 10 + odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + - name: Optimized core library tests + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat + odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true - name: Vendor library tests shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat - cd tests\vendor - call build.bat - timeout-minutes: 10 + copy vendor\lua\5.4\windows\*.dll . + odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true - name: Odin internals tests shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat - cd tests\internal - call build.bat - timeout-minutes: 10 + odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true + - name: Check benchmarks + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat + odin check tests/benchmark -vet -strict-style -no-entry-point - name: Odin documentation tests shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat cd tests\documentation call build.bat - timeout-minutes: 10 - 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 + + build_linux_riscv64: + runs-on: ubuntu-latest + name: Linux riscv64 (emulated) Build, Check and Test + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + + - name: Download LLVM (Linux) + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 18 + echo "/usr/lib/llvm-18/bin" >> $GITHUB_PATH + + - name: Build Odin + run: ./build_odin.sh release + + - name: Odin version + run: ./odin version + + - name: Odin report + run: ./odin report + + - name: Compile needed Vendor + run: | + make -C vendor/stb/src + make -C vendor/cgltf/src + make -C vendor/miniaudio/src + + - name: Odin check + run: ./odin check examples/all -target:linux_riscv64 -vet -strict-style -disallow-do + + - name: Install riscv64 toolchain and qemu + run: sudo apt-get install -y qemu-user qemu-user-static gcc-12-riscv64-linux-gnu libc6-riscv64-cross + + - name: Odin run + run: ./odin run examples/demo -vet -strict-style -disallow-do -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath + + - name: Odin run -debug + run: ./odin run examples/demo -debug -vet -strict-style -disallow-do -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath + + - name: Normal Core library tests + run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath + + - name: Optimized Core library tests + run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath + + - name: Internals tests + run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 27d370cc5..314711efb 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -7,10 +7,11 @@ on: jobs: build_windows: + name: Windows Build 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: | @@ -29,102 +30,181 @@ jobs: cp LICENSE dist cp LLVM-C.dll dist cp -r shared dist + cp -r base dist cp -r core dist cp -r vendor dist cp -r bin dist cp -r examples dist - name: Upload artifact - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: + include-hidden-files: true name: windows_artifacts path: dist - build_ubuntu: + build_linux: + name: Linux Build if: github.repository == 'odin-lang/Odin' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 + - uses: jirutka/setup-alpine@v1 + with: + branch: v3.20 - name: (Linux) Download LLVM - run: sudo apt-get install llvm-11 clang-11 + run: | + apk add --no-cache \ + musl-dev llvm18-dev clang18 git mold lz4 \ + libxml2-static llvm18-static zlib-static zstd-static \ + make + shell: alpine.sh --root {0} - name: build odin - run: make nightly + # NOTE: this build does slow compile times because of musl + run: ci/build_linux_static.sh + shell: alpine.sh {0} - name: Odin run run: ./odin run examples/demo - name: Copy artifacts run: | - mkdir dist - cp odin dist - cp LICENSE dist - cp libLLVM* dist - cp -r shared dist - cp -r core dist - cp -r vendor dist - cp -r examples dist + FILE="odin-linux-amd64-nightly+$(date -I)" + mkdir $FILE + cp odin $FILE + cp LICENSE $FILE + cp -r shared $FILE + cp -r base $FILE + cp -r core $FILE + cp -r vendor $FILE + cp -r examples $FILE + # Creating a tarball so executable permissions are retained, see https://github.com/actions/upload-artifact/issues/38 + tar -czvf dist.tar.gz $FILE + - name: Odin run + run: | + FILE="odin-linux-amd64-nightly+$(date -I)" + $FILE/odin run examples/demo - name: Upload artifact - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: - name: ubuntu_artifacts - path: dist + name: linux_artifacts + path: dist.tar.gz 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@13 - echo "/usr/local/opt/llvm@13/bin" >> $GITHUB_PATH - TMP_PATH=$(xcrun --show-sdk-path)/user/include - echo "CPATH=$TMP_PATH" >> $GITHUB_ENV + brew install llvm@18 dylibbundler + echo "/usr/local/opt/llvm@18/bin" >> $GITHUB_PATH - name: build odin - run: make nightly - - name: Odin run - run: ./odin run examples/demo - - name: Copy artifacts + # 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. + run: CXXFLAGS="-L/usr/lib/system -L/usr/lib" make nightly + - name: Bundle run: | - mkdir dist - cp odin dist - cp LICENSE dist - cp -r shared dist - cp -r core dist - cp -r vendor dist - cp -r examples dist + FILE="odin-macos-amd64-nightly+$(date -I)" + mkdir $FILE + cp odin $FILE + cp LICENSE $FILE + cp -r shared $FILE + cp -r base $FILE + cp -r core $FILE + cp -r vendor $FILE + cp -r examples $FILE + dylibbundler -b -x $FILE/odin -d $FILE/libs -od -p @executable_path/libs + # Creating a tarball so executable permissions are retained, see https://github.com/actions/upload-artifact/issues/38 + tar -czvf dist.tar.gz $FILE + - name: Odin run + run: | + FILE="odin-macos-amd64-nightly+$(date -I)" + $FILE/odin run examples/demo - name: Upload artifact - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: name: macos_artifacts - path: dist + path: dist.tar.gz + build_macos_arm: + name: MacOS ARM Build + if: github.repository == 'odin-lang/Odin' + runs-on: macos-14 # ARM machine + steps: + - uses: actions/checkout@v4 + - name: Download LLVM and setup PATH + run: | + 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. + run: CXXFLAGS="-L/usr/lib/system -L/usr/lib" make nightly + - name: Bundle + run: | + FILE="odin-macos-arm64-nightly+$(date -I)" + mkdir $FILE + cp odin $FILE + cp LICENSE $FILE + cp -r shared $FILE + cp -r base $FILE + cp -r core $FILE + cp -r vendor $FILE + cp -r examples $FILE + dylibbundler -b -x $FILE/odin -d $FILE/libs -od -p @executable_path/libs + # Creating a tarball so executable permissions are retained, see https://github.com/actions/upload-artifact/issues/38 + tar -czvf dist.tar.gz $FILE + - name: Odin run + run: | + FILE="odin-macos-arm64-nightly+$(date -I)" + $FILE/odin run examples/demo + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: macos_arm_artifacts + path: dist.tar.gz upload_b2: runs-on: [ubuntu-latest] - needs: [build_windows, build_macos, build_ubuntu] + needs: [build_windows, build_macos, build_macos_arm, build_linux] steps: - - uses: actions/checkout@v1 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 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)" - name: Download Windows artifacts - uses: actions/download-artifact@v1 + + uses: actions/download-artifact@v4.1.7 with: name: windows_artifacts + path: windows_artifacts - name: Download Ubuntu artifacts - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4.1.7 with: - name: ubuntu_artifacts + name: linux_artifacts + path: linux_artifacts - name: Download macOS artifacts - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4.1.7 with: name: macos_artifacts + path: macos_artifacts + + - name: Download macOS arm artifacts + uses: actions/download-artifact@v4.1.7 + with: + name: macos_arm_artifacts + path: macos_arm_artifacts + + - name: Debug + run: | + tree -L 2 - name: Create archives and upload shell: bash @@ -134,23 +214,10 @@ 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/ - ./ci/upload_create_nightly.sh "$BUCKET" macos-amd64 macos_artifacts/ - - 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 + file linux_artifacts/dist.tar.gz + python3 ci/nightly.py artifact windows-amd64 windows_artifacts/ + python3 ci/nightly.py artifact linux-amd64 linux_artifacts/dist.tar.gz + python3 ci/nightly.py artifact macos-amd64 macos_artifacts/dist.tar.gz + python3 ci/nightly.py artifact macos-arm64 macos_arm_artifacts/dist.tar.gz + python3 ci/nightly.py prune + python3 ci/nightly.py json diff --git a/.gitignore b/.gitignore index 59b5adb6d..32e5f5b0f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,15 +17,12 @@ [Rr]eleases/ x64/ x86/ +!/core/simd/x86 bld/ [Bb]in/ [Oo]bj/ [Ll]og/ ![Cc]ore/[Ll]og/ -tests/documentation/verify/ -tests/documentation/all.odin-doc -tests/internal/test_map -tests/internal/test_rtti # Visual Studio 2015 cache/options directory .vs/ # Visual Studio Code options directory @@ -269,11 +266,14 @@ bin/ *.exe *.obj *.pdb +*.res +desktop.ini +Thumbs.db # - Linux/MacOS odin !odin/ -odin.dSYM +**/*.dSYM *.bin demo.bin libLLVM*.so* @@ -290,3 +290,8 @@ shared/ examples/bug/ build.sh !core/debug/ + +# RAD debugger project file +*.raddbg + +misc/featuregen/featuregen diff --git a/LICENSE b/LICENSE index 9a87ab8da..4d155def4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016-2022 Ginger Bill. All rights reserved. +Copyright (c) 2016-2024 Ginger Bill. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 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/Makefile b/Makefile index 91010a620..ec848192a 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -all: debug +all: default demo: ./odin run examples/demo/demo.odin -file @@ -6,12 +6,18 @@ demo: report: ./odin report +default: + PROGRAM=make ./build_odin.sh # debug + debug: ./build_odin.sh debug release: ./build_odin.sh release +release-native: + ./build_odin.sh release-native + release_native: ./build_odin.sh release-native diff --git a/README.md b/README.md index 4df71015d..b4bb6bac7 100644 --- a/README.md +++ b/README.md @@ -76,9 +76,9 @@ Answers to common questions about Odin. Documentation for all the official packages part of the [core](https://pkg.odin-lang.org/core/) and [vendor](https://pkg.odin-lang.org/vendor/) library collections. -#### [The Odin Wiki](https://github.com/odin-lang/Odin/wiki) +#### [Odin Documentation](https://odin-lang.org/docs/) -A wiki maintained by the Odin community. +Documentation for the Odin language itself. #### [Odin Discord](https://discord.gg/sVBPHEv) diff --git a/core/builtin/builtin.odin b/base/builtin/builtin.odin similarity index 95% rename from core/builtin/builtin.odin rename to base/builtin/builtin.odin index 211db9770..227ceeb49 100644 --- a/core/builtin/builtin.odin +++ b/base/builtin/builtin.odin @@ -1,6 +1,8 @@ // This is purely for documentation package builtin +import "base:runtime" + nil :: nil false :: 0!=0 true :: 0==0 @@ -110,7 +112,7 @@ typeid_of :: proc($T: typeid) -> typeid --- swizzle :: proc(x: [N]T, indices: ..int) -> [len(indices)]T --- complex :: proc(real, imag: Float) -> Complex_Type --- -quaternion :: proc(real, imag, jmag, kmag: Float) -> Quaternion_Type --- +quaternion :: proc(imag, jmag, kmag, real: Float) -> Quaternion_Type --- // fields must be named real :: proc(value: Complex_Or_Quaternion) -> Float --- imag :: proc(value: Complex_Or_Quaternion) -> Float --- jmag :: proc(value: Quaternion) -> Float --- @@ -126,3 +128,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/core/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin similarity index 71% rename from core/intrinsics/intrinsics.odin rename to base/intrinsics/intrinsics.odin index 33c4e2b07..ee0d357e4 100644 --- a/core/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -1,10 +1,18 @@ // This is purely for documentation -//+build ignore +#+build ignore package intrinsics +import "base:runtime" + // Package-Related is_package_imported :: proc(package_name: string) -> bool --- +// Matrix Related Procedures +transpose :: proc(m: $T/matrix[$R, $C]$E) -> matrix[C, R]E --- +outer_product :: proc(a: $A/[$X]$E, b: $B/[$Y]E) -> matrix[X, Y]E --- +hadamard_product :: proc(a, b: $T/matrix[$R, $C]$E) -> T --- +matrix_flatten :: proc(m: $T/matrix[$R, $C]$E) -> [R*C]E --- + // Types soa_struct :: proc($N: int, $T: typeid) -> type/#soa[N]T @@ -32,9 +40,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) #optional_ok --- -overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok --- -overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok --- +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 --- + +saturating_add :: proc(lhs, rhs: $T) -> T where type_is_integer(T) --- +saturating_sub :: proc(lhs, rhs: $T) -> T where type_is_integer(T) --- sqrt :: proc(x: $T) -> T where type_is_float(T) || (type_is_simd_vector(T) && type_is_float(type_elem_type(T))) --- @@ -63,10 +74,12 @@ prefetch_write_instruction :: proc(address: rawptr, #const locality: i32 /* 0..= prefetch_write_data :: proc(address: rawptr, #const locality: i32 /* 0..=3 */) --- // Compiler Hints -expect :: proc(val, expected_val: T) -> T --- +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 @@ -161,10 +174,23 @@ type_is_matrix :: proc($T: typeid) -> bool --- type_has_nil :: proc($T: typeid) -> bool --- -type_is_specialization_of :: proc($T, $S: typeid) -> bool --- -type_is_variant_of :: proc($U, $V: typeid) -> bool where type_is_union(U) --- +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_has_field :: proc($T: typeid, $name: string) -> bool --- +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_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) --- @@ -173,7 +199,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 --- @@ -194,14 +221,21 @@ type_map_cell_info :: proc($T: typeid) -> ^runtime.Map_Cell_Info --- type_convert_variants_to_pointers :: proc($T: typeid) -> typeid where type_is_union(T) --- type_merge :: proc($U, $V: typeid) -> typeid where type_is_union(U), type_is_union(V) --- +type_has_shared_fields :: proc($U, $V: typeid) -> bool where type_is_struct(U), type_is_struct(V) --- + constant_utf16_cstring :: proc($literal: string) -> [^]u16 --- +constant_log2 :: proc($v: $T) -> T where type_is_integer(T) --- + // SIMD related simd_add :: proc(a, b: #simd[N]T) -> #simd[N]T --- simd_sub :: proc(a, b: #simd[N]T) -> #simd[N]T --- simd_mul :: proc(a, b: #simd[N]T) -> #simd[N]T --- simd_div :: proc(a, b: #simd[N]T) -> #simd[N]T where type_is_float(T) --- +simd_saturating_add :: proc(a, b: #simd[N]T) -> #simd[N]T where type_is_integer(T) --- +simd_saturating_sub :: proc(a, b: #simd[N]T) -> #simd[N]T where type_is_integer(T) --- + // Keeps Odin's Behaviour // (x << y) if y <= mask else 0 simd_shl :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T --- @@ -212,9 +246,6 @@ simd_shr :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T --- simd_shl_masked :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T --- simd_shr_masked :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T --- -simd_add_sat :: proc(a, b: #simd[N]T) -> #simd[N]T --- -simd_sub_sat :: proc(a, b: #simd[N]T) -> #simd[N]T --- - simd_bit_and :: proc(a, b: #simd[N]T) -> #simd[N]T --- simd_bit_or :: proc(a, b: #simd[N]T) -> #simd[N]T --- simd_bit_xor :: proc(a, b: #simd[N]T) -> #simd[N]T --- @@ -243,13 +274,28 @@ simd_lanes_ge :: proc(a, b: #simd[N]T) -> #simd[N]Integer --- simd_extract :: proc(a: #simd[N]T, idx: uint) -> T --- simd_replace :: proc(a: #simd[N]T, idx: uint, elem: T) -> #simd[N]T --- -simd_reduce_add_ordered :: proc(a: #simd[N]T) -> T --- -simd_reduce_mul_ordered :: proc(a: #simd[N]T) -> T --- -simd_reduce_min :: proc(a: #simd[N]T) -> T --- -simd_reduce_max :: proc(a: #simd[N]T) -> T --- -simd_reduce_and :: proc(a: #simd[N]T) -> T --- -simd_reduce_or :: proc(a: #simd[N]T) -> T --- -simd_reduce_xor :: proc(a: #simd[N]T) -> T --- +simd_reduce_add_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- +simd_reduce_mul_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- +simd_reduce_min :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- +simd_reduce_max :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- +simd_reduce_and :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- +simd_reduce_or :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- +simd_reduce_xor :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- + +simd_reduce_any :: proc(a: #simd[N]T) -> T where type_is_boolean(T) --- +simd_reduce_all :: proc(a: #simd[N]T) -> T where type_is_boolean(T) --- + + +simd_gather :: proc(ptr: #simd[N]rawptr, val: #simd[N]T, mask: #simd[N]U) -> #simd[N]T where type_is_integer(U) || type_is_boolean(U) --- +simd_scatter :: proc(ptr: #simd[N]rawptr, val: #simd[N]T, mask: #simd[N]U) where type_is_integer(U) || type_is_boolean(U) --- + +simd_masked_load :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) -> #simd[N]T where type_is_integer(U) || type_is_boolean(U) --- +simd_masked_store :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) where type_is_integer(U) || type_is_boolean(U) --- + +simd_masked_expand_load :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) -> #simd[N]T where type_is_integer(U) || type_is_boolean(U) --- +simd_masked_compress_store :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) where type_is_integer(U) || type_is_boolean(U) --- + + simd_shuffle :: proc(a, b: #simd[N]T, indices: ..int) -> #simd[len(indices)]T --- simd_select :: proc(cond: #simd[N]boolean_or_integer, true, false: #simd[N]T) -> #simd[N]T --- @@ -263,12 +309,22 @@ simd_nearest :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- simd_to_bits :: proc(v: #simd[N]T) -> #simd[N]Integer where size_of(T) == size_of(Integer), type_is_unsigned(Integer) --- -// equivalent a swizzle with descending indices, e.g. reserve(a, 3, 2, 1, 0) -simd_reverse :: proc(a: #simd[N]T) -> #simd[N]T --- +// equivalent to a swizzle with descending indices, e.g. reserve(a, 3, 2, 1, 0) +simd_lanes_reverse :: proc(a: #simd[N]T) -> #simd[N]T --- -simd_rotate_left :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T --- -simd_rotate_right :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T --- +simd_lanes_rotate_left :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T --- +simd_lanes_rotate_right :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T --- +// Checks if the current target supports the given target features. +// +// 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 --- @@ -280,7 +336,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 +@(require_target_feature="atomics") wasm_memory_atomic_wait32 :: proc(ptr: ^u32, expected: u32, timeout_ns: i64) -> u32 --- +@(require_target_feature="atomics") wasm_memory_atomic_notify32 :: proc(ptr: ^u32, waiters: u32) -> (waiters_woken_up: u32) --- // x86 Targets (i386, amd64) diff --git a/core/runtime/core.odin b/base/runtime/core.odin similarity index 77% rename from core/runtime/core.odin rename to base/runtime/core.odin index 2d176f909..e47f3ecbc 100644 --- a/core/runtime/core.odin +++ b/base/runtime/core.odin @@ -18,9 +18,10 @@ // This could change at a later date if the all these data structures are // implemented within the compiler rather than in this "preload" file // +#+no-instrumentation package runtime -import "core:intrinsics" +import "base:intrinsics" // NOTE(bill): This must match the compiler's Calling_Convention :: enum u8 { @@ -65,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 {} @@ -111,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, @@ -141,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, @@ -161,14 +171,6 @@ Type_Info_Simd_Vector :: struct { elem_size: int, count: int, } -Type_Info_Relative_Pointer :: struct { - pointer: ^Type_Info, // ^T - base_integer: ^Type_Info, -} -Type_Info_Relative_Multi_Pointer :: struct { - pointer: ^Type_Info, // [^]T - base_integer: ^Type_Info, -} Type_Info_Matrix :: struct { elem: ^Type_Info, elem_size: int, @@ -176,10 +178,23 @@ Type_Info_Matrix :: struct { row_count: int, column_count: int, // Total element count = column_count * elem_stride + layout: enum u8 { + Column_Major, // array of column vectors + Row_Major, // array of row vectors + }, } Type_Info_Soa_Pointer :: struct { elem: ^Type_Info, } +Type_Info_Bit_Field :: struct { + backing_type: ^Type_Info, + 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 { Comparable = 0, @@ -218,10 +233,9 @@ Type_Info :: struct { Type_Info_Map, Type_Info_Bit_Set, Type_Info_Simd_Vector, - Type_Info_Relative_Pointer, - Type_Info_Relative_Multi_Pointer, Type_Info_Matrix, Type_Info_Soa_Pointer, + Type_Info_Bit_Field, }, } @@ -251,25 +265,24 @@ Typeid_Kind :: enum u8 { Map, Bit_Set, Simd_Vector, - Relative_Pointer, - Relative_Multi_Pointer, Matrix, Soa_Pointer, + Bit_Field, } #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 -type_table: []Type_Info +type_table: []^Type_Info args__: []cstring @@ -284,6 +297,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) @@ -295,6 +310,14 @@ Source_Code_Location :: struct { procedure: string, } +/* + Used by the built-in directory `#load_directory(path: string) -> []Load_Directory_File` +*/ +Load_Directory_File :: struct { + name: string, + data: []byte, // immutable data +} + Assertion_Failure_Proc :: #type proc(prefix, message: string, loc: Source_Code_Location) -> ! // Allocation Stuff @@ -306,6 +329,7 @@ Allocator_Mode :: enum byte { Query_Features, Query_Info, Alloc_Non_Zeroed, + Resize_Non_Zeroed, } Allocator_Mode_Set :: distinct bit_set[Allocator_Mode] @@ -373,11 +397,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, @@ -446,6 +493,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} /* @@ -458,8 +514,11 @@ Raw_Soa_Pointer :: struct { Essence, FreeBSD, OpenBSD, + NetBSD, + Haiku, WASI, JS, + Orca, Freestanding, } */ @@ -475,15 +534,29 @@ Odin_OS_Type :: type_of(ODIN_OS) arm64, wasm32, wasm64p32, + riscv64, } */ Odin_Arch_Type :: type_of(ODIN_ARCH) +Odin_Arch_Types :: bit_set[Odin_Arch_Type] + +ALL_ODIN_ARCH_TYPES :: Odin_Arch_Types{ + .amd64, + .i386, + .arm32, + .arm64, + .wasm32, + .wasm64p32, + .riscv64, +} + /* // Defined internally by the compiler Odin_Build_Mode_Type :: enum int { Executable, Dynamic, + Static, Object, Assembly, LLVM_IR, @@ -501,6 +574,22 @@ Odin_Build_Mode_Type :: type_of(ODIN_BUILD_MODE) */ Odin_Endian_Type :: type_of(ODIN_ENDIAN) +Odin_OS_Types :: bit_set[Odin_OS_Type] + +ALL_ODIN_OS_TYPES :: Odin_OS_Types{ + .Windows, + .Darwin, + .Linux, + .Essence, + .FreeBSD, + .OpenBSD, + .NetBSD, + .Haiku, + .WASI, + .JS, + .Orca, + .Freestanding, +} /* // Defined internally by the compiler @@ -518,12 +607,25 @@ Odin_Platform_Subtarget_Type :: type_of(ODIN_PLATFORM_SUBTARGET) Memory = 1, Thread = 2, } - Odin_Sanitizer_Flags :: distinct bitset[Odin_Sanitizer_Flag; u32] + Odin_Sanitizer_Flags :: distinct bit_set[Odin_Sanitizer_Flag; u32] ODIN_SANITIZER_FLAGS // is a constant */ 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 // @@ -573,8 +675,9 @@ type_info_core :: proc "contextless" (info: ^Type_Info) -> ^Type_Info { base := info loop: for { #partial switch i in base.variant { - case Type_Info_Named: base = i.base - case Type_Info_Enum: base = i.base + case Type_Info_Named: base = i.base + case Type_Info_Enum: base = i.base + case Type_Info_Bit_Field: base = i.backing_type case: break loop } } @@ -589,7 +692,7 @@ __type_info_of :: proc "contextless" (id: typeid) -> ^Type_Info #no_bounds_check if n < 0 || n >= len(type_table) { n = 0 } - return &type_table[n] + return type_table[n] } when !ODIN_NO_RTTI { @@ -658,13 +761,20 @@ __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) -> ! { + default_assertion_contextless_failure_proc(prefix, message, loc) +} + +default_assertion_contextless_failure_proc :: proc "contextless" (prefix, message: string, loc: Source_Code_Location) -> ! { when ODIN_OS == .Freestanding { // Do nothing } else { - when !ODIN_DISABLE_ASSERT { + when ODIN_OS != .Orca && !ODIN_DISABLE_ASSERT { print_caller_location(loc) print_string(" ") } @@ -673,7 +783,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/core/runtime/core_builtin.odin b/base/runtime/core_builtin.odin similarity index 55% rename from core/runtime/core_builtin.odin rename to base/runtime/core_builtin.odin index a73a3d712..cf379aacb 100644 --- a/core/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -1,11 +1,44 @@ package runtime -import "core:intrinsics" +import "base:intrinsics" @builtin Maybe :: union($T: typeid) {T} +/* +Recovers the containing/parent struct from a pointer to one of its fields. +Works by "walking back" to the struct's starting address using the offset between the field and the struct. + +Inputs: +- ptr: Pointer to the field of a container struct +- T: The type of the container struct +- field_name: The name of the field in the `T` struct + +Returns: +- A pointer to the container struct based on a pointer to a field in it + +Example: + package container_of + import "base:runtime" + + Node :: struct { + value: int, + prev: ^Node, + next: ^Node, + } + + main :: proc() { + node: Node + field_ptr := &node.next + container_struct_ptr: ^Node = runtime.container_of(field_ptr, Node, "next") + assert(container_struct_ptr == &node) + assert(uintptr(field_ptr) - uintptr(container_struct_ptr) == size_of(node.value) + size_of(node.prev)) + } + +Output: + ^Node +*/ @(builtin, require_results) container_of :: #force_inline proc "contextless" (ptr: $P/^$Field_Type, $T: typeid, $field_name: string) -> ^T where intrinsics.type_has_field(T, field_name), @@ -40,7 +73,7 @@ copy_slice :: proc "contextless" (dst, src: $T/[]$E) -> int { } return n } -// `copy_from_string` is a built-in procedure that copies elements from a source slice `src` to a destination string `dst`. +// `copy_from_string` is a built-in procedure that copies elements from a source string `src` to a destination slice `dst`. // The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum // of len(src) and len(dst). // @@ -53,7 +86,7 @@ copy_from_string :: proc "contextless" (dst: $T/[]$E/u8, src: $S/string) -> int } return n } -// `copy` is a built-in procedure that copies elements from a source slice `src` to a destination slice/string `dst`. +// `copy` is a built-in procedure that copies elements from a source slice/string `src` to a destination slice `dst`. // The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum // of len(src) and len(dst). @builtin @@ -65,10 +98,10 @@ 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 { +unordered_remove :: proc(array: ^$D/[dynamic]$T, #any_int index: int, loc := #caller_location) #no_bounds_check { bounds_check_error_loc(loc, index, len(array)) n := len(array)-1 if index != n { @@ -79,10 +112,10 @@ 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 { +ordered_remove :: proc(array: ^$D/[dynamic]$T, #any_int index: int, loc := #caller_location) #no_bounds_check { bounds_check_error_loc(loc, index, len(array)) if index+1 < len(array) { copy(array[index:], array[index+1:]) @@ -95,7 +128,7 @@ ordered_remove :: proc(array: ^$D/[dynamic]$T, index: int, loc := #caller_locati // Note: This is an O(N) operation. // Note: If the range is out of bounds, this procedure will panic. @builtin -remove_range :: proc(array: ^$D/[dynamic]$T, lo, hi: int, loc := #caller_location) #no_bounds_check { +remove_range :: proc(array: ^$D/[dynamic]$T, #any_int lo, hi: int, loc := #caller_location) #no_bounds_check { slice_expr_error_lo_hi_loc(loc, lo, hi, len(array)) n := max(hi-lo, 0) if n > 0 { @@ -109,7 +142,7 @@ remove_range :: proc(array: ^$D/[dynamic]$T, lo, hi: int, loc := #caller_locatio // `pop` will remove and return the end value of dynamic array `array` and reduces the length of `array` by 1. // -// Note: If the dynamic array as no elements (`len(array) == 0`), this procedure will panic. +// Note: If the dynamic array has no elements (`len(array) == 0`), this procedure will panic. @builtin pop :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { assert(len(array) > 0, loc=loc) @@ -122,7 +155,7 @@ pop :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bou // `pop_safe` trys to remove and return the end value of dynamic array `array` and reduces the length of `array` by 1. // If the operation is not possible, it will return false. @builtin -pop_safe :: proc(array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { +pop_safe :: proc "contextless" (array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { if len(array) == 0 { return } @@ -148,7 +181,7 @@ pop_front :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) # // `pop_front_safe` trys to return and remove the first value of dynamic array `array` and reduces the length of `array` by 1. // If the operation is not possible, it will return false. @builtin -pop_front_safe :: proc(array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { +pop_front_safe :: proc "contextless" (array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { if len(array) == 0 { return } @@ -163,15 +196,43 @@ pop_front_safe :: proc(array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_ // `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, +} -// `resize` will try to resize memory of a passed dynamic array or map to the requested element count (setting the `len`, and possibly `cap`). @builtin -resize :: proc{resize_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_soa, +} + +@builtin +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 @@ -234,6 +295,8 @@ delete :: proc{ delete_dynamic_array, delete_slice, delete_map, + delete_soa_slice, + delete_soa_dynamic_array, } @@ -260,7 +323,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 { @@ -287,7 +350,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. @@ -303,28 +366,47 @@ 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) - 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 } -// `make_map` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. + +@(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` initializes a map with an allocator. Like `new`, the first argument is a type, not a value. // Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. // // Note: Prefer using the procedure group `make`. @(builtin, require_results) -make_map :: proc($T: typeid/map[$K]$E, #any_int capacity: int = 1< (m: T, err: Allocator_Error) #optional_allocator_error { +make_map :: proc($T: typeid/map[$K]$E, allocator := context.allocator, loc := #caller_location) -> (m: T) { + m.allocator = allocator + return m +} + +// `make_map_cap` initializes a map with an allocator and allocates space using `capacity`. +// Like `new`, the first argument is a type, not a value. +// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. +// +// Note: Prefer using the procedure group `make`. +@(builtin, require_results) +make_map_cap :: proc($T: typeid/map[$K]$E, #any_int capacity: int, allocator := context.allocator, loc := #caller_location) -> (m: T, err: Allocator_Error) #optional_allocator_error { make_map_expr_error_loc(loc, capacity) context.allocator = allocator err = reserve_map(&m, capacity, loc) return } -// `make_multi_pointer` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. +// `make_multi_pointer` allocates and initializes a multi-pointer. Like `new`, the first argument is a type, not a value. // Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. // // This is "similar" to doing `raw_data(make([]E, len, allocator))`. @@ -346,7 +428,7 @@ make_multi_pointer :: proc($T: typeid/[^]$E, #any_int len: int, allocator := con // // Similar to `new`, the first argument is a type, not a value. Unlike new, make's return type is the same as the // type of its argument, not a pointer to it. -// Make uses the specified allocator, default is context.allocator, default is context.allocator +// Make uses the specified allocator, default is context.allocator. @builtin make :: proc{ make_slice, @@ -354,7 +436,13 @@ make :: proc{ make_dynamic_array_len, make_dynamic_array_len_cap, make_map, + make_map_cap, make_multi_pointer, + + make_soa_slice, + make_soa_dynamic_array, + make_soa_dynamic_array_len, + make_soa_dynamic_array_len_cap, } @@ -374,7 +462,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 } @@ -404,75 +492,112 @@ delete_key :: proc(m: ^$T/map[$K]$V, key: K) -> (deleted_key: K, deleted_value: return } +_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 + } + 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, arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - if array == nil { - return 0, nil - } +append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { when size_of(E) == 0 { - array := (^Raw_Dynamic_Array)(array) - array.len += 1 + (^Raw_Dynamic_Array)(array).len += 1 return 1, nil } else { - if cap(array) < len(array)+1 { - cap := 2 * cap(array) + max(8, 1) - err = reserve(array, cap, loc) // do not 'or_return' here as it could be a partial success - } - 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 + arg := arg + return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, true, loc=loc) } } @builtin -append_elems :: proc(array: ^$T/[dynamic]$E, args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { +non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + 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: ^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) + 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 + 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, nil + } + 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 { + when size_of(E) == 0 { + a := (^Raw_Dynamic_Array)(array) + a.len += len(args) + return len(args), nil } else { - if cap(array) < len(array)+arg_len { - cap := 2 * cap(array) + max(8, arg_len) - err = reserve(array, cap, loc) // do not 'or_return' here as it could be a partial success - } - 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 + 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 { + 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 { + return _append_elems((^Raw_Dynamic_Array)(array), 1, 1, should_zero, loc, raw_data(arg), len(arg)) +} + @builtin append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - args := transmute([]E)arg - return append_elems(array, ..args, loc=loc) + return _append_elem_string(array, arg, true, loc) +} +@builtin +non_zero_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + return _append_elem_string(array, arg, false, loc) } @@ -491,7 +616,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 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 @@ -506,7 +647,7 @@ append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: i @builtin -inject_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { if array == nil { return } @@ -524,7 +665,7 @@ inject_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #calle } @builtin -inject_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { if array == nil { return } @@ -547,7 +688,7 @@ inject_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #c } @builtin -inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { if array == nil { return } @@ -572,7 +713,7 @@ inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, index: int, arg: string @builtin -assign_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +assign_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { if index < len(array) { array[index] = arg ok = true @@ -586,12 +727,15 @@ 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 { - if index+len(args) < len(array) { +assign_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { + new_size := index + len(args) + if len(args) == 0 { + ok = true + } else if new_size < len(array) { copy(array[index:], args) ok = true } else { - resize(array, index+1+len(args), loc) or_return + resize(array, new_size, loc) or_return copy(array[index:], args) ok = true } @@ -600,7 +744,7 @@ assign_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #c @builtin -assign_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +assign_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { new_size := index + len(arg) if len(arg) == 0 { ok = true @@ -633,12 +777,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`. -@builtin -reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, capacity: int, 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 @@ -649,11 +791,16 @@ reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, capacity: int, loc := #cal } 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 := mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return + new_data: []byte + if should_zero { + 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_elem, allocator, loc) or_return + } if new_data == nil && new_size > 0 { return .Out_Of_Memory } @@ -663,15 +810,26 @@ reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, capacity: int, loc := #cal 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 { - if array == nil { +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, #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 :: #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 should_zero && a.len < length { + num_reused := min(a.cap, length) - a.len + intrinsics.mem_zero(([^]byte)(a.data)[a.len*size_of_elem:], num_reused*size_of_elem) + } if length <= a.cap { a.len = max(length, 0) @@ -683,11 +841,16 @@ resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller } 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 := mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return + new_data : []byte + if should_zero { + 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_elem, allocator, loc) or_return + } if new_data == nil && new_size > 0 { return .Out_Of_Memory } @@ -698,6 +861,19 @@ resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller return nil } +// `resize_dynamic_array` will try to resize memory of a passed dynamic array or map to the requested element count (setting the `len`, and possibly `cap`). +// +// Note: Prefer the procedure group `resize` +@builtin +resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error { + return _resize_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), length, true, loc=loc) +} + +@builtin +non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error { + return _resize_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), length, false, loc=loc) +} + /* Shrinks the capacity of a dynamic array down to the current length, or the given capacity. @@ -709,11 +885,14 @@ resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller 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 { +shrink_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { + return _shrink_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), new_cap, loc) +} + +_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 @@ -726,10 +905,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) @@ -743,62 +922,59 @@ map_insert :: proc(m: ^$T/map[$K]$V, key: K, value: V, loc := #caller_location) return (^V)(__dynamic_map_set_without_hash((^Raw_Map)(m), map_info(T), rawptr(&key), rawptr(&value), loc)) } - -@builtin -incl_elem :: proc(s: ^$S/bit_set[$E; $U], elem: E) { - s^ |= {elem} -} -@builtin -incl_elems :: proc(s: ^$S/bit_set[$E; $U], elems: ..E) { - for elem in elems { - s^ |= {elem} +// Explicitly inserts a key and value into a map `m`, the same as `map_insert`, but the return values differ. +// - `prev_key` will return the previous pointer of a key if it exists, check `found_previous` if was previously found +// - `value_ptr` will return the pointer of the memory where the insertion happens, and `nil` if the map failed to resize +// - `found_previous` will be true a previous key was found +@(builtin, require_results) +map_upsert :: proc(m: ^$T/map[$K]$V, key: K, value: V, loc := #caller_location) -> (prev_key: K, value_ptr: ^V, found_previous: bool) { + key, value := key, value + kp, vp := __dynamic_map_set_extra_without_hash((^Raw_Map)(m), map_info(T), rawptr(&key), rawptr(&value), loc) + if kp != nil { + prev_key = (^K)(kp)^ + found_previous = true } -} -@builtin -incl_bit_set :: proc(s: ^$S/bit_set[$E; $U], other: S) { - s^ |= other -} -@builtin -excl_elem :: proc(s: ^$S/bit_set[$E; $U], elem: E) { - s^ &~= {elem} -} -@builtin -excl_elems :: proc(s: ^$S/bit_set[$E; $U], elems: ..E) { - for elem in elems { - s^ &~= {elem} - } -} -@builtin -excl_bit_set :: proc(s: ^$S/bit_set[$E; $U], other: S) { - s^ &~= other + value_ptr = (^V)(vp) + return } -@builtin incl :: proc{incl_elem, incl_elems, incl_bit_set} -@builtin excl :: proc{excl_elem, excl_elems, excl_bit_set} +/* +Retrieves a pointer to the key and value for a possibly just inserted entry into the map. + +If the `key` was not in the map `m`, an entry is inserted with the zero value and `just_inserted` will be `true`. +Otherwise the existing entry is left untouched and pointers to its key and value are returned. + +If the map has to grow in order to insert the entry and the allocation fails, `err` is set and returned. + +If `err` is `nil`, `key_ptr` and `value_ptr` are valid pointers and will not be `nil`. + +WARN: User modification of the key pointed at by `key_ptr` should only be done if the new key is equal to (in hash) the old key. +If that is not the case you will corrupt the map. +*/ +@(builtin, require_results) +map_entry :: proc(m: ^$T/map[$K]$V, key: K, loc := #caller_location) -> (key_ptr: ^K, value_ptr: ^V, just_inserted: bool, err: Allocator_Error) { + key := key + zero: V + + _key_ptr, _value_ptr: rawptr + _key_ptr, _value_ptr, just_inserted, err = __dynamic_map_entry((^Raw_Map)(m), map_info(T), &key, &zero, loc) + + key_ptr = (^K)(_key_ptr) + value_ptr = (^V)(_value_ptr) + return +} @builtin -card :: proc(s: $S/bit_set[$E; $U]) -> int { - when size_of(S) == 1 { - return int(intrinsics.count_ones(transmute(u8)s)) - } else when size_of(S) == 2 { - return int(intrinsics.count_ones(transmute(u16)s)) - } else when size_of(S) == 4 { - return int(intrinsics.count_ones(transmute(u32)s)) - } else when size_of(S) == 8 { - return int(intrinsics.count_ones(transmute(u64)s)) - } else when size_of(S) == 16 { - return int(intrinsics.count_ones(transmute(u128)s)) - } else { - #panic("Unhandled card bit_set size") - } +card :: proc "contextless" (s: $S/bit_set[$E; $U]) -> int { + return int(intrinsics.count_ones(transmute(intrinsics.type_bit_set_underlying_type(S))s)) } @builtin @(disabled=ODIN_DISABLE_ASSERT) -assert :: proc(condition: bool, message := "", loc := #caller_location) { +assert :: proc(condition: bool, message := #caller_expression(condition), loc := #caller_location) { if !condition { // NOTE(bill): This is wrapped in a procedure call // to improve performance to make the CPU not @@ -816,6 +992,24 @@ assert :: proc(condition: bool, message := "", loc := #caller_location) { } } +// Evaluates the condition and aborts the program iff the condition is +// false. This routine ignores `ODIN_DISABLE_ASSERT`, and will always +// execute. +@builtin +ensure :: proc(condition: bool, message := #caller_expression(condition), loc := #caller_location) { + if !condition { + @(cold) + internal :: proc(message: string, loc: Source_Code_Location) { + p := context.assertion_failure_proc + if p == nil { + p = default_assertion_failure_proc + } + p("unsatisfied ensure", message, loc) + } + internal(message, loc) + } +} + @builtin panic :: proc(message: string, loc := #caller_location) -> ! { p := context.assertion_failure_proc @@ -833,3 +1027,41 @@ unimplemented :: proc(message := "", loc := #caller_location) -> ! { } p("not yet implemented", message, loc) } + + +@builtin +@(disabled=ODIN_DISABLE_ASSERT) +assert_contextless :: proc "contextless" (condition: bool, message := #caller_expression(condition), loc := #caller_location) { + if !condition { + // NOTE(bill): This is wrapped in a procedure call + // to improve performance to make the CPU not + // execute speculatively, making it about an order of + // magnitude faster + @(cold) + internal :: proc "contextless" (message: string, loc: Source_Code_Location) { + default_assertion_contextless_failure_proc("runtime assertion", message, loc) + } + internal(message, loc) + } +} + +@builtin +ensure_contextless :: proc "contextless" (condition: bool, message := #caller_expression(condition), loc := #caller_location) { + if !condition { + @(cold) + internal :: proc "contextless" (message: string, loc: Source_Code_Location) { + default_assertion_contextless_failure_proc("unsatisfied ensure", message, loc) + } + internal(message, loc) + } +} + +@builtin +panic_contextless :: proc "contextless" (message: string, loc := #caller_location) -> ! { + default_assertion_contextless_failure_proc("panic", message, loc) +} + +@builtin +unimplemented_contextless :: proc "contextless" (message := "", loc := #caller_location) -> ! { + default_assertion_contextless_failure_proc("not yet implemented", message, loc) +} diff --git a/core/runtime/core_builtin_soa.odin b/base/runtime/core_builtin_soa.odin similarity index 51% rename from core/runtime/core_builtin_soa.odin rename to base/runtime/core_builtin_soa.odin index f3882a9a8..ff27a4559 100644 --- a/core/runtime/core_builtin_soa.odin +++ b/base/runtime/core_builtin_soa.odin @@ -1,6 +1,6 @@ package runtime -import "core:intrinsics" +import "base:intrinsics" _ :: intrinsics /* @@ -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 } @@ -81,7 +76,7 @@ raw_soa_footer :: proc{ @(builtin, require_results) -make_soa_aligned :: proc($T: typeid/#soa[]$E, length: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { +make_soa_aligned :: proc($T: typeid/#soa[]$E, #any_int length, alignment: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { if length <= 0 { return } @@ -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 { +make_soa_slice :: proc($T: typeid/#soa[]$E, #any_int length: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { return make_soa_aligned(T, length, align_of(E), allocator, loc) } @(builtin, require_results) make_soa_dynamic_array :: proc($T: typeid/#soa[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { context.allocator = allocator - reserve_soa(&array, DEFAULT_RESERVE_CAPACITY, loc) or_return + array.allocator = allocator + reserve_soa(&array, 0, loc) or_return return array, nil } @(builtin, require_results) make_soa_dynamic_array_len :: proc($T: typeid/#soa[dynamic]$E, #any_int length: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { context.allocator = allocator + array.allocator = allocator resize_soa(&array, length, loc) or_return return array, nil } @@ -177,7 +174,7 @@ make_soa :: proc{ @builtin -resize_soa :: proc(array: ^$T/#soa[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error { +resize_soa :: proc(array: ^$T/#soa[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error { if array == nil { return nil } @@ -188,7 +185,27 @@ resize_soa :: proc(array: ^$T/#soa[dynamic]$E, length: int, loc := #caller_locat } @builtin -reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error { +non_zero_resize_soa :: proc(array: ^$T/#soa[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error { + if array == nil { + return nil + } + 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, #any_int capacity: int, loc := #caller_location) -> Allocator_Error { + return _reserve_soa(array, capacity, true, loc) +} + +@builtin +non_zero_reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, #any_int 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 +230,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 +238,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) if size_of(E) > 0 && cap(array)-len(array) > 0 { - ti := type_info_of(typeid_of(T)) + 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 +325,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 +365,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 +375,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 +386,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 +427,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 +446,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 } @@ -425,4 +456,82 @@ clear_soa_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) { @builtin clear_soa :: proc{ clear_soa_dynamic_array, -} \ No newline at end of file +} + +// Converts soa slice into a soa dynamic array without cloning or allocating memory +@(require_results) +into_dynamic_soa :: proc(array: $T/#soa[]$E) -> #soa[dynamic]E { + d: #soa[dynamic]E + footer := raw_soa_footer_dynamic_array(&d) + footer^ = { + cap = len(array), + len = 0, + allocator = nil_allocator(), + } + + 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] + slice_data := ([^]rawptr)(&array)[:field_count] + copy(dynamic_data, slice_data) + + return d +} + +// `unordered_remove_soa` removed the element at the specified `index`. It does so by replacing the current end value +// with the old value, and reducing the length of the dynamic array by 1. +// +// Note: This is an O(1) operation. +// Note: If you the elements to remain in their order, use `ordered_remove_soa`. +// Note: If the index is out of bounds, this procedure will panic. +@builtin +unordered_remove_soa :: proc(array: ^$T/#soa[dynamic]$E, #any_int index: int, loc := #caller_location) #no_bounds_check { + bounds_check_error_loc(loc, index, len(array)) + if index+1 < len(array) { + ti := type_info_of(typeid_of(T)) + ti = type_info_base(ti) + si := &ti.variant.(Type_Info_Struct) + + 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.. ([]byte, Allocator_Error) { + size, alignment: int, + old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { switch mode { case .Alloc, .Alloc_Non_Zeroed: return nil, .Out_Of_Memory @@ -10,7 +10,7 @@ nil_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, return nil, .None case .Free_All: return nil, .Mode_Not_Implemented - case .Resize: + case .Resize, .Resize_Non_Zeroed: if size == 0 { return nil, .None } @@ -31,14 +31,6 @@ nil_allocator :: proc() -> Allocator { } - -when ODIN_OS == .Freestanding { - default_allocator_proc :: nil_allocator_proc - default_allocator :: nil_allocator -} - - - panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, size, alignment: int, old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { @@ -55,6 +47,10 @@ panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, if size > 0 { panic("panic allocator, .Resize called", loc=loc) } + case .Resize_Non_Zeroed: + if size > 0 { + panic("panic allocator, .Alloc_Non_Zeroed called", loc=loc) + } case .Free: if old_memory != nil { panic("panic allocator, .Free called", loc=loc) @@ -78,9 +74,7 @@ panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, panic_allocator :: proc() -> Allocator { return Allocator{ - procedure = nil_allocator_proc, + procedure = panic_allocator_proc, data = nil, } } - - diff --git a/core/runtime/default_allocators_arena.odin b/base/runtime/default_temp_allocator_arena.odin similarity index 90% rename from core/runtime/default_allocators_arena.odin rename to base/runtime/default_temp_allocator_arena.odin index 1c36c4f7c..db157b267 100644 --- a/core/runtime/default_allocators_arena.odin +++ b/base/runtime/default_temp_allocator_arena.odin @@ -1,6 +1,6 @@ package runtime -import "core:intrinsics" +import "base:intrinsics" DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE) @@ -12,6 +12,8 @@ Memory_Block :: struct { capacity: uint, } +// NOTE: This is a growing arena that is only used for the default temp allocator. +// For your own growing arena needs, prefer `Arena` from `core:mem/virtual`. Arena :: struct { backing_allocator: Allocator, curr_block: ^Memory_Block, @@ -28,11 +30,11 @@ safe_add :: #force_inline proc "contextless" (x, y: uint) -> (uint, bool) { } @(require_results) -memory_block_alloc :: proc(allocator: Allocator, capacity: uint, loc := #caller_location) -> (block: ^Memory_Block, err: Allocator_Error) { - total_size := uint(capacity + size_of(Memory_Block)) - base_offset := uintptr(size_of(Memory_Block)) +memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint, loc := #caller_location) -> (block: ^Memory_Block, err: Allocator_Error) { + total_size := uint(capacity + max(alignment, size_of(Memory_Block))) + base_offset := uintptr(max(alignment, size_of(Memory_Block))) - min_alignment: int = max(16, align_of(Memory_Block)) + min_alignment: int = max(16, align_of(Memory_Block), int(alignment)) data := mem_alloc(int(total_size), min_alignment, allocator, loc) or_return block = (^Memory_Block)(raw_data(data)) end := uintptr(raw_data(data)[len(data):]) @@ -102,20 +104,20 @@ arena_alloc :: proc(arena: ^Arena, size, alignment: uint, loc := #caller_locatio if size == 0 { return } - - if arena.curr_block == nil || (safe_add(arena.curr_block.used, size) or_else 0) > arena.curr_block.capacity { - size = align_forward_uint(size, alignment) + + needed := align_forward_uint(size, alignment) + if arena.curr_block == nil || (safe_add(arena.curr_block.used, needed) or_else 0) > arena.curr_block.capacity { if arena.minimum_block_size == 0 { arena.minimum_block_size = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE } - block_size := max(size, arena.minimum_block_size) + block_size := max(needed, arena.minimum_block_size) if arena.backing_allocator.procedure == nil { arena.backing_allocator = default_allocator() } - new_block := memory_block_alloc(arena.backing_allocator, block_size, loc) or_return + new_block := memory_block_alloc(arena.backing_allocator, block_size, alignment, loc) or_return new_block.prev = arena.curr_block arena.curr_block = new_block arena.total_capacity += new_block.capacity @@ -127,14 +129,14 @@ arena_alloc :: proc(arena: ^Arena, size, alignment: uint, loc := #caller_locatio return } -// `arena_init` will initialize the arena with a usuable block. +// `arena_init` will initialize the arena with a usable block. // This procedure is not necessary to use the Arena as the default zero as `arena_alloc` will set things up if necessary @(require_results) arena_init :: proc(arena: ^Arena, size: uint, backing_allocator: Allocator, loc := #caller_location) -> Allocator_Error { arena^ = {} arena.backing_allocator = backing_allocator arena.minimum_block_size = max(size, 1<<12) // minimum block size of 4 KiB - new_block := memory_block_alloc(arena.backing_allocator, arena.minimum_block_size, loc) or_return + new_block := memory_block_alloc(arena.backing_allocator, arena.minimum_block_size, 0, loc) or_return arena.curr_block = new_block arena.total_capacity += new_block.capacity return nil @@ -195,7 +197,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, err = .Mode_Not_Implemented case .Free_All: arena_free_all(arena, location) - case .Resize: + case .Resize, .Resize_Non_Zeroed: old_data := ([^]byte)(old_memory) switch { diff --git a/core/runtime/default_temporary_allocator.odin b/base/runtime/default_temporary_allocator.odin similarity index 98% rename from core/runtime/default_temporary_allocator.odin rename to base/runtime/default_temporary_allocator.odin index c90f0388d..b355ded70 100644 --- a/core/runtime/default_temporary_allocator.odin +++ b/base/runtime/default_temporary_allocator.odin @@ -1,7 +1,7 @@ package runtime DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE: int : #config(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE, 4 * Megabyte) -NO_DEFAULT_TEMP_ALLOCATOR: bool : ODIN_OS == .Freestanding || ODIN_OS == .JS || ODIN_DEFAULT_TO_NIL_ALLOCATOR +NO_DEFAULT_TEMP_ALLOCATOR: bool : ODIN_OS == .Freestanding || ODIN_DEFAULT_TO_NIL_ALLOCATOR when NO_DEFAULT_TEMP_ALLOCATOR { Default_Temp_Allocator :: struct {} diff --git a/core/runtime/docs.odin b/base/runtime/docs.odin similarity index 92% rename from core/runtime/docs.odin rename to base/runtime/docs.odin index a520584c5..f6b439aa0 100644 --- a/core/runtime/docs.odin +++ b/base/runtime/docs.odin @@ -44,7 +44,7 @@ memcpy memove -## Procedures required by the LLVM backend +## Procedures required by the LLVM backend if u128/i128 is used umodti3 udivti3 modti3 @@ -59,11 +59,12 @@ truncdfhf2 gnu_h2f_ieee gnu_f2h_ieee extendhfsf2 + +## Procedures required by the LLVM backend if f16 is used __ashlti3 // wasm specific __multi3 // wasm specific - ## Required an entry point is defined (i.e. 'main') args__ @@ -156,7 +157,7 @@ __dynamic_map_get // dynamic map calls __dynamic_map_set // dynamic map calls -## Dynamic literals ([dymamic]T and map[K]V) (can be disabled with -no-dynamic-literals) +## Dynamic literals ([dynamic]T and map[K]V) (can be disabled with -no-dynamic-literals) __dynamic_array_reserve __dynamic_array_append diff --git a/core/runtime/dynamic_array_internal.odin b/base/runtime/dynamic_array_internal.odin similarity index 100% rename from core/runtime/dynamic_array_internal.odin rename to base/runtime/dynamic_array_internal.odin diff --git a/core/runtime/dynamic_map_internal.odin b/base/runtime/dynamic_map_internal.odin similarity index 88% rename from core/runtime/dynamic_map_internal.odin rename to base/runtime/dynamic_map_internal.odin index bdf0979cb..b95e3cd14 100644 --- a/core/runtime/dynamic_map_internal.odin +++ b/base/runtime/dynamic_map_internal.odin @@ -1,6 +1,6 @@ package runtime -import "core:intrinsics" +import "base:intrinsics" _ :: intrinsics // High performance, cache-friendly, open-addressed Robin Hood hashing hash map @@ -44,7 +44,7 @@ _ :: intrinsics MAP_LOAD_FACTOR :: 75 // Minimum log2 capacity. -MAP_MIN_LOG2_CAPACITY :: 6 // 64 elements +MAP_MIN_LOG2_CAPACITY :: 3 // 8 elements // Has to be less than 100% though. #assert(MAP_LOAD_FACTOR < 100) @@ -158,21 +158,21 @@ map_cell_index_static :: #force_inline proc "contextless" (cells: [^]Map_Cell($T } else when (N & (N - 1)) == 0 && N <= 8*size_of(uintptr) { // Likely case, N is a power of two because T is a power of two. + // Unique case, no need to index data here since only one element. + when N == 1 { + return &cells[index].data[0] + } + // Compute the integer log 2 of N, this is the shift amount to index the // correct cell. Odin's intrinsics.count_leading_zeros does not produce a // constant, hence this approach. We only need to check up to N = 64. - SHIFT :: 1 when N < 2 else - 2 when N < 4 else - 3 when N < 8 else - 4 when N < 16 else - 5 when N < 32 else 6 + SHIFT :: 1 when N == 2 else + 2 when N == 4 else + 3 when N == 8 else + 4 when N == 16 else + 5 when N == 32 else 6 #assert(SHIFT <= MAP_CACHE_LINE_LOG2) - // Unique case, no need to index data here since only one element. - when N == 1 { - return &cells[index >> SHIFT].data[0] - } else { - return &cells[index >> SHIFT].data[index & (N - 1)] - } + return &cells[index >> SHIFT].data[index & (N - 1)] } else { // Least likely (and worst case), we pay for a division operation but we // assume the compiler does not actually generate a division. N will be in the @@ -333,7 +333,7 @@ map_kvh_data_values_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^ } -@(private, require_results) +@(require_results) map_total_allocation_size :: #force_inline proc "contextless" (capacity: uintptr, info: ^Map_Info) -> uintptr { round :: #force_inline proc "contextless" (value: uintptr) -> uintptr { CACHE_MASK :: MAP_CACHE_LINE_SIZE - 1 @@ -350,6 +350,12 @@ map_total_allocation_size :: #force_inline proc "contextless" (capacity: uintptr return size } +@(require_results) +map_total_allocation_size_from_value :: #force_inline proc "contextless" (m: $M/map[$K]$V) -> uintptr { + return map_total_allocation_size(uintptr(cap(m)), map_info(M)) +} + + // The only procedure which needs access to the context is the one which allocates the map. @(require_results) map_alloc_dynamic :: proc "odin" (info: ^Map_Info, log2_capacity: uintptr, allocator := context.allocator, loc := #caller_location) -> (result: Raw_Map, err: Allocator_Error) { @@ -391,7 +397,8 @@ map_alloc_dynamic :: proc "odin" (info: ^Map_Info, log2_capacity: uintptr, alloc // arrays to reduce variance. This swapping can only be done with memcpy since // there is no type information. // -// This procedure returns the address of the just inserted value. +// This procedure returns the address of the just inserted value, and will +// return 'nil' if there was no room to insert the entry @(require_results) map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, ik: uintptr, iv: uintptr) -> (result: uintptr) { h := h @@ -415,6 +422,11 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^ tv := map_cell_index_dynamic(sv, info.vs, 1) swap_loop: for { + if distance > mask { + // Failed to find an empty slot and prevent infinite loop + panic("unable to insert into a map") + } + element_hash := hs[pos] if map_hash_is_empty(element_hash) { @@ -565,7 +577,7 @@ map_grow_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Inf @(require_results) -map_reserve_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uintptr, loc := #caller_location) -> Allocator_Error { +map_reserve_dynamic :: #force_no_inline proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uintptr, loc := #caller_location) -> Allocator_Error { @(require_results) ceil_log2 :: #force_inline proc "contextless" (x: uintptr) -> uintptr { z := intrinsics.count_leading_zeros(x) @@ -629,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 } @@ -676,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) @@ -688,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 } @@ -711,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 } @@ -737,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 @@ -841,6 +853,33 @@ __dynamic_map_get :: proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info: } } +__dynamic_map_get_key_and_value :: proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, key: rawptr) -> (key_ptr, value_ptr: rawptr) { + if m.len == 0 { + return nil, nil + } + pos := map_desired_position(m^, h) + distance := uintptr(0) + mask := (uintptr(1) << map_log2_cap(m^)) - 1 + ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info) + for { + element_hash := hs[pos] + if map_hash_is_empty(element_hash) { + return nil, nil + } else if distance > map_probe_distance(m^, element_hash, pos) { + return nil, nil + } else if element_hash == h { + other_key := rawptr(map_cell_index_dynamic(ks, info.ks, pos)) + if info.key_equal(key, other_key) { + key_ptr = other_key + value_ptr = rawptr(map_cell_index_dynamic(vs, info.vs, pos)) + return + } + } + pos = (pos + 1) & mask + distance += 1 + } +} + // IMPORTANT: USED WITHIN THE COMPILER __dynamic_map_check_grow :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> (err: Allocator_Error, has_grown: bool) { if m.len >= map_resize_threshold(m^) { @@ -871,9 +910,60 @@ __dynamic_map_set :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_In } result := map_insert_hash_dynamic(m, info, hash, uintptr(key), uintptr(value)) - m.len += 1 + if result != 0 { + m.len += 1 + } return rawptr(result) } +__dynamic_map_set_extra_without_hash :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, key, value: rawptr, loc := #caller_location) -> (prev_key_ptr, value_ptr: rawptr) { + return __dynamic_map_set_extra(m, info, info.key_hasher(key, map_seed(m^)), key, value, loc) +} + +__dynamic_map_set_extra :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, hash: Map_Hash, key, value: rawptr, loc := #caller_location) -> (prev_key_ptr, value_ptr: rawptr) { + if prev_key_ptr, value_ptr = __dynamic_map_get_key_and_value(m, info, hash, key); value_ptr != nil { + intrinsics.mem_copy_non_overlapping(value_ptr, value, info.vs.size_of_type) + return + } + + hash := hash + err, has_grown := __dynamic_map_check_grow(m, info, loc) + if err != nil { + return nil, nil + } + if has_grown { + hash = info.key_hasher(key, map_seed(m^)) + } + + result := map_insert_hash_dynamic(m, info, hash, uintptr(key), uintptr(value)) + if result != 0 { + m.len += 1 + } + return nil, rawptr(result) +} + +__dynamic_map_entry :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, key: rawptr, zero: rawptr, loc := #caller_location) -> (key_ptr: rawptr, value_ptr: rawptr, just_inserted: bool, err: Allocator_Error) { + hash := info.key_hasher(key, map_seed(m^)) + + if key_ptr, value_ptr = __dynamic_map_get_key_and_value(m, info, hash, key); value_ptr != nil { + return + } + + has_grown: bool + if err, has_grown = __dynamic_map_check_grow(m, info, loc); err != nil { + return + } else if has_grown { + hash = info.key_hasher(key, map_seed(m^)) + } + + value_ptr = rawptr(map_insert_hash_dynamic(m, info, hash, uintptr(key), uintptr(zero))) + assert(value_ptr != nil) + key_ptr = rawptr(map_cell_index_dynamic(map_data(m^), info.ks, map_desired_position(m^, hash))) + + m.len += 1 + just_inserted = true + return +} + // IMPORTANT: USED WITHIN THE COMPILER @(private) diff --git a/core/runtime/entry_unix.odin b/base/runtime/entry_unix.odin similarity index 77% rename from core/runtime/entry_unix.odin rename to base/runtime/entry_unix.odin index 0c718445a..e2223d5d6 100644 --- a/core/runtime/entry_unix.odin +++ b/base/runtime/entry_unix.odin @@ -1,8 +1,9 @@ -//+private -//+build linux, darwin, freebsd, openbsd +#+private +#+build linux, darwin, freebsd, openbsd, netbsd, haiku +#+no-instrumentation package runtime -import "core:intrinsics" +import "base:intrinsics" when ODIN_BUILD_MODE == .Dynamic { @(link_name="_odin_entry_point", linkage="strong", require/*, link_section=".init"*/) @@ -26,8 +27,16 @@ when ODIN_BUILD_MODE == .Dynamic { // to retrieve argc and argv from the stack when ODIN_ARCH == .amd64 { @require foreign import entry "entry_unix_no_crt_amd64.asm" + SYS_exit :: 60 } else when ODIN_ARCH == .i386 { @require foreign import entry "entry_unix_no_crt_i386.asm" + SYS_exit :: 1 + } else when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 { + @require foreign import entry "entry_unix_no_crt_darwin_arm64.asm" + SYS_exit :: 1 + } else when ODIN_ARCH == .riscv64 { + @require foreign import entry "entry_unix_no_crt_riscv64.asm" + SYS_exit :: 93 } @(link_name="_start_odin", linkage="strong", require) _start_odin :: proc "c" (argc: i32, argv: [^]cstring) -> ! { @@ -36,11 +45,7 @@ when ODIN_BUILD_MODE == .Dynamic { #force_no_inline _startup_runtime() intrinsics.__entry_point() #force_no_inline _cleanup_runtime() - when ODIN_ARCH == .amd64 { - intrinsics.syscall(/*SYS_exit = */60) - } else when ODIN_ARCH == .i386 { - intrinsics.syscall(/*SYS_exit = */1) - } + intrinsics.syscall(SYS_exit, 0) unreachable() } } else { diff --git a/core/runtime/entry_unix_no_crt_amd64.asm b/base/runtime/entry_unix_no_crt_amd64.asm similarity index 100% rename from core/runtime/entry_unix_no_crt_amd64.asm rename to base/runtime/entry_unix_no_crt_amd64.asm diff --git a/base/runtime/entry_unix_no_crt_darwin_arm64.asm b/base/runtime/entry_unix_no_crt_darwin_arm64.asm new file mode 100644 index 000000000..0f71fbdf8 --- /dev/null +++ b/base/runtime/entry_unix_no_crt_darwin_arm64.asm @@ -0,0 +1,20 @@ + .section __TEXT,__text + + ; NOTE(laytan): this should ideally be the -minimum-os-version flag but there is no nice way of preprocessing assembly in Odin. + ; 10 seems to be the lowest it goes and I don't see it mess with any targeted os version so this seems fine. + .build_version macos, 10, 0 + + .extern __start_odin + + .global _main + .align 2 +_main: + mov x5, sp ; use x5 as the stack pointer + + str x0, [x5] ; get argc into x0 (kernel passes 32-bit int argc as 64-bits on stack to keep alignment) + str x1, [x5, #8] ; get argv into x1 + + and sp, x5, #~15 ; force 16-byte alignment of the stack + + bl __start_odin ; call into Odin entry point + ret ; should never get here diff --git a/core/runtime/entry_unix_no_crt_i386.asm b/base/runtime/entry_unix_no_crt_i386.asm similarity index 100% rename from core/runtime/entry_unix_no_crt_i386.asm rename to base/runtime/entry_unix_no_crt_i386.asm diff --git a/base/runtime/entry_unix_no_crt_riscv64.asm b/base/runtime/entry_unix_no_crt_riscv64.asm new file mode 100644 index 000000000..756515b72 --- /dev/null +++ b/base/runtime/entry_unix_no_crt_riscv64.asm @@ -0,0 +1,10 @@ +.text + +.globl _start + +_start: + ld a0, 0(sp) + addi a1, sp, 8 + addi sp, sp, ~15 + call _start_odin + ebreak diff --git a/base/runtime/entry_wasm.odin b/base/runtime/entry_wasm.odin new file mode 100644 index 000000000..52bb0e072 --- /dev/null +++ b/base/runtime/entry_wasm.odin @@ -0,0 +1,39 @@ +#+private +#+build wasm32, wasm64p32 +#+no-instrumentation +package runtime + +import "base:intrinsics" + +when !ODIN_TEST && !ODIN_NO_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() + } + } +} diff --git a/core/runtime/entry_windows.odin b/base/runtime/entry_windows.odin similarity index 79% rename from core/runtime/entry_windows.odin rename to base/runtime/entry_windows.odin index a315c1209..8eb4cc7d1 100644 --- a/core/runtime/entry_windows.odin +++ b/base/runtime/entry_windows.odin @@ -1,16 +1,18 @@ -//+private -//+build windows +#+private +#+build windows +#+no-instrumentation package runtime -import "core:intrinsics" +import "base:intrinsics" when ODIN_BUILD_MODE == .Dynamic { @(link_name="DllMain", linkage="strong", require) - DllMain :: proc "stdcall" (hinstDLL: rawptr, fdwReason: u32, lpReserved: rawptr) -> b32 { + 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: @@ -28,7 +30,7 @@ when ODIN_BUILD_MODE == .Dynamic { } else when !ODIN_TEST && !ODIN_NO_ENTRY_POINT { when ODIN_ARCH == .i386 || ODIN_NO_CRT { @(link_name="mainCRTStartup", linkage="strong", require) - mainCRTStartup :: proc "stdcall" () -> i32 { + mainCRTStartup :: proc "system" () -> i32 { context = default_context() #force_no_inline _startup_runtime() intrinsics.__entry_point() diff --git a/core/runtime/error_checks.odin b/base/runtime/error_checks.odin similarity index 89% rename from core/runtime/error_checks.odin rename to base/runtime/error_checks.odin index 9d484979a..32a895c3f 100644 --- a/core/runtime/error_checks.odin +++ b/base/runtime/error_checks.odin @@ -1,27 +1,34 @@ package runtime +@(no_instrumentation) bounds_trap :: proc "contextless" () -> ! { when ODIN_OS == .Windows { windows_trap_array_bounds() + } else when ODIN_OS == .Orca { + abort_ext("", "", 0, "bounds trap") } else { trap() } } +@(no_instrumentation) 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() } } +@(disabled=ODIN_NO_BOUNDS_CHECK) bounds_check_error :: proc "contextless" (file: string, line, column: i32, index, count: int) { if uint(index) < uint(count) { return } - @(cold) + @(cold, no_instrumentation) handle_error :: proc "contextless" (file: string, line, column: i32, index, count: int) -> ! { print_caller_location(Source_Code_Location{file, line, column, ""}) print_string(" Index ") @@ -34,6 +41,7 @@ bounds_check_error :: proc "contextless" (file: string, line, column: i32, index handle_error(file, line, column, index, count) } +@(no_instrumentation) slice_handle_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int, len: int) -> ! { print_caller_location(Source_Code_Location{file, line, column, ""}) print_string(" Invalid slice indices ") @@ -46,6 +54,7 @@ slice_handle_error :: proc "contextless" (file: string, line, column: i32, lo, h bounds_trap() } +@(no_instrumentation) multi_pointer_slice_handle_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int) -> ! { print_caller_location(Source_Code_Location{file, line, column, ""}) print_string(" Invalid slice indices ") @@ -57,6 +66,7 @@ multi_pointer_slice_handle_error :: proc "contextless" (file: string, line, colu } +@(disabled=ODIN_NO_BOUNDS_CHECK) multi_pointer_slice_expr_error :: proc "contextless" (file: string, line, column: i32, lo, hi: int) { if lo <= hi { return @@ -64,6 +74,7 @@ multi_pointer_slice_expr_error :: proc "contextless" (file: string, line, column multi_pointer_slice_handle_error(file, line, column, lo, hi) } +@(disabled=ODIN_NO_BOUNDS_CHECK) slice_expr_error_hi :: proc "contextless" (file: string, line, column: i32, hi: int, len: int) { if 0 <= hi && hi <= len { return @@ -71,6 +82,7 @@ slice_expr_error_hi :: proc "contextless" (file: string, line, column: i32, hi: slice_handle_error(file, line, column, 0, hi, len) } +@(disabled=ODIN_NO_BOUNDS_CHECK) slice_expr_error_lo_hi :: proc "contextless" (file: string, line, column: i32, lo, hi: int, len: int) { if 0 <= lo && lo <= len && lo <= hi && hi <= len { return @@ -78,11 +90,12 @@ slice_expr_error_lo_hi :: proc "contextless" (file: string, line, column: i32, l slice_handle_error(file, line, column, lo, hi, len) } +@(disabled=ODIN_NO_BOUNDS_CHECK) dynamic_array_expr_error :: proc "contextless" (file: string, line, column: i32, low, high, max: int) { if 0 <= low && low <= high && high <= max { return } - @(cold) + @(cold, no_instrumentation) handle_error :: proc "contextless" (file: string, line, column: i32, low, high, max: int) -> ! { print_caller_location(Source_Code_Location{file, line, column, ""}) print_string(" Invalid dynamic array indices ") @@ -98,12 +111,13 @@ dynamic_array_expr_error :: proc "contextless" (file: string, line, column: i32, } +@(disabled=ODIN_NO_BOUNDS_CHECK) matrix_bounds_check_error :: proc "contextless" (file: string, line, column: i32, row_index, column_index, row_count, column_count: int) { if uint(row_index) < uint(row_count) && uint(column_index) < uint(column_count) { return } - @(cold) + @(cold, no_instrumentation) handle_error :: proc "contextless" (file: string, line, column: i32, row_index, column_index, row_count, column_count: int) -> ! { print_caller_location(Source_Code_Location{file, line, column, ""}) print_string(" Matrix indices [") @@ -127,7 +141,7 @@ when ODIN_NO_RTTI { if ok { return } - @(cold) + @(cold, no_instrumentation) handle_error :: proc "contextless" (file: string, line, column: i32) -> ! { print_caller_location(Source_Code_Location{file, line, column, ""}) print_string(" Invalid type assertion\n") @@ -140,7 +154,7 @@ when ODIN_NO_RTTI { if ok { return } - @(cold) + @(cold, no_instrumentation) handle_error :: proc "contextless" (file: string, line, column: i32) -> ! { print_caller_location(Source_Code_Location{file, line, column, ""}) print_string(" Invalid type assertion\n") @@ -153,7 +167,7 @@ when ODIN_NO_RTTI { if ok { return } - @(cold) + @(cold, no_instrumentation) handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid) -> ! { print_caller_location(Source_Code_Location{file, line, column, ""}) print_string(" Invalid type assertion from ") @@ -198,7 +212,7 @@ when ODIN_NO_RTTI { return id } - @(cold) + @(cold, no_instrumentation) handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid, from_data: rawptr) -> ! { actual := variant_type(from, from_data) @@ -220,11 +234,12 @@ when ODIN_NO_RTTI { } +@(disabled=ODIN_NO_BOUNDS_CHECK) make_slice_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len: int) { if 0 <= len { return } - @(cold) + @(cold, no_instrumentation) handle_error :: proc "contextless" (loc: Source_Code_Location, len: int) -> ! { print_caller_location(loc) print_string(" Invalid slice length for make: ") @@ -235,11 +250,12 @@ make_slice_error_loc :: #force_inline proc "contextless" (loc := #caller_locatio handle_error(loc, len) } +@(disabled=ODIN_NO_BOUNDS_CHECK) make_dynamic_array_error_loc :: #force_inline proc "contextless" (loc := #caller_location, len, cap: int) { if 0 <= len && len <= cap { return } - @(cold) + @(cold, no_instrumentation) handle_error :: proc "contextless" (loc: Source_Code_Location, len, cap: int) -> ! { print_caller_location(loc) print_string(" Invalid dynamic array parameters for make: ") @@ -252,11 +268,12 @@ make_dynamic_array_error_loc :: #force_inline proc "contextless" (loc := #caller handle_error(loc, len, cap) } +@(disabled=ODIN_NO_BOUNDS_CHECK) make_map_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_location, cap: int) { if 0 <= cap { return } - @(cold) + @(cold, no_instrumentation) handle_error :: proc "contextless" (loc: Source_Code_Location, cap: int) -> ! { print_caller_location(loc) print_string(" Invalid map capacity for make: ") @@ -270,19 +287,22 @@ make_map_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_loca - +@(disabled=ODIN_NO_BOUNDS_CHECK) bounds_check_error_loc :: #force_inline proc "contextless" (loc := #caller_location, index, count: int) { bounds_check_error(loc.file_path, loc.line, loc.column, index, count) } +@(disabled=ODIN_NO_BOUNDS_CHECK) slice_expr_error_hi_loc :: #force_inline proc "contextless" (loc := #caller_location, hi: int, len: int) { slice_expr_error_hi(loc.file_path, loc.line, loc.column, hi, len) } +@(disabled=ODIN_NO_BOUNDS_CHECK) slice_expr_error_lo_hi_loc :: #force_inline proc "contextless" (loc := #caller_location, lo, hi: int, len: int) { slice_expr_error_lo_hi(loc.file_path, loc.line, loc.column, lo, hi, len) } +@(disabled=ODIN_NO_BOUNDS_CHECK) dynamic_array_expr_error_loc :: #force_inline proc "contextless" (loc := #caller_location, low, high, max: int) { dynamic_array_expr_error(loc.file_path, loc.line, loc.column, low, high, max) } diff --git a/base/runtime/heap_allocator.odin b/base/runtime/heap_allocator.odin new file mode 100644 index 000000000..4b04dffef --- /dev/null +++ b/base/runtime/heap_allocator.odin @@ -0,0 +1,119 @@ +package runtime + +import "base:intrinsics" + +heap_allocator :: proc() -> Allocator { + return Allocator{ + procedure = heap_allocator_proc, + data = nil, + } +} + +heap_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { + // + // NOTE(tetra, 2020-01-14): The heap doesn't respect alignment. + // Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert + // padding. We also store the original pointer returned by heap_alloc right before + // the pointer we return to the user. + // + + aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr, old_size: int, zero_memory := true) -> ([]byte, Allocator_Error) { + // Not(flysand): We need to reserve enough space for alignment, which + // includes the user data itself, the space to store the pointer to + // allocation start, as well as the padding required to align both + // the user data and the pointer. + a := max(alignment, align_of(rawptr)) + space := a-1 + size_of(rawptr) + size + allocated_mem: rawptr + + force_copy := old_ptr != nil && alignment > align_of(rawptr) + + if old_ptr != nil && !force_copy { + original_old_ptr := ([^]rawptr)(old_ptr)[-1] + allocated_mem = heap_resize(original_old_ptr, space) + } else { + allocated_mem = heap_alloc(space, zero_memory) + } + aligned_mem := rawptr(([^]u8)(allocated_mem)[size_of(rawptr):]) + + ptr := uintptr(aligned_mem) + aligned_ptr := (ptr + uintptr(a)-1) & ~(uintptr(a)-1) + if allocated_mem == nil { + aligned_free(old_ptr) + aligned_free(allocated_mem) + return nil, .Out_Of_Memory + } + + aligned_mem = rawptr(aligned_ptr) + ([^]rawptr)(aligned_mem)[-1] = allocated_mem + + if force_copy { + mem_copy_non_overlapping(aligned_mem, old_ptr, min(old_size, size)) + aligned_free(old_ptr) + } + + return byte_slice(aligned_mem, size), nil + } + + aligned_free :: proc(p: rawptr) { + if p != nil { + heap_free(([^]rawptr)(p)[-1]) + } + } + + aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int, zero_memory := true) -> (new_memory: []byte, err: Allocator_Error) { + if p == nil { + return aligned_alloc(new_size, new_alignment, nil, old_size, zero_memory) + } + + new_memory = aligned_alloc(new_size, new_alignment, p, old_size, zero_memory) or_return + + // NOTE: heap_resize does not zero the new memory, so we do it + if zero_memory && new_size > old_size { + new_region := raw_data(new_memory[old_size:]) + intrinsics.mem_zero(new_region, new_size - old_size) + } + return + } + + switch mode { + case .Alloc, .Alloc_Non_Zeroed: + return aligned_alloc(size, alignment, nil, 0, mode == .Alloc) + + case .Free: + aligned_free(old_memory) + + case .Free_All: + return nil, .Mode_Not_Implemented + + case .Resize, .Resize_Non_Zeroed: + return aligned_resize(old_memory, old_size, size, alignment, mode == .Resize) + + 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 + + case .Query_Info: + return nil, .Mode_Not_Implemented + } + + return nil, nil +} + + +heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr { + return _heap_alloc(size, zero_memory) +} + +heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr { + return _heap_resize(ptr, new_size) +} + +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..be032a207 --- /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 new file mode 100644 index 000000000..507dbf318 --- /dev/null +++ b/base/runtime/heap_allocator_other.odin @@ -0,0 +1,18 @@ +#+build js, wasi, freestanding, essence +#+private +package runtime + +_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 "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 "contextless" (ptr: rawptr) { + context = default_context() + unimplemented("base:runtime 'heap_free' procedure is not supported on this platform") +} diff --git a/base/runtime/heap_allocator_unix.odin b/base/runtime/heap_allocator_unix.odin new file mode 100644 index 000000000..d4590d2dd --- /dev/null +++ b/base/runtime/heap_allocator_unix.odin @@ -0,0 +1,38 @@ +#+build linux, darwin, freebsd, openbsd, netbsd, haiku +#+private +package runtime + +when ODIN_OS == .Darwin { + foreign import libc "system:System.framework" +} else { + foreign import libc "system:c" +} + +@(default_calling_convention="c") +foreign libc { + @(link_name="malloc") _unix_malloc :: proc(size: int) -> rawptr --- + @(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 --- +} + +_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr { + if size <= 0 { + return nil + } + if zero_memory { + return _unix_calloc(1, size) + } else { + return _unix_malloc(size) + } +} + +_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 "contextless" (ptr: rawptr) { + _unix_free(ptr) +} diff --git a/base/runtime/heap_allocator_windows.odin b/base/runtime/heap_allocator_windows.odin new file mode 100644 index 000000000..e07df7559 --- /dev/null +++ b/base/runtime/heap_allocator_windows.odin @@ -0,0 +1,39 @@ +package runtime + +foreign import kernel32 "system:Kernel32.lib" + +@(private="file") +@(default_calling_convention="system") +foreign kernel32 { + // NOTE(bill): The types are not using the standard names (e.g. DWORD and LPVOID) to just minimizing the dependency + + // default_allocator + GetProcessHeap :: proc() -> rawptr --- + HeapAlloc :: proc(hHeap: rawptr, dwFlags: u32, dwBytes: uint) -> rawptr --- + HeapReAlloc :: proc(hHeap: rawptr, dwFlags: u32, lpMem: rawptr, dwBytes: uint) -> rawptr --- + HeapFree :: proc(hHeap: rawptr, dwFlags: u32, lpMem: rawptr) -> b32 --- +} + +_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr { + HEAP_ZERO_MEMORY :: 0x00000008 + return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size)) +} +_heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr { + if new_size == 0 { + _heap_free(ptr) + return nil + } + if ptr == nil { + return _heap_alloc(new_size) + } + + HEAP_ZERO_MEMORY :: 0x00000008 + return HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size)) +} +_heap_free :: proc "contextless" (ptr: rawptr) { + if ptr == nil { + return + } + HeapFree(GetProcessHeap(), 0, ptr) +} + diff --git a/core/runtime/internal.odin b/base/runtime/internal.odin similarity index 83% rename from core/runtime/internal.odin rename to base/runtime/internal.odin index d0e550743..59811a525 100644 --- a/core/runtime/internal.odin +++ b/base/runtime/internal.odin @@ -1,17 +1,17 @@ +#+vet !cast package runtime -import "core:intrinsics" +import "base:intrinsics" @(private="file") IS_WASM :: ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 @(private) RUNTIME_LINKAGE :: "strong" when ( - (ODIN_USE_SEPARATE_MODULES || + ODIN_USE_SEPARATE_MODULES || ODIN_BUILD_MODE == .Dynamic || - !ODIN_NO_CRT) && - !IS_WASM) else "internal" -RUNTIME_REQUIRE :: !ODIN_TILDE + !ODIN_NO_CRT) else "internal" +RUNTIME_REQUIRE :: false // !ODIN_TILDE @(private) __float16 :: f16 when __ODIN_LLVM_F16_SUPPORTED else u16 @@ -22,58 +22,14 @@ byte_slice :: #force_inline proc "contextless" (data: rawptr, len: int) -> []byt return ([^]byte)(data)[:max(len, 0)] } -bswap_16 :: proc "contextless" (x: u16) -> u16 { - return x>>8 | x<<8 -} - -bswap_32 :: proc "contextless" (x: u32) -> u32 { - return x>>24 | (x>>8)&0xff00 | (x<<8)&0xff0000 | x<<24 -} - -bswap_64 :: proc "contextless" (x: u64) -> u64 { - z := x - z = (z & 0x00000000ffffffff) << 32 | (z & 0xffffffff00000000) >> 32 - z = (z & 0x0000ffff0000ffff) << 16 | (z & 0xffff0000ffff0000) >> 16 - z = (z & 0x00ff00ff00ff00ff) << 8 | (z & 0xff00ff00ff00ff00) >> 8 - return z -} - -bswap_128 :: proc "contextless" (x: u128) -> u128 { - z := transmute([4]u32)x - z[0], z[3] = bswap_32(z[3]), bswap_32(z[0]) - z[1], z[2] = bswap_32(z[2]), bswap_32(z[1]) - return transmute(u128)z -} - -bswap_f16 :: proc "contextless" (f: f16) -> f16 { - x := transmute(u16)f - z := bswap_16(x) - return transmute(f16)z - -} - -bswap_f32 :: proc "contextless" (f: f32) -> f32 { - x := transmute(u32)f - z := bswap_32(x) - return transmute(f32)z - -} - -bswap_f64 :: proc "contextless" (f: f64) -> f64 { - x := transmute(u64)f - z := bswap_64(x) - return transmute(f64)z -} - - -is_power_of_two_int :: #force_inline proc(x: int) -> bool { +is_power_of_two_int :: #force_inline proc "contextless" (x: int) -> bool { if x <= 0 { return false } 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 @@ -84,14 +40,32 @@ align_forward_int :: #force_inline proc(ptr, align: int) -> int { return p } -is_power_of_two_uintptr :: #force_inline proc(x: uintptr) -> bool { +is_power_of_two_uint :: #force_inline proc "contextless" (x: uint) -> bool { if x <= 0 { return false } return (x & (x-1)) == 0 } -align_forward_uintptr :: #force_inline proc(ptr, align: uintptr) -> uintptr { +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 + } + return (x & (x-1)) == 0 +} + +align_forward_uintptr :: #force_inline proc "odin" (ptr, align: uintptr) -> uintptr { assert(is_power_of_two_uintptr(align)) p := ptr @@ -102,6 +76,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 @@ -132,16 +118,15 @@ mem_copy_non_overlapping :: proc "contextless" (dst, src: rawptr, len: int) -> r DEFAULT_ALIGNMENT :: 2*align_of(rawptr) mem_alloc_bytes :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { - if size == 0 { - return nil, nil - } - if allocator.procedure == nil { + assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc) + if size == 0 || allocator.procedure == nil{ return nil, nil } return allocator.procedure(allocator.data, .Alloc, size, alignment, nil, 0, loc) } mem_alloc :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { + assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc) if size == 0 || allocator.procedure == nil { return nil, nil } @@ -149,6 +134,7 @@ mem_alloc :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, a } mem_alloc_non_zeroed :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { + assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc) if size == 0 || allocator.procedure == nil { return nil, nil } @@ -187,7 +173,8 @@ mem_free_all :: #force_inline proc(allocator := context.allocator, loc := #calle return } -mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { +_mem_resize :: #force_inline proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, should_zero: bool, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { + assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc) if allocator.procedure == nil { return nil, nil } @@ -198,15 +185,27 @@ mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAUL } return } else if ptr == nil { - return allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc) + if should_zero { + return allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc) + } else { + return allocator.procedure(allocator.data, .Alloc_Non_Zeroed, new_size, alignment, nil, 0, loc) + } } else if old_size == new_size && uintptr(ptr) % uintptr(alignment) == 0 { data = ([^]byte)(ptr)[:old_size] return } - data, err = allocator.procedure(allocator.data, .Resize, new_size, alignment, ptr, old_size, loc) + if should_zero { + data, err = allocator.procedure(allocator.data, .Resize, new_size, alignment, ptr, old_size, loc) + } else { + data, err = allocator.procedure(allocator.data, .Resize_Non_Zeroed, new_size, alignment, ptr, old_size, loc) + } if err == .Mode_Not_Implemented { - data, err = allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc) + if should_zero { + data, err = allocator.procedure(allocator.data, .Alloc, new_size, alignment, nil, 0, loc) + } else { + data, err = allocator.procedure(allocator.data, .Alloc_Non_Zeroed, new_size, alignment, nil, 0, loc) + } if err != nil { return } @@ -216,6 +215,15 @@ mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAUL return } +mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { + assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc) + return _mem_resize(ptr, old_size, new_size, alignment, allocator, true, loc) +} +non_zero_mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { + assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc) + return _mem_resize(ptr, old_size, new_size, alignment, allocator, false, loc) +} + memory_equal :: proc "contextless" (x, y: rawptr, n: int) -> bool { switch { case n == 0: return true @@ -478,7 +486,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 @@ -499,7 +507,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}, @@ -589,36 +597,6 @@ string_decode_last_rune :: proc "contextless" (s: string) -> (rune, int) { return r, size } - -abs_f16 :: #force_inline proc "contextless" (x: f16) -> f16 { - return -x if x < 0 else x -} -abs_f32 :: #force_inline proc "contextless" (x: f32) -> f32 { - return -x if x < 0 else x -} -abs_f64 :: #force_inline proc "contextless" (x: f64) -> f64 { - return -x if x < 0 else x -} - -min_f16 :: #force_inline proc "contextless" (a, b: f16) -> f16 { - return a if a < b else b -} -min_f32 :: #force_inline proc "contextless" (a, b: f32) -> f32 { - return a if a < b else b -} -min_f64 :: #force_inline proc "contextless" (a, b: f64) -> f64 { - return a if a < b else b -} -max_f16 :: #force_inline proc "contextless" (a, b: f16) -> f16 { - return a if a > b else b -} -max_f32 :: #force_inline proc "contextless" (a, b: f32) -> f32 { - return a if a > b else b -} -max_f64 :: #force_inline proc "contextless" (a, b: f64) -> f64 { - return a if a > b else b -} - abs_complex32 :: #force_inline proc "contextless" (x: complex32) -> f16 { p, q := abs(real(x)), abs(imag(x)) if p < q { @@ -667,21 +645,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)) } @@ -722,15 +703,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(t0, t1, t2, t3) + return quaternion(w=f16(t0), x=f16(t1), y=f16(t2), z=f16(t3)) } mul_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 { @@ -742,7 +723,7 @@ mul_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 { t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1 t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0 - return quaternion(t0, t1, t2, t3) + return quaternion(w=t0, x=t1, y=t2, z=t3) } mul_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 { @@ -754,12 +735,12 @@ mul_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 { t2 := r0*q2 + r1*q3 + r2*q0 - r3*q1 t3 := r0*q3 - r1*q2 + r2*q1 + r3*q0 - return quaternion(t0, t1, t2, t3) + return quaternion(w=t0, x=t1, y=t2, z=t3) } quo_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 { - q0, q1, q2, q3 := real(q), imag(q), jmag(q), kmag(q) - r0, r1, r2, r3 := real(r), imag(r), jmag(r), kmag(r) + 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) @@ -768,7 +749,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(t0, t1, t2, t3) + return quaternion(w=f16(t0), x=f16(t1), y=f16(t2), z=f16(t3)) } quo_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 { @@ -782,7 +763,7 @@ quo_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 { t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2 t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2 - return quaternion(t0, t1, t2, t3) + return quaternion(w=t0, x=t1, y=t2, z=t3) } quo_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 { @@ -796,7 +777,7 @@ quo_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 { t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2 t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2 - return quaternion(t0, t1, t2, t3) + return quaternion(w=t0, x=t1, y=t2, z=t3) } @(link_name="__truncsfhf2", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) @@ -856,6 +837,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 { @@ -896,9 +881,6 @@ extendhfsf2 :: proc "c" (value: __float16) -> f32 { @(link_name="__floattidf", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) floattidf :: proc "c" (a: i128) -> f64 { -when IS_WASM { - return 0 -} else { DBL_MANT_DIG :: 53 if a == 0 { return 0.0 @@ -938,14 +920,10 @@ when IS_WASM { fb[0] = u32(a) // mantissa-low return transmute(f64)fb } -} @(link_name="__floattidf_unsigned", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) floattidf_unsigned :: proc "c" (a: u128) -> f64 { -when IS_WASM { - return 0 -} else { DBL_MANT_DIG :: 53 if a == 0 { return 0.0 @@ -983,7 +961,6 @@ when IS_WASM { fb[0] = u32(a) // mantissa-low return transmute(f64)fb } -} @@ -1017,9 +994,11 @@ udivmodti4 :: proc "c" (a, b: u128, rem: ^u128) -> u128 { return udivmod128(a, b, rem) } -@(link_name="__udivti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) -udivti3 :: proc "c" (a, b: u128) -> u128 { - return udivmodti4(a, b, nil) +when !IS_WASM { + @(link_name="__udivti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) + udivti3 :: proc "c" (a, b: u128) -> u128 { + return udivmodti4(a, b, nil) + } } @@ -1031,26 +1010,44 @@ 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 + s_a := a >> (128 - 1) // -1 if negative or 0 + s_b := b >> (128 - 1) + an := (a ~ s_a) - s_a // absolute + bn := (b ~ s_b) - s_b + + s_b ~= s_a // quotient sign + u_s_b := u128(s_b) + u_s_a := u128(s_a) + + r: u128 = --- + u := i128((udivmodti4(u128(an), u128(bn), &r) ~ u_s_b) - u_s_b) // negate if negative + rem^ = i128((r ~ u_s_a) - u_s_a) + return u } @(link_name="__divti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE) divti3 :: proc "c" (a, b: i128) -> i128 { - u := udivmodti4(transmute(u128)a, transmute(u128)b, nil) - return transmute(i128)u + s_a := a >> (128 - 1) // -1 if negative or 0 + s_b := b >> (128 - 1) + an := (a ~ s_a) - s_a // absolute + bn := (b ~ s_b) - s_b + + s_a ~= s_b // quotient sign + u_s_a := u128(s_a) + + return i128((udivmodti4(u128(an), u128(bn), nil) ~ u_s_a) - u_s_a) // negate if negative } @(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) @@ -1089,3 +1086,23 @@ 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) + } +} diff --git a/base/runtime/os_specific.odin b/base/runtime/os_specific.odin new file mode 100644 index 000000000..2807eaf90 --- /dev/null +++ b/base/runtime/os_specific.odin @@ -0,0 +1,7 @@ +package runtime + +_OS_Errno :: distinct int + +stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { + return _stderr_write(data) +} diff --git a/base/runtime/os_specific_bsd.odin b/base/runtime/os_specific_bsd.odin new file mode 100644 index 000000000..5d198484b --- /dev/null +++ b/base/runtime/os_specific_bsd.odin @@ -0,0 +1,26 @@ +#+build freebsd, openbsd, netbsd +#+private +package runtime + +foreign import libc "system:c" + +@(default_calling_convention="c") +foreign libc { + @(link_name="write") + _unix_write :: proc(fd: i32, buf: rawptr, size: int) -> int --- + + when ODIN_OS == .NetBSD { + @(link_name="__errno") __error :: proc() -> ^i32 --- + } else { + __error :: proc() -> ^i32 --- + } +} + +_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { + ret := _unix_write(2, raw_data(data), len(data)) + if ret < len(data) { + err := __error() + return int(ret), _OS_Errno(err^ if err != nil else 0) + } + return int(ret), 0 +} diff --git a/base/runtime/os_specific_darwin.odin b/base/runtime/os_specific_darwin.odin new file mode 100644 index 000000000..907899d7c --- /dev/null +++ b/base/runtime/os_specific_darwin.odin @@ -0,0 +1,28 @@ +#+build darwin +#+private +package runtime + +import "base:intrinsics" + +_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { + STDERR :: 2 + 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()^) + } +} diff --git a/base/runtime/os_specific_freestanding.odin b/base/runtime/os_specific_freestanding.odin new file mode 100644 index 000000000..f975f61b8 --- /dev/null +++ b/base/runtime/os_specific_freestanding.odin @@ -0,0 +1,8 @@ +#+build freestanding +#+private +package runtime + +// TODO(bill): reimplement `os.write` +_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { + return 0, -1 +} diff --git a/base/runtime/os_specific_haiku.odin b/base/runtime/os_specific_haiku.odin new file mode 100644 index 000000000..0d1ec7a03 --- /dev/null +++ b/base/runtime/os_specific_haiku.odin @@ -0,0 +1,21 @@ +#+build haiku +#+private +package runtime + +foreign import libc "system:c" + +foreign libc { + @(link_name="write") + _unix_write :: proc(fd: i32, buf: rawptr, size: int) -> int --- + + _errnop :: proc() -> ^i32 --- +} + +_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { + ret := _unix_write(2, raw_data(data), len(data)) + if ret < len(data) { + err := _errnop() + return int(ret), _OS_Errno(err^ if err != nil else 0) + } + return int(ret), 0 +} diff --git a/core/runtime/os_specific_js.odin b/base/runtime/os_specific_js.odin similarity index 63% rename from core/runtime/os_specific_js.odin rename to base/runtime/os_specific_js.odin index 246141d87..f1d12c808 100644 --- a/core/runtime/os_specific_js.odin +++ b/base/runtime/os_specific_js.odin @@ -1,9 +1,10 @@ -//+build js +#+build js +#+private package runtime foreign import "odin_env" -_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { +_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { foreign odin_env { write :: proc "contextless" (fd: u32, p: []byte) --- } diff --git a/base/runtime/os_specific_linux.odin b/base/runtime/os_specific_linux.odin new file mode 100644 index 000000000..d7b7371a7 --- /dev/null +++ b/base/runtime/os_specific_linux.odin @@ -0,0 +1,26 @@ +#+private +package runtime + +import "base:intrinsics" + +_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { + when ODIN_ARCH == .amd64 { + SYS_write :: uintptr(1) + } else when ODIN_ARCH == .arm64 { + SYS_write :: uintptr(64) + } else when ODIN_ARCH == .i386 { + SYS_write :: uintptr(4) + } else when ODIN_ARCH == .arm32 { + SYS_write :: uintptr(4) + } else when ODIN_ARCH == .riscv64 { + SYS_write :: uintptr(64) + } + + stderr :: 2 + + ret := int(intrinsics.syscall(SYS_write, uintptr(stderr), uintptr(raw_data(data)), uintptr(len(data)))) + if ret < 0 && ret > -4096 { + return 0, _OS_Errno(-ret) + } + return ret, 0 +} diff --git a/base/runtime/os_specific_orca.odin b/base/runtime/os_specific_orca.odin new file mode 100644 index 000000000..876d7d44b --- /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 new file mode 100644 index 000000000..aa562050c --- /dev/null +++ b/base/runtime/os_specific_wasi.odin @@ -0,0 +1,55 @@ +#+build wasi +#+private +package runtime + +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) { + 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/os_specific_windows.odin b/base/runtime/os_specific_windows.odin new file mode 100644 index 000000000..b966193ca --- /dev/null +++ b/base/runtime/os_specific_windows.odin @@ -0,0 +1,51 @@ +#+build windows +#+private +package runtime + +foreign import kernel32 "system:Kernel32.lib" + +@(private="file") +@(default_calling_convention="system") +foreign kernel32 { + // NOTE(bill): The types are not using the standard names (e.g. DWORD and LPVOID) to just minimizing the dependency + + // stderr_write + GetStdHandle :: proc(which: u32) -> rawptr --- + SetHandleInformation :: proc(hObject: rawptr, dwMask: u32, dwFlags: u32) -> b32 --- + WriteFile :: proc(hFile: rawptr, lpBuffer: rawptr, nNumberOfBytesToWrite: u32, lpNumberOfBytesWritten: ^u32, lpOverlapped: rawptr) -> b32 --- + GetLastError :: proc() -> u32 --- +} + +_stderr_write :: proc "contextless" (data: []byte) -> (n: int, err: _OS_Errno) #no_bounds_check { + if len(data) == 0 { + return 0, 0 + } + + STD_ERROR_HANDLE :: ~u32(0) -12 + 1 + HANDLE_FLAG_INHERIT :: 0x00000001 + MAX_RW :: 1<<30 + + h := GetStdHandle(STD_ERROR_HANDLE) + when size_of(uintptr) == 8 { + SetHandleInformation(h, HANDLE_FLAG_INHERIT, 0) + } + + single_write_length: u32 + total_write: i64 + length := i64(len(data)) + + for total_write < length { + remaining := length - total_write + to_write := u32(min(i32(remaining), MAX_RW)) + + e := WriteFile(h, &data[total_write], to_write, &single_write_length, nil) + if single_write_length <= 0 || !e { + err = _OS_Errno(GetLastError()) + n = int(total_write) + return + } + total_write += i64(single_write_length) + } + n = int(total_write) + return +} diff --git a/core/runtime/print.odin b/base/runtime/print.odin similarity index 82% rename from core/runtime/print.odin rename to base/runtime/print.odin index 87c8757d5..c28fd593d 100644 --- a/core/runtime/print.odin +++ b/base/runtime/print.odin @@ -6,7 +6,7 @@ _INTEGER_DIGITS :: "0123456789abcdefghijklmnopqrstuvwxyz" _INTEGER_DIGITS_VAR := _INTEGER_DIGITS when !ODIN_NO_RTTI { - print_any_single :: proc "contextless" (arg: any) { + print_any_single :: #force_no_inline proc "contextless" (arg: any) { x := arg if x.data == nil { print_string("nil") @@ -72,7 +72,7 @@ when !ODIN_NO_RTTI { print_string("") } } - println_any :: proc "contextless" (args: ..any) { + println_any :: #force_no_inline proc "contextless" (args: ..any) { context = default_context() loop: for arg, i in args { assert(arg.id != nil) @@ -122,14 +122,14 @@ encode_rune :: proc "contextless" (c: rune) -> ([4]u8, int) { return buf, 4 } -print_string :: proc "contextless" (str: string) -> (n: int) { - n, _ = os_write(transmute([]byte)str) +print_string :: #force_no_inline proc "contextless" (str: string) -> (n: int) { + n, _ = stderr_write(transmute([]byte)str) return } -print_strings :: proc "contextless" (args: ..string) -> (n: int) { +print_strings :: #force_no_inline proc "contextless" (args: ..string) -> (n: int) { for str in args { - m, err := os_write(transmute([]byte)str) + m, err := stderr_write(transmute([]byte)str) n += m if err != 0 { break @@ -138,12 +138,12 @@ print_strings :: proc "contextless" (args: ..string) -> (n: int) { return } -print_byte :: proc "contextless" (b: byte) -> (n: int) { - n, _ = os_write([]byte{b}) +print_byte :: #force_no_inline proc "contextless" (b: byte) -> (n: int) { + n, _ = stderr_write([]byte{b}) return } -print_encoded_rune :: proc "contextless" (r: rune) { +print_encoded_rune :: #force_no_inline proc "contextless" (r: rune) { print_byte('\'') switch r { @@ -170,7 +170,7 @@ print_encoded_rune :: proc "contextless" (r: rune) { print_byte('\'') } -print_rune :: proc "contextless" (r: rune) -> int #no_bounds_check { +print_rune :: #force_no_inline proc "contextless" (r: rune) -> int #no_bounds_check { RUNE_SELF :: 0x80 if r < RUNE_SELF { @@ -178,12 +178,12 @@ print_rune :: proc "contextless" (r: rune) -> int #no_bounds_check { } b, n := encode_rune(r) - m, _ := os_write(b[:n]) + m, _ := stderr_write(b[:n]) return m } -print_u64 :: proc "contextless" (x: u64) #no_bounds_check { +print_u64 :: #force_no_inline proc "contextless" (x: u64) #no_bounds_check { a: [129]byte i := len(a) b := u64(10) @@ -194,11 +194,11 @@ print_u64 :: proc "contextless" (x: u64) #no_bounds_check { } i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b] - os_write(a[i:]) + stderr_write(a[i:]) } -print_i64 :: proc "contextless" (x: i64) #no_bounds_check { +print_i64 :: #force_no_inline proc "contextless" (x: i64) #no_bounds_check { b :: i64(10) u := x @@ -216,32 +216,36 @@ print_i64 :: proc "contextless" (x: i64) #no_bounds_check { i -= 1; a[i] = '-' } - os_write(a[i:]) + stderr_write(a[i:]) } print_uint :: proc "contextless" (x: uint) { print_u64(u64(x)) } print_uintptr :: proc "contextless" (x: uintptr) { print_u64(u64(x)) } print_int :: proc "contextless" (x: int) { print_i64(i64(x)) } -print_caller_location :: proc "contextless" (loc: Source_Code_Location) { +print_caller_location :: #force_no_inline proc "contextless" (loc: Source_Code_Location) { print_string(loc.file_path) when ODIN_ERROR_POS_STYLE == .Default { print_byte('(') print_u64(u64(loc.line)) - print_byte(':') - print_u64(u64(loc.column)) + if loc.column != 0 { + print_byte(':') + print_u64(u64(loc.column)) + } print_byte(')') } else when ODIN_ERROR_POS_STYLE == .Unix { print_byte(':') print_u64(u64(loc.line)) - print_byte(':') - print_u64(u64(loc.column)) + if loc.column != 0 { + print_byte(':') + print_u64(u64(loc.column)) + } print_byte(':') } else { #panic("unhandled ODIN_ERROR_POS_STYLE") } } -print_typeid :: proc "contextless" (id: typeid) { +print_typeid :: #force_no_inline proc "contextless" (id: typeid) { when ODIN_NO_RTTI { if id == nil { print_string("nil") @@ -257,7 +261,9 @@ print_typeid :: proc "contextless" (id: typeid) { } } } -print_type :: proc "contextless" (ti: ^Type_Info) { + +@(optimization_mode="favor_size") +print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) { if ti == nil { print_string("nil") return @@ -395,15 +401,16 @@ print_type :: 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(": ") @@ -459,24 +466,26 @@ print_type :: proc "contextless" (ti: ^Type_Info) { } print_byte(']') + case Type_Info_Bit_Field: + print_string("bit_field ") + print_type(info.backing_type) + print_string(" {") + for name, i in info.names[:info.field_count] { + if i > 0 { print_string(", ") } + print_string(name) + print_string(": ") + print_type(info.types[i]) + print_string(" | ") + print_u64(u64(info.bit_sizes[i])) + } + print_byte('}') + case Type_Info_Simd_Vector: print_string("#simd[") print_u64(u64(info.count)) print_byte(']') print_type(info.elem) - - case Type_Info_Relative_Pointer: - print_string("#relative(") - print_type(info.base_integer) - print_string(") ") - print_type(info.pointer) - - case Type_Info_Relative_Multi_Pointer: - print_string("#relative(") - print_type(info.base_integer) - print_string(") ") - print_type(info.pointer) case Type_Info_Matrix: print_string("matrix[") diff --git a/core/runtime/procs.odin b/base/runtime/procs.odin similarity index 61% rename from core/runtime/procs.odin rename to base/runtime/procs.odin index 1592c608b..002a6501f 100644 --- a/core/runtime/procs.odin +++ b/base/runtime/procs.odin @@ -4,7 +4,7 @@ when ODIN_NO_CRT && ODIN_OS == .Windows { foreign import lib "system:NtDll.lib" @(private="file") - @(default_calling_convention="stdcall") + @(default_calling_convention="system") foreign lib { RtlMoveMemory :: proc(dst, s: rawptr, length: int) --- RtlFillMemory :: proc(dst: rawptr, length: int, fill: i32) --- @@ -25,21 +25,38 @@ 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 } } return ptr } - + + @(link_name="bzero", linkage="strong", require) + bzero :: proc "c" (ptr: rawptr, #any_int len: int_t) -> rawptr { + if ptr != nil && len != 0 { + p := ([^]byte)(ptr) + for i := int_t(0); i < len; i += 1 { + p[i] = 0 + } + } + return ptr + } + @(link_name="memmove", linkage="strong", require) - memmove :: proc "c" (dst, src: rawptr, len: int) -> rawptr { + 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 @@ -52,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 @@ -60,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] } } @@ -81,4 +98,4 @@ when ODIN_NO_CRT && ODIN_OS == .Windows { } return ptr } -} \ No newline at end of file +} diff --git a/core/runtime/procs_darwin.odin b/base/runtime/procs_darwin.odin similarity index 95% rename from core/runtime/procs_darwin.odin rename to base/runtime/procs_darwin.odin index 9c53b5b16..4f4903d47 100644 --- a/core/runtime/procs_darwin.odin +++ b/base/runtime/procs_darwin.odin @@ -1,9 +1,9 @@ -//+private +#+private package runtime foreign import "system:Foundation.framework" -import "core:intrinsics" +import "base:intrinsics" objc_id :: ^intrinsics.objc_object objc_Class :: ^intrinsics.objc_class diff --git a/core/runtime/procs_js.odin b/base/runtime/procs_js.odin similarity index 96% rename from core/runtime/procs_js.odin rename to base/runtime/procs_js.odin index d3e12410c..58bed808d 100644 --- a/core/runtime/procs_js.odin +++ b/base/runtime/procs_js.odin @@ -1,4 +1,4 @@ -//+build js +#+build js package runtime init_default_context_for_js: Context diff --git a/base/runtime/procs_wasm.odin b/base/runtime/procs_wasm.odin new file mode 100644 index 000000000..7e03656ca --- /dev/null +++ b/base/runtime/procs_wasm.odin @@ -0,0 +1,97 @@ +#+build wasm32, wasm64p32 +package runtime + +@(private="file") +ti_int :: struct #raw_union { + using s: struct { lo, hi: u64 }, + all: i128, +} + +@(private="file") +ti_uint :: struct #raw_union { + using s: struct { lo, hi: u64 }, + all: u128, +} + +@(link_name="__ashlti3", linkage="strong") +__ashlti3 :: proc "contextless" (a: i128, b: u32) -> i128 { + bits :: 64 + + input: ti_int = --- + result: ti_int = --- + input.all = a + if b & bits != 0 { + result.lo = 0 + result.hi = input.lo << (b-bits) + } else { + if b == 0 { + return a + } + result.lo = input.lo<>(bits-b)) + } + return result.all +} + +__ashlti3_unsigned :: proc "contextless" (a: u128, b: u32) -> u128 { + return cast(u128)__ashlti3(cast(i128)a, b) +} + +@(link_name="__mulddi3", linkage="strong") +__mulddi3 :: proc "contextless" (a, b: u64) -> i128 { + r: ti_int + bits :: 32 + + mask :: ~u64(0) >> bits + r.lo = (a & mask) * (b & mask) + t := r.lo >> bits + r.lo &= mask + t += (a >> bits) * (b & mask) + r.lo += (t & mask) << bits + r.hi = t >> bits + t = r.lo >> bits + r.lo &= mask + t += (b >> bits) * (a & mask) + r.lo += (t & mask) << bits + r.hi += t >> bits + r.hi += (a >> bits) * (b >> bits) + return r.all +} + +@(link_name="__multi3", linkage="strong") +__multi3 :: proc "contextless" (a, b: i128) -> i128 { + x, y, r: ti_int + + x.all = a + y.all = b + r.all = __mulddi3(x.lo, y.lo) + r.hi += x.hi*y.lo + x.lo*y.hi + return r.all +} + +@(link_name="__udivti3", linkage="strong") +udivti3 :: proc "c" (la, ha, lb, hb: u64) -> u128 { + a, b: ti_uint + a.lo, a.hi = la, ha + b.lo, b.hi = lb, hb + return udivmodti4(a.all, b.all, nil) +} + +@(link_name="__lshrti3", linkage="strong") +__lshrti3 :: proc "c" (a: i128, b: u32) -> i128 { + bits :: 64 + + input, result: ti_int + input.all = a + if b & bits != 0 { + result.hi = 0 + result.lo = input.hi >> (b - bits) + } else if b == 0 { + return a + } else { + result.hi = input.hi >> b + result.lo = (input.hi << (bits - b)) | (input.lo >> b) + } + + return result.all +} diff --git a/core/runtime/procs_windows_amd64.asm b/base/runtime/procs_windows_amd64.asm similarity index 100% rename from core/runtime/procs_windows_amd64.asm rename to base/runtime/procs_windows_amd64.asm diff --git a/core/runtime/procs_windows_amd64.odin b/base/runtime/procs_windows_amd64.odin similarity index 74% rename from core/runtime/procs_windows_amd64.odin rename to base/runtime/procs_windows_amd64.odin index e430357be..81d2cfb5d 100644 --- a/core/runtime/procs_windows_amd64.odin +++ b/base/runtime/procs_windows_amd64.odin @@ -1,11 +1,12 @@ -//+private +#+private +#+no-instrumentation package runtime foreign import kernel32 "system:Kernel32.lib" @(private) foreign kernel32 { - RaiseException :: proc "stdcall" (dwExceptionCode, dwExceptionFlags, nNumberOfArguments: u32, lpArguments: ^uint) -> ! --- + RaiseException :: proc "system" (dwExceptionCode, dwExceptionFlags, nNumberOfArguments: u32, lpArguments: ^uint) -> ! --- } windows_trap_array_bounds :: proc "contextless" () -> ! { diff --git a/core/runtime/procs_windows_i386.odin b/base/runtime/procs_windows_i386.odin similarity index 79% rename from core/runtime/procs_windows_i386.odin rename to base/runtime/procs_windows_i386.odin index f810197f1..99c314228 100644 --- a/core/runtime/procs_windows_i386.odin +++ b/base/runtime/procs_windows_i386.odin @@ -1,4 +1,5 @@ -//+private +#+private +#+no-instrumentation package runtime @require foreign import "system:int64.lib" @@ -12,7 +13,7 @@ windows_trap_array_bounds :: proc "contextless" () -> ! { EXCEPTION_ARRAY_BOUNDS_EXCEEDED :: 0xC000008C foreign kernel32 { - RaiseException :: proc "stdcall" (dwExceptionCode, dwExceptionFlags, nNumberOfArguments: DWORD, lpArguments: ^ULONG_PTR) -> ! --- + RaiseException :: proc "system" (dwExceptionCode, dwExceptionFlags, nNumberOfArguments: DWORD, lpArguments: ^ULONG_PTR) -> ! --- } RaiseException(EXCEPTION_ARRAY_BOUNDS_EXCEEDED, 0, 0, nil) 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/thread_management.odin b/base/runtime/thread_management.odin new file mode 100644 index 000000000..cabd4691c --- /dev/null +++ b/base/runtime/thread_management.odin @@ -0,0 +1,34 @@ +package runtime + +Thread_Local_Cleaner :: #type proc "odin" () + +@(private="file") +thread_local_cleaners: [8]Thread_Local_Cleaner + +// Add a procedure that will be run at the end of a thread for the purpose of +// deallocating state marked as `thread_local`. +// +// Intended to be called in an `init` procedure of a package with +// dynamically-allocated memory that is stored in `thread_local` variables. +add_thread_local_cleaner :: proc "contextless" (p: Thread_Local_Cleaner) { + for &v in thread_local_cleaners { + if v == nil { + v = p + return + } + } + panic_contextless("There are no more thread-local cleaner slots available.") +} + +// Run all of the thread-local cleaner procedures. +// +// Intended to be called by the internals of a threading API at the end of a +// thread's lifetime. +run_thread_local_cleaners :: proc "odin" () { + for p in thread_local_cleaners { + if p == nil { + break + } + p() + } +} diff --git a/core/runtime/udivmod128.odin b/base/runtime/udivmod128.odin similarity index 94% rename from core/runtime/udivmod128.odin rename to base/runtime/udivmod128.odin index 87ef73c2c..8cc70df55 100644 --- a/core/runtime/udivmod128.odin +++ b/base/runtime/udivmod128.odin @@ -1,6 +1,6 @@ package runtime -import "core:intrinsics" +import "base:intrinsics" udivmod128 :: proc "c" (a, b: u128, rem: ^u128) -> u128 { _ctz :: intrinsics.count_trailing_zeros @@ -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..574f3dd06 --- /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/RAD-LICENSE b/bin/RAD-LICENSE new file mode 100644 index 000000000..1a1ccbcd2 --- /dev/null +++ b/bin/RAD-LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2024 Epic Games Tools + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the “Software”), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/bin/lld-link.exe b/bin/lld-link.exe new file mode 100644 index 000000000..da6527264 Binary files /dev/null and b/bin/lld-link.exe differ 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/bin/radlink.exe b/bin/radlink.exe new file mode 100644 index 000000000..bb2fd206e Binary files /dev/null and b/bin/radlink.exe differ diff --git a/bin/wasm-ld.exe b/bin/wasm-ld.exe new file mode 100644 index 000000000..da6527264 Binary files /dev/null and b/bin/wasm-ld.exe differ diff --git a/build.bat b/build.bat index b0ebfe634..a788a8c04 100644 --- a/build.bat +++ b/build.bat @@ -19,12 +19,16 @@ if "%VSCMD_ARG_TGT_ARCH%" neq "x64" ( ) ) -for /f "usebackq tokens=1,2 delims=,=- " %%i in (`wmic os get LocalDateTime /value`) do @if %%i==LocalDateTime ( - set CURR_DATE_TIME=%%j -) +pushd misc +cl /nologo get-date.c +popd +for /f %%i in ('misc\get-date') do ( + set CURR_DATE_TIME=%%i +) set curr_year=%CURR_DATE_TIME:~0,4% set curr_month=%CURR_DATE_TIME:~4,2% +set curr_day=%CURR_DATE_TIME:~6,2% :: Make sure this is a decent name and not generic set exe_name=odin.exe @@ -45,23 +49,49 @@ if "%2" == "1" ( set nightly=0 ) -set odin_version_raw="dev-%curr_year%-%curr_month%" +if %release_mode% equ 0 ( + set V1=%curr_year% + set V2=%curr_month% + set V3=%curr_day% +) else ( + set V1=%curr_year% + set V2=%curr_month% + set V3=0 +) +set V4=0 +set odin_version_full="%V1%.%V2%.%V3%.%V4%" +set odin_version_raw="dev-%V1%-%V2%" 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%\" +rem fileversion is defined as {Major,Minor,Build,Private: u16} so a bit limited +set rc_flags=-nologo ^ +-DV1=%V1% -DV2=%V2% -DV3=%V3% -DV4=%V4% ^ +-DVF=%odin_version_full% -DNIGHTLY=%nightly% + +where /Q git.exe || goto skip_git_hash if not exist .git\ goto skip_git_hash for /f "tokens=1,2" %%i IN ('git show "--pretty=%%cd %%h" "--date=format:%%Y-%%m" --no-patch --no-notes HEAD') do ( set odin_version_raw=dev-%%i set GIT_SHA=%%j ) -if %ERRORLEVEL% equ 0 set compiler_defines=%compiler_defines% -DGIT_SHA=\"%GIT_SHA%\" +if %ERRORLEVEL% equ 0 ( + set compiler_defines=%compiler_defines% -DGIT_SHA=\"%GIT_SHA%\" + set rc_flags=%rc_flags% -DGIT_SHA=%GIT_SHA% -DVP=%odin_version_raw%:%GIT_SHA% +) else ( + set rc_flags=%rc_flags% -DVP=%odin_version_raw% +) :skip_git_hash if %nightly% equ 1 set compiler_defines=%compiler_defines% -DNIGHTLY if %release_mode% EQU 0 ( rem Debug set compiler_flags=%compiler_flags% -Od -MDd -Z7 + set rc_flags=%rc_flags% -D_DEBUG ) else ( rem Release set compiler_flags=%compiler_flags% -O2 -MT -Z7 set compiler_defines=%compiler_defines% -DNO_ARRAY_BOUNDS_CHECK @@ -79,6 +109,8 @@ set libs= ^ kernel32.lib ^ Synchronization.lib ^ bin\llvm\windows\LLVM-C.lib +set odin_res=misc\odin.res +set odin_rc=misc\odin.rc rem DO NOT TOUCH! rem THIS TILDE STUFF IS FOR DEVELOPMENT ONLY! @@ -90,7 +122,7 @@ if %tilde_backend% EQU 1 ( rem DO NOT TOUCH! -set linker_flags= -incremental:no -opt:ref -subsystem:console +set linker_flags= -incremental:no -opt:ref -subsystem:console -MANIFEST:EMBED if %release_mode% EQU 0 ( rem Debug set linker_flags=%linker_flags% -debug /NATVIS:src\odin_compiler.natvis @@ -99,18 +131,24 @@ if %release_mode% EQU 0 ( rem Debug ) set compiler_settings=%compiler_includes% %compiler_flags% %compiler_warnings% %compiler_defines% -set linker_settings=%libs% %linker_flags% +set linker_settings=%libs% %odin_res% %linker_flags% del *.pdb > NUL 2> NUL del *.ilk > NUL 2> NUL +rc %rc_flags% %odin_rc% cl %compiler_settings% "src\main.cpp" "src\libtommath.cpp" /link %linker_settings% -OUT:%exe_name% +mt -nologo -inputresource:%exe_name%;#1 -manifest misc\odin.manifest -outputresource:%exe_name%;#1 -validate_manifest -identity:"odin, processorArchitecture=amd64, version=%odin_version_full%, type=win32" if %errorlevel% neq 0 goto end_of_build call build_vendor.bat if %errorlevel% neq 0 goto end_of_build -if %release_mode% EQU 0 odin run examples/demo +rem If the demo doesn't run for you and your CPU is more than a decade old, try -microarch:native +if %release_mode% EQU 0 odin run examples/demo -vet -strict-style -resource:examples/demo/demo.rc -- Hellope World + +rem Many non-compiler devs seem to run debug build but don't realize. +if %release_mode% EQU 0 echo: & echo Debug compiler built. Note: run "build.bat release" if you want a faster, release mode compiler. del *.obj > NUL 2> NUL diff --git a/build_odin.sh b/build_odin.sh index 2a2505c97..d909de5c8 100755 --- a/build_odin.sh +++ b/build_odin.sh @@ -2,7 +2,6 @@ set -eu : ${CPPFLAGS=} -: ${CXX=clang++} : ${CXXFLAGS=} : ${LDFLAGS=} : ${LLVM_CONFIG=} @@ -24,17 +23,32 @@ error() { exit 1 } +# Brew advises people not to add llvm to their $PATH, so try and use brew to find it. +if [ -z "$LLVM_CONFIG" ] && [ -n "$(command -v brew)" ]; then + if [ -n "$(command -v $(brew --prefix llvm@19)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@19)/bin/llvm-config" + elif [ -n "$(command -v $(brew --prefix llvm@18)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@18)/bin/llvm-config" + elif [ -n "$(command -v $(brew --prefix llvm@17)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@17)/bin/llvm-config" + elif [ -n "$(command -v $(brew --prefix llvm@14)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@14)/bin/llvm-config" + fi +fi + if [ -z "$LLVM_CONFIG" ]; then # darwin, linux, openbsd - if [ -n "$(command -v llvm-config-17)" ]; then LLVM_CONFIG="llvm-config-17" + if [ -n "$(command -v llvm-config-19)" ]; then LLVM_CONFIG="llvm-config-19" + elif [ -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-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-config19)" ]; then LLVM_CONFIG="llvm-config19" + 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 @@ -42,42 +56,66 @@ 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 19 ]; then + error "Invalid LLVM version $LLVM_VERSION: must be 11, 12, 13, 14, 17, 18 or 19" 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 [ "$OS_ARCH" = "arm64" ]; then + if [ $LLVM_VERSION_MAJOR -lt 13 ]; then + error "Invalid LLVM version $LLVM_VERSION: Darwin Arm64 requires LLVM 13, 14, 17, 18 or 19" fi fi - CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" - LDFLAGS="$LDFLAGS -liconv -ldl -framework System" - LDFLAGS="$LDFLAGS -lLLVM-C" + 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)" # Copy libLLVM*.so into current directory for linking # NOTE: This is needed by the Linux release pipeline! - cp $(readlink -f $($LLVM_CONFIG --libfiles)) ./ + # cp $(readlink -f $($LLVM_CONFIG --libfiles)) ./ LDFLAGS="$LDFLAGS -Wl,-rpath=\$ORIGIN" ;; OpenBSD) - CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" + CXXFLAGS="$CXXFLAGS -I/usr/local/include $($LLVM_CONFIG --cxxflags --ldflags)" + LDFLAGS="$LDFLAGS -L/usr/local/lib -liconv" + LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)" + ;; +Haiku) + CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags) -I/system/develop/headers/private/shared -I/system/develop/headers/private/kernel" LDFLAGS="$LDFLAGS -liconv" LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)" ;; @@ -95,7 +133,7 @@ build_odin() { EXTRAFLAGS="-O3" ;; release-native) - if [ "$OS_ARCH" == "arm64" ]; then + if [ "$OS_ARCH" = "arm64" ] || [ "$OS_ARCH" = "aarch64" ]; then # Use preferred flag for Arm (ie arm64 / aarch64 / etc) EXTRAFLAGS="-O3 -mcpu=native" else @@ -117,23 +155,32 @@ build_odin() { } run_demo() { - ./odin run examples/demo/demo.odin -file + ./odin run examples/demo -vet -strict-style -- Hellope World } if [ $# -eq 0 ]; then build_odin debug run_demo + + : ${PROGRAM:=$0} + printf "\nDebug compiler built. Note: run \"$PROGRAM release\" or \"$PROGRAM release-native\" if you want a faster, release mode compiler.\n" elif [ $# -eq 1 ]; then case $1 in report) - [ ! -f "./odin" ] && build_odin debug + if [ ! -f "./odin" ]; then + build_odin debug + run_demo + fi ./odin report ;; + debug) + build_odin debug + run_demo + ;; *) build_odin $1 ;; esac - run_demo else error "Too many arguments!" fi diff --git a/ci/build_linux_static.sh b/ci/build_linux_static.sh new file mode 100755 index 000000000..f821cbb59 --- /dev/null +++ b/ci/build_linux_static.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env sh +# Intended for use in Alpine containers, see the "nightly" Github action for a list of dependencies + +CXX="clang++-18" +LLVM_CONFIG="llvm-config-18" + +DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value" + +CPPFLAGS="-DODIN_VERSION_RAW=\"dev-$(date +"%Y-%m")\"" +CXXFLAGS="-std=c++14 $($LLVM_CONFIG --cxxflags --ldflags)" + +LDFLAGS="-static -lm -lzstd -lz -lffi -pthread -ldl -fuse-ld=mold" +LDFLAGS="$LDFLAGS $($LLVM_CONFIG --link-static --ldflags --libs --system-libs --libfiles)" +LDFLAGS="$LDFLAGS -Wl,-rpath=\$ORIGIN" + +EXTRAFLAGS="-DNIGHTLY -O3" + +set -x +$CXX src/main.cpp src/libtommath.cpp $DISABLED_WARNINGS $CPPFLAGS $CXXFLAGS $EXTRAFLAGS $LDFLAGS -o odin 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..923c4e36d --- /dev/null +++ b/ci/nightly.py @@ -0,0 +1,145 @@ +import os +import sys +from zipfile import ZipFile, ZIP_DEFLATED +from b2sdk.v2 import InMemoryAccountInfo, B2Api +from datetime import datetime, timezone +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 is 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.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) + + source_archive: str + destination_name = f'odin-{platform}-nightly+{now.strftime("%Y-%m-%d")}' + + if platform.startswith("linux") or platform.startswith("macos"): + destination_name += ".tar.gz" + source_archive = artifact + else: + destination_name += ".zip" + source_archive = destination_name + + print(f"Creating archive {destination_name} from {artifact} and uploading to {bucket_name}") + with ZipFile(source_archive, 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_archive): + print(f"Error: archive {source_archive} not found.") + return 1 + + print("Uploading {} to {}".format(source_archive, UPLOAD_FOLDER + destination_name)) + bucket = get_bucket() + res = bucket.upload_local_file( + source_archive, # Local file to upload + "nightly/" + destination_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, tz=timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) + now = datetime.now(timezone.utc).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.now(timezone.utc).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) diff --git a/ci/upload_create_nightly.sh b/ci/upload_create_nightly.sh deleted file mode 100644 index 754b9b87c..000000000 --- a/ci/upload_create_nightly.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -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" - -7z a -bd "output/$filename" -r "$artifact" -b2 upload-file --noProgress "$bucket" "output/$filename" "nightly/$filename" \ No newline at end of file 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/scanner.odin b/core/bufio/scanner.odin index b9e620250..ee2d5d1f6 100644 --- a/core/bufio/scanner.odin +++ b/core/bufio/scanner.odin @@ -4,7 +4,7 @@ import "core:bytes" import "core:io" import "core:mem" import "core:unicode/utf8" -import "core:intrinsics" +import "base:intrinsics" // Extra errors returns by scanning procedures Scanner_Extra_Error :: enum i32 { diff --git a/core/bufio/writer.odin b/core/bufio/writer.odin index c3debdaaa..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 @@ -226,7 +230,6 @@ writer_to_writer :: proc(b: ^Writer) -> (s: io.Writer) { -@(private) _writer_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { b := (^Writer)(stream_data) #partial switch mode { diff --git a/core/bytes/buffer.odin b/core/bytes/buffer.odin index abfee6f2f..f4d883353 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,34 @@ _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) { + if len(p) == 0 { + return 0, nil + } 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 +163,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) @@ -246,10 +249,13 @@ buffer_read_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int) -> (n: int, err: io. } buffer_read_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.Error) { + if len(p) == 0 { + return 0, nil + } b.last_read = .Invalid if uint(offset) >= len(b.buf) { - err = .Invalid_Offset + err = .EOF return } n = copy(p, b.buf[offset:]) @@ -310,6 +316,27 @@ buffer_unread_rune :: proc(b: ^Buffer) -> io.Error { return nil } +buffer_seek :: proc(b: ^Buffer, offset: i64, whence: io.Seek_From) -> (i64, io.Error) { + abs: i64 + switch whence { + case .Start: + abs = offset + case .Current: + abs = i64(b.off) + offset + case .End: + abs = i64(len(b.buf)) + offset + case: + return 0, .Invalid_Whence + } + + abs_int := int(abs) + if abs_int < 0 { + return 0, .Invalid_Offset + } + b.last_read = .Invalid + b.off = abs_int + return abs, nil +} buffer_read_bytes :: proc(b: ^Buffer, delim: byte) -> (line: []byte, err: io.Error) { i := index_byte(b.buf[b.off:], delim) @@ -359,7 +386,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 } @@ -395,14 +422,17 @@ _buffer_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offse return io._i64_err(buffer_write(b, p)) case .Write_At: return io._i64_err(buffer_write_at(b, p, int(offset))) + case .Seek: + n, err = buffer_seek(b, offset, whence) + return case .Size: - n = i64(buffer_capacity(b)) + n = i64(buffer_length(b)) return case .Destroy: buffer_destroy(b) return case .Query: - return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Size, .Destroy}) + return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Destroy, .Query}) } return 0, .Empty } diff --git a/core/bytes/bytes.odin b/core/bytes/bytes.odin index d39f01b06..c0d25bcce 100644 --- a/core/bytes/bytes.odin +++ b/core/bytes/bytes.odin @@ -1,9 +1,38 @@ package bytes +import "base:intrinsics" import "core:mem" +import "core:simd" import "core:unicode" import "core:unicode/utf8" +when ODIN_ARCH == .amd64 && intrinsics.has_target_feature("avx2") { + @(private) + SCANNER_INDICES_256 : simd.u8x32 : { + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + } + @(private) + SCANNER_SENTINEL_MAX_256: simd.u8x32 : u8(0x00) + @(private) + SCANNER_SENTINEL_MIN_256: simd.u8x32 : u8(0xff) + @(private) + SIMD_REG_SIZE_256 :: 32 +} +@(private) +SCANNER_INDICES_128 : simd.u8x16 : { + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, +} +@(private) +SCANNER_SENTINEL_MAX_128: simd.u8x16 : u8(0x00) +@(private) +SCANNER_SENTINEL_MIN_128: simd.u8x16 : u8(0xff) +@(private) +SIMD_REG_SIZE_128 :: 16 + clone :: proc(s: []byte, allocator := context.allocator, loc := #caller_location) -> []byte { c := make([]byte, len(s), allocator, loc) copy(c, s) @@ -293,28 +322,277 @@ split_after_iterator :: proc(s: ^[]byte, sep: []byte) -> ([]byte, bool) { return _split_iterator(s, sep, len(sep)) } +/* +Scan a slice of bytes for a specific byte. -index_byte :: proc(s: []byte, c: byte) -> int { - for i := 0; i < len(s); i += 1 { +This procedure safely handles slices of any length, including empty slices. + +Inputs: +- data: A slice of bytes. +- c: The byte to search for. + +Returns: +- index: The index of the byte `c`, or -1 if it was not found. +*/ +index_byte :: proc "contextless" (s: []byte, c: byte) -> (index: int) #no_bounds_check { + i, l := 0, len(s) + + // Guard against small strings. On modern systems, it is ALWAYS + // worth vectorizing assuming there is a hardware vector unit, and + // the data size is large enough. + if l < SIMD_REG_SIZE_128 { + for /**/; i < l; i += 1 { + if s[i] == c { + return i + } + } + return -1 + } + + c_vec: simd.u8x16 = c + when !simd.IS_EMULATED { + // Note: While this is something that could also logically take + // advantage of AVX512, the various downclocking and power + // consumption related woes make premature to have a dedicated + // code path. + when ODIN_ARCH == .amd64 && intrinsics.has_target_feature("avx2") { + c_vec_256: simd.u8x32 = c + + s_vecs: [4]simd.u8x32 = --- + c_vecs: [4]simd.u8x32 = --- + m_vec: [4]u8 = --- + + // Scan 128-byte chunks, using 256-bit SIMD. + for nr_blocks := l / (4 * SIMD_REG_SIZE_256); nr_blocks > 0; nr_blocks -= 1 { + #unroll for j in 0..<4 { + s_vecs[j] = intrinsics.unaligned_load(cast(^simd.u8x32)raw_data(s[i+j*SIMD_REG_SIZE_256:])) + c_vecs[j] = simd.lanes_eq(s_vecs[j], c_vec_256) + m_vec[j] = simd.reduce_or(c_vecs[j]) + } + if m_vec[0] | m_vec[1] | m_vec[2] | m_vec[3] > 0 { + #unroll for j in 0..<4 { + if m_vec[j] > 0 { + sel := simd.select(c_vecs[j], SCANNER_INDICES_256, SCANNER_SENTINEL_MIN_256) + off := simd.reduce_min(sel) + return i + j * SIMD_REG_SIZE_256 + int(off) + } + } + } + + i += 4 * SIMD_REG_SIZE_256 + } + + // Scan 64-byte chunks, using 256-bit SIMD. + for nr_blocks := (l - i) / (2 * SIMD_REG_SIZE_256); nr_blocks > 0; nr_blocks -= 1 { + #unroll for j in 0..<2 { + s_vecs[j] = intrinsics.unaligned_load(cast(^simd.u8x32)raw_data(s[i+j*SIMD_REG_SIZE_256:])) + c_vecs[j] = simd.lanes_eq(s_vecs[j], c_vec_256) + m_vec[j] = simd.reduce_or(c_vecs[j]) + } + if m_vec[0] | m_vec[1] > 0 { + #unroll for j in 0..<2 { + if m_vec[j] > 0 { + sel := simd.select(c_vecs[j], SCANNER_INDICES_256, SCANNER_SENTINEL_MIN_256) + off := simd.reduce_min(sel) + return i + j * SIMD_REG_SIZE_256 + int(off) + } + } + } + + i += 2 * SIMD_REG_SIZE_256 + } + } else { + s_vecs: [4]simd.u8x16 = --- + c_vecs: [4]simd.u8x16 = --- + m_vecs: [4]u8 = --- + + // Scan 64-byte chunks, using 128-bit SIMD. + for nr_blocks := l / (4 * SIMD_REG_SIZE_128); nr_blocks > 0; nr_blocks -= 1 { + #unroll for j in 0..<4 { + s_vecs[j]= intrinsics.unaligned_load(cast(^simd.u8x16)raw_data(s[i+j*SIMD_REG_SIZE_128:])) + c_vecs[j] = simd.lanes_eq(s_vecs[j], c_vec) + m_vecs[j] = simd.reduce_or(c_vecs[j]) + } + if m_vecs[0] | m_vecs[1] | m_vecs[2] | m_vecs[3] > 0 { + #unroll for j in 0..<4 { + if m_vecs[j] > 0 { + sel := simd.select(c_vecs[j], SCANNER_INDICES_128, SCANNER_SENTINEL_MIN_128) + off := simd.reduce_min(sel) + return i + j * SIMD_REG_SIZE_128 + int(off) + } + } + } + + i += 4 * SIMD_REG_SIZE_128 + } + } + } + + // Scan the remaining SIMD register sized chunks. + // + // Apparently LLVM does ok with 128-bit SWAR, so this path is also taken + // on potato targets. Scanning more at a time when LLVM is emulating SIMD + // likely does not buy much, as all that does is increase GP register + // pressure. + for nr_blocks := (l - i) / SIMD_REG_SIZE_128; nr_blocks > 0; nr_blocks -= 1 { + s0 := intrinsics.unaligned_load(cast(^simd.u8x16)raw_data(s[i:])) + c0 := simd.lanes_eq(s0, c_vec) + if simd.reduce_or(c0) > 0 { + sel := simd.select(c0, SCANNER_INDICES_128, SCANNER_SENTINEL_MIN_128) + off := simd.reduce_min(sel) + return i + int(off) + } + + i += SIMD_REG_SIZE_128 + } + + // Scan serially for the remainder. + for /**/; i < l; i += 1 { if s[i] == c { return i } } + return -1 } -// Returns -1 if c is not present -last_index_byte :: proc(s: []byte, c: byte) -> int { - for i := len(s)-1; i >= 0; i -= 1 { +/* +Scan a slice of bytes for a specific byte, starting from the end and working +backwards to the start. + +This procedure safely handles slices of any length, including empty slices. + +Inputs: +- data: A slice of bytes. +- c: The byte to search for. + +Returns: +- index: The index of the byte `c`, or -1 if it was not found. +*/ +last_index_byte :: proc "contextless" (s: []byte, c: byte) -> int #no_bounds_check { + i := len(s) + + // Guard against small strings. On modern systems, it is ALWAYS + // worth vectorizing assuming there is a hardware vector unit, and + // the data size is large enough. + if i < SIMD_REG_SIZE_128 { + #reverse for ch, j in s { + if ch == c { + return j + } + } + return -1 + } + + c_vec: simd.u8x16 = c + when !simd.IS_EMULATED { + // Note: While this is something that could also logically take + // advantage of AVX512, the various downclocking and power + // consumption related woes make premature to have a dedicated + // code path. + when ODIN_ARCH == .amd64 && intrinsics.has_target_feature("avx2") { + c_vec_256: simd.u8x32 = c + + s_vecs: [4]simd.u8x32 = --- + c_vecs: [4]simd.u8x32 = --- + m_vec: [4]u8 = --- + + // Scan 128-byte chunks, using 256-bit SIMD. + for i >= 4 * SIMD_REG_SIZE_256 { + i -= 4 * SIMD_REG_SIZE_256 + + #unroll for j in 0..<4 { + s_vecs[j] = intrinsics.unaligned_load(cast(^simd.u8x32)raw_data(s[i+j*SIMD_REG_SIZE_256:])) + c_vecs[j] = simd.lanes_eq(s_vecs[j], c_vec_256) + m_vec[j] = simd.reduce_or(c_vecs[j]) + } + if m_vec[0] | m_vec[1] | m_vec[2] | m_vec[3] > 0 { + #unroll for j in 0..<4 { + if m_vec[3-j] > 0 { + sel := simd.select(c_vecs[3-j], SCANNER_INDICES_256, SCANNER_SENTINEL_MAX_256) + off := simd.reduce_max(sel) + return i + (3-j) * SIMD_REG_SIZE_256 + int(off) + } + } + } + } + + // Scan 64-byte chunks, using 256-bit SIMD. + for i >= 2 * SIMD_REG_SIZE_256 { + i -= 2 * SIMD_REG_SIZE_256 + + #unroll for j in 0..<2 { + s_vecs[j] = intrinsics.unaligned_load(cast(^simd.u8x32)raw_data(s[i+j*SIMD_REG_SIZE_256:])) + c_vecs[j] = simd.lanes_eq(s_vecs[j], c_vec_256) + m_vec[j] = simd.reduce_or(c_vecs[j]) + } + if m_vec[0] | m_vec[1] > 0 { + #unroll for j in 0..<2 { + if m_vec[1-j] > 0 { + sel := simd.select(c_vecs[1-j], SCANNER_INDICES_256, SCANNER_SENTINEL_MAX_256) + off := simd.reduce_max(sel) + return i + (1-j) * SIMD_REG_SIZE_256 + int(off) + } + } + } + } + } else { + s_vecs: [4]simd.u8x16 = --- + c_vecs: [4]simd.u8x16 = --- + m_vecs: [4]u8 = --- + + // Scan 64-byte chunks, using 128-bit SIMD. + for i >= 4 * SIMD_REG_SIZE_128 { + i -= 4 * SIMD_REG_SIZE_128 + + #unroll for j in 0..<4 { + s_vecs[j] = intrinsics.unaligned_load(cast(^simd.u8x16)raw_data(s[i+j*SIMD_REG_SIZE_128:])) + c_vecs[j] = simd.lanes_eq(s_vecs[j], c_vec) + m_vecs[j] = simd.reduce_or(c_vecs[j]) + } + if m_vecs[0] | m_vecs[1] | m_vecs[2] | m_vecs[3] > 0 { + #unroll for j in 0..<4 { + if m_vecs[3-j] > 0 { + sel := simd.select(c_vecs[3-j], SCANNER_INDICES_128, SCANNER_SENTINEL_MAX_128) + off := simd.reduce_max(sel) + return i + (3-j) * SIMD_REG_SIZE_128 + int(off) + } + } + } + } + } + } + + // Scan the remaining SIMD register sized chunks. + // + // Apparently LLVM does ok with 128-bit SWAR, so this path is also taken + // on potato targets. Scanning more at a time when LLVM is emulating SIMD + // likely does not buy much, as all that does is increase GP register + // pressure. + for i >= SIMD_REG_SIZE_128 { + i -= SIMD_REG_SIZE_128 + + s0 := intrinsics.unaligned_load(cast(^simd.u8x16)raw_data(s[i:])) + c0 := simd.lanes_eq(s0, c_vec) + if simd.reduce_or(c0) > 0 { + sel := simd.select(c0, SCANNER_INDICES_128, SCANNER_SENTINEL_MAX_128) + off := simd.reduce_max(sel) + return i + int(off) + } + } + + // Scan serially for the remainder. + for i > 0 { + i -= 1 if s[i] == c { return i } } + return -1 } - @private PRIME_RABIN_KARP :: 16777619 index :: proc(s, substr: []byte) -> int { @@ -895,7 +1173,7 @@ split_multi_iterator :: proc(s: ^[]byte, substrs: [][]byte, skip_empty := false) -// scrub scruvs invalid utf-8 characters and replaces them with the replacement string +// Scrubs invalid utf-8 characters and replaces them with the replacement string // Adjacent invalid bytes are only replaced once scrub :: proc(s: []byte, replacement: []byte, allocator := context.allocator) -> []byte { str := s @@ -1167,3 +1445,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/bytes/reader.odin b/core/bytes/reader.odin index 4b18345ba..2e1c5ed42 100644 --- a/core/bytes/reader.odin +++ b/core/bytes/reader.odin @@ -9,10 +9,11 @@ Reader :: struct { prev_rune: int, // previous reading index of rune or < 0 } -reader_init :: proc(r: ^Reader, s: []byte) { +reader_init :: proc(r: ^Reader, s: []byte) -> io.Stream { r.s = s r.i = 0 r.prev_rune = -1 + return reader_to_stream(r) } reader_to_stream :: proc(r: ^Reader) -> (s: io.Stream) { @@ -33,6 +34,9 @@ reader_size :: proc(r: ^Reader) -> i64 { } reader_read :: proc(r: ^Reader, p: []byte) -> (n: int, err: io.Error) { + if len(p) == 0 { + return 0, nil + } if r.i >= i64(len(r.s)) { return 0, .EOF } @@ -42,6 +46,9 @@ reader_read :: proc(r: ^Reader, p: []byte) -> (n: int, err: io.Error) { return } reader_read_at :: proc(r: ^Reader, p: []byte, off: i64) -> (n: int, err: io.Error) { + if len(p) == 0 { + return 0, nil + } if off < 0 { return 0, .Invalid_Offset } @@ -97,7 +104,6 @@ reader_unread_rune :: proc(r: ^Reader) -> io.Error { return nil } reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.Error) { - r.prev_rune = -1 abs: i64 switch whence { case .Start: @@ -114,6 +120,7 @@ reader_seek :: proc(r: ^Reader, offset: i64, whence: io.Seek_From) -> (i64, io.E return 0, .Invalid_Offset } r.i = abs + r.prev_rune = -1 return abs, nil } reader_write_to :: proc(r: ^Reader, w: io.Writer) -> (n: i64, err: io.Error) { diff --git a/core/c/c.odin b/core/c/c.odin index 05732476f..3dfc19ffc 100644 --- a/core/c/c.odin +++ b/core/c/c.odin @@ -1,6 +1,6 @@ package c -import builtin "core:builtin" +import builtin "base:builtin" char :: builtin.u8 // assuming -funsigned-char @@ -104,3 +104,13 @@ NULL :: rawptr(uintptr(0)) NDEBUG :: !ODIN_DEBUG CHAR_BIT :: 8 + +// Since there are no types in C with an alignment larger than that of +// max_align_t, which cannot be larger than sizeof(long double) as any other +// exposed type wouldn't be valid C, the maximum alignment possible in a +// strictly conformant C implementation is 16 on the platforms we care about. +// The choice of 4096 bytes for storage of this type is more than enough on all +// relevant platforms. +va_list :: struct #align(16) { + _: [4096]u8, +} diff --git a/core/c/frontend/preprocessor/const_expr.odin b/core/c/frontend/preprocessor/const_expr.odin deleted file mode 100644 index ff13f6432..000000000 --- a/core/c/frontend/preprocessor/const_expr.odin +++ /dev/null @@ -1,25 +0,0 @@ -package c_frontend_preprocess - -import "core:c/frontend/tokenizer" - -const_expr :: proc(rest: ^^Token, tok: ^Token) -> i64 { - // TODO(bill): Handle const_expr correctly - // This is effectively a mini-parser - - assert(rest != nil) - assert(tok != nil) - rest^ = tokenizer.new_eof(tok) - switch v in tok.val { - case i64: - return v - case f64: - return i64(v) - case string: - return 0 - case []u16: - // TODO - case []u32: - // TODO - } - return 0 -} diff --git a/core/c/frontend/preprocessor/preprocess.odin b/core/c/frontend/preprocessor/preprocess.odin deleted file mode 100644 index b5eab0bb3..000000000 --- a/core/c/frontend/preprocessor/preprocess.odin +++ /dev/null @@ -1,1510 +0,0 @@ -package c_frontend_preprocess - -import "../tokenizer" - -import "core:strings" -import "core:strconv" -import "core:path/filepath" -import "core:unicode/utf8" -import "core:unicode/utf16" -import "core:os" -import "core:io" - -@(private) -Tokenizer :: tokenizer.Tokenizer -@(private) -Token :: tokenizer.Token - -Error_Handler :: tokenizer.Error_Handler - -Macro_Param :: struct { - next: ^Macro_Param, - name: string, -} - -Macro_Arg :: struct { - next: ^Macro_Arg, - name: string, - tok: ^Token, - is_va_args: bool, -} - -Macro_Kind :: enum u8 { - Function_Like, - Value_Like, -} - -Macro_Handler :: #type proc(^Preprocessor, ^Token) -> ^Token - -Macro :: struct { - name: string, - kind: Macro_Kind, - params: ^Macro_Param, - va_args_name: string, - body: ^Token, - handler: Macro_Handler, -} - -Cond_Incl_State :: enum u8 { - In_Then, - In_Elif, - In_Else, -} - -Cond_Incl :: struct { - next: ^Cond_Incl, - tok: ^Token, - state: Cond_Incl_State, - included: bool, -} - -Pragma_Handler :: #type proc(^Preprocessor, ^Token) - -Preprocessor :: struct { - // Lookup tables - macros: map[string]^Macro, - pragma_once: map[string]bool, - include_guards: map[string]string, - filepath_cache: map[string]string, - - // Include path data - include_paths: []string, - - // Counter for __COUNTER__ macro - counter: i64, - - // Include information - cond_incl: ^Cond_Incl, - include_level: int, - include_next_index: int, - - wide_char_size: int, - - // Mutable data - err: Error_Handler, - warn: Error_Handler, - pragma_handler: Pragma_Handler, - error_count: int, - warning_count: int, -} - -MAX_INCLUDE_LEVEL :: 1024 - -error :: proc(cpp: ^Preprocessor, tok: ^Token, msg: string, args: ..any) { - if cpp.err != nil { - cpp.err(tok.pos, msg, ..args) - } - cpp.error_count += 1 -} - -warn :: proc(cpp: ^Preprocessor, tok: ^Token, msg: string, args: ..any) { - if cpp.warn != nil { - cpp.warn(tok.pos, msg, ..args) - } - cpp.warning_count += 1 -} - -is_hash :: proc(tok: ^Token) -> bool { - return tok.at_bol && tok.lit == "#" -} - -skip_line :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token { - tok := tok - if tok.at_bol { - return tok - } - warn(cpp, tok, "extra token") - for tok.at_bol { - tok = tok.next - } - return tok -} - - -append_token :: proc(a, b: ^Token) -> ^Token { - if a.kind == .EOF { - return b - } - - head: Token - curr := &head - - for tok := a; tok.kind != .EOF; tok = tok.next { - curr.next = tokenizer.copy_token(tok) - curr = curr.next - } - curr.next = b - return head.next -} - - -is_hex_digit :: proc(x: byte) -> bool { - switch x { - case '0'..='9', 'a'..='f', 'A'..='F': - return true - } - return false -} -from_hex :: proc(x: byte) -> i32 { - switch x { - case '0'..='9': - return i32(x) - '0' - case 'a'..='f': - return i32(x) - 'a' + 10 - case 'A'..='F': - return i32(x) - 'A' + 10 - } - return 16 -} - - -convert_pp_number :: proc(tok: ^Token) { - convert_pp_int :: proc(tok: ^Token) -> bool { - p := tok.lit - base := 10 - if len(p) > 2 { - if strings.equal_fold(p[:2], "0x") && is_hex_digit(p[2]) { - p = p[2:] - base = 16 - } else if strings.equal_fold(p[:2], "0b") && p[2] == '0' || p[2] == '1' { - p = p[2:] - base = 2 - } - } - if base == 10 && p[0] == '0' { - base = 8 - } - - - tok.val, _ = strconv.parse_i64_of_base(p, base) - - l, u: int - - suf: [3]byte - suf_n := 0 - i := len(p)-1 - for /**/; i >= 0 && suf_n < len(suf); i -= 1 { - switch p[i] { - case 'l', 'L': - suf[suf_n] = 'l' - l += 1 - suf_n += 1 - case 'u', 'U': - suf[suf_n] = 'u' - u += 1 - suf_n += 1 - } - } - if i < len(p) { - if !is_hex_digit(p[i]) && p[i] != '.' { - return false - } - } - if u > 1 { - return false - } - - if l > 2 { - return false - } - - if u == 1 { - switch l { - case 0: tok.type_hint = .Unsigned_Int - case 1: tok.type_hint = .Unsigned_Long - case 2: tok.type_hint = .Unsigned_Long_Long - } - } else { - switch l { - case 0: tok.type_hint = .Int - case 1: tok.type_hint = .Long - case 2: tok.type_hint = .Long_Long - } - } - return true - } - - if convert_pp_int(tok) { - return - } - - fval, _ := strconv.parse_f64(tok.lit) - tok.val = fval - - end := tok.lit[len(tok.lit)-1] - switch end { - case 'f', 'F': - tok.type_hint = .Float - case 'l', 'L': - tok.type_hint = .Long_Double - case: - tok.type_hint = .Double - } - -} - -convert_pp_char :: proc(tok: ^Token) { - assert(len(tok.lit) >= 2) - r, _, _, _ := unquote_char(tok.lit, tok.lit[0]) - tok.val = i64(r) - - tok.type_hint = .Int - switch tok.prefix { - case "u": tok.type_hint = .UTF_16 - case "U": tok.type_hint = .UTF_32 - case "L": tok.type_hint = .UTF_Wide - } -} - -wide_char_size :: proc(cpp: ^Preprocessor) -> int { - char_size := 4 - if cpp.wide_char_size > 0 { - char_size = clamp(cpp.wide_char_size, 1, 4) - assert(char_size & (char_size-1) == 0) - } - return char_size -} - -convert_pp_string :: proc(cpp: ^Preprocessor, tok: ^Token) { - assert(len(tok.lit) >= 2) - str, _, _ := unquote_string(tok.lit) - tok.val = str - - char_size := 1 - - switch tok.prefix { - case "u8": - tok.type_hint = .UTF_8 - char_size = 1 - case "u": - tok.type_hint = .UTF_16 - char_size = 2 - case "U": - tok.type_hint = .UTF_32 - char_size = 4 - case "L": - tok.type_hint = .UTF_Wide - char_size = wide_char_size(cpp) - } - - switch char_size { - case 2: - n: int - buf := make([]u16, len(str)) - for c in str { - ch := c - if ch < 0x10000 { - buf[n] = u16(ch) - n += 1 - } else { - ch -= 0x10000 - buf[n+0] = 0xd800 + u16((ch >> 10) & 0x3ff) - buf[n+1] = 0xdc00 + u16(ch & 0x3ff) - n += 2 - } - } - tok.val = buf[:n] - case 4: - n: int - buf := make([]u32, len(str)) - for ch in str { - buf[n] = u32(ch) - n += 1 - } - tok.val = buf[:n] - } - -} - -convert_pp_token :: proc(cpp: ^Preprocessor, t: ^Token, is_keyword: tokenizer.Is_Keyword_Proc) { - switch { - case t.kind == .Char: - convert_pp_char(t) - case t.kind == .String: - convert_pp_string(cpp, t) - case is_keyword != nil && is_keyword(t): - t.kind = .Keyword - case t.kind == .PP_Number: - convert_pp_number(t) - } -} -convert_pp_tokens :: proc(cpp: ^Preprocessor, tok: ^Token, is_keyword: tokenizer.Is_Keyword_Proc) { - for t := tok; t != nil && t.kind != .EOF; t = t.next { - convert_pp_token(cpp, tok, is_keyword) - } -} - -join_adjacent_string_literals :: proc(cpp: ^Preprocessor, initial_tok: ^Token) { - for tok1 := initial_tok; tok1.kind != .EOF; /**/ { - if tok1.kind != .String || tok1.next.kind != .String { - tok1 = tok1.next - continue - } - - type_hint := tokenizer.Token_Type_Hint.None - char_size := 1 - - start := tok1 - for t := tok1; t != nil && t.kind == .String; t = t.next { - if t.val == nil { - convert_pp_string(cpp, t) - } - tok1 = t.next - if type_hint != t.type_hint { - if t.type_hint != .None && type_hint != .None { - error(cpp, t, "unsupported non-standard concatenation of string literals of different types") - } - prev_char_size := char_size - - #partial switch type_hint { - case .UTF_8: char_size = max(char_size, 1) - case .UTF_16: char_size = max(char_size, 2) - case .UTF_32: char_size = max(char_size, 4) - case .UTF_Wide: char_size = max(char_size, wide_char_size(cpp)) - } - - if type_hint == .None || prev_char_size < char_size { - type_hint = t.type_hint - } - } - } - - // NOTE(bill): Verbose logic in order to correctly concantenate strings, even if they different in type - max_len := 0 - switch char_size { - case 1: - for t := start; t != nil && t.kind == .String; t = t.next { - #partial switch v in t.val { - case string: max_len += len(v) - case []u16: max_len += 2*len(v) - case []u32: max_len += 4*len(v) - } - } - n := 0 - buf := make([]byte, max_len) - for t := start; t != nil && t.kind == .String; t = t.next { - #partial switch v in t.val { - case string: - n += copy(buf[n:], v) - case []u16: - for i := 0; i < len(v); /**/ { - c1 := v[i] - r: rune - if !utf16.is_surrogate(rune(c1)) { - r = rune(c1) - i += 1 - } else if i+1 == len(v) { - r = utf16.REPLACEMENT_CHAR - i += 1 - } else { - c2 := v[i+1] - i += 2 - r = utf16.decode_surrogate_pair(rune(c1), rune(c2)) - } - - b, w := utf8.encode_rune(r) - n += copy(buf[n:], b[:w]) - } - case []u32: - for r in v { - b, w := utf8.encode_rune(rune(r)) - n += copy(buf[n:], b[:w]) - } - } - } - - new_tok := tokenizer.copy_token(start) - new_tok.lit = "" - new_tok.val = string(buf[:n]) - new_tok.next = tok1 - new_tok.type_hint = type_hint - start^ = new_tok^ - case 2: - for t := start; t != nil && t.kind == .String; t = t.next { - #partial switch v in t.val { - case string: max_len += len(v) - case []u16: max_len += len(v) - case []u32: max_len += 2*len(v) - } - } - n := 0 - buf := make([]u16, max_len) - for t := start; t != nil && t.kind == .String; t = t.next { - #partial switch v in t.val { - case string: - for r in v { - if r >= 0x10000 { - c1, c2 := utf16.encode_surrogate_pair(r) - buf[n+0] = u16(c1) - buf[n+1] = u16(c2) - n += 2 - } else { - buf[n] = u16(r) - n += 1 - } - } - case []u16: - n += copy(buf[n:], v) - case []u32: - for r in v { - if r >= 0x10000 { - c1, c2 := utf16.encode_surrogate_pair(rune(r)) - buf[n+0] = u16(c1) - buf[n+1] = u16(c2) - n += 2 - } else { - buf[n] = u16(r) - n += 1 - } - } - } - } - - new_tok := tokenizer.copy_token(start) - new_tok.lit = "" - new_tok.val = buf[:n] - new_tok.next = tok1 - new_tok.type_hint = type_hint - start^ = new_tok^ - case 4: - for t := start; t != nil && t.kind == .String; t = t.next { - #partial switch v in t.val { - case string: max_len += len(v) - case []u16: max_len += len(v) - case []u32: max_len += len(v) - } - } - n := 0 - buf := make([]u32, max_len) - for t := start; t != nil && t.kind == .String; t = t.next { - #partial switch v in t.val { - case string: - for r in v { - buf[n] = u32(r) - n += 1 - } - case []u16: - for i := 0; i < len(v); /**/ { - c1 := v[i] - if !utf16.is_surrogate(rune(c1)) { - buf[n] = u32(c1) - n += 1 - i += 1 - } else if i+1 == len(v) { - buf[n] = utf16.REPLACEMENT_CHAR - n += 1 - i += 1 - } else { - c2 := v[i+1] - i += 2 - r := utf16.decode_surrogate_pair(rune(c1), rune(c2)) - buf[n] = u32(r) - n += 1 - } - } - case []u32: - n += copy(buf[n:], v) - } - } - - new_tok := tokenizer.copy_token(start) - new_tok.lit = "" - new_tok.val = buf[:n] - new_tok.next = tok1 - new_tok.type_hint = type_hint - start^ = new_tok^ - } - } -} - - -quote_string :: proc(s: string) -> []byte { - b := strings.builder_make(0, len(s)+2) - io.write_quoted_string(strings.to_writer(&b), s, '"') - return b.buf[:] -} - - -_init_tokenizer_from_preprocessor :: proc(t: ^Tokenizer, cpp: ^Preprocessor) -> ^Tokenizer { - t.warn = cpp.warn - t.err = cpp.err - return t -} - -new_string_token :: proc(cpp: ^Preprocessor, str: string, tok: ^Token) -> ^Token { - assert(tok != nil) - assert(str != "") - t := _init_tokenizer_from_preprocessor(&Tokenizer{}, cpp) - src := quote_string(str) - return tokenizer.inline_tokenize(t, tok, src) -} - -stringize :: proc(cpp: ^Preprocessor, hash, arg: ^Token) -> ^Token { - s := join_tokens(arg, nil) - return new_string_token(cpp, s, hash) -} - - -new_number_token :: proc(cpp: ^Preprocessor, i: i64, tok: ^Token) -> ^Token { - t := _init_tokenizer_from_preprocessor(&Tokenizer{}, cpp) - buf: [32]byte - n := len(strconv.append_int(buf[:], i, 10)) - src := make([]byte, n) - copy(src, buf[:n]) - return tokenizer.inline_tokenize(t, tok, src) -} - - -find_macro :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Macro { - if tok.kind != .Ident { - return nil - } - return cpp.macros[tok.lit] -} - -add_macro :: proc(cpp: ^Preprocessor, name: string, kind: Macro_Kind, body: ^Token) -> ^Macro { - m := new(Macro) - m.name = name - m.kind = kind - m.body = body - cpp.macros[name] = m - return m -} - - -undef_macro :: proc(cpp: ^Preprocessor, name: string) { - delete_key(&cpp.macros, name) -} - -add_builtin :: proc(cpp: ^Preprocessor, name: string, handler: Macro_Handler) -> ^Macro { - m := add_macro(cpp, name, .Value_Like, nil) - m.handler = handler - return m -} - - -skip :: proc(cpp: ^Preprocessor, tok: ^Token, op: string) -> ^Token { - if tok.lit != op { - error(cpp, tok, "expected '%q'", op) - } - return tok.next -} - -consume :: proc(rest: ^^Token, tok: ^Token, lit: string) -> bool { - if tok.lit == lit { - rest^ = tok.next - return true - } - rest^ = tok - return false -} - -read_macro_params :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) -> (param: ^Macro_Param, va_args_name: string) { - head: Macro_Param - curr := &head - - tok := tok - for tok.lit != ")" && tok.kind != .EOF { - if curr != &head { - tok = skip(cpp, tok, ",") - } - - if tok.lit == "..." { - va_args_name = "__VA_ARGS__" - rest^ = skip(cpp, tok.next, ")") - param = head.next - return - } - - if tok.kind != .Ident { - error(cpp, tok, "expected an identifier") - } - - if tok.next.lit == "..." { - va_args_name = tok.lit - rest^ = skip(cpp, tok.next.next, ")") - param = head.next - return - } - - m := new(Macro_Param) - m.name = tok.lit - curr.next = m - curr = curr.next - tok = tok.next - } - - - rest^ = tok.next - param = head.next - return -} - -copy_line :: proc(rest: ^^Token, tok: ^Token) -> ^Token { - head: Token - curr := &head - - tok := tok - for ; !tok.at_bol; tok = tok.next { - curr.next = tokenizer.copy_token(tok) - curr = curr.next - } - curr.next = tokenizer.new_eof(tok) - rest^ = tok - return head.next -} - -read_macro_definition :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) { - tok := tok - if tok.kind != .Ident { - error(cpp, tok, "macro name must be an identifier") - } - name := tok.lit - tok = tok.next - - if !tok.has_space && tok.lit == "(" { - params, va_args_name := read_macro_params(cpp, &tok, tok.next) - - m := add_macro(cpp, name, .Function_Like, copy_line(rest, tok)) - m.params = params - m.va_args_name = va_args_name - } else { - add_macro(cpp, name, .Value_Like, copy_line(rest, tok)) - } -} - - -join_tokens :: proc(tok, end: ^Token) -> string { - n := 1 - for t := tok; t != end && t.kind != .EOF; t = t.next { - if t != tok && t.has_space { - n += 1 - } - n += len(t.lit) - } - - buf := make([]byte, n) - - pos := 0 - for t := tok; t != end && t.kind != .EOF; t = t.next { - if t != tok && t.has_space { - buf[pos] = ' ' - pos += 1 - } - copy(buf[pos:], t.lit) - pos += len(t.lit) - } - - return string(buf[:pos]) -} - -read_include_filename :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) -> (filename: string, is_quote: bool) { - tok := tok - - if tok.kind == .String { - rest^ = skip_line(cpp, tok.next) - filename = tok.lit[1:len(tok.lit)-1] - is_quote = true - return - } - - if tok.lit == "<" { - start := tok - for ; tok.kind != .EOF; tok = tok.next { - if tok.at_bol || tok.kind == .EOF { - error(cpp, tok, "expected '>'") - } - is_quote = false - if tok.lit == ">" { - break - } - } - rest^ = skip_line(cpp, tok.next) - filename = join_tokens(start.next, tok) - return - } - - if tok.kind == .Ident { - tok2 := preprocess_internal(cpp, copy_line(rest, tok)) - return read_include_filename(cpp, &tok2, tok2) - } - - error(cpp, tok, "expected a filename") - return -} - -skip_cond_incl :: proc(tok: ^Token) -> ^Token { - next_skip :: proc(tok: ^Token) -> ^Token { - tok := tok - for tok.kind != .EOF { - if is_hash(tok) { - switch tok.next.lit { - case "if", "ifdef", "ifndef": - tok = next_skip(tok.next.next) - continue - - case "endif": - return tok.next.next - } - } - tok = tok.next - } - return tok - } - - tok := tok - - loop: for tok.kind != .EOF { - if is_hash(tok) { - switch tok.next.lit { - case "if", "ifdef", "ifndef": - tok = next_skip(tok.next.next) - continue loop - - case "elif", "else", "endif": - break loop - } - } - - tok = tok.next - } - return tok -} - -check_for_include_guard :: proc(tok: ^Token) -> (guard: string, ok: bool) { - if !is_hash(tok) || tok.next.lit != "ifndef" { - return - } - tok := tok - tok = tok.next.next - - if tok.kind != .Ident { - return - } - - m := tok.lit - tok = tok.next - - if !is_hash(tok) || tok.next.lit != "define" || tok.next.lit != "macro" { - return - } - - for tok.kind != .EOF { - if !is_hash(tok) { - tok = tok.next - continue - } - - if tok.next.lit == "endif" && tok.next.next.kind == .EOF { - return m, true - } - - switch tok.lit { - case "if", "ifdef", "ifndef": - tok = skip_cond_incl(tok.next) - case: - tok = tok.next - } - } - return -} - -include_file :: proc(cpp: ^Preprocessor, tok: ^Token, path: string, filename_tok: ^Token) -> ^Token { - if cpp.pragma_once[path] { - return tok - } - - guard_name, guard_name_found := cpp.include_guards[path] - if guard_name_found && cpp.macros[guard_name] != nil { - return tok - } - - if !os.exists(path) { - error(cpp, filename_tok, "%s: cannot open file", path) - return tok - } - - cpp.include_level += 1 - if cpp.include_level > MAX_INCLUDE_LEVEL { - error(cpp, tok, "exceeded maximum nest amount: %d", MAX_INCLUDE_LEVEL) - return tok - } - - t := _init_tokenizer_from_preprocessor(&Tokenizer{}, cpp) - tok2 := tokenizer.tokenize_file(t, path, /*file.id*/1) - if tok2 == nil { - error(cpp, filename_tok, "%s: cannot open file", path) - } - cpp.include_level -= 1 - - guard_name, guard_name_found = check_for_include_guard(tok2) - if guard_name_found { - cpp.include_guards[path] = guard_name - } - - return append_token(tok2, tok) -} - -find_arg :: proc(args: ^Macro_Arg, tok: ^Token) -> ^Macro_Arg { - for ap := args; ap != nil; ap = ap.next { - if tok.lit == ap.name { - return ap - } - } - return nil -} - -paste :: proc(cpp: ^Preprocessor, lhs, rhs: ^Token) -> ^Token { - buf := strings.concatenate({lhs.lit, rhs.lit}) - t := _init_tokenizer_from_preprocessor(&Tokenizer{}, cpp) - tok := tokenizer.inline_tokenize(t, lhs, transmute([]byte)buf) - if tok.next.kind != .EOF { - error(cpp, lhs, "pasting forms '%s', an invalid token", buf) - } - return tok -} - -has_varargs :: proc(args: ^Macro_Arg) -> bool { - for ap := args; ap != nil; ap = ap.next { - if ap.name == "__VA_ARGS__" { - return ap.tok.kind != .EOF - } - } - return false -} - -substitute_token :: proc(cpp: ^Preprocessor, tok: ^Token, args: ^Macro_Arg) -> ^Token { - head: Token - curr := &head - tok := tok - for tok.kind != .EOF { - if tok.lit == "#" { - arg := find_arg(args, tok.next) - if arg == nil { - error(cpp, tok.next, "'#' is not followed by a macro parameter") - } - arg_tok := arg.tok if arg != nil else tok.next - curr.next = stringize(cpp, tok, arg_tok) - curr = curr.next - tok = tok.next.next - continue - } - - if tok.lit == "," && tok.next.lit == "##" { - if arg := find_arg(args, tok.next.next); arg != nil && arg.is_va_args { - if arg.tok.kind == .EOF { - tok = tok.next.next.next - } else { - curr.next = tokenizer.copy_token(tok) - curr = curr.next - tok = tok.next.next - } - continue - } - } - - if tok.lit == "##" { - if curr == &head { - error(cpp, tok, "'##' cannot appear at start of macro expansion") - } - if tok.next.kind == .EOF { - error(cpp, tok, "'##' cannot appear at end of macro expansion") - } - - if arg := find_arg(args, tok.next); arg != nil { - if arg.tok.kind != .EOF { - curr^ = paste(cpp, curr, arg.tok)^ - for t := arg.tok.next; t.kind != .EOF; t = t.next { - curr.next = tokenizer.copy_token(t) - curr = curr.next - } - } - tok = tok.next.next - continue - } - - curr^ = paste(cpp, curr, tok.next)^ - tok = tok.next.next - continue - } - - arg := find_arg(args, tok) - - if arg != nil && tok.next.lit == "##" { - rhs := tok.next.next - - if arg.tok.kind == .EOF { - args2 := find_arg(args, rhs) - if args2 != nil { - for t := args.tok; t.kind != .EOF; t = t.next { - curr.next = tokenizer.copy_token(t) - curr = curr.next - } - } else { - curr.next = tokenizer.copy_token(rhs) - curr = curr.next - } - tok = rhs.next - continue - } - - for t := arg.tok; t.kind != .EOF; t = t.next { - curr.next = tokenizer.copy_token(t) - curr = curr.next - } - tok = tok.next - continue - } - - if tok.lit == "__VA_OPT__" && tok.next.lit == "(" { - opt_arg := read_macro_arg_one(cpp, &tok, tok.next.next, true) - if has_varargs(args) { - for t := opt_arg.tok; t.kind != .EOF; t = t.next { - curr.next = t - curr = curr.next - } - } - tok = skip(cpp, tok, ")") - continue - } - - if arg != nil { - t := preprocess_internal(cpp, arg.tok) - t.at_bol = tok.at_bol - t.has_space = tok.has_space - for ; t.kind != .EOF; t = t.next { - curr.next = tokenizer.copy_token(t) - curr = curr.next - } - tok = tok.next - continue - } - - curr.next = tokenizer.copy_token(tok) - curr = curr.next - tok = tok.next - continue - } - - curr.next = tok - return head.next -} - -read_macro_arg_one :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token, read_rest: bool) -> ^Macro_Arg { - tok := tok - head: Token - curr := &head - level := 0 - for { - if level == 0 && tok.lit == ")" { - break - } - if level == 0 && !read_rest && tok.lit == "," { - break - } - - if tok.kind == .EOF { - error(cpp, tok, "premature end of input") - } - - switch tok.lit { - case "(": level += 1 - case ")": level -= 1 - } - - curr.next = tokenizer.copy_token(tok) - curr = curr.next - tok = tok.next - } - curr.next = tokenizer.new_eof(tok) - - arg := new(Macro_Arg) - arg.tok = head.next - rest^ = tok - return arg -} - -read_macro_args :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token, params: ^Macro_Param, va_args_name: string) -> ^Macro_Arg { - tok := tok - start := tok - tok = tok.next.next - - head: Macro_Arg - curr := &head - - pp := params - for ; pp != nil; pp = pp.next { - if curr != &head { - tok = skip(cpp, tok, ",") - } - curr.next = read_macro_arg_one(cpp, &tok, tok, false) - curr = curr.next - curr.name = pp.name - } - - if va_args_name != "" { - arg: ^Macro_Arg - if tok.lit == ")" { - arg = new(Macro_Arg) - arg.tok = tokenizer.new_eof(tok) - } else { - if pp != params { - tok = skip(cpp, tok, ",") - } - arg = read_macro_arg_one(cpp, &tok, tok, true) - } - arg.name = va_args_name - arg.is_va_args = true - curr.next = arg - curr = curr.next - } else if pp != nil { - error(cpp, start, "too many arguments") - } - - skip(cpp, tok, ")") - rest^ = tok - return head.next -} - -expand_macro :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) -> bool { - if tokenizer.hide_set_contains(tok.hide_set, tok.lit) { - return false - } - tok := tok - m := find_macro(cpp, tok) - if m == nil { - return false - } - - if m.handler != nil { - rest^ = m.handler(cpp, tok) - rest^.next = tok.next - return true - } - - if m.kind == .Value_Like { - hs := tokenizer.hide_set_union(tok.hide_set, tokenizer.new_hide_set(m.name)) - body := tokenizer.add_hide_set(m.body, hs) - for t := body; t.kind != .EOF; t = t.next { - t.origin = tok - } - rest^ = append_token(body, tok.next) - rest^.at_bol = tok.at_bol - rest^.has_space = tok.has_space - return true - } - - if tok.next.lit != "(" { - return false - } - - macro_token := tok - args := read_macro_args(cpp, &tok, tok, m.params, m.va_args_name) - close_paren := tok - - hs := tokenizer.hide_set_intersection(macro_token.hide_set, close_paren.hide_set) - hs = tokenizer.hide_set_union(hs, tokenizer.new_hide_set(m.name)) - - body := substitute_token(cpp, m.body, args) - body = tokenizer.add_hide_set(body, hs) - for t := body; t.kind != .EOF; t = t.next { - t.origin = macro_token - } - rest^ = append_token(body, tok.next) - rest^.at_bol = macro_token.at_bol - rest^.has_space = macro_token.has_space - return true -} - -search_include_next :: proc(cpp: ^Preprocessor, filename: string) -> (path: string, ok: bool) { - for ; cpp.include_next_index < len(cpp.include_paths); cpp.include_next_index += 1 { - tpath := filepath.join({cpp.include_paths[cpp.include_next_index], filename}, allocator=context.temp_allocator) - if os.exists(tpath) { - return strings.clone(tpath), true - } - } - return -} - -search_include_paths :: proc(cpp: ^Preprocessor, filename: string) -> (path: string, ok: bool) { - if filepath.is_abs(filename) { - return filename, true - } - - if path, ok = cpp.filepath_cache[filename]; ok { - return - } - - for include_path in cpp.include_paths { - tpath := filepath.join({include_path, filename}, allocator=context.temp_allocator) - if os.exists(tpath) { - path, ok = strings.clone(tpath), true - cpp.filepath_cache[filename] = path - return - } - } - - return -} - -read_const_expr :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) -> ^Token { - tok := tok - tok = copy_line(rest, tok) - head: Token - curr := &head - for tok.kind != .EOF { - if tok.lit == "defined" { - start := tok - has_paren := consume(&tok, tok.next, "(") - if tok.kind != .Ident { - error(cpp, start, "macro name must be an identifier") - } - m := find_macro(cpp, tok) - tok = tok.next - - if has_paren { - tok = skip(cpp, tok, ")") - } - - curr.next = new_number_token(cpp, 1 if m != nil else 0, start) - curr = curr.next - continue - } - - curr.next = tok - curr = curr.next - tok = tok.next - } - - curr.next = tok - return head.next -} - -eval_const_expr :: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) -> (val: i64) { - tok := tok - start := tok - expr := read_const_expr(cpp, rest, tok.next) - expr = preprocess_internal(cpp, expr) - - if expr.kind == .EOF { - error(cpp, start, "no expression") - } - - for t := expr; t.kind != .EOF; t = t.next { - if t.kind == .Ident { - next := t.next - t^ = new_number_token(cpp, 0, t)^ - t.next = next - } - } - - val = 1 - convert_pp_tokens(cpp, expr, tokenizer.default_is_keyword) - - rest2: ^Token - val = const_expr(&rest2, expr) - if rest2 != nil && rest2.kind != .EOF { - error(cpp, rest2, "extra token") - } - return -} - -push_cond_incl :: proc(cpp: ^Preprocessor, tok: ^Token, included: bool) -> ^Cond_Incl { - ci := new(Cond_Incl) - ci.next = cpp.cond_incl - ci.state = .In_Then - ci.tok = tok - ci.included = included - cpp.cond_incl = ci - return ci -} - -read_line_marker:: proc(cpp: ^Preprocessor, rest: ^^Token, tok: ^Token) { - tok := tok - start := tok - tok = preprocess(cpp, copy_line(rest, tok)) - if tok.kind != .Number { - error(cpp, tok, "invalid line marker") - } - ival, _ := tok.val.(i64) - start.file.line_delta = int(ival - i64(start.pos.line)) - tok = tok.next - if tok.kind == .EOF { - return - } - - if tok.kind != .String { - error(cpp, tok, "filename expected") - } - start.file.display_name = tok.lit -} - -preprocess_internal :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token { - head: Token - curr := &head - - tok := tok - for tok != nil && tok.kind != .EOF { - if expand_macro(cpp, &tok, tok) { - continue - } - - if !is_hash(tok) { - if tok.file != nil { - tok.line_delta = tok.file.line_delta - } - curr.next = tok - curr = curr.next - tok = tok.next - continue - } - - start := tok - tok = tok.next - - switch tok.lit { - case "include": - filename, is_quote := read_include_filename(cpp, &tok, tok.next) - is_absolute := filepath.is_abs(filename) - if is_absolute { - tok = include_file(cpp, tok, filename, start.next.next) - continue - } - - if is_quote { - dir := "" - if start.file != nil { - dir = filepath.dir(start.file.name) - } - path := filepath.join({dir, filename}) - if os.exists(path) { - tok = include_file(cpp, tok, path, start.next.next) - continue - } - } - - path, ok := search_include_paths(cpp, filename) - if !ok { - path = filename - } - tok = include_file(cpp, tok, path, start.next.next) - continue - - case "include_next": - filename, _ := read_include_filename(cpp, &tok, tok.next) - path, ok := search_include_next(cpp, filename) - if !ok { - path = filename - } - tok = include_file(cpp, tok, path, start.next.next) - continue - - case "define": - read_macro_definition(cpp, &tok, tok.next) - continue - - case "undef": - tok = tok.next - if tok.kind != .Ident { - error(cpp, tok, "macro name must be an identifier") - } - undef_macro(cpp, tok.lit) - tok = skip_line(cpp, tok.next) - continue - - case "if": - val := eval_const_expr(cpp, &tok, tok) - push_cond_incl(cpp, start, val != 0) - if val == 0 { - tok = skip_cond_incl(tok) - } - continue - - case "ifdef": - defined := find_macro(cpp, tok.next) - push_cond_incl(cpp, tok, defined != nil) - tok = skip_line(cpp, tok.next.next) - if defined == nil { - tok = skip_cond_incl(tok) - } - continue - - case "ifndef": - defined := find_macro(cpp, tok.next) - push_cond_incl(cpp, tok, defined != nil) - tok = skip_line(cpp, tok.next.next) - if !(defined == nil) { - tok = skip_cond_incl(tok) - } - continue - - case "elif": - if cpp.cond_incl == nil || cpp.cond_incl.state == .In_Else { - error(cpp, start, "stray #elif") - } - if cpp.cond_incl != nil { - cpp.cond_incl.state = .In_Elif - } - - if (cpp.cond_incl != nil && !cpp.cond_incl.included) && eval_const_expr(cpp, &tok, tok) != 0 { - cpp.cond_incl.included = true - } else { - tok = skip_cond_incl(tok) - } - continue - - case "else": - if cpp.cond_incl == nil || cpp.cond_incl.state == .In_Else { - error(cpp, start, "stray #else") - } - if cpp.cond_incl != nil { - cpp.cond_incl.state = .In_Else - } - tok = skip_line(cpp, tok.next) - - if cpp.cond_incl != nil { - tok = skip_cond_incl(tok) - } - continue - - case "endif": - if cpp.cond_incl == nil { - error(cpp, start, "stray #endif") - } else { - cpp.cond_incl = cpp.cond_incl.next - } - tok = skip_line(cpp, tok.next) - continue - - case "line": - read_line_marker(cpp, &tok, tok.next) - continue - - case "pragma": - if tok.next.lit == "once" { - cpp.pragma_once[tok.pos.file] = true - tok = skip_line(cpp, tok.next.next) - continue - } - - pragma_tok, pragma_end := tok, tok - - for tok != nil && tok.kind != .EOF { - pragma_end = tok - tok = tok.next - if tok.at_bol { - break - } - } - pragma_end.next = tokenizer.new_eof(tok) - if cpp.pragma_handler != nil { - cpp.pragma_handler(cpp, pragma_tok.next) - continue - } - - continue - - case "error": - error(cpp, tok, "error") - } - - if tok.kind == .PP_Number { - read_line_marker(cpp, &tok, tok) - continue - } - - if !tok.at_bol { - error(cpp, tok, "invalid preprocessor directive") - } - } - - curr.next = tok - return head.next -} - - -preprocess :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token { - tok := tok - tok = preprocess_internal(cpp, tok) - if cpp.cond_incl != nil { - error(cpp, tok, "unterminated conditional directive") - } - convert_pp_tokens(cpp, tok, tokenizer.default_is_keyword) - join_adjacent_string_literals(cpp, tok) - for t := tok; t != nil; t = t.next { - t.pos.line += t.line_delta - } - return tok -} - - -define_macro :: proc(cpp: ^Preprocessor, name, def: string) { - src := transmute([]byte)def - - file := new(tokenizer.File) - file.id = -1 - file.src = src - file.name = "" - file.display_name = file.name - - - t := _init_tokenizer_from_preprocessor(&Tokenizer{}, cpp) - tok := tokenizer.tokenize(t, file) - add_macro(cpp, name, .Value_Like, tok) -} - - -file_macro :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token { - tok := tok - for tok.origin != nil { - tok = tok.origin - } - i := i64(tok.pos.line + tok.file.line_delta) - return new_number_token(cpp, i, tok) -} -line_macro :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token { - tok := tok - for tok.origin != nil { - tok = tok.origin - } - return new_string_token(cpp, tok.file.display_name, tok) -} -counter_macro :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token { - i := cpp.counter - cpp.counter += 1 - return new_number_token(cpp, i, tok) -} - -init_default_macros :: proc(cpp: ^Preprocessor) { - define_macro(cpp, "__C99_MACRO_WITH_VA_ARGS", "1") - define_macro(cpp, "__alignof__", "_Alignof") - define_macro(cpp, "__const__", "const") - define_macro(cpp, "__inline__", "inline") - define_macro(cpp, "__signed__", "signed") - define_macro(cpp, "__typeof__", "typeof") - define_macro(cpp, "__volatile__", "volatile") - - add_builtin(cpp, "__FILE__", file_macro) - add_builtin(cpp, "__LINE__", line_macro) - add_builtin(cpp, "__COUNTER__", counter_macro) -} - -init_lookup_tables :: proc(cpp: ^Preprocessor, allocator := context.allocator) { - context.allocator = allocator - reserve(&cpp.macros, max(16, cap(cpp.macros))) - reserve(&cpp.pragma_once, max(16, cap(cpp.pragma_once))) - reserve(&cpp.include_guards, max(16, cap(cpp.include_guards))) - reserve(&cpp.filepath_cache, max(16, cap(cpp.filepath_cache))) -} - - -init_defaults :: proc(cpp: ^Preprocessor, lookup_tables_allocator := context.allocator) { - if cpp.warn == nil { - cpp.warn = tokenizer.default_warn_handler - } - if cpp.err == nil { - cpp.err = tokenizer.default_error_handler - } - init_lookup_tables(cpp, lookup_tables_allocator) - init_default_macros(cpp) -} diff --git a/core/c/frontend/preprocessor/unquote.odin b/core/c/frontend/preprocessor/unquote.odin deleted file mode 100644 index 5869fa7ef..000000000 --- a/core/c/frontend/preprocessor/unquote.odin +++ /dev/null @@ -1,154 +0,0 @@ -package c_frontend_preprocess - -import "core:unicode/utf8" - -unquote_char :: proc(str: string, quote: byte) -> (r: rune, multiple_bytes: bool, tail_string: string, success: bool) { - hex_to_int :: proc(c: byte) -> int { - switch c { - case '0'..='9': return int(c-'0') - case 'a'..='f': return int(c-'a')+10 - case 'A'..='F': return int(c-'A')+10 - } - return -1 - } - w: int - - if str[0] == quote && quote == '"' { - return - } else if str[0] >= 0x80 { - r, w = utf8.decode_rune_in_string(str) - return r, true, str[w:], true - } else if str[0] != '\\' { - return rune(str[0]), false, str[1:], true - } - - if len(str) <= 1 { - return - } - s := str - c := s[1] - s = s[2:] - - switch c { - case: r = rune(c) - - case 'a': r = '\a' - case 'b': r = '\b' - case 'e': r = '\e' - case 'f': r = '\f' - case 'n': r = '\n' - case 'r': r = '\r' - case 't': r = '\t' - case 'v': r = '\v' - case '\\': r = '\\' - - case '"': r = '"' - case '\'': r = '\'' - - case '0'..='7': - v := int(c-'0') - if len(s) < 2 { - return - } - for i in 0.. 7 { - return - } - v = (v<<3) | d - } - s = s[2:] - if v > 0xff { - return - } - r = rune(v) - - case 'x', 'u', 'U': - count: int - switch c { - case 'x': count = 2 - case 'u': count = 4 - case 'U': count = 8 - } - - if len(s) < count { - return - } - - for i in 0.. utf8.MAX_RUNE { - return - } - multiple_bytes = true - } - - success = true - tail_string = s - return -} - -unquote_string :: proc(lit: string, allocator := context.allocator) -> (res: string, allocated, success: bool) { - contains_rune :: proc(s: string, r: rune) -> int { - for c, offset in s { - if c == r { - return offset - } - } - return -1 - } - - assert(len(lit) >= 2) - - s := lit - quote := '"' - - if s == `""` { - return "", false, true - } - - if contains_rune(s, '\n') >= 0 { - return s, false, false - } - - if contains_rune(s, '\\') < 0 && contains_rune(s, quote) < 0 { - if quote == '"' { - return s, false, true - } - } - s = s[1:len(s)-1] - - - buf_len := 3*len(s) / 2 - buf := make([]byte, buf_len, allocator) - offset := 0 - for len(s) > 0 { - r, multiple_bytes, tail_string, ok := unquote_char(s, byte(quote)) - if !ok { - delete(buf) - return s, false, false - } - s = tail_string - if r < 0x80 || !multiple_bytes { - buf[offset] = byte(r) - offset += 1 - } else { - b, w := utf8.encode_rune(r) - copy(buf[offset:], b[:w]) - offset += w - } - } - - new_string := string(buf[:offset]) - - return new_string, true, true -} diff --git a/core/c/frontend/tokenizer/doc.odin b/core/c/frontend/tokenizer/doc.odin deleted file mode 100644 index 9b1734fc4..000000000 --- a/core/c/frontend/tokenizer/doc.odin +++ /dev/null @@ -1,34 +0,0 @@ -/* -package demo - -import tokenizer "core:c/frontend/tokenizer" -import preprocessor "core:c/frontend/preprocessor" -import "core:fmt" - -main :: proc() { - t := &tokenizer.Tokenizer{}; - tokenizer.init_defaults(t); - - cpp := &preprocessor.Preprocessor{}; - cpp.warn, cpp.err = t.warn, t.err; - preprocessor.init_lookup_tables(cpp); - preprocessor.init_default_macros(cpp); - cpp.include_paths = {"my/path/to/include"}; - - tok := tokenizer.tokenize_file(t, "the/source/file.c", 1); - - tok = preprocessor.preprocess(cpp, tok); - if tok != nil { - for t := tok; t.kind != .EOF; t = t.next { - fmt.println(t.lit); - } - } - - fmt.println("[Done]"); -} -*/ - - -package c_frontend_tokenizer - - diff --git a/core/c/frontend/tokenizer/hide_set.odin b/core/c/frontend/tokenizer/hide_set.odin deleted file mode 100644 index ec8b77e6e..000000000 --- a/core/c/frontend/tokenizer/hide_set.odin +++ /dev/null @@ -1,68 +0,0 @@ -package c_frontend_tokenizer - -// NOTE(bill): This is a really dumb approach for a hide set, -// but it's really simple and probably fast enough in practice - - -Hide_Set :: struct { - next: ^Hide_Set, - name: string, -} - - -new_hide_set :: proc(name: string) -> ^Hide_Set { - hs := new(Hide_Set) - hs.name = name - return hs -} - -hide_set_contains :: proc(hs: ^Hide_Set, name: string) -> bool { - for h := hs; h != nil; h = h.next { - if h.name == name { - return true - } - } - return false -} - - -hide_set_union :: proc(a, b: ^Hide_Set) -> ^Hide_Set { - head: Hide_Set - curr := &head - - for h := a; h != nil; h = h.next { - curr.next = new_hide_set(h.name) - curr = curr.next - } - curr.next = b - return head.next -} - - -hide_set_intersection :: proc(a, b: ^Hide_Set) -> ^Hide_Set { - head: Hide_Set - curr := &head - - for h := a; h != nil; h = h.next { - if hide_set_contains(b, h.name) { - curr.next = new_hide_set(h.name) - curr = curr.next - } - } - return head.next -} - - -add_hide_set :: proc(tok: ^Token, hs: ^Hide_Set) -> ^Token { - head: Token - curr := &head - - tok := tok - for ; tok != nil; tok = tok.next { - t := copy_token(tok) - t.hide_set = hide_set_union(t.hide_set, hs) - curr.next = t - curr = curr.next - } - return head.next -} diff --git a/core/c/frontend/tokenizer/token.odin b/core/c/frontend/tokenizer/token.odin deleted file mode 100644 index 1376a651f..000000000 --- a/core/c/frontend/tokenizer/token.odin +++ /dev/null @@ -1,169 +0,0 @@ -package c_frontend_tokenizer - - -Pos :: struct { - file: string, - line: int, - column: int, - offset: int, -} - -Token_Kind :: enum { - Invalid, - Ident, - Punct, - Keyword, - Char, - String, - Number, - PP_Number, - Comment, - EOF, -} - -File :: struct { - name: string, - id: int, - src: []byte, - - display_name: string, - line_delta: int, -} - - -Token_Type_Hint :: enum u8 { - None, - - Int, - Long, - Long_Long, - - Unsigned_Int, - Unsigned_Long, - Unsigned_Long_Long, - - Float, - Double, - Long_Double, - - UTF_8, - UTF_16, - UTF_32, - UTF_Wide, -} - -Token_Value :: union { - i64, - f64, - string, - []u16, - []u32, -} - -Token :: struct { - kind: Token_Kind, - next: ^Token, - lit: string, - - pos: Pos, - file: ^File, - line_delta: int, - at_bol: bool, - has_space: bool, - - type_hint: Token_Type_Hint, - val: Token_Value, - prefix: string, - - // Preprocessor values - hide_set: ^Hide_Set, - origin: ^Token, -} - -Is_Keyword_Proc :: #type proc(tok: ^Token) -> bool - -copy_token :: proc(tok: ^Token) -> ^Token { - t, _ := new_clone(tok^) - t.next = nil - return t -} - -new_eof :: proc(tok: ^Token) -> ^Token { - t, _ := new_clone(tok^) - t.kind = .EOF - t.lit = "" - return t -} - -default_is_keyword :: proc(tok: ^Token) -> bool { - if tok.kind == .Keyword { - return true - } - if len(tok.lit) > 0 { - return default_keyword_set[tok.lit] - } - return false -} - - -token_name := [Token_Kind]string { - .Invalid = "invalid", - .Ident = "ident", - .Punct = "punct", - .Keyword = "keyword", - .Char = "char", - .String = "string", - .Number = "number", - .PP_Number = "preprocessor number", - .Comment = "comment", - .EOF = "eof", -} - -default_keyword_set := map[string]bool{ - "auto" = true, - "break" = true, - "case" = true, - "char" = true, - "const" = true, - "continue" = true, - "default" = true, - "do" = true, - "double" = true, - "else" = true, - "enum" = true, - "extern" = true, - "float" = true, - "for" = true, - "goto" = true, - "if" = true, - "int" = true, - "long" = true, - "register" = true, - "restrict" = true, - "return" = true, - "short" = true, - "signed" = true, - "sizeof" = true, - "static" = true, - "struct" = true, - "switch" = true, - "typedef" = true, - "union" = true, - "unsigned" = true, - "void" = true, - "volatile" = true, - "while" = true, - "_Alignas" = true, - "_Alignof" = true, - "_Atomic" = true, - "_Bool" = true, - "_Generic" = true, - "_Noreturn" = true, - "_Thread_local" = true, - "__restrict" = true, - "typeof" = true, - "asm" = true, - "__restrict__" = true, - "__thread" = true, - "__attribute__" = true, -} diff --git a/core/c/frontend/tokenizer/tokenizer.odin b/core/c/frontend/tokenizer/tokenizer.odin deleted file mode 100644 index 2415e06a0..000000000 --- a/core/c/frontend/tokenizer/tokenizer.odin +++ /dev/null @@ -1,667 +0,0 @@ -package c_frontend_tokenizer - -import "core:fmt" -import "core:os" -import "core:strings" -import "core:unicode/utf8" - - -Error_Handler :: #type proc(pos: Pos, fmt: string, args: ..any) - - -Tokenizer :: struct { - // Immutable data - path: string, - src: []byte, - - - // Tokenizing state - ch: rune, - offset: int, - read_offset: int, - line_offset: int, - line_count: int, - - // Extra information for tokens - at_bol: bool, - has_space: bool, - - // Mutable data - err: Error_Handler, - warn: Error_Handler, - error_count: int, - warning_count: int, -} - -init_defaults :: proc(t: ^Tokenizer, err: Error_Handler = default_error_handler, warn: Error_Handler = default_warn_handler) { - t.err = err - t.warn = warn -} - - -@(private) -offset_to_pos :: proc(t: ^Tokenizer, offset: int) -> (pos: Pos) { - pos.file = t.path - pos.offset = offset - pos.line = t.line_count - pos.column = offset - t.line_offset + 1 - return -} - -default_error_handler :: proc(pos: Pos, msg: string, args: ..any) { - fmt.eprintf("%s(%d:%d) ", pos.file, pos.line, pos.column) - fmt.eprintf(msg, ..args) - fmt.eprintf("\n") -} - -default_warn_handler :: proc(pos: Pos, msg: string, args: ..any) { - fmt.eprintf("%s(%d:%d) warning: ", pos.file, pos.line, pos.column) - fmt.eprintf(msg, ..args) - fmt.eprintf("\n") -} - -error_offset :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) { - pos := offset_to_pos(t, offset) - if t.err != nil { - t.err(pos, msg, ..args) - } - t.error_count += 1 -} - -warn_offset :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) { - pos := offset_to_pos(t, offset) - if t.warn != nil { - t.warn(pos, msg, ..args) - } - t.warning_count += 1 -} - -error :: proc(t: ^Tokenizer, tok: ^Token, msg: string, args: ..any) { - pos := tok.pos - if t.err != nil { - t.err(pos, msg, ..args) - } - t.error_count += 1 -} - -warn :: proc(t: ^Tokenizer, tok: ^Token, msg: string, args: ..any) { - pos := tok.pos - if t.warn != nil { - t.warn(pos, msg, ..args) - } - t.warning_count += 1 -} - - -advance_rune :: proc(t: ^Tokenizer) { - if t.read_offset < len(t.src) { - t.offset = t.read_offset - if t.ch == '\n' { - t.at_bol = true - t.line_offset = t.offset - t.line_count += 1 - } - r, w := rune(t.src[t.read_offset]), 1 - switch { - case r == 0: - error_offset(t, t.offset, "illegal character NUL") - case r >= utf8.RUNE_SELF: - r, w = utf8.decode_rune(t.src[t.read_offset:]) - if r == utf8.RUNE_ERROR && w == 1 { - error_offset(t, t.offset, "illegal UTF-8 encoding") - } else if r == utf8.RUNE_BOM && t.offset > 0 { - error_offset(t, t.offset, "illegal byte order mark") - } - } - t.read_offset += w - t.ch = r - } else { - t.offset = len(t.src) - if t.ch == '\n' { - t.at_bol = true - t.line_offset = t.offset - t.line_count += 1 - } - t.ch = -1 - } -} - -advance_rune_n :: proc(t: ^Tokenizer, n: int) { - for _ in 0.. bool { - return '0' <= r && r <= '9' -} - -skip_whitespace :: proc(t: ^Tokenizer) { - for { - switch t.ch { - case ' ', '\t', '\r', '\v', '\f', '\n': - t.has_space = true - advance_rune(t) - case: - return - } - } -} - -scan_comment :: proc(t: ^Tokenizer) -> string { - offset := t.offset-1 - next := -1 - general: { - if t.ch == '/'{ // line comments - advance_rune(t) - for t.ch != '\n' && t.ch >= 0 { - advance_rune(t) - } - - next = t.offset - if t.ch == '\n' { - next += 1 - } - break general - } - - /* style comment */ - advance_rune(t) - for t.ch >= 0 { - ch := t.ch - advance_rune(t) - if ch == '*' && t.ch == '/' { - advance_rune(t) - next = t.offset - break general - } - } - - error_offset(t, offset, "comment not terminated") - } - - lit := t.src[offset : t.offset] - - // NOTE(bill): Strip CR for line comments - for len(lit) > 2 && lit[1] == '/' && lit[len(lit)-1] == '\r' { - lit = lit[:len(lit)-1] - } - - - return string(lit) -} - -scan_identifier :: proc(t: ^Tokenizer) -> string { - offset := t.offset - - for is_ident1(t.ch) { - advance_rune(t) - } - - return string(t.src[offset : t.offset]) -} - -scan_string :: proc(t: ^Tokenizer) -> string { - offset := t.offset-1 - - for { - ch := t.ch - if ch == '\n' || ch < 0 { - error_offset(t, offset, "string literal was not terminated") - break - } - advance_rune(t) - if ch == '"' { - break - } - if ch == '\\' { - scan_escape(t) - } - } - - return string(t.src[offset : t.offset]) -} - -digit_val :: proc(r: rune) -> int { - switch r { - case '0'..='9': - return int(r-'0') - case 'A'..='F': - return int(r-'A' + 10) - case 'a'..='f': - return int(r-'a' + 10) - } - return 16 -} - -scan_escape :: proc(t: ^Tokenizer) -> bool { - offset := t.offset - - esc := t.ch - n: int - base, max: u32 - switch esc { - case 'a', 'b', 'e', 'f', 'n', 't', 'v', 'r', '\\', '\'', '"': - advance_rune(t) - return true - - case '0'..='7': - for digit_val(t.ch) < 8 { - advance_rune(t) - } - return true - case 'x': - advance_rune(t) - for digit_val(t.ch) < 16 { - advance_rune(t) - } - return true - case 'u': - advance_rune(t) - n, base, max = 4, 16, utf8.MAX_RUNE - case 'U': - advance_rune(t) - n, base, max = 8, 16, utf8.MAX_RUNE - case: - if t.ch < 0 { - error_offset(t, offset, "escape sequence was not terminated") - } else { - break - } - return false - } - - x: u32 - main_loop: for n > 0 { - d := u32(digit_val(t.ch)) - if d >= base { - if t.ch == '"' || t.ch == '\'' { - break main_loop - } - if t.ch < 0 { - error_offset(t, t.offset, "escape sequence was not terminated") - } else { - error_offset(t, t.offset, "illegal character '%r' : %d in escape sequence", t.ch, t.ch) - } - return false - } - - x = x*base + d - advance_rune(t) - n -= 1 - } - - if x > max || 0xd800 <= x && x <= 0xe000 { - error_offset(t, offset, "escape sequence is an invalid Unicode code point") - return false - } - return true -} - -scan_rune :: proc(t: ^Tokenizer) -> string { - offset := t.offset-1 - valid := true - n := 0 - for { - ch := t.ch - if ch == '\n' || ch < 0 { - if valid { - error_offset(t, offset, "rune literal not terminated") - valid = false - } - break - } - advance_rune(t) - if ch == '\'' { - break - } - n += 1 - if ch == '\\' { - if !scan_escape(t) { - valid = false - } - } - } - - if valid && n != 1 { - error_offset(t, offset, "illegal rune literal") - } - - return string(t.src[offset : t.offset]) -} - -scan_number :: proc(t: ^Tokenizer, seen_decimal_point: bool) -> (Token_Kind, string) { - scan_mantissa :: proc(t: ^Tokenizer, base: int) { - for digit_val(t.ch) < base { - advance_rune(t) - } - } - scan_exponent :: proc(t: ^Tokenizer) { - if t.ch == 'e' || t.ch == 'E' || t.ch == 'p' || t.ch == 'P' { - advance_rune(t) - if t.ch == '-' || t.ch == '+' { - advance_rune(t) - } - if digit_val(t.ch) < 10 { - scan_mantissa(t, 10) - } else { - error_offset(t, t.offset, "illegal floating-point exponent") - } - } - } - scan_fraction :: proc(t: ^Tokenizer) -> (early_exit: bool) { - if t.ch == '.' && peek(t) == '.' { - return true - } - if t.ch == '.' { - advance_rune(t) - scan_mantissa(t, 10) - } - return false - } - - check_end := true - - - offset := t.offset - seen_point := seen_decimal_point - - if seen_point { - offset -= 1 - scan_mantissa(t, 10) - scan_exponent(t) - } else { - if t.ch == '0' { - int_base :: proc(t: ^Tokenizer, base: int, msg: string) { - prev := t.offset - advance_rune(t) - scan_mantissa(t, base) - if t.offset - prev <= 1 { - error_offset(t, t.offset, msg) - } - } - - advance_rune(t) - switch t.ch { - case 'b', 'B': - int_base(t, 2, "illegal binary integer") - case 'x', 'X': - int_base(t, 16, "illegal hexadecimal integer") - case: - seen_point = false - scan_mantissa(t, 10) - if t.ch == '.' { - seen_point = true - if scan_fraction(t) { - check_end = false - } - } - if check_end { - scan_exponent(t) - check_end = false - } - } - } - } - - if check_end { - scan_mantissa(t, 10) - - if !scan_fraction(t) { - scan_exponent(t) - } - } - - return .Number, string(t.src[offset : t.offset]) -} - -scan_punct :: proc(t: ^Tokenizer, ch: rune) -> (kind: Token_Kind) { - kind = .Punct - switch ch { - case: - kind = .Invalid - - case '<', '>': - if t.ch == ch { - advance_rune(t) - } - if t.ch == '=' { - advance_rune(t) - } - case '!', '+', '-', '*', '/', '%', '^', '=': - if t.ch == '=' { - advance_rune(t) - } - case '#': - if t.ch == '#' { - advance_rune(t) - } - case '&': - if t.ch == '=' || t.ch == '&' { - advance_rune(t) - } - case '|': - if t.ch == '=' || t.ch == '|' { - advance_rune(t) - } - case '(', ')', '[', ']', '{', '}': - // okay - case '~', ',', ':', ';', '?': - // okay - case '`': - // okay - case '.': - if t.ch == '.' && peek(t) == '.' { - advance_rune(t) - advance_rune(t) // consume last '.' - } - } - return -} - -peek :: proc(t: ^Tokenizer) -> byte { - if t.read_offset < len(t.src) { - return t.src[t.read_offset] - } - return 0 -} -peek_str :: proc(t: ^Tokenizer, str: string) -> bool { - if t.read_offset < len(t.src) { - return strings.has_prefix(string(t.src[t.offset:]), str) - } - return false -} - -scan_literal_prefix :: proc(t: ^Tokenizer, str: string, prefix: ^string) -> bool { - if peek_str(t, str) { - offset := t.offset - for _ in str { - advance_rune(t) - } - prefix^ = string(t.src[offset:][:len(str)-1]) - return true - } - return false -} - - -allow_next_to_be_newline :: proc(t: ^Tokenizer) -> bool { - if t.ch == '\n' { - advance_rune(t) - return true - } else if t.ch == '\r' && peek(t) == '\n' { // allow for MS-DOS style line endings - advance_rune(t) // \r - advance_rune(t) // \n - return true - } - return false -} - -scan :: proc(t: ^Tokenizer, f: ^File) -> ^Token { - skip_whitespace(t) - - offset := t.offset - - kind: Token_Kind - lit: string - prefix: string - - switch ch := t.ch; { - case scan_literal_prefix(t, `u8"`, &prefix): - kind = .String - lit = scan_string(t) - case scan_literal_prefix(t, `u"`, &prefix): - kind = .String - lit = scan_string(t) - case scan_literal_prefix(t, `L"`, &prefix): - kind = .String - lit = scan_string(t) - case scan_literal_prefix(t, `U"`, &prefix): - kind = .String - lit = scan_string(t) - case scan_literal_prefix(t, `u'`, &prefix): - kind = .Char - lit = scan_rune(t) - case scan_literal_prefix(t, `L'`, &prefix): - kind = .Char - lit = scan_rune(t) - case scan_literal_prefix(t, `U'`, &prefix): - kind = .Char - lit = scan_rune(t) - - case is_ident0(ch): - lit = scan_identifier(t) - kind = .Ident - case '0' <= ch && ch <= '9': - kind, lit = scan_number(t, false) - case: - advance_rune(t) - switch ch { - case -1: - kind = .EOF - case '\\': - kind = .Punct - if allow_next_to_be_newline(t) { - t.at_bol = true - t.has_space = false - return scan(t, f) - } - - case '.': - if is_digit(t.ch) { - kind, lit = scan_number(t, true) - } else { - kind = scan_punct(t, ch) - } - case '"': - kind = .String - lit = scan_string(t) - case '\'': - kind = .Char - lit = scan_rune(t) - case '/': - if t.ch == '/' || t.ch == '*' { - kind = .Comment - lit = scan_comment(t) - t.has_space = true - break - } - fallthrough - case: - kind = scan_punct(t, ch) - if kind == .Invalid && ch != utf8.RUNE_BOM { - error_offset(t, t.offset, "illegal character '%r': %d", ch, ch) - } - } - } - - if lit == "" { - lit = string(t.src[offset : t.offset]) - } - - if kind == .Comment { - return scan(t, f) - } - - tok := new(Token) - tok.kind = kind - tok.lit = lit - tok.pos = offset_to_pos(t, offset) - tok.file = f - tok.prefix = prefix - tok.at_bol = t.at_bol - tok.has_space = t.has_space - - t.at_bol, t.has_space = false, false - - return tok -} - -tokenize :: proc(t: ^Tokenizer, f: ^File) -> ^Token { - setup_tokenizer: { - t.src = f.src - t.ch = ' ' - t.offset = 0 - t.read_offset = 0 - t.line_offset = 0 - t.line_count = len(t.src) > 0 ? 1 : 0 - t.error_count = 0 - t.path = f.name - - - advance_rune(t) - if t.ch == utf8.RUNE_BOM { - advance_rune(t) - } - } - - - t.at_bol = true - t.has_space = false - - head: Token - curr := &head - for { - tok := scan(t, f) - if tok == nil { - break - } - curr.next = tok - curr = curr.next - if tok.kind == .EOF { - break - } - } - - return head.next -} - -add_new_file :: proc(t: ^Tokenizer, name: string, src: []byte, id: int) -> ^File { - file := new(File) - file.id = id - file.src = src - file.name = name - file.display_name = name - return file -} - -tokenize_file :: proc(t: ^Tokenizer, path: string, id: int, loc := #caller_location) -> ^Token { - src, ok := os.read_entire_file(path) - if !ok { - return nil - } - return tokenize(t, add_new_file(t, path, src, id)) -} - - -inline_tokenize :: proc(t: ^Tokenizer, tok: ^Token, src: []byte) -> ^Token { - file := new(File) - file.src = src - if tok.file != nil { - file.id = tok.file.id - file.name = tok.file.name - file.display_name = tok.file.name - } - - return tokenize(t, file) -} diff --git a/core/c/frontend/tokenizer/unicode.odin b/core/c/frontend/tokenizer/unicode.odin deleted file mode 100644 index 317ee160e..000000000 --- a/core/c/frontend/tokenizer/unicode.odin +++ /dev/null @@ -1,116 +0,0 @@ -package c_frontend_tokenizer - - -in_range :: proc(range: []rune, c: rune) -> bool #no_bounds_check { - for i := 0; range[i] != -1; i += 2 { - if range[i] <= c && c <= range[i+1] { - return true - } - } - return false -} - - -// [https://www.sigbus.info/n1570#D] C11 allows ASCII and some multibyte characters in certan Unicode ranges to be used in an identifier. -// -// is_ident0 returns true if a given character is acceptable as the first character of an identifier. -is_ident0 :: proc(c: rune) -> bool { - return in_range(_range_ident0, c) -} -// is_ident0 returns true if a given character is acceptable as a non-first character of an identifier. -is_ident1 :: proc(c: rune) -> bool { - return is_ident0(c) || in_range(_range_ident1, c) -} - -// Returns the number of columns needed to display a given character in a fixed-width font. -// Based on https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c -char_width :: proc(c: rune) -> int { - switch { - case in_range(_range_width0, c): - return 0 - case in_range(_range_width2, c): - return 2 - } - return 1 -} - -display_width :: proc(str: string) -> (w: int) { - for c in str { - w += char_width(c) - } - return -} - - - -_range_ident0 := []rune{ - '_', '_', 'a', 'z', 'A', 'Z', '$', '$', - 0x00A8, 0x00A8, 0x00AA, 0x00AA, 0x00AD, 0x00AD, 0x00AF, 0x00AF, - 0x00B2, 0x00B5, 0x00B7, 0x00BA, 0x00BC, 0x00BE, 0x00C0, 0x00D6, - 0x00D8, 0x00F6, 0x00F8, 0x00FF, 0x0100, 0x02FF, 0x0370, 0x167F, - 0x1681, 0x180D, 0x180F, 0x1DBF, 0x1E00, 0x1FFF, 0x200B, 0x200D, - 0x202A, 0x202E, 0x203F, 0x2040, 0x2054, 0x2054, 0x2060, 0x206F, - 0x2070, 0x20CF, 0x2100, 0x218F, 0x2460, 0x24FF, 0x2776, 0x2793, - 0x2C00, 0x2DFF, 0x2E80, 0x2FFF, 0x3004, 0x3007, 0x3021, 0x302F, - 0x3031, 0x303F, 0x3040, 0xD7FF, 0xF900, 0xFD3D, 0xFD40, 0xFDCF, - 0xFDF0, 0xFE1F, 0xFE30, 0xFE44, 0xFE47, 0xFFFD, - 0x10000, 0x1FFFD, 0x20000, 0x2FFFD, 0x30000, 0x3FFFD, 0x40000, 0x4FFFD, - 0x50000, 0x5FFFD, 0x60000, 0x6FFFD, 0x70000, 0x7FFFD, 0x80000, 0x8FFFD, - 0x90000, 0x9FFFD, 0xA0000, 0xAFFFD, 0xB0000, 0xBFFFD, 0xC0000, 0xCFFFD, - 0xD0000, 0xDFFFD, 0xE0000, 0xEFFFD, - -1, -} - -_range_ident1 := []rune{ - '0', '9', '$', '$', 0x0300, 0x036F, 0x1DC0, 0x1DFF, 0x20D0, 0x20FF, 0xFE20, 0xFE2F, - -1, -} - - -_range_width0 := []rune{ - 0x0000, 0x001F, 0x007f, 0x00a0, 0x0300, 0x036F, 0x0483, 0x0486, - 0x0488, 0x0489, 0x0591, 0x05BD, 0x05BF, 0x05BF, 0x05C1, 0x05C2, - 0x05C4, 0x05C5, 0x05C7, 0x05C7, 0x0600, 0x0603, 0x0610, 0x0615, - 0x064B, 0x065E, 0x0670, 0x0670, 0x06D6, 0x06E4, 0x06E7, 0x06E8, - 0x06EA, 0x06ED, 0x070F, 0x070F, 0x0711, 0x0711, 0x0730, 0x074A, - 0x07A6, 0x07B0, 0x07EB, 0x07F3, 0x0901, 0x0902, 0x093C, 0x093C, - 0x0941, 0x0948, 0x094D, 0x094D, 0x0951, 0x0954, 0x0962, 0x0963, - 0x0981, 0x0981, 0x09BC, 0x09BC, 0x09C1, 0x09C4, 0x09CD, 0x09CD, - 0x09E2, 0x09E3, 0x0A01, 0x0A02, 0x0A3C, 0x0A3C, 0x0A41, 0x0A42, - 0x0A47, 0x0A48, 0x0A4B, 0x0A4D, 0x0A70, 0x0A71, 0x0A81, 0x0A82, - 0x0ABC, 0x0ABC, 0x0AC1, 0x0AC5, 0x0AC7, 0x0AC8, 0x0ACD, 0x0ACD, - 0x0AE2, 0x0AE3, 0x0B01, 0x0B01, 0x0B3C, 0x0B3C, 0x0B3F, 0x0B3F, - 0x0B41, 0x0B43, 0x0B4D, 0x0B4D, 0x0B56, 0x0B56, 0x0B82, 0x0B82, - 0x0BC0, 0x0BC0, 0x0BCD, 0x0BCD, 0x0C3E, 0x0C40, 0x0C46, 0x0C48, - 0x0C4A, 0x0C4D, 0x0C55, 0x0C56, 0x0CBC, 0x0CBC, 0x0CBF, 0x0CBF, - 0x0CC6, 0x0CC6, 0x0CCC, 0x0CCD, 0x0CE2, 0x0CE3, 0x0D41, 0x0D43, - 0x0D4D, 0x0D4D, 0x0DCA, 0x0DCA, 0x0DD2, 0x0DD4, 0x0DD6, 0x0DD6, - 0x0E31, 0x0E31, 0x0E34, 0x0E3A, 0x0E47, 0x0E4E, 0x0EB1, 0x0EB1, - 0x0EB4, 0x0EB9, 0x0EBB, 0x0EBC, 0x0EC8, 0x0ECD, 0x0F18, 0x0F19, - 0x0F35, 0x0F35, 0x0F37, 0x0F37, 0x0F39, 0x0F39, 0x0F71, 0x0F7E, - 0x0F80, 0x0F84, 0x0F86, 0x0F87, 0x0F90, 0x0F97, 0x0F99, 0x0FBC, - 0x0FC6, 0x0FC6, 0x102D, 0x1030, 0x1032, 0x1032, 0x1036, 0x1037, - 0x1039, 0x1039, 0x1058, 0x1059, 0x1160, 0x11FF, 0x135F, 0x135F, - 0x1712, 0x1714, 0x1732, 0x1734, 0x1752, 0x1753, 0x1772, 0x1773, - 0x17B4, 0x17B5, 0x17B7, 0x17BD, 0x17C6, 0x17C6, 0x17C9, 0x17D3, - 0x17DD, 0x17DD, 0x180B, 0x180D, 0x18A9, 0x18A9, 0x1920, 0x1922, - 0x1927, 0x1928, 0x1932, 0x1932, 0x1939, 0x193B, 0x1A17, 0x1A18, - 0x1B00, 0x1B03, 0x1B34, 0x1B34, 0x1B36, 0x1B3A, 0x1B3C, 0x1B3C, - 0x1B42, 0x1B42, 0x1B6B, 0x1B73, 0x1DC0, 0x1DCA, 0x1DFE, 0x1DFF, - 0x200B, 0x200F, 0x202A, 0x202E, 0x2060, 0x2063, 0x206A, 0x206F, - 0x20D0, 0x20EF, 0x302A, 0x302F, 0x3099, 0x309A, 0xA806, 0xA806, - 0xA80B, 0xA80B, 0xA825, 0xA826, 0xFB1E, 0xFB1E, 0xFE00, 0xFE0F, - 0xFE20, 0xFE23, 0xFEFF, 0xFEFF, 0xFFF9, 0xFFFB, 0x10A01, 0x10A03, - 0x10A05, 0x10A06, 0x10A0C, 0x10A0F, 0x10A38, 0x10A3A, 0x10A3F, 0x10A3F, - 0x1D167, 0x1D169, 0x1D173, 0x1D182, 0x1D185, 0x1D18B, 0x1D1AA, 0x1D1AD, - 0x1D242, 0x1D244, 0xE0001, 0xE0001, 0xE0020, 0xE007F, 0xE0100, 0xE01EF, - -1, -} - -_range_width2 := []rune{ - 0x1100, 0x115F, 0x2329, 0x2329, 0x232A, 0x232A, 0x2E80, 0x303E, - 0x3040, 0xA4CF, 0xAC00, 0xD7A3, 0xF900, 0xFAFF, 0xFE10, 0xFE19, - 0xFE30, 0xFE6F, 0xFF00, 0xFF60, 0xFFE0, 0xFFE6, 0x1F000, 0x1F644, - 0x20000, 0x2FFFD, 0x30000, 0x3FFFD, - -1, -} diff --git a/core/c/libc/README.md b/core/c/libc/README.md index 95053b963..08e789757 100644 --- a/core/c/libc/README.md +++ b/core/c/libc/README.md @@ -14,7 +14,7 @@ The following is a mostly-complete projection of the C11 standard library as def | `` | Fully projected | | `` | Not applicable, use Odin's operators | | `` | Not projected | -| `` | Not projected | +| `` | Fully projected | | `` | Mostly projected, see [limitations](#Limitations) | | `` | Fully projected | | `` | Fully projected | @@ -70,4 +70,4 @@ with the following copyright. ``` Copyright 2021 Dale Weiler . -``` \ No newline at end of file +``` diff --git a/core/c/libc/complex.odin b/core/c/libc/complex.odin index 7f2ca37ae..98fd7b1bb 100644 --- a/core/c/libc/complex.odin +++ b/core/c/libc/complex.odin @@ -47,8 +47,8 @@ foreign libc { clogf :: proc(z: complex_float) -> complex_float --- // 7.3.8 Power and absolute-value functions - cabs :: proc(z: complex_double) -> complex_double --- - cabsf :: proc(z: complex_float) -> complex_float --- + cabs :: proc(z: complex_double) -> double --- + cabsf :: proc(z: complex_float) -> float --- cpow :: proc(x, y: complex_double) -> complex_double --- cpowf :: proc(x, y: complex_float) -> complex_float --- csqrt :: proc(z: complex_double) -> complex_double --- @@ -67,7 +67,7 @@ foreign libc { crealf :: proc(z: complex_float) -> float --- } -import builtin "core:builtin" +import builtin "base:builtin" complex_float :: distinct builtin.complex64 complex_double :: distinct builtin.complex128 diff --git a/core/c/libc/errno.odin b/core/c/libc/errno.odin index fe6fbb073..de429a6ec 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 { @@ -80,10 +80,36 @@ when ODIN_OS == .Darwin { ERANGE :: 34 } +when ODIN_OS == .Haiku { + @(private="file") + @(default_calling_convention="c") + foreign libc { + @(link_name="_errnop") + _get_errno :: proc() -> ^int --- + } + + @(private="file") + B_GENERAL_ERROR_BASE :: min(i32) + @(private="file") + B_POSIX_ERROR_BASE :: B_GENERAL_ERROR_BASE + 0x7000 + + EDOM :: B_POSIX_ERROR_BASE + 16 + EILSEQ :: B_POSIX_ERROR_BASE + 38 + ERANGE :: B_POSIX_ERROR_BASE + 17 +} + +when ODIN_OS == .JS { + _ :: libc + _get_errno :: proc "c" () -> ^int { + @(static) errno: int + return &errno + } +} + // Odin has no way to make an identifier "errno" behave as a function call to // read the value, or to produce an lvalue such that you can assign a different // error value to errno. To work around this, just expose it as a function like // it actually is. -errno :: #force_inline proc() -> ^int { +errno :: #force_inline proc "contextless" () -> ^int { return _get_errno() } diff --git a/core/c/libc/locale.odin b/core/c/libc/locale.odin new file mode 100644 index 000000000..371d755c5 --- /dev/null +++ b/core/c/libc/locale.odin @@ -0,0 +1,133 @@ +package libc + +import "core:c" + +when ODIN_OS == .Windows { + foreign import libc "system:libucrt.lib" +} else when ODIN_OS == .Darwin { + foreign import libc "system:System.framework" +} else { + foreign import libc "system:c" +} + +// locale.h - category macros + +foreign libc { + /* + Sets the components of an object with the type lconv with the values appropriate for the + formatting of numeric quantities (monetary and otherwise) according to the rules of the current + locale. + + Returns: a pointer to the lconv structure, might be invalidated by subsequent calls to localeconv() and setlocale() + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/localeconv.html ]] + */ + localeconv :: proc() -> ^lconv --- + + /* + Selects the appropriate piece of the global locale, as specified by the category and locale arguments, + and can be used to change or query the entire global locale or portions thereof. + + Returns: the current locale if `locale` is `nil`, the set locale otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setlocale.html ]] + */ + @(link_name=LSETLOCALE) + setlocale :: proc(category: Locale_Category, locale: cstring) -> cstring --- +} + +Locale_Category :: enum c.int { + ALL = LC_ALL, + COLLATE = LC_COLLATE, + CTYPE = LC_CTYPE, + MESSAGES = LC_MESSAGES, + MONETARY = LC_MONETARY, + NUMERIC = LC_NUMERIC, + TIME = LC_TIME, +} + +when ODIN_OS == .NetBSD { + @(private) LSETLOCALE :: "__setlocale50" +} else { + @(private) LSETLOCALE :: "setlocale" +} + +when ODIN_OS == .Windows { + lconv :: struct { + decimal_point: cstring, + thousand_sep: cstring, + grouping: cstring, + int_curr_symbol: cstring, + currency_symbol: cstring, + mon_decimal_points: cstring, + mon_thousands_sep: cstring, + mon_grouping: cstring, + positive_sign: cstring, + negative_sign: cstring, + int_frac_digits: c.char, + frac_digits: c.char, + p_cs_precedes: c.char, + p_sep_by_space: c.char, + n_cs_precedes: c.char, + n_sep_by_space: c.char, + p_sign_posn: c.char, + n_sign_posn: c.char, + _W_decimal_point: [^]u16 `fmt:"s,0"`, + _W_thousands_sep: [^]u16 `fmt:"s,0"`, + _W_int_curr_symbol: [^]u16 `fmt:"s,0"`, + _W_currency_symbol: [^]u16 `fmt:"s,0"`, + _W_mon_decimal_point: [^]u16 `fmt:"s,0"`, + _W_mon_thousands_sep: [^]u16 `fmt:"s,0"`, + _W_positive_sign: [^]u16 `fmt:"s,0"`, + _W_negative_sign: [^]u16 `fmt:"s,0"`, + } +} else { + lconv :: struct { + decimal_point: cstring, + thousand_sep: cstring, + grouping: cstring, + int_curr_symbol: cstring, + currency_symbol: cstring, + mon_decimal_points: cstring, + mon_thousands_sep: cstring, + mon_grouping: cstring, + positive_sign: cstring, + negative_sign: cstring, + int_frac_digits: c.char, + frac_digits: c.char, + p_cs_precedes: c.char, + p_sep_by_space: c.char, + n_cs_precedes: c.char, + n_sep_by_space: c.char, + p_sign_posn: c.char, + n_sign_posn: c.char, + _int_p_cs_precedes: c.char, + _int_n_cs_precedes: c.char, + _int_p_sep_by_space: c.char, + _int_n_sep_by_space: c.char, + _int_p_sign_posn: c.char, + _int_n_sign_posn: c.char, + } +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Windows { + + LC_ALL :: 0 + LC_COLLATE :: 1 + LC_CTYPE :: 2 + LC_MESSAGES :: 6 + LC_MONETARY :: 3 + LC_NUMERIC :: 4 + LC_TIME :: 5 + +} else when ODIN_OS == .Linux { + + LC_CTYPE :: 0 + LC_NUMERIC :: 1 + LC_TIME :: 2 + LC_COLLATE :: 3 + LC_MONETARY :: 4 + LC_MESSAGES :: 5 + LC_ALL :: 6 + +} diff --git a/core/c/libc/math.odin b/core/c/libc/math.odin index 0a6ecc0c3..81d51728d 100644 --- a/core/c/libc/math.odin +++ b/core/c/libc/math.odin @@ -2,7 +2,7 @@ package libc // 7.12 Mathematics -import "core:intrinsics" +import "base:intrinsics" when ODIN_OS == .Windows { foreign import libc "system:libucrt.lib" diff --git a/core/c/libc/setjmp.odin b/core/c/libc/setjmp.odin index 68f5ac010..101b614b3 100644 --- a/core/c/libc/setjmp.odin +++ b/core/c/libc/setjmp.odin @@ -32,24 +32,21 @@ when ODIN_OS == .Windows { // the RDX register will contain zero and correctly set the flag to disable // stack unwinding. @(link_name="_setjmp") - setjmp :: proc(env: ^jmp_buf, hack: rawptr = nil) -> int --- + setjmp :: proc(env: ^jmp_buf, hack: rawptr = nil) -> int --- } } else { @(default_calling_convention="c") foreign libc { // 7.13.1 Save calling environment - // - // NOTE(dweiler): C11 requires setjmp be a macro, which means it won't - // necessarily export a symbol named setjmp but rather _setjmp in the case - // of musl, glibc, BSD libc, and msvcrt. - @(link_name="_setjmp") - setjmp :: proc(env: ^jmp_buf) -> int --- + @(link_name=LSETJMP) + setjmp :: proc(env: ^jmp_buf) -> int --- } } @(default_calling_convention="c") foreign libc { // 7.13.2 Restore calling environment + @(link_name=LLONGJMP) longjmp :: proc(env: ^jmp_buf, val: int) -> ! --- } @@ -64,3 +61,11 @@ foreign libc { // The choice of 4096 bytes for storage of this type is more than enough on all // relevant platforms. jmp_buf :: struct #align(16) { _: [4096]char, } + +when ODIN_OS == .NetBSD { + @(private) LSETJMP :: "__setjmp14" + @(private) LLONGJMP :: "__longjmp14" +} else { + @(private) LSETJMP :: "setjmp" + @(private) LLONGJMP :: "longjmp" +} diff --git a/core/c/libc/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/stdarg.odin b/core/c/libc/stdarg.odin index b79b22b5a..232471713 100644 --- a/core/c/libc/stdarg.odin +++ b/core/c/libc/stdarg.odin @@ -2,7 +2,9 @@ package libc // 7.16 Variable arguments -import "core:intrinsics" +import "base:intrinsics" + +import "core:c" @(private="file") @(default_calling_convention="none") @@ -12,15 +14,7 @@ foreign _ { @(link_name="llvm.va_copy") _va_copy :: proc(dst, src: ^i8) --- } -// Since there are no types in C with an alignment larger than that of -// max_align_t, which cannot be larger than sizeof(long double) as any other -// exposed type wouldn't be valid C, the maximum alignment possible in a -// strictly conformant C implementation is 16 on the platforms we care about. -// The choice of 4096 bytes for storage of this type is more than enough on all -// relevant platforms. -va_list :: struct #align(16) { - _: [4096]u8, -} +va_list :: c.va_list va_start :: #force_inline proc(ap: ^va_list, _: any) { _va_start(cast(^i8)ap) diff --git a/core/c/libc/stdatomic.odin b/core/c/libc/stdatomic.odin index 6e1581c58..43a0e51df 100644 --- a/core/c/libc/stdatomic.odin +++ b/core/c/libc/stdatomic.odin @@ -2,7 +2,7 @@ package libc // 7.17 Atomics -import "core:intrinsics" +import "base:intrinsics" ATOMIC_BOOL_LOCK_FREE :: true ATOMIC_CHAR_LOCK_FREE :: true @@ -235,7 +235,7 @@ atomic_compare_exchange_weak :: #force_inline proc(object, expected: ^$T, desire return ok } -atomic_compare_exchange_weak_explicit :: #force_inline proc(object, expected: ^$T, desited: T, success, failure: memory_order) -> bool { +atomic_compare_exchange_weak_explicit :: #force_inline proc(object, expected: ^$T, desired: T, success, failure: memory_order) -> bool { assert(failure != .release) assert(failure != .acq_rel) diff --git a/core/c/libc/stdio.odin b/core/c/libc/stdio.odin index 94007595c..a94a53696 100644 --- a/core/c/libc/stdio.odin +++ b/core/c/libc/stdio.odin @@ -1,5 +1,7 @@ package libc +import "core:io" + when ODIN_OS == .Windows { foreign import libc { "system:libucrt.lib", @@ -15,6 +17,12 @@ when ODIN_OS == .Windows { FILE :: struct {} +Whence :: enum int { + SET = SEEK_SET, + CUR = SEEK_CUR, + END = SEEK_END, +} + // MSVCRT compatible. when ODIN_OS == .Windows { _IOFBF :: 0x0000 @@ -81,7 +89,31 @@ when ODIN_OS == .Linux { } } -when ODIN_OS == .OpenBSD { +when ODIN_OS == .JS { + fpos_t :: struct #raw_union { _: [16]char, _: longlong, _: double, } + + _IOFBF :: 0 + _IOLBF :: 1 + _IONBF :: 2 + + BUFSIZ :: 1024 + + EOF :: int(-1) + + FOPEN_MAX :: 1000 + + FILENAME_MAX :: 4096 + + L_tmpnam :: 20 + + SEEK_SET :: 0 + SEEK_CUR :: 1 + SEEK_END :: 2 + + TMP_MAX :: 308915776 +} + +when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { fpos_t :: distinct i64 _IOFBF :: 0 @@ -99,11 +131,15 @@ when ODIN_OS == .OpenBSD { SEEK_CUR :: 1 SEEK_END :: 2 + TMP_MAX :: 308915776 + 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 { @@ -124,10 +160,12 @@ when ODIN_OS == .FreeBSD { SEEK_CUR :: 1 SEEK_END :: 2 + TMP_MAX :: 308915776 + foreign libc { - stderr: ^FILE - stdin: ^FILE - stdout: ^FILE + @(link_name="__stderrp") stderr: ^FILE + @(link_name="__stdinp") stdin: ^FILE + @(link_name="__stdoutp") stdout: ^FILE } } @@ -161,10 +199,51 @@ when ODIN_OS == .Darwin { } } +when ODIN_OS == .Haiku { + fpos_t :: distinct i64 + + _IOFBF :: 0 + _IOLBF :: 1 + _IONBF :: 2 + + BUFSIZ :: 8192 + + EOF :: int(-1) + + FOPEN_MAX :: 128 + + FILENAME_MAX :: 256 + + L_tmpnam :: 512 + + SEEK_SET :: 0 + SEEK_CUR :: 1 + SEEK_END :: 2 + + TMP_MAX :: 32768 + + foreign libc { + stderr: ^FILE + stdin: ^FILE + stdout: ^FILE + } +} + +when ODIN_OS == .NetBSD { + @(private) LRENAME :: "__posix_rename" + @(private) LFGETPOS :: "__fgetpos50" + @(private) LFSETPOS :: "__fsetpos50" +} else { + @(private) LRENAME :: "rename" + @(private) LFGETPOS :: "fgetpos" + @(private) LFSETPOS :: "fsetpos" +} + @(default_calling_convention="c") foreign libc { // 7.21.4 Operations on files remove :: proc(filename: cstring) -> int --- + @(link_name=LRENAME) rename :: proc(old, new: cstring) -> int --- tmpfile :: proc() -> ^FILE --- tmpnam :: proc(s: [^]char) -> [^]char --- @@ -206,8 +285,10 @@ foreign libc { fwrite :: proc(ptr: rawptr, size: size_t, nmemb: size_t, stream: ^FILE) -> size_t --- // 7.21.9 File positioning functions + @(link_name=LFGETPOS) fgetpos :: proc(stream: ^FILE, pos: ^fpos_t) -> int --- - fseek :: proc(stream: ^FILE, offset: long, whence: int) -> int --- + fseek :: proc(stream: ^FILE, offset: long, whence: Whence) -> int --- + @(link_name=LFSETPOS) fsetpos :: proc(stream: ^FILE, pos: ^fpos_t) -> int --- ftell :: proc(stream: ^FILE) -> long --- rewind :: proc(stream: ^FILE) --- @@ -218,3 +299,106 @@ foreign libc { ferror :: proc(stream: ^FILE) -> int --- perror :: proc(s: cstring) --- } + +to_stream :: proc(file: ^FILE) -> io.Stream { + stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { + unknown_or_eof :: proc(f: ^FILE) -> io.Error { + switch { + case ferror(f) != 0: + return .Unknown + case feof(f) != 0: + return .EOF + case: + return nil + } + } + + file := (^FILE)(stream_data) + switch mode { + case .Close: + if fclose(file) != 0 { + return 0, unknown_or_eof(file) + } + + case .Flush: + if fflush(file) != 0 { + return 0, unknown_or_eof(file) + } + + case .Read: + n = i64(fread(raw_data(p), size_of(byte), len(p), file)) + if n == 0 { err = unknown_or_eof(file) } + + case .Read_At: + curr := ftell(file) + if curr == -1 { + return 0, unknown_or_eof(file) + } + + if fseek(file, long(offset), .SET) != 0 { + return 0, unknown_or_eof(file) + } + + defer fseek(file, long(curr), .SET) + + n = i64(fread(raw_data(p), size_of(byte), len(p), file)) + if n == 0 { err = unknown_or_eof(file) } + + case .Write: + n = i64(fwrite(raw_data(p), size_of(byte), len(p), file)) + if n == 0 { err = unknown_or_eof(file) } + + case .Write_At: + curr := ftell(file) + if curr == -1 { + return 0, unknown_or_eof(file) + } + + if fseek(file, long(offset), .SET) != 0 { + return 0, unknown_or_eof(file) + } + + defer fseek(file, long(curr), .SET) + + n = i64(fwrite(raw_data(p), size_of(byte), len(p), file)) + if n == 0 { err = unknown_or_eof(file) } + + case .Seek: + #assert(int(Whence.SET) == int(io.Seek_From.Start)) + #assert(int(Whence.CUR) == int(io.Seek_From.Current)) + #assert(int(Whence.END) == int(io.Seek_From.End)) + + if fseek(file, long(offset), Whence(whence)) != 0 { + return 0, unknown_or_eof(file) + } + + case .Size: + curr := ftell(file) + if curr == -1 { + return 0, unknown_or_eof(file) + } + defer fseek(file, curr, .SET) + + if fseek(file, 0, .END) != 0 { + return 0, unknown_or_eof(file) + } + + n = i64(ftell(file)) + if n == -1 { + return 0, unknown_or_eof(file) + } + + case .Destroy: + return 0, .Empty + + case .Query: + return io.query_utility({ .Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query }) + } + return + } + + return { + data = file, + procedure = stream_proc, + } +} diff --git a/core/c/libc/stdlib.odin b/core/c/libc/stdlib.odin index d797b8746..98280e44b 100644 --- a/core/c/libc/stdlib.odin +++ b/core/c/libc/stdlib.odin @@ -10,6 +10,9 @@ when ODIN_OS == .Windows { foreign import libc "system:c" } +@(require) +import "base:runtime" + when ODIN_OS == .Windows { RAND_MAX :: 0x7fff @@ -40,10 +43,9 @@ when ODIN_OS == .Linux { } -when ODIN_OS == .Darwin { +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD { RAND_MAX :: 0x7fffffff - // GLIBC and MUSL only @(private="file") @(default_calling_convention="c") foreign libc { @@ -55,6 +57,20 @@ when ODIN_OS == .Darwin { } } +when ODIN_OS == .NetBSD { + RAND_MAX :: 0x7fffffff + + @(private="file") + @(default_calling_convention="c") + foreign libc { + __mb_cur_max: size_t + } + + MB_CUR_MAX :: #force_inline proc() -> size_t { + return __mb_cur_max + } +} + // C does not declare what these values should be, as an implementation is free // to use any two distinct values it wants to indicate success or failure. // However, nobody actually does and everyone appears to have agreed upon these @@ -99,7 +115,7 @@ foreign libc { at_quick_exit :: proc(func: proc "c" ()) -> int --- exit :: proc(status: int) -> ! --- _Exit :: proc(status: int) -> ! --- - getenv :: proc(name: cstring) -> [^]char --- + getenv :: proc(name: cstring) -> cstring --- quick_exit :: proc(status: int) -> ! --- system :: proc(cmd: cstring) -> int --- @@ -132,6 +148,10 @@ aligned_alloc :: #force_inline proc "c" (alignment, size: size_t) -> rawptr { _aligned_malloc :: proc(size, alignment: size_t) -> rawptr --- } return _aligned_malloc(size=size, alignment=alignment) + } else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 { + context = runtime.default_context() + data, _ := runtime.mem_alloc_bytes(auto_cast size, auto_cast alignment) + return raw_data(data) } else { foreign libc { aligned_alloc :: proc(alignment, size: size_t) -> rawptr --- @@ -147,7 +167,10 @@ aligned_free :: #force_inline proc "c" (ptr: rawptr) { _aligned_free :: proc(ptr: rawptr) --- } _aligned_free(ptr) + } else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 { + context = runtime.default_context() + runtime.mem_free(ptr) } else { free(ptr) } -} \ No newline at end of file +} diff --git a/core/c/libc/string.odin b/core/c/libc/string.odin index 8f83ee1b9..4ec4f3a7a 100644 --- a/core/c/libc/string.odin +++ b/core/c/libc/string.odin @@ -1,6 +1,6 @@ package libc -import "core:runtime" +import "base:runtime" // 7.24 String handling @@ -12,6 +12,7 @@ when ODIN_OS == .Windows { foreign import libc "system:c" } +@(default_calling_convention="c") foreign libc { // 7.24.2 Copying functions memcpy :: proc(s1, s2: rawptr, n: size_t) -> rawptr --- @@ -40,7 +41,7 @@ foreign libc { strtok :: proc(s1: [^]char, s2: cstring) -> [^]char --- // 7.24.6 Miscellaneous functions - strerror :: proc(errnum: int) -> [^]char --- + strerror :: proc(errnum: int) -> cstring --- strlen :: proc(s: cstring) -> size_t --- } memset :: proc "c" (s: rawptr, c: int, n: size_t) -> rawptr { diff --git a/core/c/libc/time.odin b/core/c/libc/time.odin index 72b899546..6828793ec 100644 --- a/core/c/libc/time.odin +++ b/core/c/libc/time.odin @@ -45,35 +45,61 @@ when ODIN_OS == .Windows { } } -when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD { +when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku || ODIN_OS == .JS { @(default_calling_convention="c") foreign libc { // 7.27.2 Time manipulation functions clock :: proc() -> clock_t --- + @(link_name=LDIFFTIME) difftime :: proc(time1, time2: time_t) -> double --- + @(link_name=LMKTIME) mktime :: proc(timeptr: ^tm) -> time_t --- + @(link_name=LTIME) time :: proc(timer: ^time_t) -> time_t --- timespec_get :: proc(ts: ^timespec, base: int) -> int --- // 7.27.3 Time conversion functions asctime :: proc(timeptr: ^tm) -> [^]char --- + @(link_name=LCTIME) ctime :: proc(timer: ^time_t) -> [^]char --- + @(link_name=LGMTIME) gmtime :: proc(timer: ^time_t) -> ^tm --- + @(link_name=LLOCALTIME) localtime :: proc(timer: ^time_t) -> ^tm --- strftime :: proc(s: [^]char, maxsize: size_t, format: cstring, timeptr: ^tm) -> size_t --- } + when ODIN_OS == .NetBSD { + @(private) LDIFFTIME :: "__difftime50" + @(private) LMKTIME :: "__mktime50" + @(private) LTIME :: "__time50" + @(private) LCTIME :: "__ctime50" + @(private) LGMTIME :: "__gmtime50" + @(private) LLOCALTIME :: "__localtime50" + } else { + @(private) LDIFFTIME :: "difftime" + @(private) LMKTIME :: "mktime" + @(private) LTIME :: "time" + @(private) LCTIME :: "ctime" + @(private) LGMTIME :: "gmtime" + @(private) LLOCALTIME :: "localtime" + } + when ODIN_OS == .OpenBSD { CLOCKS_PER_SEC :: 100 } else { CLOCKS_PER_SEC :: 1000000 } - TIME_UTC :: 1 + TIME_UTC :: 1 - time_t :: distinct i64 + time_t :: distinct i64 - clock_t :: long + when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { + clock_t :: distinct int32_t + } else { + clock_t :: distinct long + } timespec :: struct { tv_sec: time_t, diff --git a/core/c/libc/types.odin b/core/c/libc/types.odin index a49e52fb6..cc844c1c5 100644 --- a/core/c/libc/types.odin +++ b/core/c/libc/types.odin @@ -2,6 +2,8 @@ package libc import "core:c" +#assert(!ODIN_NO_CRT, `"core:c/libc" cannot be imported when '-no-crt' is used`) + char :: c.char // assuming -funsigned-char schar :: c.schar diff --git a/core/c/libc/wctype.odin b/core/c/libc/wctype.odin index 43aee9dc6..b96410b4c 100644 --- a/core/c/libc/wctype.odin +++ b/core/c/libc/wctype.odin @@ -14,7 +14,7 @@ when ODIN_OS == .Windows { wctrans_t :: distinct wchar_t wctype_t :: distinct ushort -} else when ODIN_OS == .Linux { +} else when ODIN_OS == .Linux || ODIN_OS == .JS { wctrans_t :: distinct intptr_t wctype_t :: distinct ulong @@ -22,14 +22,18 @@ 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 } else when ODIN_OS == .FreeBSD { wctrans_t :: distinct int wctype_t :: distinct ulong - + +} else when ODIN_OS == .Haiku { + wctrans_t :: distinct i32 + wctype_t :: distinct i32 + } @(default_calling_convention="c") diff --git a/core/compress/common.odin b/core/compress/common.odin index e1cfb4cb5..242538e78 100644 --- a/core/compress/common.odin +++ b/core/compress/common.odin @@ -12,7 +12,7 @@ package compress import "core:io" import "core:bytes" -import "core:runtime" +import "base:runtime" /* These settings bound how much compression algorithms will allocate for their output buffer. @@ -20,10 +20,9 @@ import "core:runtime" */ -/* - When a decompression routine doesn't stream its output, but writes to a buffer, - we pre-allocate an output buffer to speed up decompression. The default is 1 MiB. -*/ + +// When a decompression routine doesn't stream its output, but writes to a buffer, +// we pre-allocate an output buffer to speed up decompression. The default is 1 MiB. COMPRESS_OUTPUT_ALLOCATE_MIN :: int(#config(COMPRESS_OUTPUT_ALLOCATE_MIN, 1 << 20)) /* @@ -34,15 +33,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. - */ + + // 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)) } @@ -69,9 +66,8 @@ General_Error :: enum { Incompatible_Options, Unimplemented, - /* - Memory errors - */ + // Memory errors + Allocation_Failed, Resize_Failed, } @@ -86,17 +82,16 @@ GZIP_Error :: enum { Payload_Length_Invalid, Payload_CRC_Invalid, - /* - GZIP's payload can be a maximum of max(u32le), or 4 GiB. - If you tell it you expect it to contain more, that's obviously an error. - */ - Payload_Size_Exceeds_Max_Payload, - /* - For buffered instead of streamed output, the payload size can't exceed - the max set by the `COMPRESS_OUTPUT_ALLOCATE_MAX` switch in compress/common.odin. + // GZIP's payload can be a maximum of max(u32le), or 4 GiB. + // If you tell it you expect it to contain more, that's obviously an error. + + Payload_Size_Exceeds_Max_Payload, + + // For buffered instead of streamed output, the payload size can't exceed + // the max set by the `COMPRESS_OUTPUT_ALLOCATE_MAX` switch in compress/common.odin. + // + // You can tweak this setting using `-define:COMPRESS_OUTPUT_ALLOCATE_MAX=size_in_bytes` - You can tweak this setting using `-define:COMPRESS_OUTPUT_ALLOCATE_MAX=size_in_bytes` - */ Output_Exceeds_COMPRESS_OUTPUT_ALLOCATE_MAX, } @@ -137,9 +132,8 @@ Context_Memory_Input :: struct #packed { code_buffer: u64, num_bits: u64, - /* - If we know the data size, we can optimize the reads and writes. - */ + // If we know the data size, we can optimize the reads and writes. + size_packed: i64, size_unpacked: i64, } @@ -159,18 +153,16 @@ Context_Stream_Input :: struct #packed { code_buffer: u64, num_bits: u64, - /* - If we know the data size, we can optimize the reads and writes. - */ + // If we know the data size, we can optimize the reads and writes. + size_packed: i64, size_unpacked: i64, - /* - Flags: - `input_fully_in_memory` - true = This tells us we read input from `input_data` exclusively. [] = EOF. - false = Try to refill `input_data` from the `input` stream. - */ + // Flags: + // `input_fully_in_memory` + // true = This tells us we read input from `input_data` exclusively. [] = EOF. + // false = Try to refill `input_data` from the `input` stream. + input_fully_in_memory: b8, padding: [1]u8, @@ -194,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 { @@ -211,10 +203,10 @@ 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 - // the is literally no need for it + // there is literally no need for it b := make([]u8, size, context.temp_allocator) _ = io.read(z.input, b[:]) or_return return b, nil @@ -222,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 { @@ -240,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 @@ -248,11 +240,9 @@ read_u8_from_stream :: #force_inline proc(z: ^Context_Stream_Input) -> (res: u8, 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") +// You would typically only use this at the end of Inflate, to drain bits from the code buffer +// preferentially. +@(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)) @@ -267,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) @@ -285,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) @@ -303,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) @@ -327,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) @@ -362,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) @@ -395,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) @@ -424,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) @@ -438,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) @@ -446,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) @@ -456,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)) @@ -470,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) @@ -486,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) @@ -503,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/gzip/doc.odin b/core/compress/gzip/doc.odin new file mode 100644 index 000000000..fd7ef5a19 --- /dev/null +++ b/core/compress/gzip/doc.odin @@ -0,0 +1,90 @@ +/* + Copyright 2021 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Jeroen van Rijn: Initial implementation. + Ginger Bill: Cosmetic changes. + + A small GZIP implementation as an example. +*/ + +/* +Example: + import "core:bytes" + import "core:os" + import "core:compress" + import "core:fmt" + + // Small GZIP file with fextra, fname and fcomment present. + @private + TEST: []u8 = { + 0x1f, 0x8b, 0x08, 0x1c, 0xcb, 0x3b, 0x3a, 0x5a, + 0x02, 0x03, 0x07, 0x00, 0x61, 0x62, 0x03, 0x00, + 0x63, 0x64, 0x65, 0x66, 0x69, 0x6c, 0x65, 0x6e, + 0x61, 0x6d, 0x65, 0x00, 0x54, 0x68, 0x69, 0x73, + 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x63, 0x6f, + 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x2b, 0x48, + 0xac, 0xcc, 0xc9, 0x4f, 0x4c, 0x01, 0x00, 0x15, + 0x6a, 0x2c, 0x42, 0x07, 0x00, 0x00, 0x00, + } + + main :: proc() { + // Set up output buffer. + buf := bytes.Buffer{} + + stdout :: proc(s: string) { + os.write_string(os.stdout, s) + } + stderr :: proc(s: string) { + os.write_string(os.stderr, s) + } + + args := os.args + + if len(args) < 2 { + stderr("No input file specified.\n") + err := load(data=TEST, buf=&buf, known_gzip_size=len(TEST)) + if err == nil { + stdout("Displaying test vector: ") + stdout(bytes.buffer_to_string(&buf)) + stdout("\n") + } else { + fmt.printf("gzip.load returned %v\n", err) + } + bytes.buffer_destroy(&buf) + os.exit(0) + } + + // The rest are all files. + args = args[1:] + err: Error + + for file in args { + if file == "-" { + // Read from stdin + s := os.stream_from_handle(os.stdin) + ctx := &compress.Context_Stream_Input{ + input = s, + } + err = load(ctx, &buf) + } else { + err = load(file, &buf) + } + if err != nil { + if err != E_General.File_Not_Found { + stderr("File not found: ") + stderr(file) + stderr("\n") + os.exit(1) + } + stderr("GZIP returned an error.\n") + bytes.buffer_destroy(&buf) + os.exit(2) + } + stdout(bytes.buffer_to_string(&buf)) + } + bytes.buffer_destroy(&buf) + } +*/ +package compress_gzip diff --git a/core/compress/gzip/example.odin b/core/compress/gzip/example.odin deleted file mode 100644 index 635134e40..000000000 --- a/core/compress/gzip/example.odin +++ /dev/null @@ -1,89 +0,0 @@ -//+build ignore -package gzip - -/* - Copyright 2021 Jeroen van Rijn . - Made available under Odin's BSD-3 license. - - List of contributors: - Jeroen van Rijn: Initial implementation. - Ginger Bill: Cosmetic changes. - - A small GZIP implementation as an example. -*/ - -import "core:bytes" -import "core:os" -import "core:compress" -import "core:fmt" - -// Small GZIP file with fextra, fname and fcomment present. -@private -TEST: []u8 = { - 0x1f, 0x8b, 0x08, 0x1c, 0xcb, 0x3b, 0x3a, 0x5a, - 0x02, 0x03, 0x07, 0x00, 0x61, 0x62, 0x03, 0x00, - 0x63, 0x64, 0x65, 0x66, 0x69, 0x6c, 0x65, 0x6e, - 0x61, 0x6d, 0x65, 0x00, 0x54, 0x68, 0x69, 0x73, - 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x63, 0x6f, - 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x2b, 0x48, - 0xac, 0xcc, 0xc9, 0x4f, 0x4c, 0x01, 0x00, 0x15, - 0x6a, 0x2c, 0x42, 0x07, 0x00, 0x00, 0x00, -} - -main :: proc() { - // Set up output buffer. - buf := bytes.Buffer{} - - stdout :: proc(s: string) { - os.write_string(os.stdout, s) - } - stderr :: proc(s: string) { - os.write_string(os.stderr, s) - } - - args := os.args - - if len(args) < 2 { - stderr("No input file specified.\n") - err := load(data=TEST, buf=&buf, known_gzip_size=len(TEST)) - if err == nil { - stdout("Displaying test vector: ") - stdout(bytes.buffer_to_string(&buf)) - stdout("\n") - } else { - fmt.printf("gzip.load returned %v\n", err) - } - bytes.buffer_destroy(&buf) - os.exit(0) - } - - // The rest are all files. - args = args[1:] - err: Error - - for file in args { - if file == "-" { - // Read from stdin - s := os.stream_from_handle(os.stdin) - ctx := &compress.Context_Stream_Input{ - input = s, - } - err = load(ctx, &buf) - } else { - err = load(file, &buf) - } - if err != nil { - if err != E_General.File_Not_Found { - stderr("File not found: ") - stderr(file) - stderr("\n") - os.exit(1) - } - stderr("GZIP returned an error.\n") - bytes.buffer_destroy(&buf) - os.exit(2) - } - stdout(bytes.buffer_to_string(&buf)) - } - bytes.buffer_destroy(&buf) -} diff --git a/core/compress/gzip/gzip.odin b/core/compress/gzip/gzip.odin index 50945fc77..57ed3c3c5 100644 --- a/core/compress/gzip/gzip.odin +++ b/core/compress/gzip/gzip.odin @@ -1,4 +1,4 @@ -package gzip +package compress_gzip /* Copyright 2021 Jeroen van Rijn . diff --git a/core/compress/shoco/model.odin b/core/compress/shoco/model.odin index bbc38903d..919563441 100644 --- a/core/compress/shoco/model.odin +++ b/core/compress/shoco/model.odin @@ -4,8 +4,7 @@ which is an English word model. */ -// package shoco is an implementation of the shoco short string compressor -package shoco +package compress_shoco DEFAULT_MODEL :: Shoco_Model { min_char = 39, @@ -145,4 +144,4 @@ DEFAULT_MODEL :: Shoco_Model { { 0xc0000000, 2, 4, { 25, 22, 19, 16, 16, 16, 16, 16 }, { 15, 7, 7, 7, 0, 0, 0, 0 }, 0xe0, 0xc0 }, { 0xe0000000, 4, 8, { 23, 19, 15, 11, 8, 5, 2, 0 }, { 31, 15, 15, 15, 7, 7, 7, 3 }, 0xf0, 0xe0 }, }, -} \ No newline at end of file +} diff --git a/core/compress/shoco/shoco.odin b/core/compress/shoco/shoco.odin index 04b0bfdc2..b393b8356 100644 --- a/core/compress/shoco/shoco.odin +++ b/core/compress/shoco/shoco.odin @@ -8,10 +8,10 @@ An implementation of [shoco](https://github.com/Ed-von-Schleck/shoco) by Christian Schramm. */ -// package shoco is an implementation of the shoco short string compressor -package shoco +// package shoco is an implementation of the shoco short string compressor. +package compress_shoco -import "core:intrinsics" +import "base:intrinsics" import "core:compress" Shoco_Pack :: struct { @@ -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 } @@ -311,4 +308,4 @@ compress_string :: proc(input: string, model := DEFAULT_MODEL, allocator := cont resize(&buf, length) or_return return buf[:length], result } -compress :: proc{compress_string_to_buffer, compress_string} \ No newline at end of file +compress :: proc{compress_string_to_buffer, compress_string} diff --git a/core/compress/zlib/doc.odin b/core/compress/zlib/doc.odin new file mode 100644 index 000000000..6ae537a87 --- /dev/null +++ b/core/compress/zlib/doc.odin @@ -0,0 +1,51 @@ +/* + Copyright 2021 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Jeroen van Rijn: Initial implementation. + + An example of how to use `zlib.inflate`. +*/ + +/* +Example: + package main + + import "core:bytes" + import "core:compress/zlib" + import "core:fmt" + + main :: proc() { + ODIN_DEMO := []u8{ + 120, 218, 101, 144, 65, 110, 131, 48, 16, 69, 215, 246, 41, 190, 44, 69, 73, 32, 148, 182, + 75, 75, 28, 32, 251, 46, 217, 88, 238, 0, 86, 192, 32, 219, 36, 170, 170, 172, 122, 137, + 238, 122, 197, 30, 161, 70, 162, 20, 81, 203, 139, 25, 191, 255, 191, 60, 51, 40, 125, 81, + 53, 33, 144, 15, 156, 155, 110, 232, 93, 128, 208, 189, 35, 89, 117, 65, 112, 222, 41, 99, + 33, 37, 6, 215, 235, 195, 17, 239, 156, 197, 170, 118, 170, 131, 44, 32, 82, 164, 72, 240, + 253, 245, 249, 129, 12, 185, 224, 76, 105, 61, 118, 99, 171, 66, 239, 38, 193, 35, 103, 85, + 172, 66, 127, 33, 139, 24, 244, 235, 141, 49, 204, 223, 76, 208, 205, 204, 166, 7, 173, 60, + 97, 159, 238, 37, 214, 41, 105, 129, 167, 5, 102, 27, 152, 173, 97, 178, 129, 73, 129, 231, + 5, 230, 27, 152, 175, 225, 52, 192, 127, 243, 170, 157, 149, 18, 121, 142, 115, 109, 227, 122, + 64, 87, 114, 111, 161, 49, 182, 6, 181, 158, 162, 226, 206, 167, 27, 215, 246, 48, 56, 99, + 67, 117, 16, 47, 13, 45, 35, 151, 98, 231, 75, 1, 173, 90, 61, 101, 146, 71, 136, 244, + 170, 218, 145, 176, 123, 45, 173, 56, 113, 134, 191, 51, 219, 78, 235, 95, 28, 249, 253, 7, + 159, 150, 133, 125, + } + OUTPUT_SIZE :: 432 + + buf: bytes.Buffer + + // We can pass ", true" to inflate a raw DEFLATE stream instead of a ZLIB wrapped one. + err := zlib.inflate(input=ODIN_DEMO, buf=&buf, expected_output_size=OUTPUT_SIZE) + defer bytes.buffer_destroy(&buf) + + if err != nil { + fmt.printf("\nError: %v\n", err) + } + s := bytes.buffer_to_string(&buf) + fmt.printf("Input: %v bytes, output (%v bytes):\n%v\n", len(ODIN_DEMO), len(s), s) + assert(len(s) == OUTPUT_SIZE) + } +*/ +package compress_zlib diff --git a/core/compress/zlib/example.odin b/core/compress/zlib/example.odin deleted file mode 100644 index 19017b279..000000000 --- a/core/compress/zlib/example.odin +++ /dev/null @@ -1,47 +0,0 @@ -//+build ignore -package zlib - -/* - Copyright 2021 Jeroen van Rijn . - Made available under Odin's BSD-3 license. - - List of contributors: - Jeroen van Rijn: Initial implementation. - - An example of how to use `zlib.inflate`. -*/ - -import "core:bytes" -import "core:fmt" - -main :: proc() { - ODIN_DEMO := []u8{ - 120, 218, 101, 144, 65, 110, 131, 48, 16, 69, 215, 246, 41, 190, 44, 69, 73, 32, 148, 182, - 75, 75, 28, 32, 251, 46, 217, 88, 238, 0, 86, 192, 32, 219, 36, 170, 170, 172, 122, 137, - 238, 122, 197, 30, 161, 70, 162, 20, 81, 203, 139, 25, 191, 255, 191, 60, 51, 40, 125, 81, - 53, 33, 144, 15, 156, 155, 110, 232, 93, 128, 208, 189, 35, 89, 117, 65, 112, 222, 41, 99, - 33, 37, 6, 215, 235, 195, 17, 239, 156, 197, 170, 118, 170, 131, 44, 32, 82, 164, 72, 240, - 253, 245, 249, 129, 12, 185, 224, 76, 105, 61, 118, 99, 171, 66, 239, 38, 193, 35, 103, 85, - 172, 66, 127, 33, 139, 24, 244, 235, 141, 49, 204, 223, 76, 208, 205, 204, 166, 7, 173, 60, - 97, 159, 238, 37, 214, 41, 105, 129, 167, 5, 102, 27, 152, 173, 97, 178, 129, 73, 129, 231, - 5, 230, 27, 152, 175, 225, 52, 192, 127, 243, 170, 157, 149, 18, 121, 142, 115, 109, 227, 122, - 64, 87, 114, 111, 161, 49, 182, 6, 181, 158, 162, 226, 206, 167, 27, 215, 246, 48, 56, 99, - 67, 117, 16, 47, 13, 45, 35, 151, 98, 231, 75, 1, 173, 90, 61, 101, 146, 71, 136, 244, - 170, 218, 145, 176, 123, 45, 173, 56, 113, 134, 191, 51, 219, 78, 235, 95, 28, 249, 253, 7, - 159, 150, 133, 125, - } - OUTPUT_SIZE :: 432 - - buf: bytes.Buffer - - // We can pass ", true" to inflate a raw DEFLATE stream instead of a ZLIB wrapped one. - err := inflate(input=ODIN_DEMO, buf=&buf, expected_output_size=OUTPUT_SIZE) - defer bytes.buffer_destroy(&buf) - - if err != nil { - fmt.printf("\nError: %v\n", err) - } - s := bytes.buffer_to_string(&buf) - fmt.printf("Input: %v bytes, output (%v bytes):\n%v\n", len(ODIN_DEMO), len(s), s) - assert(len(s) == OUTPUT_SIZE) -} diff --git a/core/compress/zlib/zlib.odin b/core/compress/zlib/zlib.odin index d4dc6e3d7..be8a7d7d3 100644 --- a/core/compress/zlib/zlib.odin +++ b/core/compress/zlib/zlib.odin @@ -1,5 +1,5 @@ -//+vet !using-param -package zlib +#+vet !using-param +package compress_zlib /* Copyright 2021 Jeroen van Rijn . @@ -12,6 +12,7 @@ package zlib import "core:compress" +import "base:intrinsics" import "core:mem" import "core:io" import "core:hash" @@ -120,23 +121,17 @@ 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 - // by reversing all of the bits and masking out the unneeded ones. - r = n - r = ((r & 0xAAAA) >> 1) | ((r & 0x5555) << 1) - r = ((r & 0xCCCC) >> 2) | ((r & 0x3333) << 2) - r = ((r & 0xF0F0) >> 4) | ((r & 0x0F0F) << 4) - r = ((r & 0xFF00) >> 8) | ((r & 0x00FF) << 8) + r = intrinsics.reverse_bits(n) r >>= (16 - bits) return } -@(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 +149,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 +168,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 +196,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 +229,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 +288,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 +319,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 +339,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 +408,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 +481,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 +665,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 new file mode 100644 index 000000000..8a9d1f3d9 --- /dev/null +++ b/core/container/avl/avl.odin @@ -0,0 +1,675 @@ +/* +package avl implements an AVL tree. + +The implementation is non-intrusive, and non-recursive. +*/ +package container_avl + +@(require) import "base:intrinsics" +@(require) import "base:runtime" +import "core:slice" + +// Originally based on the CC0 implementation by Eric Biggers +// See: https://github.com/ebiggers/avl_tree/ + +// 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 specifies order when inserting/finding values into the tree. +Ordering :: slice.Ordering + +// Tree is an AVL tree. +Tree :: struct($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(value: Value, user_data: rawptr), + + _root: ^Node(Value), + _node_allocator: runtime.Allocator, + _cmp_fn: proc(a, b: Value) -> Ordering, + _size: int, +} + +// Node is an AVL 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($Value: typeid) { + value: Value, + + _parent: ^Node(Value), + _left: ^Node(Value), + _right: ^Node(Value), + _balance: i8, +} + +// Iterator is a tree iterator. +// +// WARNING: It is unsafe to modify the tree while iterating, except via +// the iterator_remove method. +Iterator :: struct($Value: typeid) { + _tree: ^Tree(Value), + _cur: ^Node(Value), + _next: ^Node(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($Value), + cmp_fn: proc(a, b: Value) -> 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 items, with +// a comparison function that results in an ascending order sort. +init_ordered :: proc( + t: ^$T/Tree($Value), + node_allocator := context.allocator, +) where intrinsics.type_is_ordered(Value) { + init_cmp(t, slice.cmp_proc(Value), node_allocator) +} + +// destroy de-initializes a tree. +destroy :: proc(t: ^$T/Tree($Value), call_on_remove: bool = true) { + iter := iterator(t, Direction.Forward) + for _ in iterator_next(&iter) { + iterator_remove(&iter, call_on_remove) + } +} + +// len returns the number of elements in the tree. +len :: proc "contextless" (t: ^$T/Tree($Value)) -> 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($Value)) -> ^Node(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($Value)) -> ^Node(Value) { + return tree_first_or_last_in_order(t, Direction.Forward) +} + +// find finds the value in the tree, and returns the corresponding +// node or nil iff the value is not present. +find :: proc(t: ^$T/Tree($Value), value: Value) -> ^Node(Value) { + cur := t._root + descend_loop: for cur != nil { + switch t._cmp_fn(value, cur.value) { + case .Less: + cur = cur._left + case .Greater: + cur = cur._right + case .Equal: + break descend_loop + } + } + + return cur +} + +// 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 returned un-altered. +find_or_insert :: proc( + t: ^$T/Tree($Value), + value: Value, +) -> ( + n: ^Node(Value), + inserted: bool, + err: runtime.Allocator_Error, +) { + n_ptr := &t._root + for n_ptr^ != nil { + n = n_ptr^ + switch t._cmp_fn(value, n.value) { + case .Less: + n_ptr = &n._left + case .Greater: + n_ptr = &n._right + case .Equal: + return + } + } + + parent := n + n = new(Node(Value), t._node_allocator) or_return + n.value = value + n._parent = parent + n_ptr^ = n + tree_rebalance_after_insert(t, n) + + t._size += 1 + inserted = true + + return +} + +// 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_value, + remove_node, +} + +// remove_value removes a 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_value :: proc(t: ^$T/Tree($Value), value: Value, call_on_remove: bool = true) -> bool { + n := find(t, value) + if n == nil { + return false + } + 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 value will be left intact, +// the node itself will be freed via the tree's node allocator. +remove_node :: proc(t: ^$T/Tree($Value), node: ^Node(Value), call_on_remove: bool = true) -> bool { + if node._parent == node || (node._parent == nil && t._root != node) { + return false + } + defer { + if call_on_remove && t.on_remove != nil { + t.on_remove(node.value, t.user_data) + } + free(node, t._node_allocator) + } + + parent: ^Node(Value) + left_deleted: bool + + t._size -= 1 + if node._left != nil && node._right != nil { + parent, left_deleted = tree_swap_with_successor(t, node) + } else { + child := node._left + if child == nil { + child = node._right + } + parent = node._parent + if parent != nil { + if node == parent._left { + parent._left = child + left_deleted = true + } else { + parent._right = child + left_deleted = false + } + if child != nil { + child._parent = parent + } + } else { + if child != nil { + child._parent = parent + } + t._root = child + node_reset(node) + return true + } + } + + for { + if left_deleted { + parent = tree_handle_subtree_shrink(t, parent, +1, &left_deleted) + } else { + parent = tree_handle_subtree_shrink(t, parent, -1, &left_deleted) + } + if parent == nil { + break + } + } + node_reset(node) + + return true +} + +// iterator returns a tree iterator in the specified direction. +iterator :: proc "contextless" (t: ^$T/Tree($Value), direction: Direction) -> Iterator(Value) { + it: Iterator(Value) + it._tree = transmute(^Tree(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($Value), + pos: ^Node(Value), + direction: Direction, +) -> Iterator(Value) { + it: Iterator(Value) + it._tree = transmute(^Tree(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($Value)) -> ^Node(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($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($Value)) -> (^Node(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($Value), + direction: Direction, +) -> ^Node(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) +tree_replace_child :: proc "contextless" ( + t: ^$T/Tree($Value), + parent, old_child, new_child: ^Node(Value), +) { + if parent != nil { + if old_child == parent._left { + parent._left = new_child + } else { + parent._right = new_child + } + } else { + t._root = new_child + } +} + +@(private) +tree_rotate :: proc "contextless" (t: ^$T/Tree($Value), a: ^Node(Value), sign: i8) { + b := node_get_child(a, -sign) + e := node_get_child(b, +sign) + p := a._parent + + node_set_child(a, -sign, e) + a._parent = b + + node_set_child(b, +sign, a) + b._parent = p + + if e != nil { + e._parent = a + } + + tree_replace_child(t, p, a, b) +} + +@(private) +tree_double_rotate :: proc "contextless" ( + t: ^$T/Tree($Value), + b, a: ^Node(Value), + sign: i8, +) -> ^Node(Value) { + e := node_get_child(b, +sign) + f := node_get_child(e, -sign) + g := node_get_child(e, +sign) + p := a._parent + e_bal := e._balance + + node_set_child(a, -sign, g) + a_bal := -e_bal + if sign * e_bal >= 0 { + a_bal = 0 + } + node_set_parent_balance(a, e, a_bal) + + node_set_child(b, +sign, f) + b_bal := -e_bal + if sign * e_bal <= 0 { + b_bal = 0 + } + node_set_parent_balance(b, e, b_bal) + + node_set_child(e, +sign, a) + node_set_child(e, -sign, b) + node_set_parent_balance(e, p, 0) + + if g != nil { + g._parent = a + } + + if f != nil { + f._parent = b + } + + tree_replace_child(t, p, a, e) + + return e +} + +@(private) +tree_handle_subtree_growth :: proc "contextless" ( + t: ^$T/Tree($Value), + node, parent: ^Node(Value), + sign: i8, +) -> bool { + old_balance_factor := parent._balance + if old_balance_factor == 0 { + node_adjust_balance_factor(parent, sign) + return false + } + + new_balance_factor := old_balance_factor + sign + if new_balance_factor == 0 { + node_adjust_balance_factor(parent, sign) + return true + } + + if sign * node._balance > 0 { + tree_rotate(t, parent, -sign) + node_adjust_balance_factor(parent, -sign) + node_adjust_balance_factor(node, -sign) + } else { + tree_double_rotate(t, node, parent, -sign) + } + + return true +} + +@(private) +tree_rebalance_after_insert :: proc "contextless" (t: ^$T/Tree($Value), inserted: ^Node(Value)) { + node, parent := inserted, inserted._parent + switch { + case parent == nil: + return + case node == parent._left: + node_adjust_balance_factor(parent, -1) + case: + node_adjust_balance_factor(parent, +1) + } + + if parent._balance == 0 { + return + } + + for done := false; !done; { + node = parent + if parent = node._parent; parent == nil { + return + } + + if node == parent._left { + done = tree_handle_subtree_growth(t, node, parent, -1) + } else { + done = tree_handle_subtree_growth(t, node, parent, +1) + } + } +} + +@(private) +tree_swap_with_successor :: proc "contextless" ( + t: ^$T/Tree($Value), + x: ^Node(Value), +) -> ( + ^Node(Value), + bool, +) { + ret: ^Node(Value) + left_deleted: bool + + y := x._right + if y._left == nil { + ret = y + } else { + q: ^Node(Value) + + for { + q = y + if y = y._left; y._left == nil { + break + } + } + + if q._left = y._right; q._left != nil { + q._left._parent = q + } + y._right = x._right + x._right._parent = y + ret = q + left_deleted = true + } + + y._left = x._left + x._left._parent = y + + y._parent = x._parent + y._balance = x._balance + + tree_replace_child(t, x._parent, x, y) + + return ret, left_deleted +} + +@(private) +tree_handle_subtree_shrink :: proc "contextless" ( + t: ^$T/Tree($Value), + parent: ^Node(Value), + sign: i8, + left_deleted: ^bool, +) -> ^Node(Value) { + old_balance_factor := parent._balance + if old_balance_factor == 0 { + node_adjust_balance_factor(parent, sign) + return nil + } + + node: ^Node(Value) + new_balance_factor := old_balance_factor + sign + if new_balance_factor == 0 { + node_adjust_balance_factor(parent, sign) + node = parent + } else { + node = node_get_child(parent, sign) + if sign * node._balance >= 0 { + tree_rotate(t, parent, -sign) + if node._balance == 0 { + node_adjust_balance_factor(node, -sign) + return nil + } + node_adjust_balance_factor(parent, -sign) + node_adjust_balance_factor(node, -sign) + } else { + node = tree_double_rotate(t, node, parent, -sign) + } + } + + parent := parent + if parent = node._parent; parent != nil { + left_deleted^ = node == parent._left + } + return parent +} + +@(private) +node_reset :: proc "contextless" (n: ^Node($Value)) { + // Mostly pointless as n will be deleted after this is called, but + // attempt to be able to catch cases of n not being in the tree. + n._parent = n + n._left = nil + n._right = nil + n._balance = 0 +} + +@(private) +node_set_parent_balance :: #force_inline proc "contextless" ( + n, parent: ^Node($Value), + balance: i8, +) { + n._parent = parent + n._balance = balance +} + +@(private) +node_get_child :: #force_inline proc "contextless" (n: ^Node($Value), sign: i8) -> ^Node(Value) { + if sign < 0 { + return n._left + } + return n._right +} + +@(private) +node_next_or_prev_in_order :: proc "contextless" ( + n: ^Node($Value), + direction: Direction, +) -> ^Node(Value) { + next, tmp: ^Node(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) +node_set_child :: #force_inline proc "contextless" ( + n: ^Node($Value), + sign: i8, + child: ^Node(Value), +) { + if sign < 0 { + n._left = child + } else { + n._right = child + } +} + +@(private) +node_adjust_balance_factor :: #force_inline proc "contextless" (n: ^Node($Value), amount: i8) { + n._balance += amount +} + +@(private) +iterator_first :: proc "contextless" (it: ^Iterator($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) + } +} \ 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 d649d039f..85b941d12 100644 --- a/core/container/bit_array/bit_array.odin +++ b/core/container/bit_array/bit_array.odin @@ -1,6 +1,7 @@ -package dynamic_bit_array +package container_dynamic_bit_array -import "core:intrinsics" +import "base:builtin" +import "base:intrinsics" import "core:mem" /* @@ -18,7 +19,7 @@ NUM_BITS :: 64 Bit_Array :: struct { bits: [dynamic]u64, bias: int, - max_index: int, + length: int, free_pointer: bool, } @@ -52,9 +53,9 @@ Returns: */ iterate_by_all :: proc (it: ^Bit_Array_Iterator) -> (set: bool, index: int, ok: bool) { index = it.word_idx * NUM_BITS + int(it.bit_idx) + it.array.bias - if index > it.array.max_index { return false, 0, false } + if index >= it.array.length + it.array.bias { return false, 0, false } - word := it.array.bits[it.word_idx] if len(it.array.bits) > it.word_idx else 0 + word := it.array.bits[it.word_idx] if builtin.len(it.array.bits) > it.word_idx else 0 set = (word >> it.bit_idx & 1) == 1 it.bit_idx += 1 @@ -106,22 +107,22 @@ Returns: */ @(private="file") iterate_internal_ :: proc (it: ^Bit_Array_Iterator, $ITERATE_SET_BITS: bool) -> (index: int, ok: bool) { - word := it.array.bits[it.word_idx] if len(it.array.bits) > it.word_idx else 0 + word := it.array.bits[it.word_idx] if builtin.len(it.array.bits) > it.word_idx else 0 when ! ITERATE_SET_BITS { word = ~word } // If the word is empty or we have already gone over all the bits in it, // b.bit_idx is greater than the index of any set bit in the word, // meaning that word >> b.bit_idx == 0. - for it.word_idx < len(it.array.bits) && word >> it.bit_idx == 0 { + for it.word_idx < builtin.len(it.array.bits) && word >> it.bit_idx == 0 { it.word_idx += 1 it.bit_idx = 0 - word = it.array.bits[it.word_idx] if len(it.array.bits) > it.word_idx else 0 + word = it.array.bits[it.word_idx] if builtin.len(it.array.bits) > it.word_idx else 0 when ! ITERATE_SET_BITS { word = ~word } } // If we are iterating the set bits, reaching the end of the array means we have no more bits to check when ITERATE_SET_BITS { - if it.word_idx >= len(it.array.bits) { + if it.word_idx >= builtin.len(it.array.bits) { return 0, false } } @@ -135,7 +136,7 @@ iterate_internal_ :: proc (it: ^Bit_Array_Iterator, $ITERATE_SET_BITS: bool) -> it.bit_idx = 0 it.word_idx += 1 } - return index, index <= it.array.max_index + return index, index < it.array.length + it.array.bias } /* Gets the state of a bit in the bit-array @@ -160,7 +161,7 @@ get :: proc(ba: ^Bit_Array, #any_int index: uint) -> (res: bool, ok: bool) #opti If we `get` a bit that doesn't fit in the Bit Array, it's naturally `false`. This early-out prevents unnecessary resizing. */ - if leg_index + 1 > len(ba.bits) { return false, true } + if leg_index + 1 > builtin.len(ba.bits) { return false, true } val := u64(1 << uint(bit_index)) res = ba.bits[leg_index] & val == val @@ -208,10 +209,13 @@ set :: proc(ba: ^Bit_Array, #any_int index: uint, set_to: bool = true, allocator resize_if_needed(ba, leg_index) or_return - ba.max_index = max(idx, ba.max_index) + ba.length = max(1 + idx, ba.length) - if set_to{ ba.bits[leg_index] |= 1 << uint(bit_index) } - 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,11 +257,15 @@ 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). +The range of bits created by this procedure is `min_index.. (res: ^Bit_Array, ok: bool) #optional_ok { - context.allocator = allocator size_in_bits := max_index - min_index - if size_in_bits < 1 { return {}, false } + if size_in_bits < 0 { return {}, false } - legs := size_in_bits >> INDEX_SHIFT - if size_in_bits & INDEX_MASK > 0 {legs+=1} - bits, err := make([dynamic]u64, legs) - ok = err == mem.Allocator_Error.None - res = new(Bit_Array) - res.bits = bits - res.bias = min_index - res.max_index = max_index + res = new(Bit_Array, allocator) + ok = init(res, max_index, min_index, allocator) res.free_pointer = true + + if !ok { free(res, allocator) } + return } + +/* +A helper function to initialize a Bit Array with optional bias, in case your smallest index is non-zero (including negative). + +The range of bits created by this procedure is `min_index.. (ok: bool) { + size_in_bits := max_index - min_index + + if size_in_bits < 0 { return false } + + legs := size_in_bits >> INDEX_SHIFT + if size_in_bits & INDEX_MASK > 0 { legs += 1 } + + bits, err := make([dynamic]u64, legs, allocator) + ok = err == nil + + res.bits = bits + res.bias = min_index + res.length = max_index - min_index + res.free_pointer = false + return +} + /* Sets all values in the Bit_Array to zero. @@ -296,6 +332,48 @@ clear :: proc(ba: ^Bit_Array) { mem.zero_slice(ba.bits[:]) } /* +Gets the length of set and unset valid bits in the Bit_Array. + +Inputs: +- ba: The target Bit_Array + +Returns: +- length: The length of valid bits. +*/ +len :: proc(ba: ^Bit_Array) -> (length: int) { + if ba == nil { return } + return ba.length +} +/* +Shrinks the Bit_Array's backing storage to the smallest possible size. + +Inputs: +- ba: The target Bit_Array +*/ +shrink :: proc(ba: ^Bit_Array) #no_bounds_check { + if ba == nil { return } + legs_needed := builtin.len(ba.bits) + for i := legs_needed - 1; i >= 0; i -= 1 { + if ba.bits[i] == 0 { + legs_needed -= 1 + } else { + break + } + } + if legs_needed == builtin.len(ba.bits) { + return + } + ba.length = 0 + if legs_needed > 0 { + if legs_needed > 1 { + ba.length = (legs_needed - 1) * NUM_BITS + } + ba.length += NUM_BITS - int(intrinsics.count_leading_zeros(ba.bits[legs_needed - 1])) + } + resize(&ba.bits, legs_needed) + builtin.shrink(&ba.bits) +} +/* Deallocates the Bit_Array and its backing storage Inputs: @@ -318,8 +396,8 @@ resize_if_needed :: proc(ba: ^Bit_Array, legs: int, allocator := context.allocat context.allocator = allocator - if legs + 1 > len(ba.bits) { + if legs + 1 > builtin.len(ba.bits) { resize(&ba.bits, legs + 1) } - return len(ba.bits) > legs + return builtin.len(ba.bits) > legs } diff --git a/core/container/bit_array/doc.odin b/core/container/bit_array/doc.odin index 52e252d8a..36bf90002 100644 --- a/core/container/bit_array/doc.odin +++ b/core/container/bit_array/doc.odin @@ -1,53 +1,52 @@ -package dynamic_bit_array - /* - The Bit Array can be used in several ways: +The Bit Array can be used in several ways: - -- By default you don't need to instantiate a Bit Array: +By default you don't need to instantiate a Bit Array. +Example: + package test - package test + import "core:fmt" + import "core:container/bit_array" - import "core:fmt" - import "core:container/bit_array" + main :: proc() { + using bit_array - main :: proc() { - using bit_array + bits: Bit_Array - bits: Bit_Array + // returns `true` + fmt.println(set(&bits, 42)) - // returns `true` - fmt.println(set(&bits, 42)) + // returns `false`, `false`, because this Bit Array wasn't created to allow negative indices. + was_set, was_retrieved := get(&bits, -1) + fmt.println(was_set, was_retrieved) + destroy(&bits) + } - // returns `false`, `false`, because this Bit Array wasn't created to allow negative indices. - was_set, was_retrieved := get(&bits, -1) - fmt.println(was_set, was_retrieved) - destroy(&bits) +A Bit Array can optionally allow for negative indices, if the minimum value was given during creation. +Example: + package test + + import "core:fmt" + import "core:container/bit_array" + + main :: proc() { + Foo :: enum int { + Negative_Test = -42, + Bar = 420, + Leaves = 69105, } - -- A Bit Array can optionally allow for negative indices, if the mininum value was given during creation: + using bit_array - package test + bits := create(int(max(Foo)), int(min(Foo))) + defer destroy(bits) - import "core:fmt" - import "core:container/bit_array" - - main :: proc() { - Foo :: enum int { - Negative_Test = -42, - Bar = 420, - Leaves = 69105, - } - - using bit_array - - bits := create(int(max(Foo)), int(min(Foo))) - defer destroy(bits) - - fmt.printf("Set(Bar): %v\n", set(bits, Foo.Bar)) - fmt.printf("Get(Bar): %v, %v\n", get(bits, Foo.Bar)) - fmt.printf("Set(Negative_Test): %v\n", set(bits, Foo.Negative_Test)) - fmt.printf("Get(Leaves): %v, %v\n", get(bits, Foo.Leaves)) - fmt.printf("Get(Negative_Test): %v, %v\n", get(bits, Foo.Negative_Test)) - fmt.printf("Freed.\n") - } -*/ \ No newline at end of file + fmt.printf("Set(Bar): %v\n", set(bits, Foo.Bar)) + fmt.printf("Get(Bar): %v, %v\n", get(bits, Foo.Bar)) + fmt.printf("Set(Negative_Test): %v\n", set(bits, Foo.Negative_Test)) + fmt.printf("Get(Leaves): %v, %v\n", get(bits, Foo.Leaves)) + fmt.printf("Get(Negative_Test): %v, %v\n", get(bits, Foo.Negative_Test)) + fmt.printf("Freed.\n") + } +*/ +package container_dynamic_bit_array diff --git a/core/container/intrusive/list/doc.odin b/core/container/intrusive/list/doc.odin new file mode 100644 index 000000000..155f1dfe2 --- /dev/null +++ b/core/container/intrusive/list/doc.odin @@ -0,0 +1,49 @@ +/* +Package list implements an intrusive doubly-linked list. + +An intrusive container requires a `Node` to be embedded in your own structure, like this. +Example: + My_String :: struct { + node: list.Node, + value: string, + } + +Embedding the members of a `list.Node` in your structure with the `using` keyword is also allowed. +Example: + My_String :: struct { + using node: list.Node, + value: string, + } + +Here is a full example. +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, + } + +Output: + Hello + World +*/ +package container_intrusive_list diff --git a/core/container/intrusive/list/intrusive_list.odin b/core/container/intrusive/list/intrusive_list.odin index 7302f24f5..5b29efb22 100644 --- a/core/container/intrusive/list/intrusive_list.odin +++ b/core/container/intrusive/list/intrusive_list.odin @@ -1,6 +1,6 @@ package container_intrusive_list -import "core:intrinsics" +import "base:intrinsics" // An intrusive doubly-linked list // @@ -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 b59f29f0c..f8aa55dc2 100644 --- a/core/container/lru/lru_cache.odin +++ b/core/container/lru/lru_cache.odin @@ -1,7 +1,7 @@ package container_lru -import "core:runtime" -import "core:intrinsics" +import "base:runtime" +import "base:intrinsics" _ :: runtime _ :: intrinsics @@ -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/priority_queue/priority_queue.odin b/core/container/priority_queue/priority_queue.odin index 0c5c4931d..8a6d77288 100644 --- a/core/container/priority_queue/priority_queue.odin +++ b/core/container/priority_queue/priority_queue.odin @@ -1,6 +1,6 @@ package container_priority_queue -import "core:builtin" +import "base:builtin" Priority_Queue :: struct($T: typeid) { queue: [dynamic]T, @@ -140,3 +140,18 @@ remove :: proc(pq: ^$Q/Priority_Queue($T), i: int) -> (value: T, ok: bool) { return } +peek_safe :: proc(pq: $Q/Priority_Queue($T), loc := #caller_location) -> (res: T, ok: bool) { + if builtin.len(pq.queue) > 0 { + return pq.queue[0], true + } + return +} + +peek :: proc(pq: $Q/Priority_Queue($T), loc := #caller_location) -> (res: T) { + assert(condition=builtin.len(pq.queue)>0, loc=loc) + + if builtin.len(pq.queue) > 0 { + return pq.queue[0] + } + return +} \ No newline at end of file diff --git a/core/container/queue/queue.odin b/core/container/queue/queue.odin index 5783cbc6c..f83a5f2b7 100644 --- a/core/container/queue/queue.odin +++ b/core/container/queue/queue.odin @@ -1,7 +1,7 @@ package container_queue -import "core:builtin" -import "core:runtime" +import "base:builtin" +import "base:runtime" _ :: runtime // Dynamically resizable double-ended queue/ring-buffer @@ -22,7 +22,9 @@ init :: proc(q: ^$Q/Queue($T), capacity := DEFAULT_CAPACITY, allocator := contex return reserve(q, capacity) } -// Procedure to initialize a queue from a fixed backing slice +// Procedure to initialize a queue from a fixed backing slice. +// The contents of the `backing` will be overwritten as items are pushed onto the `Queue`. +// Any previous contents are not available. init_from_slice :: proc(q: ^$Q/Queue($T), backing: []T) -> bool { clear(q) q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{ @@ -34,6 +36,21 @@ init_from_slice :: proc(q: ^$Q/Queue($T), backing: []T) -> bool { return true } +// Procedure to initialize a queue from a fixed backing slice. +// Existing contents are preserved and available on the queue. +init_with_contents :: proc(q: ^$Q/Queue($T), backing: []T) -> bool { + clear(q) + q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{ + data = raw_data(backing), + len = builtin.len(backing), + cap = builtin.len(backing), + allocator = {procedure=runtime.nil_allocator_proc, data=nil}, + } + q.len = len(backing) + q.offset = len(backing) + return true +} + // Procedure to destroy a queue destroy :: proc(q: ^$Q/Queue($T)) { delete(q.data) @@ -78,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] } @@ -172,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) { @@ -224,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 b471d1706..77bb21cbc 100644 --- a/core/container/small_array/small_array.odin +++ b/core/container/small_array/small_array.odin @@ -1,7 +1,7 @@ package container_small_array -import "core:builtin" -import "core:runtime" +import "base:builtin" +import "base:runtime" _ :: runtime Small_Array :: struct($N: int, $T: typeid) where N >= 0 { @@ -119,29 +119,33 @@ 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)) { resize(a, 0) } -push_back_elems :: proc "contextless" (a: ^$A/Small_Array($N, $T), items: ..T) { - n := copy(a.data[a.len:], items[:]) - a.len += n +push_back_elems :: proc "contextless" (a: ^$A/Small_Array($N, $T), items: ..T) -> bool { + if a.len + builtin.len(items) <= cap(a^) { + n := copy(a.data[a.len:], items[:]) + a.len += n + return true + } + return false } inject_at :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T, index: int) -> bool #no_bounds_check { diff --git a/core/container/topological_sort/topological_sort.odin b/core/container/topological_sort/topological_sort.odin index 314e3e070..10765958e 100644 --- a/core/container/topological_sort/topological_sort.odin +++ b/core/container/topological_sort/topological_sort.odin @@ -3,8 +3,8 @@ // map type is being used to accelerate lookups. package container_topological_sort -import "core:intrinsics" -import "core:runtime" +import "base:intrinsics" +import "base:runtime" _ :: intrinsics _ :: runtime @@ -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)) } @@ -80,11 +80,13 @@ sort :: proc(sorter: ^$S/Sorter($K)) -> (sorted, cycled: [dynamic]K) { } } - for root in sorted do for k, _ in relations[root].dependents { - relation := &relations[k] - relation.dependencies -= 1 - if relation.dependencies == 0 { - append(&sorted, k) + for root in sorted { + for k, _ in relations[root].dependents { + relation := &relations[k] + relation.dependencies -= 1 + if relation.dependencies == 0 { + append(&sorted, k) + } } } diff --git a/core/crypto/README.md b/core/crypto/README.md index dd594d5c2..303b1f625 100644 --- a/core/crypto/README.md +++ b/core/crypto/README.md @@ -1,95 +1,32 @@ # crypto -A crypto library for the Odin language + +A cryptography library for the Odin language. ## Supported -This library offers various algorithms implemented in Odin. -Please see the chart below for the options. -## Hashing algorithms -| Algorithm | | -|:-------------------------------------------------------------------------------------------------------------|:-----------------| -| [BLAKE](https://web.archive.org/web/20190915215948/https://131002.net/blake) | ✔️ | -| [BLAKE2B](https://datatracker.ietf.org/doc/html/rfc7693) | ✔️ | -| [BLAKE2S](https://datatracker.ietf.org/doc/html/rfc7693) | ✔️ | -| [GOST](https://datatracker.ietf.org/doc/html/rfc5831) | ✔️ | -| [Grøstl](http://www.groestl.info/Groestl.zip) | ✔️ | -| [HAVAL](https://web.archive.org/web/20150111210116/http://labs.calyptix.com/haval.php) | ✔️ | -| [JH](https://www3.ntu.edu.sg/home/wuhj/research/jh/index.html) | ✔️ | -| [Keccak](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf) | ✔️ | -| [MD2](https://datatracker.ietf.org/doc/html/rfc1319) | ✔️ | -| [MD4](https://datatracker.ietf.org/doc/html/rfc1320) | ✔️ | -| [MD5](https://datatracker.ietf.org/doc/html/rfc1321) | ✔️ | -| [RIPEMD](https://homes.esat.kuleuven.be/~bosselae/ripemd160.html) | ✔️ | -| [SHA-1](https://datatracker.ietf.org/doc/html/rfc3174) | ✔️ | -| [SHA-2](https://csrc.nist.gov/csrc/media/publications/fips/180/2/archive/2002-08-01/documents/fips180-2.pdf) | ✔️ | -| [SHA-3](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf) | ✔️ | -| [SHAKE](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf) | ✔️ | -| [SM3](https://datatracker.ietf.org/doc/html/draft-sca-cfrg-sm3-02) | ✔️ | -| [Streebog](https://datatracker.ietf.org/doc/html/rfc6986) | ✔️ | -| [Tiger](https://www.cs.technion.ac.il/~biham/Reports/Tiger/) | ✔️ | -| [Tiger2](https://www.cs.technion.ac.il/~biham/Reports/Tiger/) | ✔️ | -| [Whirlpool](https://web.archive.org/web/20171129084214/http://www.larc.usp.br/~pbarreto/WhirlpoolPage.html) | ✔️ | +This package offers various algorithms implemented in Odin, along with +useful helpers such as access to the system entropy source, and a +constant-time byte comparison. -#### High level API -Each hash algorithm contains a procedure group named `hash`, or if the algorithm provides more than one digest size `hash_`\*. -Included in these groups are six procedures. -* `hash_string` - Hash a given string and return the computed hash. Just calls `hash_bytes` internally -* `hash_bytes` - Hash a given byte slice and return the computed hash -* `hash_string_to_buffer` - Hash a given string and put the computed hash in the second proc parameter. Just calls `hash_bytes_to_buffer` internally -* `hash_bytes_to_buffer` - Hash a given string and put the computed hash in the second proc parameter. The destination buffer has to be at least as big as the digest size of the hash -* `hash_stream` - Takes a stream from io.Stream and returns the computed hash from it -* `hash_file` - Takes a file handle and returns the computed hash from it. A second optional boolean parameter controls if the file is streamed (this is the default) or read at once (set to true) +## Implementation considerations -\* On some algorithms there is another part to the name, since they might offer control about additional parameters. -For instance, `HAVAL` offers different sizes as well as three different round amounts. -Computing a 256-bit hash with 3 rounds is therefore achieved by calling `haval.hash_256_3(...)`. +- The crypto packages are not thread-safe. +- Best-effort is make to mitigate timing side-channels on reasonable + architectures. Architectures that are known to be unreasonable include + but are not limited to i386, i486, and WebAssembly. +- Implementations assume a 64-bit architecture (64-bit integer arithmetic + is fast, and includes add-with-carry, sub-with-borrow, and full-result + multiply). +- Hardware sidechannels are explicitly out of scope for this package. + Notable examples include but are not limited to: + - Power/RF side-channels etc. + - Fault injection attacks etc. + - Hardware vulnerabilities ("apply mitigations or buy a new CPU"). +- The packages attempt to santize sensitive data, however this is, and + will remain a "best-effort" implementation decision. As Thomas Pornin + puts it "In general, such memory cleansing is a fool's quest." +- All of these packages have not received independent third party review. -#### Low level API -The above mentioned procedures internally call three procedures: `init`, `update` and `final`. -You may also directly call them, if you wish. +## License -#### Example -```odin -package crypto_example - -// Import the desired package -import "core:crypto/md4" - -main :: proc() { - input := "foo" - - // Compute the hash, using the high level API - computed_hash := md4.hash(input) - - // Variant that takes a destination buffer, instead of returning the computed hash - hash := make([]byte, md4.DIGEST_SIZE) // @note: Destination buffer has to be at least as big as the digest size of the hash - md4.hash(input, hash[:]) - - // Compute the hash, using the low level API - ctx: md4.Md4_Context - computed_hash_low: [16]byte - md4.init(&ctx) - md4.update(&ctx, transmute([]byte)input) - md4.final(&ctx, computed_hash_low[:]) -} -``` -For example uses of all available algorithms, please see the tests within `tests/core/crypto`. - -#### Thread safety -The crypto package is not thread-safe at the moment. This may change in the future. - -### Disclaimer -The algorithms were ported out of curiosity and due to interest in the field. -We have not had any of the code verified by a third party or tested/fuzzed by any automatic means. -Wherever we were able to find official test vectors, those were used to verify the implementation. -We do not recommend using them in a production environment, without any additional testing and/or verification. - -### ToDo -* Ciphers (Symmetric, Asymmetric) -* MACs (Message Authentication Code) -* CSPRNGs (Cryptographically Secure PseudoRandom Number Generator) -* KDFs (Key Derivation Function) -* KEAs (Key Exchange Algorithm) - -### License This library is made available under the BSD-3 license. \ No newline at end of file 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..a522a481a --- /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 + + for l > 0 { + src: []byte = --- + 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..52669cb35 --- /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_supported 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..4320dd59b --- /dev/null +++ b/core/crypto/_aes/hw_intel/ghash.odin @@ -0,0 +1,277 @@ +// 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/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: x86.__m128i : { 0x08090a0b0c0d0e0f, 0x0001020304050607 } + +@(private = "file", require_results, enable_target_feature = "sse2,ssse3") +byteswap :: #force_inline proc "contextless" (x: x86.__m128i) -> x86.__m128i { + return x86._mm_shuffle_epi8(x, _BYTESWAP_INDEX) +} + +// 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 + for l > 0 { + src: []byte = --- + 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..bdf0d3066 --- /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/_blake2/blake2.odin b/core/crypto/_blake2/blake2.odin index 7b75541e9..2ad74843b 100644 --- a/core/crypto/_blake2/blake2.odin +++ b/core/crypto/_blake2/blake2.odin @@ -10,12 +10,13 @@ package _blake2 Implementation of the BLAKE2 hashing algorithm, as defined in and */ -import "../util" +import "core:encoding/endian" +import "core:mem" -BLAKE2S_BLOCK_SIZE :: 64 -BLAKE2S_SIZE :: 32 -BLAKE2B_BLOCK_SIZE :: 128 -BLAKE2B_SIZE :: 64 +BLAKE2S_BLOCK_SIZE :: 64 +BLAKE2S_SIZE :: 32 +BLAKE2B_BLOCK_SIZE :: 128 +BLAKE2B_SIZE :: 64 Blake2s_Context :: struct { h: [8]u32, @@ -28,7 +29,8 @@ Blake2s_Context :: struct { is_keyed: bool, size: byte, is_last_node: bool, - cfg: Blake2_Config, + + is_initialized: bool, } Blake2b_Context :: struct { @@ -42,15 +44,18 @@ Blake2b_Context :: struct { is_keyed: bool, size: byte, is_last_node: bool, - cfg: Blake2_Config, + + is_initialized: bool, } Blake2_Config :: struct { - size: byte, - key: []byte, - salt: []byte, + size: byte, + key: []byte, + salt: []byte, person: []byte, - tree: union{Blake2_Tree}, + tree: union { + Blake2_Tree, + }, } Blake2_Tree :: struct { @@ -63,11 +68,13 @@ Blake2_Tree :: struct { is_last_node: bool, } +@(private) BLAKE2S_IV := [8]u32 { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, } +@(private) BLAKE2B_IV := [8]u64 { 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, @@ -75,80 +82,94 @@ BLAKE2B_IV := [8]u64 { 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179, } -init :: proc(ctx: ^$T) { +init :: proc(ctx: ^$T, cfg: ^Blake2_Config) { when T == Blake2s_Context { - block_size :: BLAKE2S_BLOCK_SIZE + max_size :: BLAKE2S_SIZE } else when T == Blake2b_Context { - block_size :: BLAKE2B_BLOCK_SIZE + max_size :: BLAKE2B_SIZE } - p := make([]byte, block_size) - defer delete(p) + if cfg.size > max_size { + panic("blake2: requested output size exceeeds algorithm max") + } - p[0] = ctx.cfg.size - p[1] = byte(len(ctx.cfg.key)) + // To save having to allocate a scratch buffer, use the internal + // data buffer (`ctx.x`), as it is exactly the correct size. + p := ctx.x[:] - if ctx.cfg.salt != nil { + p[0] = cfg.size + p[1] = byte(len(cfg.key)) + + if cfg.salt != nil { when T == Blake2s_Context { - copy(p[16:], ctx.cfg.salt) + copy(p[16:], cfg.salt) } else when T == Blake2b_Context { - copy(p[32:], ctx.cfg.salt) + copy(p[32:], cfg.salt) } } - if ctx.cfg.person != nil { + if cfg.person != nil { when T == Blake2s_Context { - copy(p[24:], ctx.cfg.person) + copy(p[24:], cfg.person) } else when T == Blake2b_Context { - copy(p[48:], ctx.cfg.person) + copy(p[48:], cfg.person) } } - if ctx.cfg.tree != nil { - p[2] = ctx.cfg.tree.(Blake2_Tree).fanout - p[3] = ctx.cfg.tree.(Blake2_Tree).max_depth - util.PUT_U32_LE(p[4:], ctx.cfg.tree.(Blake2_Tree).leaf_size) + if cfg.tree != nil { + p[2] = cfg.tree.(Blake2_Tree).fanout + p[3] = cfg.tree.(Blake2_Tree).max_depth + endian.unchecked_put_u32le(p[4:], cfg.tree.(Blake2_Tree).leaf_size) when T == Blake2s_Context { - p[8] = byte(ctx.cfg.tree.(Blake2_Tree).node_offset) - p[9] = byte(ctx.cfg.tree.(Blake2_Tree).node_offset >> 8) - p[10] = byte(ctx.cfg.tree.(Blake2_Tree).node_offset >> 16) - p[11] = byte(ctx.cfg.tree.(Blake2_Tree).node_offset >> 24) - p[12] = byte(ctx.cfg.tree.(Blake2_Tree).node_offset >> 32) - p[13] = byte(ctx.cfg.tree.(Blake2_Tree).node_offset >> 40) - p[14] = ctx.cfg.tree.(Blake2_Tree).node_depth - p[15] = ctx.cfg.tree.(Blake2_Tree).inner_hash_size + p[8] = byte(cfg.tree.(Blake2_Tree).node_offset) + p[9] = byte(cfg.tree.(Blake2_Tree).node_offset >> 8) + p[10] = byte(cfg.tree.(Blake2_Tree).node_offset >> 16) + p[11] = byte(cfg.tree.(Blake2_Tree).node_offset >> 24) + p[12] = byte(cfg.tree.(Blake2_Tree).node_offset >> 32) + p[13] = byte(cfg.tree.(Blake2_Tree).node_offset >> 40) + p[14] = cfg.tree.(Blake2_Tree).node_depth + p[15] = cfg.tree.(Blake2_Tree).inner_hash_size } else when T == Blake2b_Context { - util.PUT_U64_LE(p[8:], ctx.cfg.tree.(Blake2_Tree).node_offset) - p[16] = ctx.cfg.tree.(Blake2_Tree).node_depth - p[17] = ctx.cfg.tree.(Blake2_Tree).inner_hash_size + endian.unchecked_put_u64le(p[8:], cfg.tree.(Blake2_Tree).node_offset) + p[16] = cfg.tree.(Blake2_Tree).node_depth + p[17] = cfg.tree.(Blake2_Tree).inner_hash_size } } else { p[2], p[3] = 1, 1 } - ctx.size = ctx.cfg.size + ctx.size = cfg.size for i := 0; i < 8; i += 1 { when T == Blake2s_Context { - ctx.h[i] = BLAKE2S_IV[i] ~ util.U32_LE(p[i * 4:]) + ctx.h[i] = BLAKE2S_IV[i] ~ endian.unchecked_get_u32le(p[i * 4:]) } when T == Blake2b_Context { - ctx.h[i] = BLAKE2B_IV[i] ~ util.U64_LE(p[i * 8:]) + ctx.h[i] = BLAKE2B_IV[i] ~ endian.unchecked_get_u64le(p[i * 8:]) } } - if ctx.cfg.tree != nil && ctx.cfg.tree.(Blake2_Tree).is_last_node { + + mem.zero(&ctx.x, size_of(ctx.x)) // Done with the scratch space, no barrier. + + if cfg.tree != nil && cfg.tree.(Blake2_Tree).is_last_node { ctx.is_last_node = true } - if len(ctx.cfg.key) > 0 { - copy(ctx.padded_key[:], ctx.cfg.key) + if len(cfg.key) > 0 { + copy(ctx.padded_key[:], cfg.key) update(ctx, ctx.padded_key[:]) ctx.is_keyed = true } copy(ctx.ih[:], ctx.h[:]) - copy(ctx.h[:], ctx.ih[:]) + copy(ctx.h[:], ctx.ih[:]) if ctx.is_keyed { update(ctx, ctx.padded_key[:]) } + + ctx.nx = 0 + + ctx.is_initialized = true } -update :: proc "contextless" (ctx: ^$T, p: []byte) { +update :: proc(ctx: ^$T, p: []byte) { + assert(ctx.is_initialized) + p := p when T == Blake2s_Context { block_size :: BLAKE2S_BLOCK_SIZE @@ -174,15 +195,43 @@ update :: proc "contextless" (ctx: ^$T, p: []byte) { ctx.nx += copy(ctx.x[ctx.nx:], p) } -final :: proc "contextless" (ctx: ^$T, hash: []byte) { - when T == Blake2s_Context { - blake2s_final(ctx, hash) +final :: proc(ctx: ^$T, hash: []byte, finalize_clone: bool = false) { + assert(ctx.is_initialized) + + ctx := ctx + if finalize_clone { + tmp_ctx: T + clone(&tmp_ctx, ctx) + ctx = &tmp_ctx } - when T == Blake2b_Context { + defer(reset(ctx)) + + when T == Blake2s_Context { + if len(hash) < int(ctx.size) { + panic("crypto/blake2s: invalid destination digest size") + } + blake2s_final(ctx, hash) + } else when T == Blake2b_Context { + if len(hash) < int(ctx.size) { + panic("crypto/blake2b: invalid destination digest size") + } blake2b_final(ctx, hash) } } +clone :: proc(ctx, other: ^$T) { + ctx^ = other^ +} + +reset :: proc(ctx: ^$T) { + if !ctx.is_initialized { + return + } + + mem.zero_explicit(ctx, size_of(ctx^)) +} + +@(private) blake2s_final :: proc "contextless" (ctx: ^Blake2s_Context, hash: []byte) { if ctx.is_keyed { for i := 0; i < len(ctx.padded_key); i += 1 { @@ -203,16 +252,14 @@ blake2s_final :: proc "contextless" (ctx: ^Blake2s_Context, hash: []byte) { blocks(ctx, ctx.x[:]) - j := 0 - for s, _ in ctx.h[:(ctx.size - 1) / 4 + 1] { - hash[j + 0] = byte(s >> 0) - hash[j + 1] = byte(s >> 8) - hash[j + 2] = byte(s >> 16) - hash[j + 3] = byte(s >> 24) - j += 4 + dst: [BLAKE2S_SIZE]byte + for i := 0; i < BLAKE2S_SIZE / 4; i += 1 { + endian.unchecked_put_u32le(dst[i * 4:], ctx.h[i]) } + copy(hash, dst[:]) } +@(private) blake2b_final :: proc "contextless" (ctx: ^Blake2b_Context, hash: []byte) { if ctx.is_keyed { for i := 0; i < len(ctx.padded_key); i += 1 { @@ -229,56 +276,52 @@ blake2b_final :: proc "contextless" (ctx: ^Blake2b_Context, hash: []byte) { ctx.f[0] = 0xffffffffffffffff if ctx.is_last_node { ctx.f[1] = 0xffffffffffffffff - } + } blocks(ctx, ctx.x[:]) - j := 0 - for s, _ in ctx.h[:(ctx.size - 1) / 8 + 1] { - hash[j + 0] = byte(s >> 0) - hash[j + 1] = byte(s >> 8) - hash[j + 2] = byte(s >> 16) - hash[j + 3] = byte(s >> 24) - hash[j + 4] = byte(s >> 32) - hash[j + 5] = byte(s >> 40) - hash[j + 6] = byte(s >> 48) - hash[j + 7] = byte(s >> 56) - j += 8 + dst: [BLAKE2B_SIZE]byte + for i := 0; i < BLAKE2B_SIZE / 8; i += 1 { + endian.unchecked_put_u64le(dst[i * 8:], ctx.h[i]) } + copy(hash, dst[:]) } +@(private) blocks :: proc "contextless" (ctx: ^$T, p: []byte) { when T == Blake2s_Context { blake2s_blocks(ctx, p) - } - when T == Blake2b_Context { + } else when T == Blake2b_Context { blake2b_blocks(ctx, p) } } +@(private) blake2s_blocks :: #force_inline proc "contextless" (ctx: ^Blake2s_Context, p: []byte) { - h0, h1, h2, h3, h4, h5, h6, h7 := ctx.h[0], ctx.h[1], ctx.h[2], ctx.h[3], ctx.h[4], ctx.h[5], ctx.h[6], ctx.h[7] + h0, h1, h2, h3, h4, h5, h6, h7 := + ctx.h[0], ctx.h[1], ctx.h[2], ctx.h[3], ctx.h[4], ctx.h[5], ctx.h[6], ctx.h[7] p := p for len(p) >= BLAKE2S_BLOCK_SIZE { ctx.t[0] += BLAKE2S_BLOCK_SIZE if ctx.t[0] < BLAKE2S_BLOCK_SIZE { ctx.t[1] += 1 - } + } v0, v1, v2, v3, v4, v5, v6, v7 := h0, h1, h2, h3, h4, h5, h6, h7 - v8 := BLAKE2S_IV[0] - v9 := BLAKE2S_IV[1] + v8 := BLAKE2S_IV[0] + v9 := BLAKE2S_IV[1] v10 := BLAKE2S_IV[2] v11 := BLAKE2S_IV[3] v12 := BLAKE2S_IV[4] ~ ctx.t[0] v13 := BLAKE2S_IV[5] ~ ctx.t[1] v14 := BLAKE2S_IV[6] ~ ctx.f[0] v15 := BLAKE2S_IV[7] ~ ctx.f[1] - m: [16]u32 - j := 0 + + m: [16]u32 = --- for i := 0; i < 16; i += 1 { - m[i] = u32(p[j]) | u32(p[j + 1]) << 8 | u32(p[j + 2]) << 16 | u32(p[j + 3]) << 24 - j += 4 + m[i] = endian.unchecked_get_u32le(p[i * 4:]) } + + // Round 1 v0 += m[0] v0 += v4 v12 ~= v0 @@ -391,6 +434,8 @@ blake2s_blocks :: #force_inline proc "contextless" (ctx: ^Blake2s_Context, p: [] v10 += v15 v5 ~= v10 v5 = v5 << (32 - 7) | v5 >> 7 + + // Round 2 v0 += m[14] v0 += v4 v12 ~= v0 @@ -503,6 +548,8 @@ blake2s_blocks :: #force_inline proc "contextless" (ctx: ^Blake2s_Context, p: [] v10 += v15 v5 ~= v10 v5 = v5 << (32 - 7) | v5 >> 7 + + // Round 3 v0 += m[11] v0 += v4 v12 ~= v0 @@ -615,6 +662,8 @@ blake2s_blocks :: #force_inline proc "contextless" (ctx: ^Blake2s_Context, p: [] v10 += v15 v5 ~= v10 v5 = v5 << (32 - 7) | v5 >> 7 + + // Round 4 v0 += m[7] v0 += v4 v12 ~= v0 @@ -727,6 +776,8 @@ blake2s_blocks :: #force_inline proc "contextless" (ctx: ^Blake2s_Context, p: [] v10 += v15 v5 ~= v10 v5 = v5 << (32 - 7) | v5 >> 7 + + // Round 5 v0 += m[9] v0 += v4 v12 ~= v0 @@ -839,6 +890,8 @@ blake2s_blocks :: #force_inline proc "contextless" (ctx: ^Blake2s_Context, p: [] v10 += v15 v5 ~= v10 v5 = v5 << (32 - 7) | v5 >> 7 + + // Round 6 v0 += m[2] v0 += v4 v12 ~= v0 @@ -951,6 +1004,8 @@ blake2s_blocks :: #force_inline proc "contextless" (ctx: ^Blake2s_Context, p: [] v10 += v15 v5 ~= v10 v5 = v5 << (32 - 7) | v5 >> 7 + + // Round 7 v0 += m[12] v0 += v4 v12 ~= v0 @@ -1063,6 +1118,8 @@ blake2s_blocks :: #force_inline proc "contextless" (ctx: ^Blake2s_Context, p: [] v10 += v15 v5 ~= v10 v5 = v5 << (32 - 7) | v5 >> 7 + + // Round 8 v0 += m[13] v0 += v4 v12 ~= v0 @@ -1175,6 +1232,8 @@ blake2s_blocks :: #force_inline proc "contextless" (ctx: ^Blake2s_Context, p: [] v10 += v15 v5 ~= v10 v5 = v5 << (32 - 7) | v5 >> 7 + + // Round 9 v0 += m[6] v0 += v4 v12 ~= v0 @@ -1287,6 +1346,8 @@ blake2s_blocks :: #force_inline proc "contextless" (ctx: ^Blake2s_Context, p: [] v10 += v15 v5 ~= v10 v5 = v5 << (32 - 7) | v5 >> 7 + + // Round 10 v0 += m[10] v0 += v4 v12 ~= v0 @@ -1399,6 +1460,7 @@ blake2s_blocks :: #force_inline proc "contextless" (ctx: ^Blake2s_Context, p: [] v10 += v15 v5 ~= v10 v5 = v5 << (32 - 7) | v5 >> 7 + h0 ~= v0 ~ v8 h1 ~= v1 ~ v9 h2 ~= v2 ~ v10 @@ -1407,19 +1469,23 @@ blake2s_blocks :: #force_inline proc "contextless" (ctx: ^Blake2s_Context, p: [] h5 ~= v5 ~ v13 h6 ~= v6 ~ v14 h7 ~= v7 ~ v15 + p = p[BLAKE2S_BLOCK_SIZE:] } - ctx.h[0], ctx.h[1], ctx.h[2], ctx.h[3], ctx.h[4], ctx.h[5], ctx.h[6], ctx.h[7] = h0, h1, h2, h3, h4, h5, h6, h7 + ctx.h[0], ctx.h[1], ctx.h[2], ctx.h[3], ctx.h[4], ctx.h[5], ctx.h[6], ctx.h[7] = + h0, h1, h2, h3, h4, h5, h6, h7 } +@(private) blake2b_blocks :: #force_inline proc "contextless" (ctx: ^Blake2b_Context, p: []byte) { - h0, h1, h2, h3, h4, h5, h6, h7 := ctx.h[0], ctx.h[1], ctx.h[2], ctx.h[3], ctx.h[4], ctx.h[5], ctx.h[6], ctx.h[7] + h0, h1, h2, h3, h4, h5, h6, h7 := + ctx.h[0], ctx.h[1], ctx.h[2], ctx.h[3], ctx.h[4], ctx.h[5], ctx.h[6], ctx.h[7] p := p for len(p) >= BLAKE2B_BLOCK_SIZE { ctx.t[0] += BLAKE2B_BLOCK_SIZE if ctx.t[0] < BLAKE2B_BLOCK_SIZE { - ctx.t[1]+=1 - } + ctx.t[1] += 1 + } v0, v1, v2, v3, v4, v5, v6, v7 := h0, h1, h2, h3, h4, h5, h6, h7 v8 := BLAKE2B_IV[0] v9 := BLAKE2B_IV[1] @@ -1429,13 +1495,13 @@ blake2b_blocks :: #force_inline proc "contextless" (ctx: ^Blake2b_Context, p: [] v13 := BLAKE2B_IV[5] ~ ctx.t[1] v14 := BLAKE2B_IV[6] ~ ctx.f[0] v15 := BLAKE2B_IV[7] ~ ctx.f[1] + m: [16]u64 = --- - j := 0 - for i := 0; i < 16; i+=1 { - m[i] = u64(p[j]) | u64(p[j + 1]) << 8 | u64(p[j + 2]) << 16 | u64(p[j + 3]) << 24 | - u64(p[j + 4]) << 32 | u64(p[j + 5]) << 40 | u64(p[j + 6]) << 48 | u64(p[j + 7]) << 56 - j += 8 + for i := 0; i < 16; i += 1 { + m[i] = endian.unchecked_get_u64le(p[i * 8:]) } + + // Round 1 v0 += m[0] v0 += v4 v12 ~= v0 @@ -1548,6 +1614,8 @@ blake2b_blocks :: #force_inline proc "contextless" (ctx: ^Blake2b_Context, p: [] v10 += v15 v5 ~= v10 v5 = v5 << (64 - 63) | v5 >> 63 + + // Round 2 v0 += m[14] v0 += v4 v12 ~= v0 @@ -1660,6 +1728,8 @@ blake2b_blocks :: #force_inline proc "contextless" (ctx: ^Blake2b_Context, p: [] v10 += v15 v5 ~= v10 v5 = v5 << (64 - 63) | v5 >> 63 + + // Round 3 v0 += m[11] v0 += v4 v12 ~= v0 @@ -1772,6 +1842,8 @@ blake2b_blocks :: #force_inline proc "contextless" (ctx: ^Blake2b_Context, p: [] v10 += v15 v5 ~= v10 v5 = v5 << (64 - 63) | v5 >> 63 + + // Round 4 v0 += m[7] v0 += v4 v12 ~= v0 @@ -1884,6 +1956,8 @@ blake2b_blocks :: #force_inline proc "contextless" (ctx: ^Blake2b_Context, p: [] v10 += v15 v5 ~= v10 v5 = v5 << (64 - 63) | v5 >> 63 + + // Round 5 v0 += m[9] v0 += v4 v12 ~= v0 @@ -1996,6 +2070,8 @@ blake2b_blocks :: #force_inline proc "contextless" (ctx: ^Blake2b_Context, p: [] v10 += v15 v5 ~= v10 v5 = v5 << (64 - 63) | v5 >> 63 + + // Round 6 v0 += m[2] v0 += v4 v12 ~= v0 @@ -2108,6 +2184,8 @@ blake2b_blocks :: #force_inline proc "contextless" (ctx: ^Blake2b_Context, p: [] v10 += v15 v5 ~= v10 v5 = v5 << (64 - 63) | v5 >> 63 + + // Round 7 v0 += m[12] v0 += v4 v12 ~= v0 @@ -2220,6 +2298,8 @@ blake2b_blocks :: #force_inline proc "contextless" (ctx: ^Blake2b_Context, p: [] v10 += v15 v5 ~= v10 v5 = v5 << (64 - 63) | v5 >> 63 + + // Round 8 v0 += m[13] v0 += v4 v12 ~= v0 @@ -2332,6 +2412,8 @@ blake2b_blocks :: #force_inline proc "contextless" (ctx: ^Blake2b_Context, p: [] v10 += v15 v5 ~= v10 v5 = v5 << (64 - 63) | v5 >> 63 + + // Round 9 v0 += m[6] v0 += v4 v12 ~= v0 @@ -2444,6 +2526,8 @@ blake2b_blocks :: #force_inline proc "contextless" (ctx: ^Blake2b_Context, p: [] v10 += v15 v5 ~= v10 v5 = v5 << (64 - 63) | v5 >> 63 + + // Round 10 v0 += m[10] v0 += v4 v12 ~= v0 @@ -2556,6 +2640,8 @@ blake2b_blocks :: #force_inline proc "contextless" (ctx: ^Blake2b_Context, p: [] v10 += v15 v5 ~= v10 v5 = v5 << (64 - 63) | v5 >> 63 + + // Round 11 v0 += m[0] v0 += v4 v12 ~= v0 @@ -2668,6 +2754,8 @@ blake2b_blocks :: #force_inline proc "contextless" (ctx: ^Blake2b_Context, p: [] v10 += v15 v5 ~= v10 v5 = v5 << (64 - 63) | v5 >> 63 + + // Round 12 v0 += m[14] v0 += v4 v12 ~= v0 @@ -2780,6 +2868,7 @@ blake2b_blocks :: #force_inline proc "contextless" (ctx: ^Blake2b_Context, p: [] v10 += v15 v5 ~= v10 v5 = v5 << (64 - 63) | v5 >> 63 + h0 ~= v0 ~ v8 h1 ~= v1 ~ v9 h2 ~= v2 ~ v10 @@ -2788,7 +2877,9 @@ blake2b_blocks :: #force_inline proc "contextless" (ctx: ^Blake2b_Context, p: [] h5 ~= v5 ~ v13 h6 ~= v6 ~ v14 h7 ~= v7 ~ v15 + p = p[BLAKE2B_BLOCK_SIZE:] } - ctx.h[0], ctx.h[1], ctx.h[2], ctx.h[3], ctx.h[4], ctx.h[5], ctx.h[6], ctx.h[7] = h0, h1, h2, h3, h4, h5, h6, h7 -} \ No newline at end of file + ctx.h[0], ctx.h[1], ctx.h[2], ctx.h[3], ctx.h[4], ctx.h[5], ctx.h[6], ctx.h[7] = + h0, h1, h2, h3, h4, h5, h6, h7 +} diff --git a/core/crypto/_chacha20/chacha20.odin b/core/crypto/_chacha20/chacha20.odin new file mode 100644 index 000000000..a907209de --- /dev/null +++ b/core/crypto/_chacha20/chacha20.odin @@ -0,0 +1,123 @@ +package _chacha20 + +import "base:intrinsics" +import "core:encoding/endian" +import "core:math/bits" +import "core:mem" + +// KEY_SIZE is the (X)ChaCha20 key size in bytes. +KEY_SIZE :: 32 +// IV_SIZE is the ChaCha20 IV size in bytes. +IV_SIZE :: 12 +// XIV_SIZE is the XChaCha20 IV size in bytes. +XIV_SIZE :: 24 + +// MAX_CTR_IETF is the maximum counter value for the IETF flavor ChaCha20. +MAX_CTR_IETF :: 0xffffffff +// BLOCK_SIZE is the (X)ChaCha20 block size in bytes. +BLOCK_SIZE :: 64 +// STATE_SIZE_U32 is the (X)ChaCha20 state size in u32s. +STATE_SIZE_U32 :: 16 +// Rounds is the (X)ChaCha20 round count. +ROUNDS :: 20 + +// SIGMA_0 is sigma[0:4]. +SIGMA_0: u32 : 0x61707865 +// SIGMA_1 is sigma[4:8]. +SIGMA_1: u32 : 0x3320646e +// SIGMA_2 is sigma[8:12]. +SIGMA_2: u32 : 0x79622d32 +// SIGMA_3 is sigma[12:16]. +SIGMA_3: u32 : 0x6b206574 + +// Context is a ChaCha20 or XChaCha20 instance. +Context :: struct { + _s: [STATE_SIZE_U32]u32, + _buffer: [BLOCK_SIZE]byte, + _off: int, + _is_ietf_flavor: bool, + _is_initialized: bool, +} + +// init inititializes a Context for ChaCha20 with the provided key and +// iv. +// +// WARNING: This ONLY handles ChaCha20. XChaCha20 sub-key and IV +// derivation is expected to be handled by the caller, so that the +// HChaCha call can be suitably accelerated. +init :: proc "contextless" (ctx: ^Context, key, iv: []byte, is_xchacha: bool) { + if len(key) != KEY_SIZE || len(iv) != IV_SIZE { + intrinsics.trap() + } + + k, n := key, iv + + ctx._s[0] = SIGMA_0 + ctx._s[1] = SIGMA_1 + ctx._s[2] = SIGMA_2 + ctx._s[3] = SIGMA_3 + ctx._s[4] = endian.unchecked_get_u32le(k[0:4]) + ctx._s[5] = endian.unchecked_get_u32le(k[4:8]) + ctx._s[6] = endian.unchecked_get_u32le(k[8:12]) + ctx._s[7] = endian.unchecked_get_u32le(k[12:16]) + ctx._s[8] = endian.unchecked_get_u32le(k[16:20]) + ctx._s[9] = endian.unchecked_get_u32le(k[20:24]) + ctx._s[10] = endian.unchecked_get_u32le(k[24:28]) + ctx._s[11] = endian.unchecked_get_u32le(k[28:32]) + ctx._s[12] = 0 + ctx._s[13] = endian.unchecked_get_u32le(n[0:4]) + ctx._s[14] = endian.unchecked_get_u32le(n[4:8]) + ctx._s[15] = endian.unchecked_get_u32le(n[8:12]) + + ctx._off = BLOCK_SIZE + ctx._is_ietf_flavor = !is_xchacha + ctx._is_initialized = true +} + +// seek seeks the (X)ChaCha20 stream counter to the specified block. +seek :: proc(ctx: ^Context, block_nr: u64) { + assert(ctx._is_initialized) + + if ctx._is_ietf_flavor { + if block_nr > MAX_CTR_IETF { + panic("crypto/chacha20: attempted to seek past maximum counter") + } + } else { + ctx._s[13] = u32(block_nr >> 32) + } + ctx._s[12] = u32(block_nr) + ctx._off = BLOCK_SIZE +} + +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. +reset :: proc(ctx: ^Context) { + mem.zero_explicit(&ctx._s, size_of(ctx._s)) + mem.zero_explicit(&ctx._buffer, size_of(ctx._buffer)) + + ctx._is_initialized = false +} + +check_counter_limit :: proc(ctx: ^Context, nr_blocks: int) { + // Enforce the maximum consumed keystream per IV. + // + // While all modern "standard" definitions of ChaCha20 use + // the IETF 32-bit counter, for XChaCha20 most common + // implementations allow for a 64-bit counter. + // + // Honestly, the answer here is "use a MRAE primitive", but + // go with "common" practice in the case of XChaCha20. + + ERR_CTR_EXHAUSTED :: "crypto/chacha20: maximum (X)ChaCha20 keystream per IV reached" + + if ctx._is_ietf_flavor { + if u64(ctx._s[12]) + u64(nr_blocks) > MAX_CTR_IETF { + panic(ERR_CTR_EXHAUSTED) + } + } else { + ctr := (u64(ctx._s[13]) << 32) | u64(ctx._s[12]) + if _, carry := bits.add_u64(ctr, u64(nr_blocks), 0); carry != 0 { + panic(ERR_CTR_EXHAUSTED) + } + } +} diff --git a/core/crypto/_chacha20/ref/chacha20_ref.odin b/core/crypto/_chacha20/ref/chacha20_ref.odin new file mode 100644 index 000000000..c111c1c76 --- /dev/null +++ b/core/crypto/_chacha20/ref/chacha20_ref.odin @@ -0,0 +1,360 @@ +package chacha20_ref + +import "core:crypto/_chacha20" +import "core:encoding/endian" +import "core:math/bits" + +stream_blocks :: proc(ctx: ^_chacha20.Context, dst, src: []byte, nr_blocks: int) { + // Enforce the maximum consumed keystream per IV. + _chacha20.check_counter_limit(ctx, nr_blocks) + + dst, src := dst, src + x := &ctx._s + for n := 0; n < nr_blocks; n = n + 1 { + x0, x1, x2, x3 := + _chacha20.SIGMA_0, _chacha20.SIGMA_1, _chacha20.SIGMA_2, _chacha20.SIGMA_3 + x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15 := + x[4], x[5], x[6], x[7], x[8], x[9], x[10], x[11], x[12], x[13], x[14], x[15] + + for i := _chacha20.ROUNDS; i > 0; i = i - 2 { + // Even when forcing inlining manually inlining all of + // these is decently faster. + + // quarterround(x, 0, 4, 8, 12) + x0 += x4 + x12 ~= x0 + x12 = bits.rotate_left32(x12, 16) + x8 += x12 + x4 ~= x8 + x4 = bits.rotate_left32(x4, 12) + x0 += x4 + x12 ~= x0 + x12 = bits.rotate_left32(x12, 8) + x8 += x12 + x4 ~= x8 + x4 = bits.rotate_left32(x4, 7) + + // quarterround(x, 1, 5, 9, 13) + x1 += x5 + x13 ~= x1 + x13 = bits.rotate_left32(x13, 16) + x9 += x13 + x5 ~= x9 + x5 = bits.rotate_left32(x5, 12) + x1 += x5 + x13 ~= x1 + x13 = bits.rotate_left32(x13, 8) + x9 += x13 + x5 ~= x9 + x5 = bits.rotate_left32(x5, 7) + + // quarterround(x, 2, 6, 10, 14) + x2 += x6 + x14 ~= x2 + x14 = bits.rotate_left32(x14, 16) + x10 += x14 + x6 ~= x10 + x6 = bits.rotate_left32(x6, 12) + x2 += x6 + x14 ~= x2 + x14 = bits.rotate_left32(x14, 8) + x10 += x14 + x6 ~= x10 + x6 = bits.rotate_left32(x6, 7) + + // quarterround(x, 3, 7, 11, 15) + x3 += x7 + x15 ~= x3 + x15 = bits.rotate_left32(x15, 16) + x11 += x15 + x7 ~= x11 + x7 = bits.rotate_left32(x7, 12) + x3 += x7 + x15 ~= x3 + x15 = bits.rotate_left32(x15, 8) + x11 += x15 + x7 ~= x11 + x7 = bits.rotate_left32(x7, 7) + + // quarterround(x, 0, 5, 10, 15) + x0 += x5 + x15 ~= x0 + x15 = bits.rotate_left32(x15, 16) + x10 += x15 + x5 ~= x10 + x5 = bits.rotate_left32(x5, 12) + x0 += x5 + x15 ~= x0 + x15 = bits.rotate_left32(x15, 8) + x10 += x15 + x5 ~= x10 + x5 = bits.rotate_left32(x5, 7) + + // quarterround(x, 1, 6, 11, 12) + x1 += x6 + x12 ~= x1 + x12 = bits.rotate_left32(x12, 16) + x11 += x12 + x6 ~= x11 + x6 = bits.rotate_left32(x6, 12) + x1 += x6 + x12 ~= x1 + x12 = bits.rotate_left32(x12, 8) + x11 += x12 + x6 ~= x11 + x6 = bits.rotate_left32(x6, 7) + + // quarterround(x, 2, 7, 8, 13) + x2 += x7 + x13 ~= x2 + x13 = bits.rotate_left32(x13, 16) + x8 += x13 + x7 ~= x8 + x7 = bits.rotate_left32(x7, 12) + x2 += x7 + x13 ~= x2 + x13 = bits.rotate_left32(x13, 8) + x8 += x13 + x7 ~= x8 + x7 = bits.rotate_left32(x7, 7) + + // quarterround(x, 3, 4, 9, 14) + x3 += x4 + x14 ~= x3 + x14 = bits.rotate_left32(x14, 16) + x9 += x14 + x4 ~= x9 + x4 = bits.rotate_left32(x4, 12) + x3 += x4 + x14 ~= x3 + x14 = bits.rotate_left32(x14, 8) + x9 += x14 + x4 ~= x9 + x4 = bits.rotate_left32(x4, 7) + } + + x0 += _chacha20.SIGMA_0 + x1 += _chacha20.SIGMA_1 + x2 += _chacha20.SIGMA_2 + x3 += _chacha20.SIGMA_3 + x4 += x[4] + x5 += x[5] + x6 += x[6] + x7 += x[7] + x8 += x[8] + x9 += x[9] + x10 += x[10] + x11 += x[11] + x12 += x[12] + x13 += x[13] + x14 += x[14] + x15 += x[15] + + // - The caller(s) ensure that src/dst are valid. + // - The compiler knows if the target is picky about alignment. + + #no_bounds_check { + if src != nil { + endian.unchecked_put_u32le(dst[0:4], endian.unchecked_get_u32le(src[0:4]) ~ x0) + endian.unchecked_put_u32le(dst[4:8], endian.unchecked_get_u32le(src[4:8]) ~ x1) + endian.unchecked_put_u32le(dst[8:12], endian.unchecked_get_u32le(src[8:12]) ~ x2) + endian.unchecked_put_u32le(dst[12:16], endian.unchecked_get_u32le(src[12:16]) ~ x3) + endian.unchecked_put_u32le(dst[16:20], endian.unchecked_get_u32le(src[16:20]) ~ x4) + endian.unchecked_put_u32le(dst[20:24], endian.unchecked_get_u32le(src[20:24]) ~ x5) + endian.unchecked_put_u32le(dst[24:28], endian.unchecked_get_u32le(src[24:28]) ~ x6) + endian.unchecked_put_u32le(dst[28:32], endian.unchecked_get_u32le(src[28:32]) ~ x7) + endian.unchecked_put_u32le(dst[32:36], endian.unchecked_get_u32le(src[32:36]) ~ x8) + endian.unchecked_put_u32le(dst[36:40], endian.unchecked_get_u32le(src[36:40]) ~ x9) + endian.unchecked_put_u32le( + dst[40:44], + endian.unchecked_get_u32le(src[40:44]) ~ x10, + ) + endian.unchecked_put_u32le( + dst[44:48], + endian.unchecked_get_u32le(src[44:48]) ~ x11, + ) + endian.unchecked_put_u32le( + dst[48:52], + endian.unchecked_get_u32le(src[48:52]) ~ x12, + ) + endian.unchecked_put_u32le( + dst[52:56], + endian.unchecked_get_u32le(src[52:56]) ~ x13, + ) + endian.unchecked_put_u32le( + dst[56:60], + endian.unchecked_get_u32le(src[56:60]) ~ x14, + ) + endian.unchecked_put_u32le( + dst[60:64], + endian.unchecked_get_u32le(src[60:64]) ~ x15, + ) + src = src[_chacha20.BLOCK_SIZE:] + } else { + endian.unchecked_put_u32le(dst[0:4], x0) + endian.unchecked_put_u32le(dst[4:8], x1) + endian.unchecked_put_u32le(dst[8:12], x2) + endian.unchecked_put_u32le(dst[12:16], x3) + endian.unchecked_put_u32le(dst[16:20], x4) + endian.unchecked_put_u32le(dst[20:24], x5) + endian.unchecked_put_u32le(dst[24:28], x6) + endian.unchecked_put_u32le(dst[28:32], x7) + endian.unchecked_put_u32le(dst[32:36], x8) + endian.unchecked_put_u32le(dst[36:40], x9) + endian.unchecked_put_u32le(dst[40:44], x10) + endian.unchecked_put_u32le(dst[44:48], x11) + endian.unchecked_put_u32le(dst[48:52], x12) + endian.unchecked_put_u32le(dst[52:56], x13) + endian.unchecked_put_u32le(dst[56:60], x14) + endian.unchecked_put_u32le(dst[60:64], x15) + } + dst = dst[_chacha20.BLOCK_SIZE:] + } + + // Increment the counter. Overflow checking is done upon + // entry into the routine, so a 64-bit increment safely + // covers both cases. + new_ctr := ((u64(ctx._s[13]) << 32) | u64(ctx._s[12])) + 1 + x[12] = u32(new_ctr) + x[13] = u32(new_ctr >> 32) + } +} + +hchacha20 :: proc "contextless" (dst, key, iv: []byte) { + x0, x1, x2, x3 := _chacha20.SIGMA_0, _chacha20.SIGMA_1, _chacha20.SIGMA_2, _chacha20.SIGMA_3 + x4 := endian.unchecked_get_u32le(key[0:4]) + x5 := endian.unchecked_get_u32le(key[4:8]) + x6 := endian.unchecked_get_u32le(key[8:12]) + x7 := endian.unchecked_get_u32le(key[12:16]) + x8 := endian.unchecked_get_u32le(key[16:20]) + x9 := endian.unchecked_get_u32le(key[20:24]) + x10 := endian.unchecked_get_u32le(key[24:28]) + x11 := endian.unchecked_get_u32le(key[28:32]) + x12 := endian.unchecked_get_u32le(iv[0:4]) + x13 := endian.unchecked_get_u32le(iv[4:8]) + x14 := endian.unchecked_get_u32le(iv[8:12]) + x15 := endian.unchecked_get_u32le(iv[12:16]) + + for i := _chacha20.ROUNDS; i > 0; i = i - 2 { + // quarterround(x, 0, 4, 8, 12) + x0 += x4 + x12 ~= x0 + x12 = bits.rotate_left32(x12, 16) + x8 += x12 + x4 ~= x8 + x4 = bits.rotate_left32(x4, 12) + x0 += x4 + x12 ~= x0 + x12 = bits.rotate_left32(x12, 8) + x8 += x12 + x4 ~= x8 + x4 = bits.rotate_left32(x4, 7) + + // quarterround(x, 1, 5, 9, 13) + x1 += x5 + x13 ~= x1 + x13 = bits.rotate_left32(x13, 16) + x9 += x13 + x5 ~= x9 + x5 = bits.rotate_left32(x5, 12) + x1 += x5 + x13 ~= x1 + x13 = bits.rotate_left32(x13, 8) + x9 += x13 + x5 ~= x9 + x5 = bits.rotate_left32(x5, 7) + + // quarterround(x, 2, 6, 10, 14) + x2 += x6 + x14 ~= x2 + x14 = bits.rotate_left32(x14, 16) + x10 += x14 + x6 ~= x10 + x6 = bits.rotate_left32(x6, 12) + x2 += x6 + x14 ~= x2 + x14 = bits.rotate_left32(x14, 8) + x10 += x14 + x6 ~= x10 + x6 = bits.rotate_left32(x6, 7) + + // quarterround(x, 3, 7, 11, 15) + x3 += x7 + x15 ~= x3 + x15 = bits.rotate_left32(x15, 16) + x11 += x15 + x7 ~= x11 + x7 = bits.rotate_left32(x7, 12) + x3 += x7 + x15 ~= x3 + x15 = bits.rotate_left32(x15, 8) + x11 += x15 + x7 ~= x11 + x7 = bits.rotate_left32(x7, 7) + + // quarterround(x, 0, 5, 10, 15) + x0 += x5 + x15 ~= x0 + x15 = bits.rotate_left32(x15, 16) + x10 += x15 + x5 ~= x10 + x5 = bits.rotate_left32(x5, 12) + x0 += x5 + x15 ~= x0 + x15 = bits.rotate_left32(x15, 8) + x10 += x15 + x5 ~= x10 + x5 = bits.rotate_left32(x5, 7) + + // quarterround(x, 1, 6, 11, 12) + x1 += x6 + x12 ~= x1 + x12 = bits.rotate_left32(x12, 16) + x11 += x12 + x6 ~= x11 + x6 = bits.rotate_left32(x6, 12) + x1 += x6 + x12 ~= x1 + x12 = bits.rotate_left32(x12, 8) + x11 += x12 + x6 ~= x11 + x6 = bits.rotate_left32(x6, 7) + + // quarterround(x, 2, 7, 8, 13) + x2 += x7 + x13 ~= x2 + x13 = bits.rotate_left32(x13, 16) + x8 += x13 + x7 ~= x8 + x7 = bits.rotate_left32(x7, 12) + x2 += x7 + x13 ~= x2 + x13 = bits.rotate_left32(x13, 8) + x8 += x13 + x7 ~= x8 + x7 = bits.rotate_left32(x7, 7) + + // quarterround(x, 3, 4, 9, 14) + x3 += x4 + x14 ~= x3 + x14 = bits.rotate_left32(x14, 16) + x9 += x14 + x4 ~= x9 + x4 = bits.rotate_left32(x4, 12) + x3 += x4 + x14 ~= x3 + x14 = bits.rotate_left32(x14, 8) + x9 += x14 + x4 ~= x9 + x4 = bits.rotate_left32(x4, 7) + } + + endian.unchecked_put_u32le(dst[0:4], x0) + endian.unchecked_put_u32le(dst[4:8], x1) + endian.unchecked_put_u32le(dst[8:12], x2) + endian.unchecked_put_u32le(dst[12:16], x3) + endian.unchecked_put_u32le(dst[16:20], x12) + endian.unchecked_put_u32le(dst[20:24], x13) + endian.unchecked_put_u32le(dst[24:28], x14) + endian.unchecked_put_u32le(dst[28:32], x15) +} diff --git a/core/crypto/_chacha20/simd128/chacha20_simd128.odin b/core/crypto/_chacha20/simd128/chacha20_simd128.odin new file mode 100644 index 000000000..fe0d0d518 --- /dev/null +++ b/core/crypto/_chacha20/simd128/chacha20_simd128.odin @@ -0,0 +1,481 @@ +package chacha20_simd128 + +import "base:intrinsics" +import "core:crypto/_chacha20" +import "core:simd" +@(require) import "core:sys/info" + +// Portable 128-bit `core:simd` implementation. +// +// This is loosely based on Ted Krovetz's public domain C intrinsic +// implementation. +// +// This is written to perform adequately on any target that has "enough" +// 128-bit vector registers, the current thought is that 4 blocks at at +// time is reasonable for amd64, though Ted's code is more conservative. +// +// See: +// supercop-20230530/crypto_stream/chacha20/krovetz/vec128 + +// Ensure the compiler emits SIMD instructions. This is a minimum, and +// setting the microarchitecture at compile time will allow for better +// code gen when applicable (eg: AVX). This is somewhat redundant with +// the default microarchitecture configurations. +when ODIN_ARCH == .arm64 || ODIN_ARCH == .arm32 { + @(private = "file") + TARGET_SIMD_FEATURES :: "neon" +} else when ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 { + // Note: LLVM appears to be smart enough to use PSHUFB despite not + // explicitly using simd.u8x16 shuffles. + @(private = "file") + TARGET_SIMD_FEATURES :: "sse2,ssse3" +} else { + @(private = "file") + TARGET_SIMD_FEATURES :: "" +} + +@(private = "file") +_ROT_7L: simd.u32x4 : {7, 7, 7, 7} +@(private = "file") +_ROT_7R: simd.u32x4 : {25, 25, 25, 25} +@(private = "file") +_ROT_12L: simd.u32x4 : {12, 12, 12, 12} +@(private = "file") +_ROT_12R: simd.u32x4 : {20, 20, 20, 20} +@(private = "file") +_ROT_8L: simd.u32x4 : {8, 8, 8, 8} +@(private = "file") +_ROT_8R: simd.u32x4 : {24, 24, 24, 24} +@(private = "file") +_ROT_16: simd.u32x4 : {16, 16, 16, 16} + +when ODIN_ENDIAN == .Big { + @(private = "file") + _increment_counter :: #force_inline proc "contextless" (ctx: ^_chacha20.Context) -> simd.u32x4 { + // In the Big Endian case, the low and high portions in the vector + // are flipped, so the 64-bit addition can't be done with a simple + // vector add. + x := &ctx._s + + new_ctr := ((u64(ctx._s[13]) << 32) | u64(ctx._s[12])) + 1 + x[12] = u32(new_ctr) + x[13] = u32(new_ctr >> 32) + + return intrinsics.unaligned_load(transmute(^simd.u32x4)&x[12]) + } + + // Convert the endian-ness of the components of a u32x4 vector, for + // the purposes of output. + @(private = "file") + _byteswap_u32x4 :: #force_inline proc "contextless" (v: simd.u32x4) -> simd.u32x4 { + return( + transmute(simd.u32x4)simd.shuffle( + transmute(simd.u8x16)v, + transmute(simd.u8x16)v, + 3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12, + ) + ) + } +} else { + @(private = "file") + _VEC_ONE: simd.u64x2 : {1, 0} +} + +@(private = "file") +_dq_round_simd128 :: #force_inline proc "contextless" ( + v0, v1, v2, v3: simd.u32x4, +) -> ( + simd.u32x4, + simd.u32x4, + simd.u32x4, + simd.u32x4, +) { + v0, v1, v2, v3 := v0, v1, v2, v3 + + // a += b; d ^= a; d = ROTW16(d); + v0 = simd.add(v0, v1) + v3 = simd.bit_xor(v3, v0) + v3 = simd.bit_xor(simd.shl(v3, _ROT_16), simd.shr(v3, _ROT_16)) + + // c += d; b ^= c; b = ROTW12(b); + v2 = simd.add(v2, v3) + v1 = simd.bit_xor(v1, v2) + v1 = simd.bit_xor(simd.shl(v1, _ROT_12L), simd.shr(v1, _ROT_12R)) + + // a += b; d ^= a; d = ROTW8(d); + v0 = simd.add(v0, v1) + v3 = simd.bit_xor(v3, v0) + v3 = simd.bit_xor(simd.shl(v3, _ROT_8L), simd.shr(v3, _ROT_8R)) + + // c += d; b ^= c; b = ROTW7(b); + v2 = simd.add(v2, v3) + v1 = simd.bit_xor(v1, v2) + v1 = simd.bit_xor(simd.shl(v1, _ROT_7L), simd.shr(v1, _ROT_7R)) + + // b = ROTV1(b); c = ROTV2(c); d = ROTV3(d); + v1 = simd.shuffle(v1, v1, 1, 2, 3, 0) + v2 = simd.shuffle(v2, v2, 2, 3, 0, 1) + v3 = simd.shuffle(v3, v3, 3, 0, 1, 2) + + // a += b; d ^= a; d = ROTW16(d); + v0 = simd.add(v0, v1) + v3 = simd.bit_xor(v3, v0) + v3 = simd.bit_xor(simd.shl(v3, _ROT_16), simd.shr(v3, _ROT_16)) + + // c += d; b ^= c; b = ROTW12(b); + v2 = simd.add(v2, v3) + v1 = simd.bit_xor(v1, v2) + v1 = simd.bit_xor(simd.shl(v1, _ROT_12L), simd.shr(v1, _ROT_12R)) + + // a += b; d ^= a; d = ROTW8(d); + v0 = simd.add(v0, v1) + v3 = simd.bit_xor(v3, v0) + v3 = simd.bit_xor(simd.shl(v3, _ROT_8L), simd.shr(v3, _ROT_8R)) + + // c += d; b ^= c; b = ROTW7(b); + v2 = simd.add(v2, v3) + v1 = simd.bit_xor(v1, v2) + v1 = simd.bit_xor(simd.shl(v1, _ROT_7L), simd.shr(v1, _ROT_7R)) + + // b = ROTV3(b); c = ROTV2(c); d = ROTV1(d); + v1 = simd.shuffle(v1, v1, 3, 0, 1, 2) + v2 = simd.shuffle(v2, v2, 2, 3, 0, 1) + v3 = simd.shuffle(v3, v3, 1, 2, 3, 0) + + return v0, v1, v2, v3 +} + +@(private = "file") +_add_state_simd128 :: #force_inline proc "contextless" ( + v0, v1, v2, v3, s0, s1, s2, s3: simd.u32x4, +) -> ( + simd.u32x4, + simd.u32x4, + simd.u32x4, + simd.u32x4, +) { + v0, v1, v2, v3 := v0, v1, v2, v3 + + v0 = simd.add(v0, s0) + v1 = simd.add(v1, s1) + v2 = simd.add(v2, s2) + v3 = simd.add(v3, s3) + + when ODIN_ENDIAN == .Big { + v0 = _byteswap_u32x4(v0) + v1 = _byteswap_u32x4(v1) + v2 = _byteswap_u32x4(v2) + v3 = _byteswap_u32x4(v3) + } + + return v0, v1, v2, v3 +} + +@(private = "file") +_xor_simd128 :: #force_inline proc "contextless" ( + src: [^]simd.u32x4, + v0, v1, v2, v3: simd.u32x4, +) -> ( + simd.u32x4, + simd.u32x4, + simd.u32x4, + simd.u32x4, +) { + v0, v1, v2, v3 := v0, v1, v2, v3 + + v0 = simd.bit_xor(v0, intrinsics.unaligned_load((^simd.u32x4)(src[0:]))) + v1 = simd.bit_xor(v1, intrinsics.unaligned_load((^simd.u32x4)(src[1:]))) + v2 = simd.bit_xor(v2, intrinsics.unaligned_load((^simd.u32x4)(src[2:]))) + v3 = simd.bit_xor(v3, intrinsics.unaligned_load((^simd.u32x4)(src[3:]))) + + return v0, v1, v2, v3 +} + +@(private = "file") +_store_simd128 :: #force_inline proc "contextless" ( + dst: [^]simd.u32x4, + v0, v1, v2, v3: simd.u32x4, +) { + intrinsics.unaligned_store((^simd.u32x4)(dst[0:]), v0) + intrinsics.unaligned_store((^simd.u32x4)(dst[1:]), v1) + intrinsics.unaligned_store((^simd.u32x4)(dst[2:]), v2) + intrinsics.unaligned_store((^simd.u32x4)(dst[3:]), v3) +} + +// is_performant returns true iff the target and current host both support +// "enough" 128-bit SIMD to make this implementation performant. +is_performant :: proc "contextless" () -> bool { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .arm32 || ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .arm32 { + req_features :: info.CPU_Features{.asimd} + } else when ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 { + req_features :: info.CPU_Features{.sse2, .ssse3} + } + + features, ok := info.cpu_features.? + if !ok { + return false + } + + return features >= req_features + } else when ODIN_ARCH == .wasm64p32 || ODIN_ARCH == .wasm32 { + return intrinsics.has_target_feature("simd128") + } else { + return false + } +} + +@(enable_target_feature = TARGET_SIMD_FEATURES) +stream_blocks :: proc(ctx: ^_chacha20.Context, dst, src: []byte, nr_blocks: int) { + // Enforce the maximum consumed keystream per IV. + _chacha20.check_counter_limit(ctx, nr_blocks) + + dst_v := ([^]simd.u32x4)(raw_data(dst)) + src_v := ([^]simd.u32x4)(raw_data(src)) + + x := &ctx._s + n := nr_blocks + + // The state vector is an array of uint32s in native byte-order. + x_v := ([^]simd.u32x4)(raw_data(x)) + s0 := intrinsics.unaligned_load((^simd.u32x4)(x_v[0:])) + s1 := intrinsics.unaligned_load((^simd.u32x4)(x_v[1:])) + s2 := intrinsics.unaligned_load((^simd.u32x4)(x_v[2:])) + s3 := intrinsics.unaligned_load((^simd.u32x4)(x_v[3:])) + + // 8 blocks at a time. + // + // Note: This is only worth it on Aarch64. + when ODIN_ARCH == .arm64 { + for ; n >= 8; n = n - 8 { + v0, v1, v2, v3 := s0, s1, s2, s3 + + when ODIN_ENDIAN == .Little { + s7 := transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s3, _VEC_ONE) + } else { + s7 := _increment_counter(ctx) + } + v4, v5, v6, v7 := s0, s1, s2, s7 + + when ODIN_ENDIAN == .Little { + s11 := transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s7, _VEC_ONE) + } else { + s11 := _increment_counter(ctx) + } + v8, v9, v10, v11 := s0, s1, s2, s11 + + when ODIN_ENDIAN == .Little { + s15 := transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s11, _VEC_ONE) + } else { + s15 := _increment_counter(ctx) + } + v12, v13, v14, v15 := s0, s1, s2, s15 + + when ODIN_ENDIAN == .Little { + s19 := transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s15, _VEC_ONE) + } else { + s19 := _increment_counter(ctx) + } + + v16, v17, v18, v19 := s0, s1, s2, s19 + when ODIN_ENDIAN == .Little { + s23 := transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s19, _VEC_ONE) + } else { + s23 := _increment_counter(ctx) + } + + v20, v21, v22, v23 := s0, s1, s2, s23 + when ODIN_ENDIAN == .Little { + s27 := transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s23, _VEC_ONE) + } else { + s27 := _increment_counter(ctx) + } + + v24, v25, v26, v27 := s0, s1, s2, s27 + when ODIN_ENDIAN == .Little { + s31 := transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s27, _VEC_ONE) + } else { + s31 := _increment_counter(ctx) + } + v28, v29, v30, v31 := s0, s1, s2, s31 + + for i := _chacha20.ROUNDS; i > 0; i = i - 2 { + v0, v1, v2, v3 = _dq_round_simd128(v0, v1, v2, v3) + v4, v5, v6, v7 = _dq_round_simd128(v4, v5, v6, v7) + v8, v9, v10, v11 = _dq_round_simd128(v8, v9, v10, v11) + v12, v13, v14, v15 = _dq_round_simd128(v12, v13, v14, v15) + v16, v17, v18, v19 = _dq_round_simd128(v16, v17, v18, v19) + v20, v21, v22, v23 = _dq_round_simd128(v20, v21, v22, v23) + v24, v25, v26, v27 = _dq_round_simd128(v24, v25, v26, v27) + v28, v29, v30, v31 = _dq_round_simd128(v28, v29, v30, v31) + } + + v0, v1, v2, v3 = _add_state_simd128(v0, v1, v2, v3, s0, s1, s2, s3) + v4, v5, v6, v7 = _add_state_simd128(v4, v5, v6, v7, s0, s1, s2, s7) + v8, v9, v10, v11 = _add_state_simd128(v8, v9, v10, v11, s0, s1, s2, s11) + v12, v13, v14, v15 = _add_state_simd128(v12, v13, v14, v15, s0, s1, s2, s15) + v16, v17, v18, v19 = _add_state_simd128(v16, v17, v18, v19, s0, s1, s2, s19) + v20, v21, v22, v23 = _add_state_simd128(v20, v21, v22, v23, s0, s1, s2, s23) + v24, v25, v26, v27 = _add_state_simd128(v24, v25, v26, v27, s0, s1, s2, s27) + v28, v29, v30, v31 = _add_state_simd128(v28, v29, v30, v31, s0, s1, s2, s31) + + #no_bounds_check { + if src != nil { + v0, v1, v2, v3 = _xor_simd128(src_v, v0, v1, v2, v3) + v4, v5, v6, v7 = _xor_simd128(src_v[4:], v4, v5, v6, v7) + v8, v9, v10, v11 = _xor_simd128(src_v[8:], v8, v9, v10, v11) + v12, v13, v14, v15 = _xor_simd128(src_v[12:], v12, v13, v14, v15) + v16, v17, v18, v19 = _xor_simd128(src_v[16:], v16, v17, v18, v19) + v20, v21, v22, v23 = _xor_simd128(src_v[20:], v20, v21, v22, v23) + v24, v25, v26, v27 = _xor_simd128(src_v[24:], v24, v25, v26, v27) + v28, v29, v30, v31 = _xor_simd128(src_v[28:], v28, v29, v30, v31) + src_v = src_v[32:] + } + + _store_simd128(dst_v, v0, v1, v2, v3) + _store_simd128(dst_v[4:], v4, v5, v6, v7) + _store_simd128(dst_v[8:], v8, v9, v10, v11) + _store_simd128(dst_v[12:], v12, v13, v14, v15) + _store_simd128(dst_v[16:], v16, v17, v18, v19) + _store_simd128(dst_v[20:], v20, v21, v22, v23) + _store_simd128(dst_v[24:], v24, v25, v26, v27) + _store_simd128(dst_v[28:], v28, v29, v30, v31) + dst_v = dst_v[32:] + } + + when ODIN_ENDIAN == .Little { + // s31 holds the most current counter, so `s3 = s31 + 1`. + s3 = transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s31, _VEC_ONE) + } else { + s3 = _increment_counter(ctx) + } + } + } + + // 4 blocks at a time. + // + // Note: The i386 target lacks the required number of registers + // for this to be performant, so it is skipped. + when ODIN_ARCH != .i386 { + for ; n >= 4; n = n - 4 { + v0, v1, v2, v3 := s0, s1, s2, s3 + + when ODIN_ENDIAN == .Little { + s7 := transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s3, _VEC_ONE) + } else { + s7 := _increment_counter(ctx) + } + v4, v5, v6, v7 := s0, s1, s2, s7 + + when ODIN_ENDIAN == .Little { + s11 := transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s7, _VEC_ONE) + } else { + s11 := _increment_counter(ctx) + } + v8, v9, v10, v11 := s0, s1, s2, s11 + + when ODIN_ENDIAN == .Little { + s15 := transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s11, _VEC_ONE) + } else { + s15 := _increment_counter(ctx) + } + v12, v13, v14, v15 := s0, s1, s2, s15 + + for i := _chacha20.ROUNDS; i > 0; i = i - 2 { + v0, v1, v2, v3 = _dq_round_simd128(v0, v1, v2, v3) + v4, v5, v6, v7 = _dq_round_simd128(v4, v5, v6, v7) + v8, v9, v10, v11 = _dq_round_simd128(v8, v9, v10, v11) + v12, v13, v14, v15 = _dq_round_simd128(v12, v13, v14, v15) + } + + v0, v1, v2, v3 = _add_state_simd128(v0, v1, v2, v3, s0, s1, s2, s3) + v4, v5, v6, v7 = _add_state_simd128(v4, v5, v6, v7, s0, s1, s2, s7) + v8, v9, v10, v11 = _add_state_simd128(v8, v9, v10, v11, s0, s1, s2, s11) + v12, v13, v14, v15 = _add_state_simd128(v12, v13, v14, v15, s0, s1, s2, s15) + + #no_bounds_check { + if src != nil { + v0, v1, v2, v3 = _xor_simd128(src_v, v0, v1, v2, v3) + v4, v5, v6, v7 = _xor_simd128(src_v[4:], v4, v5, v6, v7) + v8, v9, v10, v11 = _xor_simd128(src_v[8:], v8, v9, v10, v11) + v12, v13, v14, v15 = _xor_simd128(src_v[12:], v12, v13, v14, v15) + src_v = src_v[16:] + } + + _store_simd128(dst_v, v0, v1, v2, v3) + _store_simd128(dst_v[4:], v4, v5, v6, v7) + _store_simd128(dst_v[8:], v8, v9, v10, v11) + _store_simd128(dst_v[12:], v12, v13, v14, v15) + dst_v = dst_v[16:] + } + + when ODIN_ENDIAN == .Little { + // s15 holds the most current counter, so `s3 = s15 + 1`. + s3 = transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s15, _VEC_ONE) + } else { + s3 = _increment_counter(ctx) + } + } + } + + // 1 block at a time. + for ; n > 0; n = n - 1 { + v0, v1, v2, v3 := s0, s1, s2, s3 + + for i := _chacha20.ROUNDS; i > 0; i = i - 2 { + v0, v1, v2, v3 = _dq_round_simd128(v0, v1, v2, v3) + } + v0, v1, v2, v3 = _add_state_simd128(v0, v1, v2, v3, s0, s1, s2, s3) + + #no_bounds_check { + if src != nil { + v0, v1, v2, v3 = _xor_simd128(src_v, v0, v1, v2, v3) + src_v = src_v[4:] + } + + _store_simd128(dst_v, v0, v1, v2, v3) + dst_v = dst_v[4:] + } + + // Increment the counter. Overflow checking is done upon + // entry into the routine, so a 64-bit increment safely + // covers both cases. + when ODIN_ENDIAN == .Little { + s3 = transmute(simd.u32x4)simd.add(transmute(simd.u64x2)s3, _VEC_ONE) + } else { + s3 = _increment_counter(ctx) + } + } + + when ODIN_ENDIAN == .Little { + // Write back the counter to the state. + intrinsics.unaligned_store((^simd.u32x4)(x_v[3:]), s3) + } +} + +@(enable_target_feature = TARGET_SIMD_FEATURES) +hchacha20 :: proc "contextless" (dst, key, iv: []byte) { + v0 := simd.u32x4{_chacha20.SIGMA_0, _chacha20.SIGMA_1, _chacha20.SIGMA_2, _chacha20.SIGMA_3} + v1 := intrinsics.unaligned_load((^simd.u32x4)(&key[0])) + v2 := intrinsics.unaligned_load((^simd.u32x4)(&key[16])) + v3 := intrinsics.unaligned_load((^simd.u32x4)(&iv[0])) + + when ODIN_ENDIAN == .Big { + v1 = _byteswap_u32x4(v1) + v2 = _byteswap_u32x4(v2) + v3 = _byteswap_u32x4(v3) + } + + for i := _chacha20.ROUNDS; i > 0; i = i - 2 { + v0, v1, v2, v3 = _dq_round_simd128(v0, v1, v2, v3) + } + + when ODIN_ENDIAN == .Big { + v0 = _byteswap_u32x4(v0) + v3 = _byteswap_u32x4(v3) + } + + dst_v := ([^]simd.u32x4)(raw_data(dst)) + intrinsics.unaligned_store((^simd.u32x4)(dst_v[0:]), v0) + intrinsics.unaligned_store((^simd.u32x4)(dst_v[1:]), v3) +} diff --git a/core/crypto/_chacha20/simd256/chacha20_simd256.odin b/core/crypto/_chacha20/simd256/chacha20_simd256.odin new file mode 100644 index 000000000..ccb02a947 --- /dev/null +++ b/core/crypto/_chacha20/simd256/chacha20_simd256.odin @@ -0,0 +1,319 @@ +#+build amd64 +package chacha20_simd256 + +import "base:intrinsics" +import "core:crypto/_chacha20" +import chacha_simd128 "core:crypto/_chacha20/simd128" +import "core:simd" +import "core:sys/info" + +// This is loosely based on Ted Krovetz's public domain C intrinsic +// implementations. While written using `core:simd`, this is currently +// amd64 specific because we do not have a way to detect ARM SVE. +// +// See: +// supercop-20230530/crypto_stream/chacha20/krovetz/vec128 +// supercop-20230530/crypto_stream/chacha20/krovetz/avx2 + +#assert(ODIN_ENDIAN == .Little) + +@(private = "file") +_ROT_7L: simd.u32x8 : {7, 7, 7, 7, 7, 7, 7, 7} +@(private = "file") +_ROT_7R: simd.u32x8 : {25, 25, 25, 25, 25, 25, 25, 25} +@(private = "file") +_ROT_12L: simd.u32x8 : {12, 12, 12, 12, 12, 12, 12, 12} +@(private = "file") +_ROT_12R: simd.u32x8 : {20, 20, 20, 20, 20, 20, 20, 20} +@(private = "file") +_ROT_8L: simd.u32x8 : {8, 8, 8, 8, 8, 8, 8, 8} +@(private = "file") +_ROT_8R: simd.u32x8 : {24, 24, 24, 24, 24, 24, 24, 24} +@(private = "file") +_ROT_16: simd.u32x8 : {16, 16, 16, 16, 16, 16, 16, 16} +@(private = "file") +_VEC_ZERO_ONE: simd.u64x4 : {0, 0, 1, 0} +@(private = "file") +_VEC_TWO: simd.u64x4 : {2, 0, 2, 0} + +// is_performant returns true iff the target and current host both support +// "enough" SIMD to make this implementation performant. +is_performant :: proc "contextless" () -> bool { + req_features :: info.CPU_Features{.avx, .avx2} + + features, ok := info.cpu_features.? + if !ok { + return false + } + + return features >= req_features +} + +@(private = "file") +_dq_round_simd256 :: #force_inline proc "contextless" ( + v0, v1, v2, v3: simd.u32x8, +) -> ( + simd.u32x8, + simd.u32x8, + simd.u32x8, + simd.u32x8, +) { + v0, v1, v2, v3 := v0, v1, v2, v3 + + // a += b; d ^= a; d = ROTW16(d); + v0 = simd.add(v0, v1) + v3 = simd.bit_xor(v3, v0) + v3 = simd.bit_xor(simd.shl(v3, _ROT_16), simd.shr(v3, _ROT_16)) + + // c += d; b ^= c; b = ROTW12(b); + v2 = simd.add(v2, v3) + v1 = simd.bit_xor(v1, v2) + v1 = simd.bit_xor(simd.shl(v1, _ROT_12L), simd.shr(v1, _ROT_12R)) + + // a += b; d ^= a; d = ROTW8(d); + v0 = simd.add(v0, v1) + v3 = simd.bit_xor(v3, v0) + v3 = simd.bit_xor(simd.shl(v3, _ROT_8L), simd.shr(v3, _ROT_8R)) + + // c += d; b ^= c; b = ROTW7(b); + v2 = simd.add(v2, v3) + v1 = simd.bit_xor(v1, v2) + v1 = simd.bit_xor(simd.shl(v1, _ROT_7L), simd.shr(v1, _ROT_7R)) + + // b = ROTV1(b); c = ROTV2(c); d = ROTV3(d); + v1 = simd.shuffle(v1, v1, 1, 2, 3, 0, 5, 6, 7, 4) + v2 = simd.shuffle(v2, v2, 2, 3, 0, 1, 6, 7, 4, 5) + v3 = simd.shuffle(v3, v3, 3, 0, 1, 2, 7, 4, 5, 6) + + // a += b; d ^= a; d = ROTW16(d); + v0 = simd.add(v0, v1) + v3 = simd.bit_xor(v3, v0) + v3 = simd.bit_xor(simd.shl(v3, _ROT_16), simd.shr(v3, _ROT_16)) + + // c += d; b ^= c; b = ROTW12(b); + v2 = simd.add(v2, v3) + v1 = simd.bit_xor(v1, v2) + v1 = simd.bit_xor(simd.shl(v1, _ROT_12L), simd.shr(v1, _ROT_12R)) + + // a += b; d ^= a; d = ROTW8(d); + v0 = simd.add(v0, v1) + v3 = simd.bit_xor(v3, v0) + v3 = simd.bit_xor(simd.shl(v3, _ROT_8L), simd.shr(v3, _ROT_8R)) + + // c += d; b ^= c; b = ROTW7(b); + v2 = simd.add(v2, v3) + v1 = simd.bit_xor(v1, v2) + v1 = simd.bit_xor(simd.shl(v1, _ROT_7L), simd.shr(v1, _ROT_7R)) + + // b = ROTV3(b); c = ROTV2(c); d = ROTV1(d); + v1 = simd.shuffle(v1, v1, 3, 0, 1, 2, 7, 4, 5, 6) + v2 = simd.shuffle(v2, v2, 2, 3, 0, 1, 6, 7, 4, 5) + v3 = simd.shuffle(v3, v3, 1, 2, 3, 0, 5, 6, 7, 4) + + return v0, v1, v2, v3 +} + +@(private = "file") +_add_and_permute_state_simd256 :: #force_inline proc "contextless" ( + v0, v1, v2, v3, s0, s1, s2, s3: simd.u32x8, +) -> ( + simd.u32x8, + simd.u32x8, + simd.u32x8, + simd.u32x8, +) { + t0 := simd.add(v0, s0) + t1 := simd.add(v1, s1) + t2 := simd.add(v2, s2) + t3 := simd.add(v3, s3) + + // Big Endian would byteswap here. + + // Each of v0 .. v3 has 128-bits of keystream for 2 separate blocks. + // permute the state such that (r0, r1) contains block 0, and (r2, r3) + // contains block 1. + r0 := simd.shuffle(t0, t1, 0, 1, 2, 3, 8, 9, 10, 11) + r2 := simd.shuffle(t0, t1, 4, 5, 6, 7, 12, 13, 14, 15) + r1 := simd.shuffle(t2, t3, 0, 1, 2, 3, 8, 9, 10, 11) + r3 := simd.shuffle(t2, t3, 4, 5, 6, 7, 12, 13, 14, 15) + + return r0, r1, r2, r3 +} + +@(private = "file") +_xor_simd256 :: #force_inline proc "contextless" ( + src: [^]simd.u32x8, + v0, v1, v2, v3: simd.u32x8, +) -> ( + simd.u32x8, + simd.u32x8, + simd.u32x8, + simd.u32x8, +) { + v0, v1, v2, v3 := v0, v1, v2, v3 + + v0 = simd.bit_xor(v0, intrinsics.unaligned_load((^simd.u32x8)(src[0:]))) + v1 = simd.bit_xor(v1, intrinsics.unaligned_load((^simd.u32x8)(src[1:]))) + v2 = simd.bit_xor(v2, intrinsics.unaligned_load((^simd.u32x8)(src[2:]))) + v3 = simd.bit_xor(v3, intrinsics.unaligned_load((^simd.u32x8)(src[3:]))) + + return v0, v1, v2, v3 +} + +@(private = "file") +_xor_simd256_x1 :: #force_inline proc "contextless" ( + src: [^]simd.u32x8, + v0, v1: simd.u32x8, +) -> ( + simd.u32x8, + simd.u32x8, +) { + v0, v1 := v0, v1 + + v0 = simd.bit_xor(v0, intrinsics.unaligned_load((^simd.u32x8)(src[0:]))) + v1 = simd.bit_xor(v1, intrinsics.unaligned_load((^simd.u32x8)(src[1:]))) + + return v0, v1 +} + +@(private = "file") +_store_simd256 :: #force_inline proc "contextless" ( + dst: [^]simd.u32x8, + v0, v1, v2, v3: simd.u32x8, +) { + intrinsics.unaligned_store((^simd.u32x8)(dst[0:]), v0) + intrinsics.unaligned_store((^simd.u32x8)(dst[1:]), v1) + intrinsics.unaligned_store((^simd.u32x8)(dst[2:]), v2) + intrinsics.unaligned_store((^simd.u32x8)(dst[3:]), v3) +} + +@(private = "file") +_store_simd256_x1 :: #force_inline proc "contextless" ( + dst: [^]simd.u32x8, + v0, v1: simd.u32x8, +) { + intrinsics.unaligned_store((^simd.u32x8)(dst[0:]), v0) + intrinsics.unaligned_store((^simd.u32x8)(dst[1:]), v1) +} + +@(enable_target_feature = "sse2,ssse3,avx,avx2") +stream_blocks :: proc(ctx: ^_chacha20.Context, dst, src: []byte, nr_blocks: int) { + // Enforce the maximum consumed keystream per IV. + _chacha20.check_counter_limit(ctx, nr_blocks) + + dst_v := ([^]simd.u32x8)(raw_data(dst)) + src_v := ([^]simd.u32x8)(raw_data(src)) + + x := &ctx._s + n := nr_blocks + + // The state vector is an array of uint32s in native byte-order. + // Setup s0 .. s3 such that each register stores 2 copies of the + // state. + x_v := ([^]simd.u32x4)(raw_data(x)) + t0 := intrinsics.unaligned_load((^simd.u32x4)(x_v[0:])) + t1 := intrinsics.unaligned_load((^simd.u32x4)(x_v[1:])) + t2 := intrinsics.unaligned_load((^simd.u32x4)(x_v[2:])) + t3 := intrinsics.unaligned_load((^simd.u32x4)(x_v[3:])) + s0 := simd.swizzle(t0, 0, 1, 2, 3, 0, 1, 2, 3) + s1 := simd.swizzle(t1, 0, 1, 2, 3, 0, 1, 2, 3) + s2 := simd.swizzle(t2, 0, 1, 2, 3, 0, 1, 2, 3) + s3 := simd.swizzle(t3, 0, 1, 2, 3, 0, 1, 2, 3) + + // Advance the counter in the 2nd copy of the state by one. + s3 = transmute(simd.u32x8)simd.add(transmute(simd.u64x4)s3, _VEC_ZERO_ONE) + + // 8 blocks at a time. + for ; n >= 8; n = n - 8 { + v0, v1, v2, v3 := s0, s1, s2, s3 + + s7 := transmute(simd.u32x8)simd.add(transmute(simd.u64x4)s3, _VEC_TWO) + v4, v5, v6, v7 := s0, s1, s2, s7 + + s11 := transmute(simd.u32x8)simd.add(transmute(simd.u64x4)s7, _VEC_TWO) + v8, v9, v10, v11 := s0, s1, s2, s11 + + s15 := transmute(simd.u32x8)simd.add(transmute(simd.u64x4)s11, _VEC_TWO) + v12, v13, v14, v15 := s0, s1, s2, s15 + + for i := _chacha20.ROUNDS; i > 0; i = i - 2 { + v0, v1, v2, v3 = _dq_round_simd256(v0, v1, v2, v3) + v4, v5, v6, v7 = _dq_round_simd256(v4, v5, v6, v7) + v8, v9, v10, v11 = _dq_round_simd256(v8, v9, v10, v11) + v12, v13, v14, v15 = _dq_round_simd256(v12, v13, v14, v15) + } + + v0, v1, v2, v3 = _add_and_permute_state_simd256(v0, v1, v2, v3, s0, s1, s2, s3) + v4, v5, v6, v7 = _add_and_permute_state_simd256(v4, v5, v6, v7, s0, s1, s2, s7) + v8, v9, v10, v11 = _add_and_permute_state_simd256(v8, v9, v10, v11, s0, s1, s2, s11) + v12, v13, v14, v15 = _add_and_permute_state_simd256(v12, v13, v14, v15, s0, s1, s2, s15) + + #no_bounds_check { + if src != nil { + v0, v1, v2, v3 = _xor_simd256(src_v, v0, v1, v2, v3) + v4, v5, v6, v7 = _xor_simd256(src_v[4:], v4, v5, v6, v7) + v8, v9, v10, v11 = _xor_simd256(src_v[8:], v8, v9, v10, v11) + v12, v13, v14, v15 = _xor_simd256(src_v[12:], v12, v13, v14, v15) + src_v = src_v[16:] + } + + _store_simd256(dst_v, v0, v1, v2, v3) + _store_simd256(dst_v[4:], v4, v5, v6, v7) + _store_simd256(dst_v[8:], v8, v9, v10, v11) + _store_simd256(dst_v[12:], v12, v13, v14, v15) + dst_v = dst_v[16:] + } + + s3 = transmute(simd.u32x8)simd.add(transmute(simd.u64x4)s15, _VEC_TWO) + } + + + // 2 (or 1) block at a time. + for ; n > 0; n = n - 2 { + v0, v1, v2, v3 := s0, s1, s2, s3 + + for i := _chacha20.ROUNDS; i > 0; i = i - 2 { + v0, v1, v2, v3 = _dq_round_simd256(v0, v1, v2, v3) + } + v0, v1, v2, v3 = _add_and_permute_state_simd256(v0, v1, v2, v3, s0, s1, s2, s3) + + if n == 1 { + // Note: No need to advance src_v, dst_v, or increment the counter + // since this is guaranteed to be the final block. + #no_bounds_check { + if src != nil { + v0, v1 = _xor_simd256_x1(src_v, v0, v1) + } + + _store_simd256_x1(dst_v, v0, v1) + } + break + } + + #no_bounds_check { + if src != nil { + v0, v1, v2, v3 = _xor_simd256(src_v, v0, v1, v2, v3) + src_v = src_v[4:] + } + + _store_simd256(dst_v, v0, v1, v2, v3) + dst_v = dst_v[4:] + } + + s3 = transmute(simd.u32x8)simd.add(transmute(simd.u64x4)s3, _VEC_TWO) + } + + // Write back the counter. Doing it this way, saves having to + // pull out the correct counter value from s3. + new_ctr := ((u64(ctx._s[13]) << 32) | u64(ctx._s[12])) + u64(nr_blocks) + ctx._s[12] = u32(new_ctr) + ctx._s[13] = u32(new_ctr >> 32) +} + +@(enable_target_feature = "sse2,ssse3,avx") +hchacha20 :: proc "contextless" (dst, key, iv: []byte) { + // We can just enable AVX and call the simd128 code as going + // wider has 0 performance benefit, but VEX encoded instructions + // is nice. + #force_inline chacha_simd128.hchacha20(dst, key, iv) +} \ No newline at end of file diff --git a/core/crypto/_chacha20/simd256/chacha20_simd256_stub.odin b/core/crypto/_chacha20/simd256/chacha20_simd256_stub.odin new file mode 100644 index 000000000..ce673b42b --- /dev/null +++ b/core/crypto/_chacha20/simd256/chacha20_simd256_stub.odin @@ -0,0 +1,17 @@ +#+build !amd64 +package chacha20_simd256 + +import "base:intrinsics" +import "core:crypto/_chacha20" + +is_performant :: proc "contextless" () -> bool { + return false +} + +stream_blocks :: proc(ctx: ^_chacha20.Context, dst, src: []byte, nr_blocks: int) { + panic("crypto/chacha20: simd256 implementation unsupported") +} + +hchacha20 :: proc "contextless" (dst, key, iv: []byte) { + intrinsics.trap() +} \ No newline at end of file diff --git a/core/crypto/_edwards25519/edwards25519.odin b/core/crypto/_edwards25519/edwards25519.odin new file mode 100644 index 000000000..6495f7a3a --- /dev/null +++ b/core/crypto/_edwards25519/edwards25519.odin @@ -0,0 +1,428 @@ +package _edwards25519 + +/* +This implements the edwards25519 composite-order group, primarily for +the purpose of implementing X25519, Ed25519, and ristretto255. Use of +this package for other purposes is NOT RECOMMENDED. + +See: +- https://eprint.iacr.org/2011/368.pdf +- https://datatracker.ietf.org/doc/html/rfc8032 +- https://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html +*/ + +import "base:intrinsics" +import "core:crypto" +import field "core:crypto/_fiat/field_curve25519" +import "core:mem" + +// Group_Element is an edwards25519 group element, as extended homogenous +// coordinates, which represents the affine point `(x, y)` as `(X, Y, Z, T)`, +// with the relations `x = X/Z`, `y = Y/Z`, and `x * y = T/Z`. +// +// d = -121665/121666 = 37095705934669439343138083508754565189542113879843219016388785533085940283555 +// a = -1 +// +// Notes: +// - There is considerable scope for optimization, however that +// will not change the external API, and this is simple and reasonably +// performant. +// - The API delibarately makes it hard to create arbitrary group +// elements that are not on the curve. +// - The group element decoding routine takes the opinionated stance of +// rejecting non-canonical encodings. + +FE_D := field.Tight_Field_Element { + 929955233495203, + 466365720129213, + 1662059464998953, + 2033849074728123, + 1442794654840575, +} +@(private) +FE_A := field.Tight_Field_Element { + 2251799813685228, + 2251799813685247, + 2251799813685247, + 2251799813685247, + 2251799813685247, +} +@(private) +FE_D2 := field.Tight_Field_Element { + 1859910466990425, + 932731440258426, + 1072319116312658, + 1815898335770999, + 633789495995903, +} +@(private) +GE_BASEPOINT := Group_Element { + field.Tight_Field_Element { + 1738742601995546, + 1146398526822698, + 2070867633025821, + 562264141797630, + 587772402128613, + }, + field.Tight_Field_Element { + 1801439850948184, + 1351079888211148, + 450359962737049, + 900719925474099, + 1801439850948198, + }, + field.Tight_Field_Element{1, 0, 0, 0, 0}, + field.Tight_Field_Element { + 1841354044333475, + 16398895984059, + 755974180946558, + 900171276175154, + 1821297809914039, + }, +} +GE_IDENTITY := Group_Element { + field.Tight_Field_Element{0, 0, 0, 0, 0}, + field.Tight_Field_Element{1, 0, 0, 0, 0}, + field.Tight_Field_Element{1, 0, 0, 0, 0}, + field.Tight_Field_Element{0, 0, 0, 0, 0}, +} + +Group_Element :: struct { + x: field.Tight_Field_Element, + y: field.Tight_Field_Element, + z: field.Tight_Field_Element, + t: field.Tight_Field_Element, +} + +ge_clear :: proc "contextless" (ge: ^Group_Element) { + mem.zero_explicit(ge, size_of(Group_Element)) +} + +ge_set :: proc "contextless" (ge, a: ^Group_Element) { + field.fe_set(&ge.x, &a.x) + field.fe_set(&ge.y, &a.y) + field.fe_set(&ge.z, &a.z) + field.fe_set(&ge.t, &a.t) +} + +@(require_results) +ge_set_bytes :: proc "contextless" (ge: ^Group_Element, b: []byte) -> bool { + if len(b) != 32 { + intrinsics.trap() + } + b_ := (^[32]byte)(raw_data(b)) + + // Do the work in a scratch element, so that ge is unchanged on + // failure. + tmp: Group_Element = --- + defer ge_clear(&tmp) + field.fe_one(&tmp.z) // Z = 1 + + // The encoding is the y-coordinate, with the x-coordinate polarity + // (odd/even) encoded in the MSB. + field.fe_from_bytes(&tmp.y, b_) // ignores high bit + + // Recover the candidate x-coordinate via the curve equation: + // x^2 = (y^2 - 1) / (d * y^2 + 1) (mod p) + + fe_tmp := &tmp.t // Use this to store intermediaries. + fe_one := &tmp.z + + // x = num = y^2 - 1 + field.fe_carry_square(fe_tmp, field.fe_relax_cast(&tmp.y)) // fe_tmp = y^2 + field.fe_carry_sub(&tmp.x, fe_tmp, fe_one) + + // den = d * y^2 + 1 + field.fe_carry_mul(fe_tmp, field.fe_relax_cast(fe_tmp), field.fe_relax_cast(&FE_D)) + field.fe_carry_add(fe_tmp, fe_tmp, fe_one) + + // x = invsqrt(den/num) + is_square := field.fe_carry_sqrt_ratio_m1( + &tmp.x, + field.fe_relax_cast(&tmp.x), + field.fe_relax_cast(fe_tmp), + ) + if is_square == 0 { + return false + } + + // Pick the right x-coordinate. + field.fe_cond_negate(&tmp.x, &tmp.x, int(b[31] >> 7)) + + // t = x * y + field.fe_carry_mul(&tmp.t, field.fe_relax_cast(&tmp.x), field.fe_relax_cast(&tmp.y)) + + // Reject non-canonical encodings of ge. + buf: [32]byte = --- + field.fe_to_bytes(&buf, &tmp.y) + buf[31] |= byte(field.fe_is_negative(&tmp.x)) << 7 + is_canonical := crypto.compare_constant_time(b, buf[:]) + + ge_cond_assign(ge, &tmp, is_canonical) + + mem.zero_explicit(&buf, size_of(buf)) + + return is_canonical == 1 +} + +ge_bytes :: proc "contextless" (ge: ^Group_Element, dst: []byte) { + if len(dst) != 32 { + intrinsics.trap() + } + dst_ := (^[32]byte)(raw_data(dst)) + + // Convert the element to affine (x, y) representation. + x, y, z_inv: field.Tight_Field_Element = ---, ---, --- + field.fe_carry_inv(&z_inv, field.fe_relax_cast(&ge.z)) + field.fe_carry_mul(&x, field.fe_relax_cast(&ge.x), field.fe_relax_cast(&z_inv)) + field.fe_carry_mul(&y, field.fe_relax_cast(&ge.y), field.fe_relax_cast(&z_inv)) + + // Encode the y-coordinate. + field.fe_to_bytes(dst_, &y) + + // Copy the least significant bit of the x-coordinate to the most + // significant bit of the encoded y-coordinate. + dst_[31] |= byte((x[0] & 1) << 7) + + field.fe_clear_vec([]^field.Tight_Field_Element{&x, &y, &z_inv}) +} + +ge_identity :: proc "contextless" (ge: ^Group_Element) { + field.fe_zero(&ge.x) + field.fe_one(&ge.y) + field.fe_one(&ge.z) + field.fe_zero(&ge.t) +} + +ge_generator :: proc "contextless" (ge: ^Group_Element) { + ge_set(ge, &GE_BASEPOINT) +} + +@(private) +Addend_Group_Element :: struct { + y2_minus_x2: field.Loose_Field_Element, // t1 + y2_plus_x2: field.Loose_Field_Element, // t3 + k_times_t2: field.Tight_Field_Element, // t4 + two_times_z2: field.Loose_Field_Element, // t5 +} + +@(private) +ge_addend_set :: proc "contextless" (ge_a: ^Addend_Group_Element, ge: ^Group_Element) { + field.fe_sub(&ge_a.y2_minus_x2, &ge.y, &ge.x) + field.fe_add(&ge_a.y2_plus_x2, &ge.y, &ge.x) + field.fe_carry_mul(&ge_a.k_times_t2, field.fe_relax_cast(&FE_D2), field.fe_relax_cast(&ge.t)) + field.fe_add(&ge_a.two_times_z2, &ge.z, &ge.z) +} + +@(private) +ge_addend_conditional_assign :: proc "contextless" (ge_a, a: ^Addend_Group_Element, ctrl: int) { + field.fe_cond_select(&ge_a.y2_minus_x2, &ge_a.y2_minus_x2, &a.y2_minus_x2, ctrl) + field.fe_cond_select(&ge_a.y2_plus_x2, &ge_a.y2_plus_x2, &a.y2_plus_x2, ctrl) + field.fe_cond_select(&ge_a.k_times_t2, &ge_a.k_times_t2, &a.k_times_t2, ctrl) + field.fe_cond_select(&ge_a.two_times_z2, &ge_a.two_times_z2, &a.two_times_z2, ctrl) +} + +@(private) +Add_Scratch :: struct { + A, B, C, D: field.Tight_Field_Element, + E, F, G, H: field.Loose_Field_Element, + t0, t2: field.Loose_Field_Element, +} + +ge_add :: proc "contextless" (ge, a, b: ^Group_Element) { + b_: Addend_Group_Element = --- + ge_addend_set(&b_, b) + + scratch: Add_Scratch = --- + ge_add_addend(ge, a, &b_, &scratch) + + mem.zero_explicit(&b_, size_of(Addend_Group_Element)) + mem.zero_explicit(&scratch, size_of(Add_Scratch)) +} + +@(private) +ge_add_addend :: proc "contextless" ( + ge, a: ^Group_Element, + b: ^Addend_Group_Element, + scratch: ^Add_Scratch, +) { + // https://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#addition-add-2008-hwcd-3 + // Assumptions: k=2*d. + // + // t0 = Y1-X1 + // t1 = Y2-X2 + // A = t0*t1 + // t2 = Y1+X1 + // t3 = Y2+X2 + // B = t2*t3 + // t4 = k*T2 + // C = T1*t4 + // t5 = 2*Z2 + // D = Z1*t5 + // E = B-A + // F = D-C + // G = D+C + // H = B+A + // X3 = E*F + // Y3 = G*H + // T3 = E*H + // Z3 = F*G + // + // In order to make the scalar multiply faster, the addend is provided + // as a `Addend_Group_Element` with t1, t3, t4, and t5 precomputed, as + // it is trivially obvious that those are the only values used by the + // formula that are directly dependent on `b`, and are only dependent + // on `b` and constants. This saves 1 sub, 2 adds, and 1 multiply, + // each time the intermediate representation can be reused. + + A, B, C, D := &scratch.A, &scratch.B, &scratch.C, &scratch.D + E, F, G, H := &scratch.E, &scratch.F, &scratch.G, &scratch.H + t0, t2 := &scratch.t0, &scratch.t2 + + field.fe_sub(t0, &a.y, &a.x) + t1 := &b.y2_minus_x2 + field.fe_carry_mul(A, t0, t1) + field.fe_add(t2, &a.y, &a.x) + t3 := &b.y2_plus_x2 + field.fe_carry_mul(B, t2, t3) + t4 := &b.k_times_t2 + field.fe_carry_mul(C, field.fe_relax_cast(&a.t), field.fe_relax_cast(t4)) + t5 := &b.two_times_z2 + field.fe_carry_mul(D, field.fe_relax_cast(&a.z), t5) + field.fe_sub(E, B, A) + field.fe_sub(F, D, C) + field.fe_add(G, D, C) + field.fe_add(H, B, A) + field.fe_carry_mul(&ge.x, E, F) + field.fe_carry_mul(&ge.y, G, H) + field.fe_carry_mul(&ge.t, E, H) + field.fe_carry_mul(&ge.z, F, G) +} + +@(private) +Double_Scratch :: struct { + A, B, C, D, G: field.Tight_Field_Element, + t0, t2, t3: field.Tight_Field_Element, + E, F, H: field.Loose_Field_Element, + t1: field.Loose_Field_Element, +} + +ge_double :: proc "contextless" (ge, a: ^Group_Element, scratch: ^Double_Scratch = nil) { + // https://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#doubling-dbl-2008-hwcd + // + // A = X1^2 + // B = Y1^2 + // t0 = Z1^2 + // C = 2*t0 + // D = a*A + // t1 = X1+Y1 + // t2 = t1^2 + // t3 = t2-A + // E = t3-B + // G = D+B + // F = G-C + // H = D-B + // X3 = E*F + // Y3 = G*H + // T3 = E*H + // Z3 = F*G + + sanitize, scratch := scratch == nil, scratch + if sanitize { + tmp: Double_Scratch = --- + scratch = &tmp + } + + A, B, C, D, G := &scratch.A, &scratch.B, &scratch.C, &scratch.D, &scratch.G + t0, t2, t3 := &scratch.t0, &scratch.t2, &scratch.t3 + E, F, H := &scratch.E, &scratch.F, &scratch.H + t1 := &scratch.t1 + + field.fe_carry_square(A, field.fe_relax_cast(&a.x)) + field.fe_carry_square(B, field.fe_relax_cast(&a.y)) + field.fe_carry_square(t0, field.fe_relax_cast(&a.z)) + field.fe_carry_add(C, t0, t0) + field.fe_carry_mul(D, field.fe_relax_cast(&FE_A), field.fe_relax_cast(A)) + field.fe_add(t1, &a.x, &a.y) + field.fe_carry_square(t2, t1) + field.fe_carry_sub(t3, t2, A) + field.fe_sub(E, t3, B) + field.fe_carry_add(G, D, B) + field.fe_sub(F, G, C) + field.fe_sub(H, D, B) + G_ := field.fe_relax_cast(G) + field.fe_carry_mul(&ge.x, E, F) + field.fe_carry_mul(&ge.y, G_, H) + field.fe_carry_mul(&ge.t, E, H) + field.fe_carry_mul(&ge.z, F, G_) + + if sanitize { + mem.zero_explicit(scratch, size_of(Double_Scratch)) + } +} + +ge_negate :: proc "contextless" (ge, a: ^Group_Element) { + field.fe_carry_opp(&ge.x, &a.x) + field.fe_set(&ge.y, &a.y) + field.fe_set(&ge.z, &a.z) + field.fe_carry_opp(&ge.t, &a.t) +} + +ge_cond_negate :: proc "contextless" (ge, a: ^Group_Element, ctrl: int) { + tmp: Group_Element = --- + ge_negate(&tmp, a) + ge_cond_assign(ge, &tmp, ctrl) + + ge_clear(&tmp) +} + +ge_cond_assign :: proc "contextless" (ge, a: ^Group_Element, ctrl: int) { + field.fe_cond_assign(&ge.x, &a.x, ctrl) + field.fe_cond_assign(&ge.y, &a.y, ctrl) + field.fe_cond_assign(&ge.z, &a.z, ctrl) + field.fe_cond_assign(&ge.t, &a.t, ctrl) +} + +ge_cond_select :: proc "contextless" (ge, a, b: ^Group_Element, ctrl: int) { + field.fe_cond_select(&ge.x, &a.x, &b.x, ctrl) + field.fe_cond_select(&ge.y, &a.y, &b.y, ctrl) + field.fe_cond_select(&ge.z, &a.z, &b.z, ctrl) + field.fe_cond_select(&ge.t, &a.t, &b.t, ctrl) +} + +@(require_results) +ge_equal :: proc "contextless" (a, b: ^Group_Element) -> int { + // (x, y) ?= (x', y') -> (X/Z, Y/Z) ?= (X'/Z', Y'/Z') + // X/Z ?= X'/Z', Y/Z ?= Y'/Z' -> X*Z' ?= X'*Z, Y*Z' ?= Y'*Z + ax_bz, bx_az, ay_bz, by_az: field.Tight_Field_Element = ---, ---, ---, --- + field.fe_carry_mul(&ax_bz, field.fe_relax_cast(&a.x), field.fe_relax_cast(&b.z)) + field.fe_carry_mul(&bx_az, field.fe_relax_cast(&b.x), field.fe_relax_cast(&a.z)) + field.fe_carry_mul(&ay_bz, field.fe_relax_cast(&a.y), field.fe_relax_cast(&b.z)) + field.fe_carry_mul(&by_az, field.fe_relax_cast(&b.y), field.fe_relax_cast(&a.z)) + + ret := field.fe_equal(&ax_bz, &bx_az) & field.fe_equal(&ay_bz, &by_az) + + field.fe_clear_vec([]^field.Tight_Field_Element{&ax_bz, &ay_bz, &bx_az, &by_az}) + + return ret +} + +@(require_results) +ge_is_small_order :: proc "contextless" (ge: ^Group_Element) -> bool { + tmp: Group_Element = --- + ge_double(&tmp, ge) + ge_double(&tmp, &tmp) + ge_double(&tmp, &tmp) + return ge_equal(&tmp, &GE_IDENTITY) == 1 +} + +@(require_results) +ge_in_prime_order_subgroup_vartime :: proc "contextless" (ge: ^Group_Element) -> bool { + // This is currently *very* expensive. The faster method would be + // something like (https://eprint.iacr.org/2022/1164.pdf), however + // that is a ~50% speedup, and a lot of added complexity for something + // that is better solved by "just use ristretto255". + tmp: Group_Element = --- + _ge_scalarmult(&tmp, ge, &SC_ELL, true) + return ge_equal(&tmp, &GE_IDENTITY) == 1 +} diff --git a/core/crypto/_edwards25519/edwards25519_scalar.odin b/core/crypto/_edwards25519/edwards25519_scalar.odin new file mode 100644 index 000000000..e21fa3755 --- /dev/null +++ b/core/crypto/_edwards25519/edwards25519_scalar.odin @@ -0,0 +1,61 @@ +package _edwards25519 + +import "base:intrinsics" +import field "core:crypto/_fiat/field_scalar25519" +import "core:mem" + +Scalar :: field.Montgomery_Domain_Field_Element + +// WARNING: This is non-canonical and only to be used when checking if +// a group element is on the prime-order subgroup. +@(private) +SC_ELL := field.Non_Montgomery_Domain_Field_Element { + field.ELL[0], + field.ELL[1], + field.ELL[2], + field.ELL[3], +} + +sc_set_u64 :: proc "contextless" (sc: ^Scalar, i: u64) { + tmp := field.Non_Montgomery_Domain_Field_Element{i, 0, 0, 0} + field.fe_to_montgomery(sc, &tmp) + + mem.zero_explicit(&tmp, size_of(tmp)) +} + +@(require_results) +sc_set_bytes :: proc "contextless" (sc: ^Scalar, b: []byte) -> bool { + if len(b) != 32 { + intrinsics.trap() + } + b_ := (^[32]byte)(raw_data(b)) + return field.fe_from_bytes(sc, b_) +} + +sc_set_bytes_rfc8032 :: proc "contextless" (sc: ^Scalar, b: []byte) { + if len(b) != 32 { + intrinsics.trap() + } + b_ := (^[32]byte)(raw_data(b)) + field.fe_from_bytes_rfc8032(sc, b_) +} + +sc_clear :: proc "contextless" (sc: ^Scalar) { + mem.zero_explicit(sc, size_of(Scalar)) +} + +sc_set :: field.fe_set +sc_set_bytes_wide :: field.fe_from_bytes_wide +sc_bytes :: field.fe_to_bytes + +sc_zero :: field.fe_zero +sc_one :: field.fe_one + +sc_add :: field.fe_add +sc_sub :: field.fe_sub +sc_negate :: field.fe_opp +sc_mul :: field.fe_mul +sc_square :: field.fe_square + +sc_cond_assign :: field.fe_cond_assign +sc_equal :: field.fe_equal diff --git a/core/crypto/_edwards25519/edwards25519_scalar_mul.odin b/core/crypto/_edwards25519/edwards25519_scalar_mul.odin new file mode 100644 index 000000000..757a51257 --- /dev/null +++ b/core/crypto/_edwards25519/edwards25519_scalar_mul.odin @@ -0,0 +1,288 @@ +package _edwards25519 + +import field "core:crypto/_fiat/field_scalar25519" +import "core:math/bits" +import "core:mem" + +// GE_BASEPOINT_TABLE is 1 * G, ... 15 * G, in precomputed format. +// +// Note: When generating, the values were reduced to Tight_Field_Element +// ranges, even though that is not required. +@(private) +GE_BASEPOINT_TABLE := Multiply_Table { + { + {62697248952638, 204681361388450, 631292143396476, 338455783676468, 1213667448819585}, + {1288382639258501, 245678601348599, 269427782077623, 1462984067271730, 137412439391563}, + {301289933810280, 1259582250014073, 1422107436869536, 796239922652654, 1953934009299142}, + {2, 0, 0, 0, 0}, + }, + { + {1519297034332653, 1098796920435767, 1823476547744119, 808144629470969, 2110930855619772}, + {338005982828284, 1667856962156925, 100399270107451, 1604566703601691, 1950338038771369}, + {1920505767731247, 1443759578976892, 1659852098357048, 1484431291070208, 275018744912646}, + {763163817085987, 2195095074806923, 2167883174351839, 1868059999999762, 911071066608705}, + }, + { + {960627541894068, 1314966688943942, 1126875971034044, 2059608312958945, 605975666152586}, + {1714478358025626, 2209607666607510, 1600912834284834, 496072478982142, 481970031861896}, + {851735079403194, 1088965826757164, 141569479297499, 602804610059257, 2004026468601520}, + {197585529552380, 324719066578543, 564481854250498, 1173818332764578, 35452976395676}, + }, + { + {1152980410747203, 2196804280851952, 25745194962557, 1915167295473129, 1266299690309224}, + {809905889679060, 979732230071345, 1509972345538142, 188492426534402, 818965583123815}, + {997685409185036, 1451818320876327, 2126681166774509, 2000509606057528, 235432372486854}, + {887734189279642, 1460338685162044, 877378220074262, 102436391401299, 153369156847490}, + }, + { + {2056621900836770, 1821657694132497, 1627986892909426, 1163363868678833, 1108873376459226}, + {1187697490593623, 1066539945237335, 885654531892000, 1357534489491782, 359370291392448}, + {1509033452137525, 1305318174298508, 613642471748944, 1987256352550234, 1044283663101541}, + {220105720697037, 387661783287620, 328296827867762, 360035589590664, 795213236824054}, + }, + { + {1820794733038396, 1612235121681074, 757405923441402, 1094031020892801, 231025333128907}, + {1639067873254194, 1484176557946322, 300800382144789, 1329915446659183, 1211704578730455}, + {641900794791527, 1711751746971612, 179044712319955, 576455585963824, 1852617592509865}, + {743549047192397, 685091042550147, 1952415336873496, 1965124675654685, 513364998442917}, + }, + { + {1004557076870448, 1762911374844520, 1330807633622723, 384072910939787, 953849032243810}, + {2178275058221458, 257933183722891, 376684351537894, 2010189102001786, 1981824297484148}, + {1332915663881114, 1286540505502549, 1741691283561518, 977214932156314, 1764059494778091}, + {429702949064027, 1368332611650677, 2019867176450999, 2212258376161746, 526160996742554}, + }, + { + {2098932988258576, 2203688382075948, 2120400160059479, 1748488020948146, 1203264167282624}, + {677131386735829, 1850249298025188, 672782146532031, 2144145693078904, 2088656272813787}, + {1065622343976192, 1573853211848116, 223560413590068, 333846833073379, 27832122205830}, + {1781008836504573, 917619542051793, 544322748939913, 882577394308384, 1720521246471195}, + }, + { + {660120928379860, 2081944024858618, 1878411111349191, 424587356517195, 2111317439894005}, + {1834193977811532, 1864164086863319, 797334633289424, 150410812403062, 2085177078466389}, + {1438117271371866, 783915531014482, 388731514584658, 292113935417795, 1945855002546714}, + {1678140823166658, 679103239148744, 614102761596238, 1052962498997885, 1863983323810390}, + }, + { + {1690309392496233, 1116333140326275, 1377242323631039, 717196888780674, 82724646713353}, + {1722370213432106, 74265192976253, 264239578448472, 1714909985012994, 2216984958602173}, + {2010482366920922, 1294036471886319, 566466395005815, 1631955803657320, 1751698647538458}, + {1073230604155753, 1159087041338551, 1664057985455483, 127472702826203, 1339591128522371}, + }, + { + {478053307175577, 2179515791720985, 21146535423512, 1831683844029536, 462805561553981}, + {1945267486565588, 1298536818409655, 2214511796262989, 1904981051429012, 252904800782086}, + {268945954671210, 222740425595395, 1208025911856230, 1080418823003555, 75929831922483}, + {1884784014268948, 643868448202966, 978736549726821, 46385971089796, 1296884812292320}, + }, + { + {1861159462859103, 7077532564710, 963010365896826, 1938780006785270, 766241051941647}, + {1778966986051906, 1713995999765361, 1394565822271816, 1366699246468722, 1213407027149475}, + {1978989286560907, 2135084162045594, 1951565508865477, 671788336314416, 293123929458176}, + {902608944504080, 2167765718046481, 1285718473078022, 1222562171329269, 492109027844479}, + }, + { + {1820807832746213, 1029220580458586, 1101997555432203, 1039081975563572, 202477981158221}, + {1866134980680205, 2222325502763386, 1830284629571201, 1046966214478970, 418381946936795}, + {1783460633291322, 1719505443254998, 1810489639976220, 877049370713018, 2187801198742619}, + {197118243000763, 305493867565736, 518814410156522, 1656246186645170, 901894734874934}, + }, + { + {225454942125915, 478410476654509, 600524586037746, 643450007230715, 1018615928259319}, + {1733330584845708, 881092297970296, 507039890129464, 496397090721598, 2230888519577628}, + {690155664737246, 1010454785646677, 753170144375012, 1651277613844874, 1622648796364156}, + {1321310321891618, 1089655277873603, 235891750867089, 815878279563688, 1709264240047556}, + }, + { + {805027036551342, 1387174275567452, 1156538511461704, 1465897486692171, 1208567094120903}, + {2228417017817483, 202885584970535, 2182114782271881, 2077405042592934, 1029684358182774}, + {460447547653983, 627817697755692, 524899434670834, 1228019344939427, 740684787777653}, + {849757462467675, 447476306919899, 422618957298818, 302134659227815, 675831828440895}, + }, +} + +ge_scalarmult :: proc "contextless" (ge, p: ^Group_Element, sc: ^Scalar) { + tmp: field.Non_Montgomery_Domain_Field_Element + field.fe_from_montgomery(&tmp, sc) + + _ge_scalarmult(ge, p, &tmp) + + mem.zero_explicit(&tmp, size_of(tmp)) +} + +ge_scalarmult_basepoint :: proc "contextless" (ge: ^Group_Element, sc: ^Scalar) { + // Something like the comb method from "Fast and compact elliptic-curve + // cryptography" Section 3.3, would be more performant, but more + // complex. + // + // - https://eprint.iacr.org/2012/309 + ge_scalarmult(ge, &GE_BASEPOINT, sc) +} + +ge_scalarmult_vartime :: proc "contextless" (ge, p: ^Group_Element, sc: ^Scalar) { + tmp: field.Non_Montgomery_Domain_Field_Element + field.fe_from_montgomery(&tmp, sc) + + _ge_scalarmult(ge, p, &tmp, true) +} + +ge_double_scalarmult_basepoint_vartime :: proc "contextless" ( + ge: ^Group_Element, + a: ^Scalar, + A: ^Group_Element, + b: ^Scalar, +) { + // Strauss-Shamir, commonly referred to as the "Shamir trick", + // saves half the doublings, relative to doing this the naive way. + // + // ABGLSV-Pornin (https://eprint.iacr.org/2020/454) is faster, + // but significantly more complex, and has incompatibilities with + // mixed-order group elements. + + tmp_add: Add_Scratch = --- + tmp_addend: Addend_Group_Element = --- + tmp_dbl: Double_Scratch = --- + tmp: Group_Element = --- + + A_tbl: Multiply_Table = --- + mul_tbl_set(&A_tbl, A, &tmp_add) + + sc_a, sc_b: field.Non_Montgomery_Domain_Field_Element + field.fe_from_montgomery(&sc_a, a) + field.fe_from_montgomery(&sc_b, b) + + ge_identity(&tmp) + for i := 31; i >= 0; i = i - 1 { + limb := i / 8 + shift := uint(i & 7) * 8 + + limb_byte_a := sc_a[limb] >> shift + limb_byte_b := sc_b[limb] >> shift + + hi_a, lo_a := (limb_byte_a >> 4) & 0x0f, limb_byte_a & 0x0f + hi_b, lo_b := (limb_byte_b >> 4) & 0x0f, limb_byte_b & 0x0f + + if i != 31 { + ge_double(&tmp, &tmp, &tmp_dbl) + ge_double(&tmp, &tmp, &tmp_dbl) + ge_double(&tmp, &tmp, &tmp_dbl) + ge_double(&tmp, &tmp, &tmp_dbl) + } + mul_tbl_add(&tmp, &A_tbl, hi_a, &tmp_add, &tmp_addend, true) + mul_tbl_add(&tmp, &GE_BASEPOINT_TABLE, hi_b, &tmp_add, &tmp_addend, true) + + ge_double(&tmp, &tmp, &tmp_dbl) + ge_double(&tmp, &tmp, &tmp_dbl) + ge_double(&tmp, &tmp, &tmp_dbl) + ge_double(&tmp, &tmp, &tmp_dbl) + mul_tbl_add(&tmp, &A_tbl, lo_a, &tmp_add, &tmp_addend, true) + mul_tbl_add(&tmp, &GE_BASEPOINT_TABLE, lo_b, &tmp_add, &tmp_addend, true) + } + + ge_set(ge, &tmp) +} + +@(private) +_ge_scalarmult :: proc "contextless" ( + ge, p: ^Group_Element, + sc: ^field.Non_Montgomery_Domain_Field_Element, + unsafe_is_vartime := false, +) { + // Do the simplest possible thing that works and provides adequate, + // performance, which is windowed add-then-multiply. + + tmp_add: Add_Scratch = --- + tmp_addend: Addend_Group_Element = --- + tmp_dbl: Double_Scratch = --- + tmp: Group_Element = --- + + p_tbl: Multiply_Table = --- + mul_tbl_set(&p_tbl, p, &tmp_add) + + ge_identity(&tmp) + for i := 31; i >= 0; i = i - 1 { + limb := i / 8 + shift := uint(i & 7) * 8 + limb_byte := sc[limb] >> shift + + hi, lo := (limb_byte >> 4) & 0x0f, limb_byte & 0x0f + + if i != 31 { + ge_double(&tmp, &tmp, &tmp_dbl) + ge_double(&tmp, &tmp, &tmp_dbl) + ge_double(&tmp, &tmp, &tmp_dbl) + ge_double(&tmp, &tmp, &tmp_dbl) + } + mul_tbl_add(&tmp, &p_tbl, hi, &tmp_add, &tmp_addend, unsafe_is_vartime) + + ge_double(&tmp, &tmp, &tmp_dbl) + ge_double(&tmp, &tmp, &tmp_dbl) + ge_double(&tmp, &tmp, &tmp_dbl) + ge_double(&tmp, &tmp, &tmp_dbl) + mul_tbl_add(&tmp, &p_tbl, lo, &tmp_add, &tmp_addend, unsafe_is_vartime) + } + + ge_set(ge, &tmp) + + if !unsafe_is_vartime { + ge_clear(&tmp) + mem.zero_explicit(&tmp_add, size_of(Add_Scratch)) + mem.zero_explicit(&tmp_addend, size_of(Addend_Group_Element)) + mem.zero_explicit(&tmp_dbl, size_of(Double_Scratch)) + } +} + +@(private) +Multiply_Table :: [15]Addend_Group_Element // 0 = inf, which is implicit. + +@(private) +mul_tbl_set :: proc "contextless" ( + tbl: ^Multiply_Table, + ge: ^Group_Element, + tmp_add: ^Add_Scratch, +) { + tmp: Group_Element = --- + ge_set(&tmp, ge) + + ge_addend_set(&tbl[0], ge) + for i := 1; i < 15; i = i + 1 { + ge_add_addend(&tmp, &tmp, &tbl[0], tmp_add) + ge_addend_set(&tbl[i], &tmp) + } + + ge_clear(&tmp) +} + +@(private) +mul_tbl_add :: proc "contextless" ( + ge: ^Group_Element, + tbl: ^Multiply_Table, + idx: u64, + tmp_add: ^Add_Scratch, + tmp_addend: ^Addend_Group_Element, + unsafe_is_vartime: bool, +) { + // Variable time lookup, with the addition omitted entirely if idx == 0. + if unsafe_is_vartime { + // Skip adding the point at infinity. + if idx != 0 { + ge_add_addend(ge, ge, &tbl[idx - 1], tmp_add) + } + return + } + + // Constant time lookup. + tmp_addend^ = { + // Point at infinity (0, 1, 1, 0) in precomputed form + {1, 0, 0, 0, 0}, // y - x + {1, 0, 0, 0, 0}, // y + x + {0, 0, 0, 0, 0}, // t * 2d + {2, 0, 0, 0, 0}, // z * 2 + } + for i := u64(1); i < 16; i = i + 1 { + _, ctrl := bits.sub_u64(0, (i ~ idx), 0) + ge_addend_conditional_assign(tmp_addend, &tbl[i - 1], int(~ctrl) & 1) + } + ge_add_addend(ge, ge, tmp_addend, tmp_add) +} diff --git a/core/crypto/_fiat/fiat.odin b/core/crypto/_fiat/fiat.odin index f0551722f..cc73c6927 100644 --- a/core/crypto/_fiat/fiat.odin +++ b/core/crypto/_fiat/fiat.odin @@ -9,7 +9,7 @@ package fiat u1 :: distinct u8 i1 :: distinct i8 -@(optimization_mode="none") +@(optimization_mode = "none") cmovznz_u64 :: proc "contextless" (arg1: u1, arg2, arg3: u64) -> (out1: u64) { x1 := (u64(arg1) * 0xffffffffffffffff) x2 := ((x1 & arg3) | ((~x1) & arg2)) @@ -17,7 +17,7 @@ cmovznz_u64 :: proc "contextless" (arg1: u1, arg2, arg3: u64) -> (out1: u64) { return } -@(optimization_mode="none") +@(optimization_mode = "none") cmovznz_u32 :: proc "contextless" (arg1: u1, arg2, arg3: u32) -> (out1: u32) { x1 := (u32(arg1) * 0xffffffff) x2 := ((x1 & arg3) | ((~x1) & arg2)) diff --git a/core/crypto/_fiat/field_curve25519/field.odin b/core/crypto/_fiat/field_curve25519/field.odin index faf8ae3f7..04fc87659 100644 --- a/core/crypto/_fiat/field_curve25519/field.odin +++ b/core/crypto/_fiat/field_curve25519/field.odin @@ -3,12 +3,30 @@ package field_curve25519 import "core:crypto" import "core:mem" -fe_relax_cast :: #force_inline proc "contextless" (arg1: ^Tight_Field_Element) -> ^Loose_Field_Element { - return transmute(^Loose_Field_Element)(arg1) +fe_relax_cast :: #force_inline proc "contextless" ( + arg1: ^Tight_Field_Element, +) -> ^Loose_Field_Element { + 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) +fe_tighten_cast :: #force_inline proc "contextless" ( + arg1: ^Loose_Field_Element, +) -> ^Tight_Field_Element { + return (^Tight_Field_Element)(arg1) +} + +fe_clear :: proc "contextless" ( + arg1: $T, +) where T == ^Tight_Field_Element || T == ^Loose_Field_Element { + mem.zero_explicit(arg1, size_of(arg1^)) +} + +fe_clear_vec :: proc "contextless" ( + arg1: $T, +) where T == []^Tight_Field_Element || T == []^Loose_Field_Element { + for fe in arg1 { + fe_clear(fe) + } } fe_from_bytes :: proc "contextless" (out1: ^Tight_Field_Element, arg1: ^[32]byte) { @@ -23,12 +41,25 @@ fe_from_bytes :: proc "contextless" (out1: ^Tight_Field_Element, arg1: ^[32]byte mem.zero_explicit(&tmp1, size_of(tmp1)) } +fe_is_negative :: proc "contextless" (arg1: ^Tight_Field_Element) -> int { + tmp1: [32]byte = --- + + fe_to_bytes(&tmp1, arg1) + ret := tmp1[0] & 1 + + mem.zero_explicit(&tmp1, size_of(tmp1)) + + return int(ret) +} + fe_equal :: proc "contextless" (arg1, arg2: ^Tight_Field_Element) -> int { - tmp2: [32]byte = --- + tmp1, tmp2: [32]byte = ---, --- + fe_to_bytes(&tmp1, arg1) fe_to_bytes(&tmp2, arg2) - ret := fe_equal_bytes(arg1, &tmp2) + ret := crypto.compare_constant_time(tmp1[:], tmp2[:]) + mem.zero_explicit(&tmp1, size_of(tmp1)) mem.zero_explicit(&tmp2, size_of(tmp2)) return ret @@ -46,7 +77,11 @@ fe_equal_bytes :: proc "contextless" (arg1: ^Tight_Field_Element, arg2: ^[32]byt return ret } -fe_carry_pow2k :: proc (out1: ^Tight_Field_Element, arg1: ^Loose_Field_Element, arg2: uint) { +fe_carry_pow2k :: proc "contextless" ( + out1: ^Tight_Field_Element, + arg1: ^Loose_Field_Element, + arg2: uint, +) { // Special case: `arg1^(2 * 0) = 1`, though this should never happen. if arg2 == 0 { fe_one(out1) @@ -54,27 +89,46 @@ fe_carry_pow2k :: proc (out1: ^Tight_Field_Element, arg1: ^Loose_Field_Element, } fe_carry_square(out1, arg1) - for _ in 1.. int { - // Inverse square root taken from Monocypher. +fe_carry_abs :: #force_inline proc "contextless" (out1, arg1: ^Tight_Field_Element) { + fe_cond_negate(out1, arg1, fe_is_negative(arg1)) +} +fe_carry_sqrt_ratio_m1 :: proc "contextless" ( + out1: ^Tight_Field_Element, + arg1: ^Loose_Field_Element, // u + arg2: ^Loose_Field_Element, // v +) -> int { + // SQRT_RATIO_M1(u, v) from RFC 9496 - 4.2, based on the inverse + // square root from Monocypher. + + w: Tight_Field_Element = --- + fe_carry_mul(&w, arg1, arg2) // u * v + + // r = tmp1 = u * w^((p-5)/8) tmp1, tmp2, tmp3: Tight_Field_Element = ---, ---, --- - - // t0 = x^((p-5)/8) - // Can be achieved with a simple double & add ladder, - // but it would be slower. - fe_carry_pow2k(&tmp1, arg1, 1) + fe_carry_pow2k(&tmp1, fe_relax_cast(&w), 1) fe_carry_pow2k(&tmp2, fe_relax_cast(&tmp1), 2) - fe_carry_mul(&tmp2, arg1, fe_relax_cast(&tmp2)) + fe_carry_mul(&tmp2, fe_relax_cast(&w), fe_relax_cast(&tmp2)) fe_carry_mul(&tmp1, fe_relax_cast(&tmp1), fe_relax_cast(&tmp2)) fe_carry_pow2k(&tmp1, fe_relax_cast(&tmp1), 1) fe_carry_mul(&tmp1, fe_relax_cast(&tmp2), fe_relax_cast(&tmp1)) @@ -93,46 +147,121 @@ fe_carry_invsqrt :: proc (out1: ^Tight_Field_Element, arg1: ^Loose_Field_Element fe_carry_pow2k(&tmp2, fe_relax_cast(&tmp2), 50) fe_carry_mul(&tmp1, fe_relax_cast(&tmp2), fe_relax_cast(&tmp1)) fe_carry_pow2k(&tmp1, fe_relax_cast(&tmp1), 2) - fe_carry_mul(&tmp1, fe_relax_cast(&tmp1), arg1) + fe_carry_mul(&tmp1, fe_relax_cast(&tmp1), fe_relax_cast(&w)) // w^((p-5)/8) - // quartic = x^((p-1)/4) - quartic := &tmp2 - fe_carry_square(quartic, fe_relax_cast(&tmp1)) - fe_carry_mul(quartic, fe_relax_cast(quartic), arg1) + fe_carry_mul(&tmp1, fe_relax_cast(&tmp1), arg1) // u * w^((p-5)/8) - // Serialize quartic once to save on repeated serialization/sanitization. - quartic_buf: [32]byte = --- - fe_to_bytes(&quartic_buf, quartic) - check := &tmp3 + // Serialize `check` once to save on repeated serialization. + r, check := &tmp1, &tmp2 + b: [32]byte = --- + fe_carry_square(check, fe_relax_cast(r)) + fe_carry_mul(check, fe_relax_cast(check), arg2) // check * v + fe_to_bytes(&b, check) - fe_one(check) - p1 := fe_equal_bytes(check, &quartic_buf) - fe_carry_opp(check, check) - m1 := fe_equal_bytes(check, &quartic_buf) - fe_carry_opp(check, &SQRT_M1) - ms := fe_equal_bytes(check, &quartic_buf) + u, neg_u, neg_u_i := &tmp3, &w, check + fe_carry(u, arg1) + fe_carry_opp(neg_u, u) + fe_carry_mul(neg_u_i, fe_relax_cast(neg_u), fe_relax_cast(&FE_SQRT_M1)) - // if quartic == -1 or sqrt(-1) - // then isr = x^((p-1)/4) * sqrt(-1) - // else isr = x^((p-1)/4) - fe_carry_mul(out1, fe_relax_cast(&tmp1), fe_relax_cast(&SQRT_M1)) - fe_cond_assign(out1, &tmp1, (m1|ms) ~ 1) + correct_sign_sqrt := fe_equal_bytes(u, &b) + flipped_sign_sqrt := fe_equal_bytes(neg_u, &b) + flipped_sign_sqrt_i := fe_equal_bytes(neg_u_i, &b) - mem.zero_explicit(&tmp1, size_of(tmp1)) - mem.zero_explicit(&tmp2, size_of(tmp2)) - mem.zero_explicit(&tmp3, size_of(tmp3)) - mem.zero_explicit(&quartic_buf, size_of(quartic_buf)) + r_prime := check + fe_carry_mul(r_prime, fe_relax_cast(r), fe_relax_cast(&FE_SQRT_M1)) + fe_cond_assign(r, r_prime, flipped_sign_sqrt | flipped_sign_sqrt_i) - return p1 | m1 + // Pick the non-negative square root. + fe_carry_abs(out1, r) + + fe_clear_vec([]^Tight_Field_Element{&w, &tmp1, &tmp2, &tmp3}) + mem.zero_explicit(&b, size_of(b)) + + return correct_sign_sqrt | flipped_sign_sqrt } -fe_carry_inv :: proc (out1: ^Tight_Field_Element, arg1: ^Loose_Field_Element) { +fe_carry_inv :: proc "contextless" (out1: ^Tight_Field_Element, arg1: ^Loose_Field_Element) { tmp1: Tight_Field_Element fe_carry_square(&tmp1, arg1) - _ = fe_carry_invsqrt(&tmp1, fe_relax_cast(&tmp1)) + _ = fe_carry_sqrt_ratio_m1(&tmp1, fe_relax_cast(&FE_ONE), fe_relax_cast(&tmp1)) fe_carry_square(&tmp1, fe_relax_cast(&tmp1)) fe_carry_mul(out1, fe_relax_cast(&tmp1), arg1) - mem.zero_explicit(&tmp1, size_of(tmp1)) + fe_clear(&tmp1) +} + +fe_zero :: proc "contextless" (out1: ^Tight_Field_Element) { + out1[0] = 0 + out1[1] = 0 + out1[2] = 0 + out1[3] = 0 + out1[4] = 0 +} + +fe_one :: proc "contextless" (out1: ^Tight_Field_Element) { + out1[0] = 1 + out1[1] = 0 + out1[2] = 0 + out1[3] = 0 + out1[4] = 0 +} + +fe_set :: proc "contextless" (out1, arg1: ^Tight_Field_Element) { + x1 := arg1[0] + x2 := arg1[1] + x3 := arg1[2] + x4 := arg1[3] + x5 := arg1[4] + out1[0] = x1 + out1[1] = x2 + out1[2] = x3 + out1[3] = x4 + out1[4] = x5 +} + +@(optimization_mode = "none") +fe_cond_swap :: #force_no_inline proc "contextless" (out1, out2: ^Tight_Field_Element, arg1: int) { + mask := (u64(arg1) * 0xffffffffffffffff) + x := (out1[0] ~ out2[0]) & mask + x1, y1 := out1[0] ~ x, out2[0] ~ x + x = (out1[1] ~ out2[1]) & mask + x2, y2 := out1[1] ~ x, out2[1] ~ x + x = (out1[2] ~ out2[2]) & mask + x3, y3 := out1[2] ~ x, out2[2] ~ x + x = (out1[3] ~ out2[3]) & mask + x4, y4 := out1[3] ~ x, out2[3] ~ x + x = (out1[4] ~ out2[4]) & mask + x5, y5 := out1[4] ~ x, out2[4] ~ x + out1[0], out2[0] = x1, y1 + out1[1], out2[1] = x2, y2 + out1[2], out2[2] = x3, y3 + out1[3], out2[3] = x4, y4 + out1[4], out2[4] = x5, y5 +} + +@(optimization_mode = "none") +fe_cond_select :: #force_no_inline proc "contextless" ( + out1, arg1, arg2: $T, + arg3: int, +) where T == ^Tight_Field_Element || T == ^Loose_Field_Element { + mask := (u64(arg3) * 0xffffffffffffffff) + x1 := ((mask & arg2[0]) | ((~mask) & arg1[0])) + x2 := ((mask & arg2[1]) | ((~mask) & arg1[1])) + x3 := ((mask & arg2[2]) | ((~mask) & arg1[2])) + x4 := ((mask & arg2[3]) | ((~mask) & arg1[3])) + x5 := ((mask & arg2[4]) | ((~mask) & arg1[4])) + out1[0] = x1 + out1[1] = x2 + out1[2] = x3 + out1[3] = x4 + out1[4] = x5 +} + +fe_cond_negate :: proc "contextless" (out1, arg1: ^Tight_Field_Element, ctrl: int) { + tmp1: Tight_Field_Element = --- + fe_carry_opp(&tmp1, arg1) + fe_cond_select(out1, arg1, &tmp1, ctrl) + + fe_clear(&tmp1) } diff --git a/core/crypto/_fiat/field_curve25519/field51.odin b/core/crypto/_fiat/field_curve25519/field51.odin index 0be94eb51..d039bd411 100644 --- a/core/crypto/_fiat/field_curve25519/field51.odin +++ b/core/crypto/_fiat/field_curve25519/field51.odin @@ -30,8 +30,6 @@ package field_curve25519 // // While the base implementation is provably correct, this implementation // makes no such claims as the port and optimizations were done by hand. -// At some point, it may be worth adding support to fiat-crypto for -// generating Odin output. // // TODO: // * When fiat-crypto supports it, using a saturated 64-bit limbs @@ -44,7 +42,10 @@ import "core:math/bits" Loose_Field_Element :: distinct [5]u64 Tight_Field_Element :: distinct [5]u64 -SQRT_M1 := Tight_Field_Element{ +FE_ZERO := Tight_Field_Element{0, 0, 0, 0, 0} +FE_ONE := Tight_Field_Element{1, 0, 0, 0, 0} + +FE_SQRT_M1 := Tight_Field_Element { 1718705420411056, 234908883556509, 2233514472574048, @@ -52,7 +53,13 @@ SQRT_M1 := Tight_Field_Element{ 765476049583133, } -_addcarryx_u51 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3: u64) -> (out1: u64, out2: fiat.u1) { +_addcarryx_u51 :: #force_inline proc "contextless" ( + arg1: fiat.u1, + arg2, arg3: u64, +) -> ( + out1: u64, + out2: fiat.u1, +) { x1 := ((u64(arg1) + arg2) + arg3) x2 := (x1 & 0x7ffffffffffff) x3 := fiat.u1((x1 >> 51)) @@ -61,7 +68,13 @@ _addcarryx_u51 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3: u return } -_subborrowx_u51 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3: u64) -> (out1: u64, out2: fiat.u1) { +_subborrowx_u51 :: #force_inline proc "contextless" ( + arg1: fiat.u1, + arg2, arg3: u64, +) -> ( + out1: u64, + out2: fiat.u1, +) { x1 := ((i64(arg2) - i64(arg1)) - i64(arg3)) x2 := fiat.i1((x1 >> 51)) x3 := (u64(x1) & 0x7ffffffffffff) @@ -70,7 +83,7 @@ _subborrowx_u51 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3: return } -fe_carry_mul :: proc (out1: ^Tight_Field_Element, arg1, arg2: ^Loose_Field_Element) { +fe_carry_mul :: proc "contextless" (out1: ^Tight_Field_Element, arg1, arg2: ^Loose_Field_Element) { x2, x1 := bits.mul_u64(arg1[4], (arg2[4] * 0x13)) x4, x3 := bits.mul_u64(arg1[4], (arg2[3] * 0x13)) x6, x5 := bits.mul_u64(arg1[4], (arg2[2] * 0x13)) @@ -169,7 +182,7 @@ fe_carry_mul :: proc (out1: ^Tight_Field_Element, arg1, arg2: ^Loose_Field_Eleme out1[4] = x152 } -fe_carry_square :: proc (out1: ^Tight_Field_Element, arg1: ^Loose_Field_Element) { +fe_carry_square :: proc "contextless" (out1: ^Tight_Field_Element, arg1: ^Loose_Field_Element) { x1 := (arg1[4] * 0x13) x2 := (x1 * 0x2) x3 := (arg1[4] * 0x2) @@ -305,8 +318,11 @@ fe_opp :: proc "contextless" (out1: ^Loose_Field_Element, arg1: ^Tight_Field_Ele out1[4] = x5 } -@(optimization_mode="none") -fe_cond_assign :: #force_no_inline proc "contextless" (out1, arg1: ^Tight_Field_Element, arg2: int) { +@(optimization_mode = "none") +fe_cond_assign :: #force_no_inline proc "contextless" ( + out1, arg1: ^Tight_Field_Element, + arg2: int, +) { x1 := fiat.cmovznz_u64(fiat.u1(arg2), out1[0], arg1[0]) x2 := fiat.cmovznz_u64(fiat.u1(arg2), out1[1], arg1[1]) x3 := fiat.cmovznz_u64(fiat.u1(arg2), out1[2], arg1[2]) @@ -527,7 +543,10 @@ fe_relax :: proc "contextless" (out1: ^Loose_Field_Element, arg1: ^Tight_Field_E out1[4] = x5 } -fe_carry_scmul_121666 :: proc (out1: ^Tight_Field_Element, arg1: ^Loose_Field_Element) { +fe_carry_scmul_121666 :: proc "contextless" ( + out1: ^Tight_Field_Element, + arg1: ^Loose_Field_Element, +) { x2, x1 := bits.mul_u64(0x1db42, arg1[4]) x4, x3 := bits.mul_u64(0x1db42, arg1[3]) x6, x5 := bits.mul_u64(0x1db42, arg1[2]) @@ -565,54 +584,3 @@ fe_carry_scmul_121666 :: proc (out1: ^Tight_Field_Element, arg1: ^Loose_Field_El out1[3] = x27 out1[4] = x32 } - -// The following routines were added by hand, and do not come from fiat-crypto. - -fe_zero :: proc "contextless" (out1: ^Tight_Field_Element) { - out1[0] = 0 - out1[1] = 0 - out1[2] = 0 - out1[3] = 0 - out1[4] = 0 -} - -fe_one :: proc "contextless" (out1: ^Tight_Field_Element) { - out1[0] = 1 - out1[1] = 0 - out1[2] = 0 - out1[3] = 0 - out1[4] = 0 -} - -fe_set :: proc "contextless" (out1, arg1: ^Tight_Field_Element) { - x1 := arg1[0] - x2 := arg1[1] - x3 := arg1[2] - x4 := arg1[3] - x5 := arg1[4] - out1[0] = x1 - out1[1] = x2 - out1[2] = x3 - out1[3] = x4 - out1[4] = x5 -} - -@(optimization_mode="none") -fe_cond_swap :: #force_no_inline proc "contextless" (out1, out2: ^Tight_Field_Element, arg1: int) { - mask := -u64(arg1) - x := (out1[0] ~ out2[0]) & mask - x1, y1 := out1[0] ~ x, out2[0] ~ x - x = (out1[1] ~ out2[1]) & mask - x2, y2 := out1[1] ~ x, out2[1] ~ x - x = (out1[2] ~ out2[2]) & mask - x3, y3 := out1[2] ~ x, out2[2] ~ x - x = (out1[3] ~ out2[3]) & mask - x4, y4 := out1[3] ~ x, out2[3] ~ x - x = (out1[4] ~ out2[4]) & mask - x5, y5 := out1[4] ~ x, out2[4] ~ x - out1[0], out2[0] = x1, y1 - out1[1], out2[1] = x2, y2 - out1[2], out2[2] = x3, y3 - out1[3], out2[3] = x4, y4 - out1[4], out2[4] = x5, y5 -} diff --git a/core/crypto/_fiat/field_poly1305/field.odin b/core/crypto/_fiat/field_poly1305/field.odin index ca458e079..b12046858 100644 --- a/core/crypto/_fiat/field_poly1305/field.odin +++ b/core/crypto/_fiat/field_poly1305/field.odin @@ -1,17 +1,26 @@ package field_poly1305 -import "core:crypto/util" +import "base:intrinsics" +import "core:encoding/endian" import "core:mem" -fe_relax_cast :: #force_inline proc "contextless" (arg1: ^Tight_Field_Element) -> ^Loose_Field_Element { - return transmute(^Loose_Field_Element)(arg1) +fe_relax_cast :: #force_inline proc "contextless" ( + arg1: ^Tight_Field_Element, +) -> ^Loose_Field_Element { + 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) +fe_tighten_cast :: #force_inline proc "contextless" ( + arg1: ^Loose_Field_Element, +) -> ^Tight_Field_Element { + return (^Tight_Field_Element)(arg1) } -fe_from_bytes :: #force_inline proc (out1: ^Tight_Field_Element, arg1: []byte, arg2: byte, sanitize: bool = true) { +fe_from_bytes :: #force_inline proc "contextless" ( + out1: ^Tight_Field_Element, + arg1: []byte, + arg2: byte, +) { // fiat-crypto's deserialization routine effectively processes a // single byte at a time, and wants 256-bits of input for a value // that will be 128-bits or 129-bits. @@ -20,47 +29,68 @@ fe_from_bytes :: #force_inline proc (out1: ^Tight_Field_Element, arg1: []byte, a // makes implementing the actual MAC block processing considerably // neater. - assert(len(arg1) == 16) - - when ODIN_ARCH == .i386 || ODIN_ARCH == .amd64 { - // While it may be unwise to do deserialization here on our - // own when fiat-crypto provides equivalent functionality, - // doing it this way provides a little under 3x performance - // improvement when optimization is enabled. - src_p := transmute(^[2]u64)(&arg1[0]) - lo := src_p[0] - hi := src_p[1] - - // This is inspired by poly1305-donna, though adjustments were - // made since a Tight_Field_Element's limbs are 44-bits, 43-bits, - // and 43-bits wide. - // - // Note: This could be transplated into fe_from_u64s, but that - // code is called once per MAC, and is non-criticial path. - hibit := u64(arg2) << 41 // arg2 << 128 - out1[0] = lo & 0xfffffffffff - out1[1] = ((lo >> 44) | (hi << 20)) & 0x7ffffffffff - out1[2] = ((hi >> 23) & 0x7ffffffffff) | hibit - } else { - tmp: [32]byte - copy_slice(tmp[0:16], arg1[:]) - tmp[16] = arg2 - - _fe_from_bytes(out1, &tmp) - if sanitize { - // This is used to deserialize `s` which is confidential. - mem.zero_explicit(&tmp, size_of(tmp)) - } + if len(arg1) != 16 { + intrinsics.trap() } + + // While it may be unwise to do deserialization here on our + // own when fiat-crypto provides equivalent functionality, + // doing it this way provides a little under 3x performance + // improvement when optimization is enabled. + lo := endian.unchecked_get_u64le(arg1[0:]) + hi := endian.unchecked_get_u64le(arg1[8:]) + + // This is inspired by poly1305-donna, though adjustments were + // made since a Tight_Field_Element's limbs are 44-bits, 43-bits, + // and 43-bits wide. + // + // Note: This could be transplated into fe_from_u64s, but that + // code is called once per MAC, and is non-criticial path. + hibit := u64(arg2) << 41 // arg2 << 128 + out1[0] = lo & 0xfffffffffff + out1[1] = ((lo >> 44) | (hi << 20)) & 0x7ffffffffff + out1[2] = ((hi >> 23) & 0x7ffffffffff) | hibit } fe_from_u64s :: proc "contextless" (out1: ^Tight_Field_Element, lo, hi: u64) { tmp: [32]byte - util.PUT_U64_LE(tmp[0:8], lo) - util.PUT_U64_LE(tmp[8:16], hi) + endian.unchecked_put_u64le(tmp[0:], lo) + endian.unchecked_put_u64le(tmp[8:], hi) _fe_from_bytes(out1, &tmp) // This routine is only used to deserialize `r` which is confidential. mem.zero_explicit(&tmp, size_of(tmp)) } + +fe_zero :: proc "contextless" (out1: ^Tight_Field_Element) { + out1[0] = 0 + out1[1] = 0 + out1[2] = 0 +} + +fe_set :: #force_inline proc "contextless" (out1, arg1: ^Tight_Field_Element) { + x1 := arg1[0] + x2 := arg1[1] + x3 := arg1[2] + out1[0] = x1 + out1[1] = x2 + out1[2] = x3 +} + +@(optimization_mode = "none") +fe_cond_swap :: #force_no_inline proc "contextless" ( + out1, out2: ^Tight_Field_Element, + arg1: bool, +) { + mask := (u64(arg1) * 0xffffffffffffffff) + x := (out1[0] ~ out2[0]) & mask + x1, y1 := out1[0] ~ x, out2[0] ~ x + x = (out1[1] ~ out2[1]) & mask + x2, y2 := out1[1] ~ x, out2[1] ~ x + x = (out1[2] ~ out2[2]) & mask + x3, y3 := out1[2] ~ x, out2[2] ~ x + out1[0], out2[0] = x1, y1 + out1[1], out2[1] = x2, y2 + out1[2], out2[2] = x3, y3 +} diff --git a/core/crypto/_fiat/field_poly1305/field4344.odin b/core/crypto/_fiat/field_poly1305/field4344.odin index 8e8a7cc78..6a7a19d69 100644 --- a/core/crypto/_fiat/field_poly1305/field4344.odin +++ b/core/crypto/_fiat/field_poly1305/field4344.odin @@ -39,7 +39,13 @@ import "core:math/bits" Loose_Field_Element :: distinct [3]u64 Tight_Field_Element :: distinct [3]u64 -_addcarryx_u44 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3: u64) -> (out1: u64, out2: fiat.u1) { +_addcarryx_u44 :: #force_inline proc "contextless" ( + arg1: fiat.u1, + arg2, arg3: u64, +) -> ( + out1: u64, + out2: fiat.u1, +) { x1 := ((u64(arg1) + arg2) + arg3) x2 := (x1 & 0xfffffffffff) x3 := fiat.u1((x1 >> 44)) @@ -48,7 +54,13 @@ _addcarryx_u44 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3: u return } -_subborrowx_u44 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3: u64) -> (out1: u64, out2: fiat.u1) { +_subborrowx_u44 :: #force_inline proc "contextless" ( + arg1: fiat.u1, + arg2, arg3: u64, +) -> ( + out1: u64, + out2: fiat.u1, +) { x1 := ((i64(arg2) - i64(arg1)) - i64(arg3)) x2 := fiat.i1((x1 >> 44)) x3 := (u64(x1) & 0xfffffffffff) @@ -57,7 +69,13 @@ _subborrowx_u44 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3: return } -_addcarryx_u43 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3: u64) -> (out1: u64, out2: fiat.u1) { +_addcarryx_u43 :: #force_inline proc "contextless" ( + arg1: fiat.u1, + arg2, arg3: u64, +) -> ( + out1: u64, + out2: fiat.u1, +) { x1 := ((u64(arg1) + arg2) + arg3) x2 := (x1 & 0x7ffffffffff) x3 := fiat.u1((x1 >> 43)) @@ -66,7 +84,13 @@ _addcarryx_u43 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3: u return } -_subborrowx_u43 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3: u64) -> (out1: u64, out2: fiat.u1) { +_subborrowx_u43 :: #force_inline proc "contextless" ( + arg1: fiat.u1, + arg2, arg3: u64, +) -> ( + out1: u64, + out2: fiat.u1, +) { x1 := ((i64(arg2) - i64(arg1)) - i64(arg3)) x2 := fiat.i1((x1 >> 43)) x3 := (u64(x1) & 0x7ffffffffff) @@ -75,7 +99,7 @@ _subborrowx_u43 :: #force_inline proc "contextless" (arg1: fiat.u1, arg2, arg3: return } -fe_carry_mul :: proc (out1: ^Tight_Field_Element, arg1, arg2: ^Loose_Field_Element) { +fe_carry_mul :: proc "contextless" (out1: ^Tight_Field_Element, arg1, arg2: ^Loose_Field_Element) { x2, x1 := bits.mul_u64(arg1[2], (arg2[2] * 0x5)) x4, x3 := bits.mul_u64(arg1[2], (arg2[1] * 0xa)) x6, x5 := bits.mul_u64(arg1[1], (arg2[2] * 0xa)) @@ -120,7 +144,7 @@ fe_carry_mul :: proc (out1: ^Tight_Field_Element, arg1, arg2: ^Loose_Field_Eleme out1[2] = x62 } -fe_carry_square :: proc (out1: ^Tight_Field_Element, arg1: ^Loose_Field_Element) { +fe_carry_square :: proc "contextless" (out1: ^Tight_Field_Element, arg1: ^Loose_Field_Element) { x1 := (arg1[2] * 0x5) x2 := (x1 * 0x2) x3 := (arg1[2] * 0x2) @@ -201,8 +225,11 @@ fe_opp :: proc "contextless" (out1: ^Loose_Field_Element, arg1: ^Tight_Field_Ele out1[2] = x3 } -@(optimization_mode="none") -fe_cond_assign :: #force_no_inline proc "contextless" (out1, arg1: ^Tight_Field_Element, arg2: bool) { +@(optimization_mode = "none") +fe_cond_assign :: #force_no_inline proc "contextless" ( + out1, arg1: ^Tight_Field_Element, + arg2: bool, +) { x1 := fiat.cmovznz_u64(fiat.u1(arg2), out1[0], arg1[0]) x2 := fiat.cmovznz_u64(fiat.u1(arg2), out1[1], arg1[1]) x3 := fiat.cmovznz_u64(fiat.u1(arg2), out1[2], arg1[2]) @@ -325,34 +352,3 @@ fe_relax :: proc "contextless" (out1: ^Loose_Field_Element, arg1: ^Tight_Field_E out1[1] = x2 out1[2] = x3 } - -// The following routines were added by hand, and do not come from fiat-crypto. - -fe_zero :: proc "contextless" (out1: ^Tight_Field_Element) { - out1[0] = 0 - out1[1] = 0 - out1[2] = 0 -} - -fe_set :: #force_inline proc "contextless" (out1, arg1: ^Tight_Field_Element) { - x1 := arg1[0] - x2 := arg1[1] - x3 := arg1[2] - out1[0] = x1 - out1[1] = x2 - out1[2] = x3 -} - -@(optimization_mode="none") -fe_cond_swap :: #force_no_inline proc "contextless" (out1, out2: ^Tight_Field_Element, arg1: bool) { - mask := -u64(arg1) - x := (out1[0] ~ out2[0]) & mask - x1, y1 := out1[0] ~ x, out2[0] ~ x - x = (out1[1] ~ out2[1]) & mask - x2, y2 := out1[1] ~ x, out2[1] ~ x - x = (out1[2] ~ out2[2]) & mask - x3, y3 := out1[2] ~ x, out2[2] ~ x - out1[0], out2[0] = x1, y1 - out1[1], out2[1] = x2, y2 - out1[2], out2[2] = x3, y3 -} diff --git a/core/crypto/_fiat/field_scalar25519/field.odin b/core/crypto/_fiat/field_scalar25519/field.odin new file mode 100644 index 000000000..9b40661b7 --- /dev/null +++ b/core/crypto/_fiat/field_scalar25519/field.odin @@ -0,0 +1,153 @@ +package field_scalar25519 + +import "base:intrinsics" +import "core:encoding/endian" +import "core:math/bits" +import "core:mem" + +@(private) +_TWO_168 := Montgomery_Domain_Field_Element { + 0x5b8ab432eac74798, + 0x38afddd6de59d5d7, + 0xa2c131b399411b7c, + 0x6329a7ed9ce5a30, +} +@(private) +_TWO_336 := Montgomery_Domain_Field_Element { + 0xbd3d108e2b35ecc5, + 0x5c3a3718bdf9c90b, + 0x63aa97a331b4f2ee, + 0x3d217f5be65cb5c, +} + +fe_clear :: proc "contextless" (arg1: ^Montgomery_Domain_Field_Element) { + mem.zero_explicit(arg1, size_of(Montgomery_Domain_Field_Element)) +} + +fe_from_bytes :: proc "contextless" ( + out1: ^Montgomery_Domain_Field_Element, + arg1: ^[32]byte, + unsafe_assume_canonical := false, +) -> bool { + tmp := Non_Montgomery_Domain_Field_Element { + endian.unchecked_get_u64le(arg1[0:]), + endian.unchecked_get_u64le(arg1[8:]), + endian.unchecked_get_u64le(arg1[16:]), + endian.unchecked_get_u64le(arg1[24:]), + } + defer mem.zero_explicit(&tmp, size_of(tmp)) + + // Check that tmp is in the the range [0, ELL). + if !unsafe_assume_canonical { + _, borrow := bits.sub_u64(ELL[0] - 1, tmp[0], 0) + _, borrow = bits.sub_u64(ELL[1], tmp[1], borrow) + _, borrow = bits.sub_u64(ELL[2], tmp[2], borrow) + _, borrow = bits.sub_u64(ELL[3], tmp[3], borrow) + if borrow != 0 { + return false + } + } + + fe_to_montgomery(out1, &tmp) + + return true +} + +fe_from_bytes_rfc8032 :: proc "contextless" ( + out1: ^Montgomery_Domain_Field_Element, + arg1: ^[32]byte, +) { + tmp: [64]byte + copy(tmp[:], arg1[:]) + + // Apply "clamping" as in RFC 8032. + tmp[0] &= 248 + tmp[31] &= 127 + tmp[31] |= 64 // Sets the 254th bit, so the encoding is non-canonical. + + fe_from_bytes_wide(out1, &tmp) + + mem.zero_explicit(&tmp, size_of(tmp)) +} + +fe_from_bytes_wide :: proc "contextless" ( + out1: ^Montgomery_Domain_Field_Element, + arg1: ^[64]byte, +) { + tmp: Montgomery_Domain_Field_Element + // Use Frank Denis' trick, as documented by Filippo Valsorda + // at https://words.filippo.io/dispatches/wide-reduction/ + // + // x = c * 2^336 + b * 2^168 + a mod l + _fe_from_bytes_short(out1, arg1[:21]) // a + + _fe_from_bytes_short(&tmp, arg1[21:42]) // b + fe_mul(&tmp, &tmp, &_TWO_168) // b * 2^168 + fe_add(out1, out1, &tmp) // a + b * 2^168 + + _fe_from_bytes_short(&tmp, arg1[42:]) // c + fe_mul(&tmp, &tmp, &_TWO_336) // c * 2^336 + fe_add(out1, out1, &tmp) // a + b * 2^168 + c * 2^336 + + fe_clear(&tmp) +} + +@(private) +_fe_from_bytes_short :: proc "contextless" (out1: ^Montgomery_Domain_Field_Element, arg1: []byte) { + // INVARIANT: len(arg1) < 32. + if len(arg1) >= 32 { + intrinsics.trap() + } + tmp: [32]byte + copy(tmp[:], arg1) + + _ = fe_from_bytes(out1, &tmp, true) + mem.zero_explicit(&tmp, size_of(tmp)) +} + +fe_to_bytes :: proc "contextless" (out1: []byte, arg1: ^Montgomery_Domain_Field_Element) { + if len(out1) != 32 { + intrinsics.trap() + } + + tmp: Non_Montgomery_Domain_Field_Element + fe_from_montgomery(&tmp, arg1) + + endian.unchecked_put_u64le(out1[0:], tmp[0]) + endian.unchecked_put_u64le(out1[8:], tmp[1]) + endian.unchecked_put_u64le(out1[16:], tmp[2]) + endian.unchecked_put_u64le(out1[24:], tmp[3]) + + mem.zero_explicit(&tmp, size_of(tmp)) +} + +fe_equal :: proc "contextless" (arg1, arg2: ^Montgomery_Domain_Field_Element) -> int { + tmp: Montgomery_Domain_Field_Element + fe_sub(&tmp, arg1, arg2) + + // This will only underflow iff arg1 == arg2, and we return the borrow, + // which will be 1. + _, borrow := bits.sub_u64(fe_non_zero(&tmp), 1, 0) + + fe_clear(&tmp) + + return int(borrow) +} + +fe_zero :: proc "contextless" (out1: ^Montgomery_Domain_Field_Element) { + out1[0] = 0 + out1[1] = 0 + out1[2] = 0 + out1[3] = 0 +} + +fe_set :: proc "contextless" (out1, arg1: ^Montgomery_Domain_Field_Element) { + x1 := arg1[0] + x2 := arg1[1] + x3 := arg1[2] + x4 := arg1[3] + out1[0] = x1 + out1[1] = x2 + out1[2] = x3 + out1[3] = x4 +} diff --git a/core/crypto/_fiat/field_scalar25519/field64.odin b/core/crypto/_fiat/field_scalar25519/field64.odin new file mode 100644 index 000000000..268752e5b --- /dev/null +++ b/core/crypto/_fiat/field_scalar25519/field64.odin @@ -0,0 +1,535 @@ +// The BSD 1-Clause License (BSD-1-Clause) +// +// Copyright (c) 2015-2020 the fiat-crypto authors (see the AUTHORS file) +// 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 fiat-crypto 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 Berkeley Software Design, +// Inc. 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 field_scalar25519 + +// The file provides arithmetic on the field Z/(2^252+27742317777372353535851937790883648493) +// using a 64-bit Montgomery form internal representation. It is derived +// primarily from the machine generated Golang output from the fiat-crypto +// project. +// +// While the base implementation is provably correct, this implementation +// makes no such claims as the port and optimizations were done by hand. + +import fiat "core:crypto/_fiat" +import "core:math/bits" + +// ELL is the saturated representation of the field order, least-significant +// limb first. +ELL :: [4]u64{0x5812631a5cf5d3ed, 0x14def9dea2f79cd6, 0x0, 0x1000000000000000} + +Montgomery_Domain_Field_Element :: distinct [4]u64 +Non_Montgomery_Domain_Field_Element :: distinct [4]u64 + +fe_mul :: proc "contextless" (out1, arg1, arg2: ^Montgomery_Domain_Field_Element) { + x1 := arg1[1] + x2 := arg1[2] + x3 := arg1[3] + x4 := arg1[0] + x6, x5 := bits.mul_u64(x4, arg2[3]) + x8, x7 := bits.mul_u64(x4, arg2[2]) + x10, x9 := bits.mul_u64(x4, arg2[1]) + x12, x11 := bits.mul_u64(x4, arg2[0]) + x13, x14 := bits.add_u64(x12, x9, u64(0x0)) + x15, x16 := bits.add_u64(x10, x7, u64(fiat.u1(x14))) + x17, x18 := bits.add_u64(x8, x5, u64(fiat.u1(x16))) + x19 := (u64(fiat.u1(x18)) + x6) + _, x20 := bits.mul_u64(x11, 0xd2b51da312547e1b) + x23, x22 := bits.mul_u64(x20, 0x1000000000000000) + x25, x24 := bits.mul_u64(x20, 0x14def9dea2f79cd6) + x27, x26 := bits.mul_u64(x20, 0x5812631a5cf5d3ed) + x28, x29 := bits.add_u64(x27, x24, u64(0x0)) + x30 := (u64(fiat.u1(x29)) + x25) + _, x32 := bits.add_u64(x11, x26, u64(0x0)) + x33, x34 := bits.add_u64(x13, x28, u64(fiat.u1(x32))) + x35, x36 := bits.add_u64(x15, x30, u64(fiat.u1(x34))) + x37, x38 := bits.add_u64(x17, x22, u64(fiat.u1(x36))) + x39, x40 := bits.add_u64(x19, x23, u64(fiat.u1(x38))) + x42, x41 := bits.mul_u64(x1, arg2[3]) + x44, x43 := bits.mul_u64(x1, arg2[2]) + x46, x45 := bits.mul_u64(x1, arg2[1]) + x48, x47 := bits.mul_u64(x1, arg2[0]) + x49, x50 := bits.add_u64(x48, x45, u64(0x0)) + x51, x52 := bits.add_u64(x46, x43, u64(fiat.u1(x50))) + x53, x54 := bits.add_u64(x44, x41, u64(fiat.u1(x52))) + x55 := (u64(fiat.u1(x54)) + x42) + x56, x57 := bits.add_u64(x33, x47, u64(0x0)) + x58, x59 := bits.add_u64(x35, x49, u64(fiat.u1(x57))) + x60, x61 := bits.add_u64(x37, x51, u64(fiat.u1(x59))) + x62, x63 := bits.add_u64(x39, x53, u64(fiat.u1(x61))) + x64, x65 := bits.add_u64(u64(fiat.u1(x40)), x55, u64(fiat.u1(x63))) + _, x66 := bits.mul_u64(x56, 0xd2b51da312547e1b) + x69, x68 := bits.mul_u64(x66, 0x1000000000000000) + x71, x70 := bits.mul_u64(x66, 0x14def9dea2f79cd6) + x73, x72 := bits.mul_u64(x66, 0x5812631a5cf5d3ed) + x74, x75 := bits.add_u64(x73, x70, u64(0x0)) + x76 := (u64(fiat.u1(x75)) + x71) + _, x78 := bits.add_u64(x56, x72, u64(0x0)) + x79, x80 := bits.add_u64(x58, x74, u64(fiat.u1(x78))) + x81, x82 := bits.add_u64(x60, x76, u64(fiat.u1(x80))) + x83, x84 := bits.add_u64(x62, x68, u64(fiat.u1(x82))) + x85, x86 := bits.add_u64(x64, x69, u64(fiat.u1(x84))) + x87 := (u64(fiat.u1(x86)) + u64(fiat.u1(x65))) + x89, x88 := bits.mul_u64(x2, arg2[3]) + x91, x90 := bits.mul_u64(x2, arg2[2]) + x93, x92 := bits.mul_u64(x2, arg2[1]) + x95, x94 := bits.mul_u64(x2, arg2[0]) + x96, x97 := bits.add_u64(x95, x92, u64(0x0)) + x98, x99 := bits.add_u64(x93, x90, u64(fiat.u1(x97))) + x100, x101 := bits.add_u64(x91, x88, u64(fiat.u1(x99))) + x102 := (u64(fiat.u1(x101)) + x89) + x103, x104 := bits.add_u64(x79, x94, u64(0x0)) + x105, x106 := bits.add_u64(x81, x96, u64(fiat.u1(x104))) + x107, x108 := bits.add_u64(x83, x98, u64(fiat.u1(x106))) + x109, x110 := bits.add_u64(x85, x100, u64(fiat.u1(x108))) + x111, x112 := bits.add_u64(x87, x102, u64(fiat.u1(x110))) + _, x113 := bits.mul_u64(x103, 0xd2b51da312547e1b) + x116, x115 := bits.mul_u64(x113, 0x1000000000000000) + x118, x117 := bits.mul_u64(x113, 0x14def9dea2f79cd6) + x120, x119 := bits.mul_u64(x113, 0x5812631a5cf5d3ed) + x121, x122 := bits.add_u64(x120, x117, u64(0x0)) + x123 := (u64(fiat.u1(x122)) + x118) + _, x125 := bits.add_u64(x103, x119, u64(0x0)) + x126, x127 := bits.add_u64(x105, x121, u64(fiat.u1(x125))) + x128, x129 := bits.add_u64(x107, x123, u64(fiat.u1(x127))) + x130, x131 := bits.add_u64(x109, x115, u64(fiat.u1(x129))) + x132, x133 := bits.add_u64(x111, x116, u64(fiat.u1(x131))) + x134 := (u64(fiat.u1(x133)) + u64(fiat.u1(x112))) + x136, x135 := bits.mul_u64(x3, arg2[3]) + x138, x137 := bits.mul_u64(x3, arg2[2]) + x140, x139 := bits.mul_u64(x3, arg2[1]) + x142, x141 := bits.mul_u64(x3, arg2[0]) + x143, x144 := bits.add_u64(x142, x139, u64(0x0)) + x145, x146 := bits.add_u64(x140, x137, u64(fiat.u1(x144))) + x147, x148 := bits.add_u64(x138, x135, u64(fiat.u1(x146))) + x149 := (u64(fiat.u1(x148)) + x136) + x150, x151 := bits.add_u64(x126, x141, u64(0x0)) + x152, x153 := bits.add_u64(x128, x143, u64(fiat.u1(x151))) + x154, x155 := bits.add_u64(x130, x145, u64(fiat.u1(x153))) + x156, x157 := bits.add_u64(x132, x147, u64(fiat.u1(x155))) + x158, x159 := bits.add_u64(x134, x149, u64(fiat.u1(x157))) + _, x160 := bits.mul_u64(x150, 0xd2b51da312547e1b) + x163, x162 := bits.mul_u64(x160, 0x1000000000000000) + x165, x164 := bits.mul_u64(x160, 0x14def9dea2f79cd6) + x167, x166 := bits.mul_u64(x160, 0x5812631a5cf5d3ed) + x168, x169 := bits.add_u64(x167, x164, u64(0x0)) + x170 := (u64(fiat.u1(x169)) + x165) + _, x172 := bits.add_u64(x150, x166, u64(0x0)) + x173, x174 := bits.add_u64(x152, x168, u64(fiat.u1(x172))) + x175, x176 := bits.add_u64(x154, x170, u64(fiat.u1(x174))) + x177, x178 := bits.add_u64(x156, x162, u64(fiat.u1(x176))) + x179, x180 := bits.add_u64(x158, x163, u64(fiat.u1(x178))) + x181 := (u64(fiat.u1(x180)) + u64(fiat.u1(x159))) + x182, x183 := bits.sub_u64(x173, 0x5812631a5cf5d3ed, u64(0x0)) + x184, x185 := bits.sub_u64(x175, 0x14def9dea2f79cd6, u64(fiat.u1(x183))) + x186, x187 := bits.sub_u64(x177, u64(0x0), u64(fiat.u1(x185))) + x188, x189 := bits.sub_u64(x179, 0x1000000000000000, u64(fiat.u1(x187))) + _, x191 := bits.sub_u64(x181, u64(0x0), u64(fiat.u1(x189))) + x192 := fiat.cmovznz_u64(fiat.u1(x191), x182, x173) + x193 := fiat.cmovznz_u64(fiat.u1(x191), x184, x175) + x194 := fiat.cmovznz_u64(fiat.u1(x191), x186, x177) + x195 := fiat.cmovznz_u64(fiat.u1(x191), x188, x179) + out1[0] = x192 + out1[1] = x193 + out1[2] = x194 + out1[3] = x195 +} + +fe_square :: proc "contextless" (out1, arg1: ^Montgomery_Domain_Field_Element) { + x1 := arg1[1] + x2 := arg1[2] + x3 := arg1[3] + x4 := arg1[0] + x6, x5 := bits.mul_u64(x4, arg1[3]) + x8, x7 := bits.mul_u64(x4, arg1[2]) + x10, x9 := bits.mul_u64(x4, arg1[1]) + x12, x11 := bits.mul_u64(x4, arg1[0]) + x13, x14 := bits.add_u64(x12, x9, u64(0x0)) + x15, x16 := bits.add_u64(x10, x7, u64(fiat.u1(x14))) + x17, x18 := bits.add_u64(x8, x5, u64(fiat.u1(x16))) + x19 := (u64(fiat.u1(x18)) + x6) + _, x20 := bits.mul_u64(x11, 0xd2b51da312547e1b) + x23, x22 := bits.mul_u64(x20, 0x1000000000000000) + x25, x24 := bits.mul_u64(x20, 0x14def9dea2f79cd6) + x27, x26 := bits.mul_u64(x20, 0x5812631a5cf5d3ed) + x28, x29 := bits.add_u64(x27, x24, u64(0x0)) + x30 := (u64(fiat.u1(x29)) + x25) + _, x32 := bits.add_u64(x11, x26, u64(0x0)) + x33, x34 := bits.add_u64(x13, x28, u64(fiat.u1(x32))) + x35, x36 := bits.add_u64(x15, x30, u64(fiat.u1(x34))) + x37, x38 := bits.add_u64(x17, x22, u64(fiat.u1(x36))) + x39, x40 := bits.add_u64(x19, x23, u64(fiat.u1(x38))) + x42, x41 := bits.mul_u64(x1, arg1[3]) + x44, x43 := bits.mul_u64(x1, arg1[2]) + x46, x45 := bits.mul_u64(x1, arg1[1]) + x48, x47 := bits.mul_u64(x1, arg1[0]) + x49, x50 := bits.add_u64(x48, x45, u64(0x0)) + x51, x52 := bits.add_u64(x46, x43, u64(fiat.u1(x50))) + x53, x54 := bits.add_u64(x44, x41, u64(fiat.u1(x52))) + x55 := (u64(fiat.u1(x54)) + x42) + x56, x57 := bits.add_u64(x33, x47, u64(0x0)) + x58, x59 := bits.add_u64(x35, x49, u64(fiat.u1(x57))) + x60, x61 := bits.add_u64(x37, x51, u64(fiat.u1(x59))) + x62, x63 := bits.add_u64(x39, x53, u64(fiat.u1(x61))) + x64, x65 := bits.add_u64(u64(fiat.u1(x40)), x55, u64(fiat.u1(x63))) + _, x66 := bits.mul_u64(x56, 0xd2b51da312547e1b) + x69, x68 := bits.mul_u64(x66, 0x1000000000000000) + x71, x70 := bits.mul_u64(x66, 0x14def9dea2f79cd6) + x73, x72 := bits.mul_u64(x66, 0x5812631a5cf5d3ed) + x74, x75 := bits.add_u64(x73, x70, u64(0x0)) + x76 := (u64(fiat.u1(x75)) + x71) + _, x78 := bits.add_u64(x56, x72, u64(0x0)) + x79, x80 := bits.add_u64(x58, x74, u64(fiat.u1(x78))) + x81, x82 := bits.add_u64(x60, x76, u64(fiat.u1(x80))) + x83, x84 := bits.add_u64(x62, x68, u64(fiat.u1(x82))) + x85, x86 := bits.add_u64(x64, x69, u64(fiat.u1(x84))) + x87 := (u64(fiat.u1(x86)) + u64(fiat.u1(x65))) + x89, x88 := bits.mul_u64(x2, arg1[3]) + x91, x90 := bits.mul_u64(x2, arg1[2]) + x93, x92 := bits.mul_u64(x2, arg1[1]) + x95, x94 := bits.mul_u64(x2, arg1[0]) + x96, x97 := bits.add_u64(x95, x92, u64(0x0)) + x98, x99 := bits.add_u64(x93, x90, u64(fiat.u1(x97))) + x100, x101 := bits.add_u64(x91, x88, u64(fiat.u1(x99))) + x102 := (u64(fiat.u1(x101)) + x89) + x103, x104 := bits.add_u64(x79, x94, u64(0x0)) + x105, x106 := bits.add_u64(x81, x96, u64(fiat.u1(x104))) + x107, x108 := bits.add_u64(x83, x98, u64(fiat.u1(x106))) + x109, x110 := bits.add_u64(x85, x100, u64(fiat.u1(x108))) + x111, x112 := bits.add_u64(x87, x102, u64(fiat.u1(x110))) + _, x113 := bits.mul_u64(x103, 0xd2b51da312547e1b) + x116, x115 := bits.mul_u64(x113, 0x1000000000000000) + x118, x117 := bits.mul_u64(x113, 0x14def9dea2f79cd6) + x120, x119 := bits.mul_u64(x113, 0x5812631a5cf5d3ed) + x121, x122 := bits.add_u64(x120, x117, u64(0x0)) + x123 := (u64(fiat.u1(x122)) + x118) + _, x125 := bits.add_u64(x103, x119, u64(0x0)) + x126, x127 := bits.add_u64(x105, x121, u64(fiat.u1(x125))) + x128, x129 := bits.add_u64(x107, x123, u64(fiat.u1(x127))) + x130, x131 := bits.add_u64(x109, x115, u64(fiat.u1(x129))) + x132, x133 := bits.add_u64(x111, x116, u64(fiat.u1(x131))) + x134 := (u64(fiat.u1(x133)) + u64(fiat.u1(x112))) + x136, x135 := bits.mul_u64(x3, arg1[3]) + x138, x137 := bits.mul_u64(x3, arg1[2]) + x140, x139 := bits.mul_u64(x3, arg1[1]) + x142, x141 := bits.mul_u64(x3, arg1[0]) + x143, x144 := bits.add_u64(x142, x139, u64(0x0)) + x145, x146 := bits.add_u64(x140, x137, u64(fiat.u1(x144))) + x147, x148 := bits.add_u64(x138, x135, u64(fiat.u1(x146))) + x149 := (u64(fiat.u1(x148)) + x136) + x150, x151 := bits.add_u64(x126, x141, u64(0x0)) + x152, x153 := bits.add_u64(x128, x143, u64(fiat.u1(x151))) + x154, x155 := bits.add_u64(x130, x145, u64(fiat.u1(x153))) + x156, x157 := bits.add_u64(x132, x147, u64(fiat.u1(x155))) + x158, x159 := bits.add_u64(x134, x149, u64(fiat.u1(x157))) + _, x160 := bits.mul_u64(x150, 0xd2b51da312547e1b) + x163, x162 := bits.mul_u64(x160, 0x1000000000000000) + x165, x164 := bits.mul_u64(x160, 0x14def9dea2f79cd6) + x167, x166 := bits.mul_u64(x160, 0x5812631a5cf5d3ed) + x168, x169 := bits.add_u64(x167, x164, u64(0x0)) + x170 := (u64(fiat.u1(x169)) + x165) + _, x172 := bits.add_u64(x150, x166, u64(0x0)) + x173, x174 := bits.add_u64(x152, x168, u64(fiat.u1(x172))) + x175, x176 := bits.add_u64(x154, x170, u64(fiat.u1(x174))) + x177, x178 := bits.add_u64(x156, x162, u64(fiat.u1(x176))) + x179, x180 := bits.add_u64(x158, x163, u64(fiat.u1(x178))) + x181 := (u64(fiat.u1(x180)) + u64(fiat.u1(x159))) + x182, x183 := bits.sub_u64(x173, 0x5812631a5cf5d3ed, u64(0x0)) + x184, x185 := bits.sub_u64(x175, 0x14def9dea2f79cd6, u64(fiat.u1(x183))) + x186, x187 := bits.sub_u64(x177, u64(0x0), u64(fiat.u1(x185))) + x188, x189 := bits.sub_u64(x179, 0x1000000000000000, u64(fiat.u1(x187))) + _, x191 := bits.sub_u64(x181, u64(0x0), u64(fiat.u1(x189))) + x192 := fiat.cmovznz_u64(fiat.u1(x191), x182, x173) + x193 := fiat.cmovznz_u64(fiat.u1(x191), x184, x175) + x194 := fiat.cmovznz_u64(fiat.u1(x191), x186, x177) + x195 := fiat.cmovznz_u64(fiat.u1(x191), x188, x179) + out1[0] = x192 + out1[1] = x193 + out1[2] = x194 + out1[3] = x195 +} + +fe_add :: proc "contextless" (out1, arg1, arg2: ^Montgomery_Domain_Field_Element) { + x1, x2 := bits.add_u64(arg1[0], arg2[0], u64(0x0)) + x3, x4 := bits.add_u64(arg1[1], arg2[1], u64(fiat.u1(x2))) + x5, x6 := bits.add_u64(arg1[2], arg2[2], u64(fiat.u1(x4))) + x7, x8 := bits.add_u64(arg1[3], arg2[3], u64(fiat.u1(x6))) + x9, x10 := bits.sub_u64(x1, 0x5812631a5cf5d3ed, u64(0x0)) + x11, x12 := bits.sub_u64(x3, 0x14def9dea2f79cd6, u64(fiat.u1(x10))) + x13, x14 := bits.sub_u64(x5, u64(0x0), u64(fiat.u1(x12))) + x15, x16 := bits.sub_u64(x7, 0x1000000000000000, u64(fiat.u1(x14))) + _, x18 := bits.sub_u64(u64(fiat.u1(x8)), u64(0x0), u64(fiat.u1(x16))) + x19 := fiat.cmovznz_u64(fiat.u1(x18), x9, x1) + x20 := fiat.cmovznz_u64(fiat.u1(x18), x11, x3) + x21 := fiat.cmovznz_u64(fiat.u1(x18), x13, x5) + x22 := fiat.cmovznz_u64(fiat.u1(x18), x15, x7) + out1[0] = x19 + out1[1] = x20 + out1[2] = x21 + out1[3] = x22 +} + +fe_sub :: proc "contextless" (out1, arg1, arg2: ^Montgomery_Domain_Field_Element) { + x1, x2 := bits.sub_u64(arg1[0], arg2[0], u64(0x0)) + x3, x4 := bits.sub_u64(arg1[1], arg2[1], u64(fiat.u1(x2))) + x5, x6 := bits.sub_u64(arg1[2], arg2[2], u64(fiat.u1(x4))) + x7, x8 := bits.sub_u64(arg1[3], arg2[3], u64(fiat.u1(x6))) + x9 := fiat.cmovznz_u64(fiat.u1(x8), u64(0x0), 0xffffffffffffffff) + x10, x11 := bits.add_u64(x1, (x9 & 0x5812631a5cf5d3ed), u64(0x0)) + x12, x13 := bits.add_u64(x3, (x9 & 0x14def9dea2f79cd6), u64(fiat.u1(x11))) + x14, x15 := bits.add_u64(x5, u64(0x0), u64(fiat.u1(x13))) + x16, _ := bits.add_u64(x7, (x9 & 0x1000000000000000), u64(fiat.u1(x15))) + out1[0] = x10 + out1[1] = x12 + out1[2] = x14 + out1[3] = x16 +} + +fe_opp :: proc "contextless" (out1, arg1: ^Montgomery_Domain_Field_Element) { + x1, x2 := bits.sub_u64(u64(0x0), arg1[0], u64(0x0)) + x3, x4 := bits.sub_u64(u64(0x0), arg1[1], u64(fiat.u1(x2))) + x5, x6 := bits.sub_u64(u64(0x0), arg1[2], u64(fiat.u1(x4))) + x7, x8 := bits.sub_u64(u64(0x0), arg1[3], u64(fiat.u1(x6))) + x9 := fiat.cmovznz_u64(fiat.u1(x8), u64(0x0), 0xffffffffffffffff) + x10, x11 := bits.add_u64(x1, (x9 & 0x5812631a5cf5d3ed), u64(0x0)) + x12, x13 := bits.add_u64(x3, (x9 & 0x14def9dea2f79cd6), u64(fiat.u1(x11))) + x14, x15 := bits.add_u64(x5, u64(0x0), u64(fiat.u1(x13))) + x16, _ := bits.add_u64(x7, (x9 & 0x1000000000000000), u64(fiat.u1(x15))) + out1[0] = x10 + out1[1] = x12 + out1[2] = x14 + out1[3] = x16 +} + +fe_one :: proc "contextless" (out1: ^Montgomery_Domain_Field_Element) { + out1[0] = 0xd6ec31748d98951d + out1[1] = 0xc6ef5bf4737dcf70 + out1[2] = 0xfffffffffffffffe + out1[3] = 0xfffffffffffffff +} + +fe_non_zero :: proc "contextless" (arg1: ^Montgomery_Domain_Field_Element) -> u64 { + return arg1[0] | (arg1[1] | (arg1[2] | arg1[3])) +} + +@(optimization_mode = "none") +fe_cond_assign :: #force_no_inline proc "contextless" ( + out1, arg1: ^Montgomery_Domain_Field_Element, + arg2: int, +) { + x1 := fiat.cmovznz_u64(fiat.u1(arg2), out1[0], arg1[0]) + x2 := fiat.cmovznz_u64(fiat.u1(arg2), out1[1], arg1[1]) + x3 := fiat.cmovznz_u64(fiat.u1(arg2), out1[2], arg1[2]) + x4 := fiat.cmovznz_u64(fiat.u1(arg2), out1[3], arg1[3]) + out1[0] = x1 + out1[1] = x2 + out1[2] = x3 + out1[3] = x4 +} + +fe_from_montgomery :: proc "contextless" ( + out1: ^Non_Montgomery_Domain_Field_Element, + arg1: ^Montgomery_Domain_Field_Element, +) { + x1 := arg1[0] + _, x2 := bits.mul_u64(x1, 0xd2b51da312547e1b) + x5, x4 := bits.mul_u64(x2, 0x1000000000000000) + x7, x6 := bits.mul_u64(x2, 0x14def9dea2f79cd6) + x9, x8 := bits.mul_u64(x2, 0x5812631a5cf5d3ed) + x10, x11 := bits.add_u64(x9, x6, u64(0x0)) + _, x13 := bits.add_u64(x1, x8, u64(0x0)) + x14, x15 := bits.add_u64(u64(0x0), x10, u64(fiat.u1(x13))) + x16, x17 := bits.add_u64(x14, arg1[1], u64(0x0)) + _, x18 := bits.mul_u64(x16, 0xd2b51da312547e1b) + x21, x20 := bits.mul_u64(x18, 0x1000000000000000) + x23, x22 := bits.mul_u64(x18, 0x14def9dea2f79cd6) + x25, x24 := bits.mul_u64(x18, 0x5812631a5cf5d3ed) + x26, x27 := bits.add_u64(x25, x22, u64(0x0)) + _, x29 := bits.add_u64(x16, x24, u64(0x0)) + x30, x31 := bits.add_u64( + (u64(fiat.u1(x17)) + (u64(fiat.u1(x15)) + (u64(fiat.u1(x11)) + x7))), + x26, + u64(fiat.u1(x29)), + ) + x32, x33 := bits.add_u64(x4, (u64(fiat.u1(x27)) + x23), u64(fiat.u1(x31))) + x34, x35 := bits.add_u64(x5, x20, u64(fiat.u1(x33))) + x36, x37 := bits.add_u64(x30, arg1[2], u64(0x0)) + x38, x39 := bits.add_u64(x32, u64(0x0), u64(fiat.u1(x37))) + x40, x41 := bits.add_u64(x34, u64(0x0), u64(fiat.u1(x39))) + _, x42 := bits.mul_u64(x36, 0xd2b51da312547e1b) + x45, x44 := bits.mul_u64(x42, 0x1000000000000000) + x47, x46 := bits.mul_u64(x42, 0x14def9dea2f79cd6) + x49, x48 := bits.mul_u64(x42, 0x5812631a5cf5d3ed) + x50, x51 := bits.add_u64(x49, x46, u64(0x0)) + _, x53 := bits.add_u64(x36, x48, u64(0x0)) + x54, x55 := bits.add_u64(x38, x50, u64(fiat.u1(x53))) + x56, x57 := bits.add_u64(x40, (u64(fiat.u1(x51)) + x47), u64(fiat.u1(x55))) + x58, x59 := bits.add_u64( + (u64(fiat.u1(x41)) + (u64(fiat.u1(x35)) + x21)), + x44, + u64(fiat.u1(x57)), + ) + x60, x61 := bits.add_u64(x54, arg1[3], u64(0x0)) + x62, x63 := bits.add_u64(x56, u64(0x0), u64(fiat.u1(x61))) + x64, x65 := bits.add_u64(x58, u64(0x0), u64(fiat.u1(x63))) + _, x66 := bits.mul_u64(x60, 0xd2b51da312547e1b) + x69, x68 := bits.mul_u64(x66, 0x1000000000000000) + x71, x70 := bits.mul_u64(x66, 0x14def9dea2f79cd6) + x73, x72 := bits.mul_u64(x66, 0x5812631a5cf5d3ed) + x74, x75 := bits.add_u64(x73, x70, u64(0x0)) + _, x77 := bits.add_u64(x60, x72, u64(0x0)) + x78, x79 := bits.add_u64(x62, x74, u64(fiat.u1(x77))) + x80, x81 := bits.add_u64(x64, (u64(fiat.u1(x75)) + x71), u64(fiat.u1(x79))) + x82, x83 := bits.add_u64( + (u64(fiat.u1(x65)) + (u64(fiat.u1(x59)) + x45)), + x68, + u64(fiat.u1(x81)), + ) + x84 := (u64(fiat.u1(x83)) + x69) + x85, x86 := bits.sub_u64(x78, 0x5812631a5cf5d3ed, u64(0x0)) + x87, x88 := bits.sub_u64(x80, 0x14def9dea2f79cd6, u64(fiat.u1(x86))) + x89, x90 := bits.sub_u64(x82, u64(0x0), u64(fiat.u1(x88))) + x91, x92 := bits.sub_u64(x84, 0x1000000000000000, u64(fiat.u1(x90))) + _, x94 := bits.sub_u64(u64(0x0), u64(0x0), u64(fiat.u1(x92))) + x95 := fiat.cmovznz_u64(fiat.u1(x94), x85, x78) + x96 := fiat.cmovznz_u64(fiat.u1(x94), x87, x80) + x97 := fiat.cmovznz_u64(fiat.u1(x94), x89, x82) + x98 := fiat.cmovznz_u64(fiat.u1(x94), x91, x84) + out1[0] = x95 + out1[1] = x96 + out1[2] = x97 + out1[3] = x98 +} + +fe_to_montgomery :: proc "contextless" ( + out1: ^Montgomery_Domain_Field_Element, + arg1: ^Non_Montgomery_Domain_Field_Element, +) { + x1 := arg1[1] + x2 := arg1[2] + x3 := arg1[3] + x4 := arg1[0] + x6, x5 := bits.mul_u64(x4, 0x399411b7c309a3d) + x8, x7 := bits.mul_u64(x4, 0xceec73d217f5be65) + x10, x9 := bits.mul_u64(x4, 0xd00e1ba768859347) + x12, x11 := bits.mul_u64(x4, 0xa40611e3449c0f01) + x13, x14 := bits.add_u64(x12, x9, u64(0x0)) + x15, x16 := bits.add_u64(x10, x7, u64(fiat.u1(x14))) + x17, x18 := bits.add_u64(x8, x5, u64(fiat.u1(x16))) + _, x19 := bits.mul_u64(x11, 0xd2b51da312547e1b) + x22, x21 := bits.mul_u64(x19, 0x1000000000000000) + x24, x23 := bits.mul_u64(x19, 0x14def9dea2f79cd6) + x26, x25 := bits.mul_u64(x19, 0x5812631a5cf5d3ed) + x27, x28 := bits.add_u64(x26, x23, u64(0x0)) + _, x30 := bits.add_u64(x11, x25, u64(0x0)) + x31, x32 := bits.add_u64(x13, x27, u64(fiat.u1(x30))) + x33, x34 := bits.add_u64(x15, (u64(fiat.u1(x28)) + x24), u64(fiat.u1(x32))) + x35, x36 := bits.add_u64(x17, x21, u64(fiat.u1(x34))) + x38, x37 := bits.mul_u64(x1, 0x399411b7c309a3d) + x40, x39 := bits.mul_u64(x1, 0xceec73d217f5be65) + x42, x41 := bits.mul_u64(x1, 0xd00e1ba768859347) + x44, x43 := bits.mul_u64(x1, 0xa40611e3449c0f01) + x45, x46 := bits.add_u64(x44, x41, u64(0x0)) + x47, x48 := bits.add_u64(x42, x39, u64(fiat.u1(x46))) + x49, x50 := bits.add_u64(x40, x37, u64(fiat.u1(x48))) + x51, x52 := bits.add_u64(x31, x43, u64(0x0)) + x53, x54 := bits.add_u64(x33, x45, u64(fiat.u1(x52))) + x55, x56 := bits.add_u64(x35, x47, u64(fiat.u1(x54))) + x57, x58 := bits.add_u64( + ((u64(fiat.u1(x36)) + (u64(fiat.u1(x18)) + x6)) + x22), + x49, + u64(fiat.u1(x56)), + ) + _, x59 := bits.mul_u64(x51, 0xd2b51da312547e1b) + x62, x61 := bits.mul_u64(x59, 0x1000000000000000) + x64, x63 := bits.mul_u64(x59, 0x14def9dea2f79cd6) + x66, x65 := bits.mul_u64(x59, 0x5812631a5cf5d3ed) + x67, x68 := bits.add_u64(x66, x63, u64(0x0)) + _, x70 := bits.add_u64(x51, x65, u64(0x0)) + x71, x72 := bits.add_u64(x53, x67, u64(fiat.u1(x70))) + x73, x74 := bits.add_u64(x55, (u64(fiat.u1(x68)) + x64), u64(fiat.u1(x72))) + x75, x76 := bits.add_u64(x57, x61, u64(fiat.u1(x74))) + x78, x77 := bits.mul_u64(x2, 0x399411b7c309a3d) + x80, x79 := bits.mul_u64(x2, 0xceec73d217f5be65) + x82, x81 := bits.mul_u64(x2, 0xd00e1ba768859347) + x84, x83 := bits.mul_u64(x2, 0xa40611e3449c0f01) + x85, x86 := bits.add_u64(x84, x81, u64(0x0)) + x87, x88 := bits.add_u64(x82, x79, u64(fiat.u1(x86))) + x89, x90 := bits.add_u64(x80, x77, u64(fiat.u1(x88))) + x91, x92 := bits.add_u64(x71, x83, u64(0x0)) + x93, x94 := bits.add_u64(x73, x85, u64(fiat.u1(x92))) + x95, x96 := bits.add_u64(x75, x87, u64(fiat.u1(x94))) + x97, x98 := bits.add_u64( + ((u64(fiat.u1(x76)) + (u64(fiat.u1(x58)) + (u64(fiat.u1(x50)) + x38))) + x62), + x89, + u64(fiat.u1(x96)), + ) + _, x99 := bits.mul_u64(x91, 0xd2b51da312547e1b) + x102, x101 := bits.mul_u64(x99, 0x1000000000000000) + x104, x103 := bits.mul_u64(x99, 0x14def9dea2f79cd6) + x106, x105 := bits.mul_u64(x99, 0x5812631a5cf5d3ed) + x107, x108 := bits.add_u64(x106, x103, u64(0x0)) + _, x110 := bits.add_u64(x91, x105, u64(0x0)) + x111, x112 := bits.add_u64(x93, x107, u64(fiat.u1(x110))) + x113, x114 := bits.add_u64(x95, (u64(fiat.u1(x108)) + x104), u64(fiat.u1(x112))) + x115, x116 := bits.add_u64(x97, x101, u64(fiat.u1(x114))) + x118, x117 := bits.mul_u64(x3, 0x399411b7c309a3d) + x120, x119 := bits.mul_u64(x3, 0xceec73d217f5be65) + x122, x121 := bits.mul_u64(x3, 0xd00e1ba768859347) + x124, x123 := bits.mul_u64(x3, 0xa40611e3449c0f01) + x125, x126 := bits.add_u64(x124, x121, u64(0x0)) + x127, x128 := bits.add_u64(x122, x119, u64(fiat.u1(x126))) + x129, x130 := bits.add_u64(x120, x117, u64(fiat.u1(x128))) + x131, x132 := bits.add_u64(x111, x123, u64(0x0)) + x133, x134 := bits.add_u64(x113, x125, u64(fiat.u1(x132))) + x135, x136 := bits.add_u64(x115, x127, u64(fiat.u1(x134))) + x137, x138 := bits.add_u64( + ((u64(fiat.u1(x116)) + (u64(fiat.u1(x98)) + (u64(fiat.u1(x90)) + x78))) + x102), + x129, + u64(fiat.u1(x136)), + ) + _, x139 := bits.mul_u64(x131, 0xd2b51da312547e1b) + x142, x141 := bits.mul_u64(x139, 0x1000000000000000) + x144, x143 := bits.mul_u64(x139, 0x14def9dea2f79cd6) + x146, x145 := bits.mul_u64(x139, 0x5812631a5cf5d3ed) + x147, x148 := bits.add_u64(x146, x143, u64(0x0)) + _, x150 := bits.add_u64(x131, x145, u64(0x0)) + x151, x152 := bits.add_u64(x133, x147, u64(fiat.u1(x150))) + x153, x154 := bits.add_u64(x135, (u64(fiat.u1(x148)) + x144), u64(fiat.u1(x152))) + x155, x156 := bits.add_u64(x137, x141, u64(fiat.u1(x154))) + x157 := ((u64(fiat.u1(x156)) + (u64(fiat.u1(x138)) + (u64(fiat.u1(x130)) + x118))) + x142) + x158, x159 := bits.sub_u64(x151, 0x5812631a5cf5d3ed, u64(0x0)) + x160, x161 := bits.sub_u64(x153, 0x14def9dea2f79cd6, u64(fiat.u1(x159))) + x162, x163 := bits.sub_u64(x155, u64(0x0), u64(fiat.u1(x161))) + x164, x165 := bits.sub_u64(x157, 0x1000000000000000, u64(fiat.u1(x163))) + _, x167 := bits.sub_u64(u64(0x0), u64(0x0), u64(fiat.u1(x165))) + x168 := fiat.cmovznz_u64(fiat.u1(x167), x158, x151) + x169 := fiat.cmovznz_u64(fiat.u1(x167), x160, x153) + x170 := fiat.cmovznz_u64(fiat.u1(x167), x162, x155) + x171 := fiat.cmovznz_u64(fiat.u1(x167), x164, x157) + out1[0] = x168 + out1[1] = x169 + out1[2] = x170 + out1[3] = x171 +} diff --git a/core/crypto/_sha3/sha3.odin b/core/crypto/_sha3/sha3.odin index 9846aca42..2db76fce0 100644 --- a/core/crypto/_sha3/sha3.odin +++ b/core/crypto/_sha3/sha3.odin @@ -7,163 +7,208 @@ package _sha3 List of contributors: zhibog, dotbmp: Initial implementation. - Implementation of the Keccak hashing algorithm, standardized as SHA3 in - To use the original Keccak padding, set the is_keccak bool to true, otherwise it will use SHA3 padding. + Implementation of the Keccak hashing algorithm, standardized as SHA3 + in . + + As the only difference between the legacy Keccak and SHA3 is the domain + separation byte, set dsbyte to the appropriate value to pick the desired + algorithm. */ -import "../util" +import "core:math/bits" +import "core:mem" ROUNDS :: 24 -Sha3_Context :: struct { - st: struct #raw_union { - b: [200]u8, - q: [25]u64, - }, - pt: int, - rsiz: int, - mdlen: int, - is_keccak: bool, +RATE_128 :: 1344 / 8 // ONLY for SHAKE128. +RATE_224 :: 1152 / 8 +RATE_256 :: 1088 / 8 +RATE_384 :: 832 / 8 +RATE_512 :: 576 / 8 + +DS_KECCAK :: 0x01 +DS_SHA3 :: 0x06 +DS_SHAKE :: 0x1f +DS_CSHAKE :: 0x04 + +Context :: struct { + st: struct #raw_union { + b: [200]u8, + q: [25]u64, + }, + pt: int, + rsiz: int, + mdlen: int, + dsbyte: byte, + is_initialized: bool, + is_finalized: bool, // For SHAKE (unlimited squeeze is allowed) } +@(private) +keccakf_rndc := [?]u64 { + 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, + 0x8000000080008000, 0x000000000000808b, 0x0000000080000001, + 0x8000000080008081, 0x8000000000008009, 0x000000000000008a, + 0x0000000000000088, 0x0000000080008009, 0x000000008000000a, + 0x000000008000808b, 0x800000000000008b, 0x8000000000008089, + 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, + 0x000000000000800a, 0x800000008000000a, 0x8000000080008081, + 0x8000000000008080, 0x0000000080000001, 0x8000000080008008, +} + +@(private) +keccakf_rotc := [?]int { + 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, + 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44, +} + +@(private) +keccakf_piln := [?]i32 { + 10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, + 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1, +} + +@(private) keccakf :: proc "contextless" (st: ^[25]u64) { - keccakf_rndc := [?]u64 { - 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, - 0x8000000080008000, 0x000000000000808b, 0x0000000080000001, - 0x8000000080008081, 0x8000000000008009, 0x000000000000008a, - 0x0000000000000088, 0x0000000080008009, 0x000000008000000a, - 0x000000008000808b, 0x800000000000008b, 0x8000000000008089, - 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, - 0x000000000000800a, 0x800000008000000a, 0x8000000080008081, - 0x8000000000008080, 0x0000000080000001, 0x8000000080008008, - } + i, j, r: i32 = ---, ---, --- + t: u64 = --- + bc: [5]u64 = --- - keccakf_rotc := [?]i32 { - 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, - 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44, - } + when ODIN_ENDIAN != .Little { + for i = 0; i < 25; i += 1 { + st[i] = bits.byte_swap(st[i]) + } + } - keccakf_piln := [?]i32 { - 10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, - 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1, - } + for r = 0; r < ROUNDS; r += 1 { + // theta + for i = 0; i < 5; i += 1 { + bc[i] = st[i] ~ st[i + 5] ~ st[i + 10] ~ st[i + 15] ~ st[i + 20] + } - i, j, r: i32 = ---, ---, --- - t: u64 = --- - bc: [5]u64 = --- + for i = 0; i < 5; i += 1 { + t = bc[(i + 4) % 5] ~ bits.rotate_left64(bc[(i + 1) % 5], 1) + for j = 0; j < 25; j += 5 { + st[j + i] ~= t + } + } - when ODIN_ENDIAN != .Little { - v: uintptr = --- - for i = 0; i < 25; i += 1 { - v := uintptr(&st[i]) - st[i] = u64((^u8)(v + 0)^ << 0) | u64((^u8)(v + 1)^ << 8) | - u64((^u8)(v + 2)^ << 16) | u64((^u8)(v + 3)^ << 24) | - u64((^u8)(v + 4)^ << 32) | u64((^u8)(v + 5)^ << 40) | - u64((^u8)(v + 6)^ << 48) | u64((^u8)(v + 7)^ << 56) - } - } + // rho pi + t = st[1] + for i = 0; i < 24; i += 1 { + j = keccakf_piln[i] + bc[0] = st[j] + st[j] = bits.rotate_left64(t, keccakf_rotc[i]) + t = bc[0] + } - for r = 0; r < ROUNDS; r += 1 { - // theta - for i = 0; i < 5; i += 1 { - bc[i] = st[i] ~ st[i + 5] ~ st[i + 10] ~ st[i + 15] ~ st[i + 20] - } + // chi + for j = 0; j < 25; j += 5 { + for i = 0; i < 5; i += 1 { + bc[i] = st[j + i] + } + for i = 0; i < 5; i += 1 { + st[j + i] ~= ~bc[(i + 1) % 5] & bc[(i + 2) % 5] + } + } - for i = 0; i < 5; i += 1 { - t = bc[(i + 4) % 5] ~ util.ROTL64(bc[(i + 1) % 5], 1) - for j = 0; j < 25; j += 5 { - st[j + i] ~= t - } - } + st[0] ~= keccakf_rndc[r] + } - // rho pi - t = st[1] - for i = 0; i < 24; i += 1 { - j = keccakf_piln[i] - bc[0] = st[j] - st[j] = util.ROTL64(t, u64(keccakf_rotc[i])) - t = bc[0] - } - - // chi - for j = 0; j < 25; j += 5 { - for i = 0; i < 5; i += 1 { - bc[i] = st[j + i] - } - for i = 0; i < 5; i += 1 { - st[j + i] ~= ~bc[(i + 1) % 5] & bc[(i + 2) % 5] - } - } - - st[0] ~= keccakf_rndc[r] - } - - when ODIN_ENDIAN != .Little { - for i = 0; i < 25; i += 1 { - v = uintptr(&st[i]) - t = st[i] - (^u8)(v + 0)^ = (t >> 0) & 0xff - (^u8)(v + 1)^ = (t >> 8) & 0xff - (^u8)(v + 2)^ = (t >> 16) & 0xff - (^u8)(v + 3)^ = (t >> 24) & 0xff - (^u8)(v + 4)^ = (t >> 32) & 0xff - (^u8)(v + 5)^ = (t >> 40) & 0xff - (^u8)(v + 6)^ = (t >> 48) & 0xff - (^u8)(v + 7)^ = (t >> 56) & 0xff - } - } + when ODIN_ENDIAN != .Little { + for i = 0; i < 25; i += 1 { + st[i] = bits.byte_swap(st[i]) + } + } } -init :: proc "contextless" (c: ^Sha3_Context) { - for i := 0; i < 25; i += 1 { - c.st.q[i] = 0 - } - c.rsiz = 200 - 2 * c.mdlen +init :: proc(ctx: ^Context) { + for i := 0; i < 25; i += 1 { + ctx.st.q[i] = 0 + } + ctx.rsiz = 200 - 2 * ctx.mdlen + ctx.pt = 0 + + ctx.is_initialized = true + ctx.is_finalized = false } -update :: proc "contextless" (c: ^Sha3_Context, data: []byte) { - j := c.pt - for i := 0; i < len(data); i += 1 { - c.st.b[j] ~= data[i] - j += 1 - if j >= c.rsiz { - keccakf(&c.st.q) - j = 0 - } - } - c.pt = j +update :: proc(ctx: ^Context, data: []byte) { + assert(ctx.is_initialized) + assert(!ctx.is_finalized) + + j := ctx.pt + for i := 0; i < len(data); i += 1 { + ctx.st.b[j] ~= data[i] + j += 1 + if j >= ctx.rsiz { + keccakf(&ctx.st.q) + j = 0 + } + } + ctx.pt = j } -final :: proc "contextless" (c: ^Sha3_Context, hash: []byte) { - if c.is_keccak { - c.st.b[c.pt] ~= 0x01 - } else { - c.st.b[c.pt] ~= 0x06 - } - - c.st.b[c.rsiz - 1] ~= 0x80 - keccakf(&c.st.q) - for i := 0; i < c.mdlen; i += 1 { - hash[i] = c.st.b[i] - } +final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) { + assert(ctx.is_initialized) + + if len(hash) < ctx.mdlen { + panic("crypto/sha3: invalid destination digest size") + } + + ctx := ctx + if finalize_clone { + tmp_ctx: Context + clone(&tmp_ctx, ctx) + ctx = &tmp_ctx + } + defer (reset(ctx)) + + ctx.st.b[ctx.pt] ~= ctx.dsbyte + + ctx.st.b[ctx.rsiz - 1] ~= 0x80 + keccakf(&ctx.st.q) + for i := 0; i < ctx.mdlen; i += 1 { + hash[i] = ctx.st.b[i] + } } -shake_xof :: proc "contextless" (c: ^Sha3_Context) { - c.st.b[c.pt] ~= 0x1F - c.st.b[c.rsiz - 1] ~= 0x80 - keccakf(&c.st.q) - c.pt = 0 +clone :: proc(ctx, other: ^Context) { + ctx^ = other^ } -shake_out :: proc "contextless" (c: ^Sha3_Context, hash: []byte) { - j := c.pt - for i := 0; i < len(hash); i += 1 { - if j >= c.rsiz { - keccakf(&c.st.q) - j = 0 - } - hash[i] = c.st.b[j] - j += 1 - } - c.pt = j +reset :: proc(ctx: ^Context) { + if !ctx.is_initialized { + return + } + + mem.zero_explicit(ctx, size_of(ctx^)) +} + +shake_xof :: proc(ctx: ^Context) { + assert(ctx.is_initialized) + assert(!ctx.is_finalized) + + ctx.st.b[ctx.pt] ~= ctx.dsbyte + ctx.st.b[ctx.rsiz - 1] ~= 0x80 + keccakf(&ctx.st.q) + ctx.pt = 0 + + ctx.is_finalized = true // No more absorb, unlimited squeeze. +} + +shake_out :: proc(ctx: ^Context, hash: []byte) { + assert(ctx.is_initialized) + assert(ctx.is_finalized) + + j := ctx.pt + for i := 0; i < len(hash); i += 1 { + if j >= ctx.rsiz { + keccakf(&ctx.st.q) + j = 0 + } + hash[i] = ctx.st.b[j] + j += 1 + } + ctx.pt = j } diff --git a/core/crypto/_sha3/sp800_185.odin b/core/crypto/_sha3/sp800_185.odin new file mode 100644 index 000000000..a96f78cc1 --- /dev/null +++ b/core/crypto/_sha3/sp800_185.odin @@ -0,0 +1,147 @@ +package _sha3 + +import "core:encoding/endian" +import "core:math/bits" + +init_cshake :: proc(ctx: ^Context, n, s: []byte, sec_strength: int) { + ctx.mdlen = sec_strength / 8 + + // No domain separator is equivalent to vanilla SHAKE. + if len(n) == 0 && len(s) == 0 { + ctx.dsbyte = DS_SHAKE + init(ctx) + return + } + + ctx.dsbyte = DS_CSHAKE + init(ctx) + bytepad(ctx, [][]byte{n, s}, rate_cshake(sec_strength)) +} + +final_cshake :: proc(ctx: ^Context, dst: []byte, finalize_clone: bool = false) { + ctx := ctx + if finalize_clone { + tmp_ctx: Context + clone(&tmp_ctx, ctx) + ctx = &tmp_ctx + } + defer reset(ctx) + + encode_byte_len(ctx, len(dst), false) // right_encode + shake_xof(ctx) + shake_out(ctx, dst) +} + +rate_cshake :: #force_inline proc(sec_strength: int) -> int { + switch sec_strength { + case 128: + return RATE_128 + case 256: + return RATE_256 + } + + panic("crypto/sha3: invalid security strength") +} + +// right_encode and left_encode are defined to support 0 <= x < 2^2040 +// however, the largest value we will ever need to encode is `max(int) * 8`. +// +// This is unfortunate as the extreme upper edge is larger than +// `max(u64)`. While such values are impractical at present, +// they are possible (ie: https://arxiv.org/pdf/quant-ph/9908043.pdf). +// +// Thus we support 0 <= x < 2^128. + +@(private) +_PAD: [RATE_128]byte // Biggest possible value of w per spec. + +bytepad :: proc(ctx: ^Context, x_strings: [][]byte, w: int) { + // 1. z = left_encode(w) || X. + z_hi: u64 + z_lo := left_right_encode(ctx, 0, u64(w), true) + for x in x_strings { + // All uses of bytepad in SP 800-185 use the output from + // one or more encode_string values for `X`. + hi, lo := encode_string(ctx, x) + + carry: u64 + z_lo, carry = bits.add_u64(z_lo, lo, 0) + z_hi, carry = bits.add_u64(z_hi, hi, carry) + + // This isn't actually possible, at least with the currently + // defined SP 800-185 routines. + if carry != 0 { + panic("crypto/sha3: bytepad input length overflow") + } + } + + // We skip this step as we are doing a byte-oriented implementation + // rather than a bit oriented one. + // + // 2. while len(z) mod 8 ≠ 0: + // z = z || 0 + + // 3. while (len(z)/8) mod w != 0: + // z = z || 00000000 + z_len := u128(z_hi) << 64 | u128(z_lo) + z_rem := int(z_len % u128(w)) + if z_rem != 0 { + pad := _PAD[:w - z_rem] + + // We just add the padding to the state, instead of returning z. + // + // 4. return z. + update(ctx, pad) + } +} + +encode_string :: #force_inline proc(ctx: ^Context, s: []byte) -> (u64, u64) { + l := encode_byte_len(ctx, len(s), true) // left_encode + update(ctx, s) + + lo, hi := bits.add_u64(l, u64(len(s)), 0) + + return hi, lo +} + +encode_byte_len :: #force_inline proc(ctx: ^Context, l: int, is_left: bool) -> u64 { + hi, lo := bits.mul_u64(u64(l), 8) + return left_right_encode(ctx, hi, lo, is_left) +} + +@(private) +left_right_encode :: proc(ctx: ^Context, hi, lo: u64, is_left: bool) -> u64 { + HI_OFFSET :: 1 + LO_OFFSET :: HI_OFFSET + 8 + RIGHT_OFFSET :: LO_OFFSET + 8 + BUF_LEN :: RIGHT_OFFSET + 1 + + buf: [BUF_LEN]byte // prefix + largest uint + postfix + + endian.unchecked_put_u64be(buf[HI_OFFSET:], hi) + endian.unchecked_put_u64be(buf[LO_OFFSET:], lo) + + // 2. Strip leading `0x00` bytes. + off: int + for off = HI_OFFSET; off < RIGHT_OFFSET - 1; off = off + 1 {// Note: Minimum size is 1, not 0. + if buf[off] != 0 { + break + } + } + n := byte(RIGHT_OFFSET - off) + + // 3. Prefix (left_encode) or postfix (right_encode) the length in bytes. + b: []byte + switch is_left { + case true: + buf[off - 1] = n // n | x + b = buf[off - 1:RIGHT_OFFSET] + case false: + buf[RIGHT_OFFSET] = n // x | n + b = buf[off:] + } + + update(ctx, b) + + return u64(len(b)) +} diff --git a/core/crypto/_tiger/tiger.odin b/core/crypto/_tiger/tiger.odin deleted file mode 100644 index e1629c4ca..000000000 --- a/core/crypto/_tiger/tiger.odin +++ /dev/null @@ -1,410 +0,0 @@ -package _tiger - -/* - Copyright 2021 zhibog - Made available under the BSD-3 license. - - List of contributors: - zhibog, dotbmp: Initial implementation. - - Implementation of the Tiger hashing algorithm, as defined in -*/ - -import "../util" - -T1 := [?]u64 { - 0x02aab17cf7e90c5e, 0xac424b03e243a8ec, 0x72cd5be30dd5fcd3, 0x6d019b93f6f97f3a, - 0xcd9978ffd21f9193, 0x7573a1c9708029e2, 0xb164326b922a83c3, 0x46883eee04915870, - 0xeaace3057103ece6, 0xc54169b808a3535c, 0x4ce754918ddec47c, 0x0aa2f4dfdc0df40c, - 0x10b76f18a74dbefa, 0xc6ccb6235ad1ab6a, 0x13726121572fe2ff, 0x1a488c6f199d921e, - 0x4bc9f9f4da0007ca, 0x26f5e6f6e85241c7, 0x859079dbea5947b6, 0x4f1885c5c99e8c92, - 0xd78e761ea96f864b, 0x8e36428c52b5c17d, 0x69cf6827373063c1, 0xb607c93d9bb4c56e, - 0x7d820e760e76b5ea, 0x645c9cc6f07fdc42, 0xbf38a078243342e0, 0x5f6b343c9d2e7d04, - 0xf2c28aeb600b0ec6, 0x6c0ed85f7254bcac, 0x71592281a4db4fe5, 0x1967fa69ce0fed9f, - 0xfd5293f8b96545db, 0xc879e9d7f2a7600b, 0x860248920193194e, 0xa4f9533b2d9cc0b3, - 0x9053836c15957613, 0xdb6dcf8afc357bf1, 0x18beea7a7a370f57, 0x037117ca50b99066, - 0x6ab30a9774424a35, 0xf4e92f02e325249b, 0x7739db07061ccae1, 0xd8f3b49ceca42a05, - 0xbd56be3f51382f73, 0x45faed5843b0bb28, 0x1c813d5c11bf1f83, 0x8af0e4b6d75fa169, - 0x33ee18a487ad9999, 0x3c26e8eab1c94410, 0xb510102bc0a822f9, 0x141eef310ce6123b, - 0xfc65b90059ddb154, 0xe0158640c5e0e607, 0x884e079826c3a3cf, 0x930d0d9523c535fd, - 0x35638d754e9a2b00, 0x4085fccf40469dd5, 0xc4b17ad28be23a4c, 0xcab2f0fc6a3e6a2e, - 0x2860971a6b943fcd, 0x3dde6ee212e30446, 0x6222f32ae01765ae, 0x5d550bb5478308fe, - 0xa9efa98da0eda22a, 0xc351a71686c40da7, 0x1105586d9c867c84, 0xdcffee85fda22853, - 0xccfbd0262c5eef76, 0xbaf294cb8990d201, 0xe69464f52afad975, 0x94b013afdf133e14, - 0x06a7d1a32823c958, 0x6f95fe5130f61119, 0xd92ab34e462c06c0, 0xed7bde33887c71d2, - 0x79746d6e6518393e, 0x5ba419385d713329, 0x7c1ba6b948a97564, 0x31987c197bfdac67, - 0xde6c23c44b053d02, 0x581c49fed002d64d, 0xdd474d6338261571, 0xaa4546c3e473d062, - 0x928fce349455f860, 0x48161bbacaab94d9, 0x63912430770e6f68, 0x6ec8a5e602c6641c, - 0x87282515337ddd2b, 0x2cda6b42034b701b, 0xb03d37c181cb096d, 0xe108438266c71c6f, - 0x2b3180c7eb51b255, 0xdf92b82f96c08bbc, 0x5c68c8c0a632f3ba, 0x5504cc861c3d0556, - 0xabbfa4e55fb26b8f, 0x41848b0ab3baceb4, 0xb334a273aa445d32, 0xbca696f0a85ad881, - 0x24f6ec65b528d56c, 0x0ce1512e90f4524a, 0x4e9dd79d5506d35a, 0x258905fac6ce9779, - 0x2019295b3e109b33, 0xf8a9478b73a054cc, 0x2924f2f934417eb0, 0x3993357d536d1bc4, - 0x38a81ac21db6ff8b, 0x47c4fbf17d6016bf, 0x1e0faadd7667e3f5, 0x7abcff62938beb96, - 0xa78dad948fc179c9, 0x8f1f98b72911e50d, 0x61e48eae27121a91, 0x4d62f7ad31859808, - 0xeceba345ef5ceaeb, 0xf5ceb25ebc9684ce, 0xf633e20cb7f76221, 0xa32cdf06ab8293e4, - 0x985a202ca5ee2ca4, 0xcf0b8447cc8a8fb1, 0x9f765244979859a3, 0xa8d516b1a1240017, - 0x0bd7ba3ebb5dc726, 0xe54bca55b86adb39, 0x1d7a3afd6c478063, 0x519ec608e7669edd, - 0x0e5715a2d149aa23, 0x177d4571848ff194, 0xeeb55f3241014c22, 0x0f5e5ca13a6e2ec2, - 0x8029927b75f5c361, 0xad139fabc3d6e436, 0x0d5df1a94ccf402f, 0x3e8bd948bea5dfc8, - 0xa5a0d357bd3ff77e, 0xa2d12e251f74f645, 0x66fd9e525e81a082, 0x2e0c90ce7f687a49, - 0xc2e8bcbeba973bc5, 0x000001bce509745f, 0x423777bbe6dab3d6, 0xd1661c7eaef06eb5, - 0xa1781f354daacfd8, 0x2d11284a2b16affc, 0xf1fc4f67fa891d1f, 0x73ecc25dcb920ada, - 0xae610c22c2a12651, 0x96e0a810d356b78a, 0x5a9a381f2fe7870f, 0xd5ad62ede94e5530, - 0xd225e5e8368d1427, 0x65977b70c7af4631, 0x99f889b2de39d74f, 0x233f30bf54e1d143, - 0x9a9675d3d9a63c97, 0x5470554ff334f9a8, 0x166acb744a4f5688, 0x70c74caab2e4aead, - 0xf0d091646f294d12, 0x57b82a89684031d1, 0xefd95a5a61be0b6b, 0x2fbd12e969f2f29a, - 0x9bd37013feff9fe8, 0x3f9b0404d6085a06, 0x4940c1f3166cfe15, 0x09542c4dcdf3defb, - 0xb4c5218385cd5ce3, 0xc935b7dc4462a641, 0x3417f8a68ed3b63f, 0xb80959295b215b40, - 0xf99cdaef3b8c8572, 0x018c0614f8fcb95d, 0x1b14accd1a3acdf3, 0x84d471f200bb732d, - 0xc1a3110e95e8da16, 0x430a7220bf1a82b8, 0xb77e090d39df210e, 0x5ef4bd9f3cd05e9d, - 0x9d4ff6da7e57a444, 0xda1d60e183d4a5f8, 0xb287c38417998e47, 0xfe3edc121bb31886, - 0xc7fe3ccc980ccbef, 0xe46fb590189bfd03, 0x3732fd469a4c57dc, 0x7ef700a07cf1ad65, - 0x59c64468a31d8859, 0x762fb0b4d45b61f6, 0x155baed099047718, 0x68755e4c3d50baa6, - 0xe9214e7f22d8b4df, 0x2addbf532eac95f4, 0x32ae3909b4bd0109, 0x834df537b08e3450, - 0xfa209da84220728d, 0x9e691d9b9efe23f7, 0x0446d288c4ae8d7f, 0x7b4cc524e169785b, - 0x21d87f0135ca1385, 0xcebb400f137b8aa5, 0x272e2b66580796be, 0x3612264125c2b0de, - 0x057702bdad1efbb2, 0xd4babb8eacf84be9, 0x91583139641bc67b, 0x8bdc2de08036e024, - 0x603c8156f49f68ed, 0xf7d236f7dbef5111, 0x9727c4598ad21e80, 0xa08a0896670a5fd7, - 0xcb4a8f4309eba9cb, 0x81af564b0f7036a1, 0xc0b99aa778199abd, 0x959f1ec83fc8e952, - 0x8c505077794a81b9, 0x3acaaf8f056338f0, 0x07b43f50627a6778, 0x4a44ab49f5eccc77, - 0x3bc3d6e4b679ee98, 0x9cc0d4d1cf14108c, 0x4406c00b206bc8a0, 0x82a18854c8d72d89, - 0x67e366b35c3c432c, 0xb923dd61102b37f2, 0x56ab2779d884271d, 0xbe83e1b0ff1525af, - 0xfb7c65d4217e49a9, 0x6bdbe0e76d48e7d4, 0x08df828745d9179e, 0x22ea6a9add53bd34, - 0xe36e141c5622200a, 0x7f805d1b8cb750ee, 0xafe5c7a59f58e837, 0xe27f996a4fb1c23c, - 0xd3867dfb0775f0d0, 0xd0e673de6e88891a, 0x123aeb9eafb86c25, 0x30f1d5d5c145b895, - 0xbb434a2dee7269e7, 0x78cb67ecf931fa38, 0xf33b0372323bbf9c, 0x52d66336fb279c74, - 0x505f33ac0afb4eaa, 0xe8a5cd99a2cce187, 0x534974801e2d30bb, 0x8d2d5711d5876d90, - 0x1f1a412891bc038e, 0xd6e2e71d82e56648, 0x74036c3a497732b7, 0x89b67ed96361f5ab, - 0xffed95d8f1ea02a2, 0xe72b3bd61464d43d, 0xa6300f170bdc4820, 0xebc18760ed78a77a, -} - -T2 := [?]u64 { - 0xe6a6be5a05a12138, 0xb5a122a5b4f87c98, 0x563c6089140b6990, 0x4c46cb2e391f5dd5, - 0xd932addbc9b79434, 0x08ea70e42015aff5, 0xd765a6673e478cf1, 0xc4fb757eab278d99, - 0xdf11c6862d6e0692, 0xddeb84f10d7f3b16, 0x6f2ef604a665ea04, 0x4a8e0f0ff0e0dfb3, - 0xa5edeef83dbcba51, 0xfc4f0a2a0ea4371e, 0xe83e1da85cb38429, 0xdc8ff882ba1b1ce2, - 0xcd45505e8353e80d, 0x18d19a00d4db0717, 0x34a0cfeda5f38101, 0x0be77e518887caf2, - 0x1e341438b3c45136, 0xe05797f49089ccf9, 0xffd23f9df2591d14, 0x543dda228595c5cd, - 0x661f81fd99052a33, 0x8736e641db0f7b76, 0x15227725418e5307, 0xe25f7f46162eb2fa, - 0x48a8b2126c13d9fe, 0xafdc541792e76eea, 0x03d912bfc6d1898f, 0x31b1aafa1b83f51b, - 0xf1ac2796e42ab7d9, 0x40a3a7d7fcd2ebac, 0x1056136d0afbbcc5, 0x7889e1dd9a6d0c85, - 0xd33525782a7974aa, 0xa7e25d09078ac09b, 0xbd4138b3eac6edd0, 0x920abfbe71eb9e70, - 0xa2a5d0f54fc2625c, 0xc054e36b0b1290a3, 0xf6dd59ff62fe932b, 0x3537354511a8ac7d, - 0xca845e9172fadcd4, 0x84f82b60329d20dc, 0x79c62ce1cd672f18, 0x8b09a2add124642c, - 0xd0c1e96a19d9e726, 0x5a786a9b4ba9500c, 0x0e020336634c43f3, 0xc17b474aeb66d822, - 0x6a731ae3ec9baac2, 0x8226667ae0840258, 0x67d4567691caeca5, 0x1d94155c4875adb5, - 0x6d00fd985b813fdf, 0x51286efcb774cd06, 0x5e8834471fa744af, 0xf72ca0aee761ae2e, - 0xbe40e4cdaee8e09a, 0xe9970bbb5118f665, 0x726e4beb33df1964, 0x703b000729199762, - 0x4631d816f5ef30a7, 0xb880b5b51504a6be, 0x641793c37ed84b6c, 0x7b21ed77f6e97d96, - 0x776306312ef96b73, 0xae528948e86ff3f4, 0x53dbd7f286a3f8f8, 0x16cadce74cfc1063, - 0x005c19bdfa52c6dd, 0x68868f5d64d46ad3, 0x3a9d512ccf1e186a, 0x367e62c2385660ae, - 0xe359e7ea77dcb1d7, 0x526c0773749abe6e, 0x735ae5f9d09f734b, 0x493fc7cc8a558ba8, - 0xb0b9c1533041ab45, 0x321958ba470a59bd, 0x852db00b5f46c393, 0x91209b2bd336b0e5, - 0x6e604f7d659ef19f, 0xb99a8ae2782ccb24, 0xccf52ab6c814c4c7, 0x4727d9afbe11727b, - 0x7e950d0c0121b34d, 0x756f435670ad471f, 0xf5add442615a6849, 0x4e87e09980b9957a, - 0x2acfa1df50aee355, 0xd898263afd2fd556, 0xc8f4924dd80c8fd6, 0xcf99ca3d754a173a, - 0xfe477bacaf91bf3c, 0xed5371f6d690c12d, 0x831a5c285e687094, 0xc5d3c90a3708a0a4, - 0x0f7f903717d06580, 0x19f9bb13b8fdf27f, 0xb1bd6f1b4d502843, 0x1c761ba38fff4012, - 0x0d1530c4e2e21f3b, 0x8943ce69a7372c8a, 0xe5184e11feb5ce66, 0x618bdb80bd736621, - 0x7d29bad68b574d0b, 0x81bb613e25e6fe5b, 0x071c9c10bc07913f, 0xc7beeb7909ac2d97, - 0xc3e58d353bc5d757, 0xeb017892f38f61e8, 0xd4effb9c9b1cc21a, 0x99727d26f494f7ab, - 0xa3e063a2956b3e03, 0x9d4a8b9a4aa09c30, 0x3f6ab7d500090fb4, 0x9cc0f2a057268ac0, - 0x3dee9d2dedbf42d1, 0x330f49c87960a972, 0xc6b2720287421b41, 0x0ac59ec07c00369c, - 0xef4eac49cb353425, 0xf450244eef0129d8, 0x8acc46e5caf4deb6, 0x2ffeab63989263f7, - 0x8f7cb9fe5d7a4578, 0x5bd8f7644e634635, 0x427a7315bf2dc900, 0x17d0c4aa2125261c, - 0x3992486c93518e50, 0xb4cbfee0a2d7d4c3, 0x7c75d6202c5ddd8d, 0xdbc295d8e35b6c61, - 0x60b369d302032b19, 0xce42685fdce44132, 0x06f3ddb9ddf65610, 0x8ea4d21db5e148f0, - 0x20b0fce62fcd496f, 0x2c1b912358b0ee31, 0xb28317b818f5a308, 0xa89c1e189ca6d2cf, - 0x0c6b18576aaadbc8, 0xb65deaa91299fae3, 0xfb2b794b7f1027e7, 0x04e4317f443b5beb, - 0x4b852d325939d0a6, 0xd5ae6beefb207ffc, 0x309682b281c7d374, 0xbae309a194c3b475, - 0x8cc3f97b13b49f05, 0x98a9422ff8293967, 0x244b16b01076ff7c, 0xf8bf571c663d67ee, - 0x1f0d6758eee30da1, 0xc9b611d97adeb9b7, 0xb7afd5887b6c57a2, 0x6290ae846b984fe1, - 0x94df4cdeacc1a5fd, 0x058a5bd1c5483aff, 0x63166cc142ba3c37, 0x8db8526eb2f76f40, - 0xe10880036f0d6d4e, 0x9e0523c9971d311d, 0x45ec2824cc7cd691, 0x575b8359e62382c9, - 0xfa9e400dc4889995, 0xd1823ecb45721568, 0xdafd983b8206082f, 0xaa7d29082386a8cb, - 0x269fcd4403b87588, 0x1b91f5f728bdd1e0, 0xe4669f39040201f6, 0x7a1d7c218cf04ade, - 0x65623c29d79ce5ce, 0x2368449096c00bb1, 0xab9bf1879da503ba, 0xbc23ecb1a458058e, - 0x9a58df01bb401ecc, 0xa070e868a85f143d, 0x4ff188307df2239e, 0x14d565b41a641183, - 0xee13337452701602, 0x950e3dcf3f285e09, 0x59930254b9c80953, 0x3bf299408930da6d, - 0xa955943f53691387, 0xa15edecaa9cb8784, 0x29142127352be9a0, 0x76f0371fff4e7afb, - 0x0239f450274f2228, 0xbb073af01d5e868b, 0xbfc80571c10e96c1, 0xd267088568222e23, - 0x9671a3d48e80b5b0, 0x55b5d38ae193bb81, 0x693ae2d0a18b04b8, 0x5c48b4ecadd5335f, - 0xfd743b194916a1ca, 0x2577018134be98c4, 0xe77987e83c54a4ad, 0x28e11014da33e1b9, - 0x270cc59e226aa213, 0x71495f756d1a5f60, 0x9be853fb60afef77, 0xadc786a7f7443dbf, - 0x0904456173b29a82, 0x58bc7a66c232bd5e, 0xf306558c673ac8b2, 0x41f639c6b6c9772a, - 0x216defe99fda35da, 0x11640cc71c7be615, 0x93c43694565c5527, 0xea038e6246777839, - 0xf9abf3ce5a3e2469, 0x741e768d0fd312d2, 0x0144b883ced652c6, 0xc20b5a5ba33f8552, - 0x1ae69633c3435a9d, 0x97a28ca4088cfdec, 0x8824a43c1e96f420, 0x37612fa66eeea746, - 0x6b4cb165f9cf0e5a, 0x43aa1c06a0abfb4a, 0x7f4dc26ff162796b, 0x6cbacc8e54ed9b0f, - 0xa6b7ffefd2bb253e, 0x2e25bc95b0a29d4f, 0x86d6a58bdef1388c, 0xded74ac576b6f054, - 0x8030bdbc2b45805d, 0x3c81af70e94d9289, 0x3eff6dda9e3100db, 0xb38dc39fdfcc8847, - 0x123885528d17b87e, 0xf2da0ed240b1b642, 0x44cefadcd54bf9a9, 0x1312200e433c7ee6, - 0x9ffcc84f3a78c748, 0xf0cd1f72248576bb, 0xec6974053638cfe4, 0x2ba7b67c0cec4e4c, - 0xac2f4df3e5ce32ed, 0xcb33d14326ea4c11, 0xa4e9044cc77e58bc, 0x5f513293d934fcef, - 0x5dc9645506e55444, 0x50de418f317de40a, 0x388cb31a69dde259, 0x2db4a83455820a86, - 0x9010a91e84711ae9, 0x4df7f0b7b1498371, 0xd62a2eabc0977179, 0x22fac097aa8d5c0e, -} - -T3 := [?]u64 { - 0xf49fcc2ff1daf39b, 0x487fd5c66ff29281, 0xe8a30667fcdca83f, 0x2c9b4be3d2fcce63, - 0xda3ff74b93fbbbc2, 0x2fa165d2fe70ba66, 0xa103e279970e93d4, 0xbecdec77b0e45e71, - 0xcfb41e723985e497, 0xb70aaa025ef75017, 0xd42309f03840b8e0, 0x8efc1ad035898579, - 0x96c6920be2b2abc5, 0x66af4163375a9172, 0x2174abdcca7127fb, 0xb33ccea64a72ff41, - 0xf04a4933083066a5, 0x8d970acdd7289af5, 0x8f96e8e031c8c25e, 0xf3fec02276875d47, - 0xec7bf310056190dd, 0xf5adb0aebb0f1491, 0x9b50f8850fd58892, 0x4975488358b74de8, - 0xa3354ff691531c61, 0x0702bbe481d2c6ee, 0x89fb24057deded98, 0xac3075138596e902, - 0x1d2d3580172772ed, 0xeb738fc28e6bc30d, 0x5854ef8f63044326, 0x9e5c52325add3bbe, - 0x90aa53cf325c4623, 0xc1d24d51349dd067, 0x2051cfeea69ea624, 0x13220f0a862e7e4f, - 0xce39399404e04864, 0xd9c42ca47086fcb7, 0x685ad2238a03e7cc, 0x066484b2ab2ff1db, - 0xfe9d5d70efbf79ec, 0x5b13b9dd9c481854, 0x15f0d475ed1509ad, 0x0bebcd060ec79851, - 0xd58c6791183ab7f8, 0xd1187c5052f3eee4, 0xc95d1192e54e82ff, 0x86eea14cb9ac6ca2, - 0x3485beb153677d5d, 0xdd191d781f8c492a, 0xf60866baa784ebf9, 0x518f643ba2d08c74, - 0x8852e956e1087c22, 0xa768cb8dc410ae8d, 0x38047726bfec8e1a, 0xa67738b4cd3b45aa, - 0xad16691cec0dde19, 0xc6d4319380462e07, 0xc5a5876d0ba61938, 0x16b9fa1fa58fd840, - 0x188ab1173ca74f18, 0xabda2f98c99c021f, 0x3e0580ab134ae816, 0x5f3b05b773645abb, - 0x2501a2be5575f2f6, 0x1b2f74004e7e8ba9, 0x1cd7580371e8d953, 0x7f6ed89562764e30, - 0xb15926ff596f003d, 0x9f65293da8c5d6b9, 0x6ecef04dd690f84c, 0x4782275fff33af88, - 0xe41433083f820801, 0xfd0dfe409a1af9b5, 0x4325a3342cdb396b, 0x8ae77e62b301b252, - 0xc36f9e9f6655615a, 0x85455a2d92d32c09, 0xf2c7dea949477485, 0x63cfb4c133a39eba, - 0x83b040cc6ebc5462, 0x3b9454c8fdb326b0, 0x56f56a9e87ffd78c, 0x2dc2940d99f42bc6, - 0x98f7df096b096e2d, 0x19a6e01e3ad852bf, 0x42a99ccbdbd4b40b, 0xa59998af45e9c559, - 0x366295e807d93186, 0x6b48181bfaa1f773, 0x1fec57e2157a0a1d, 0x4667446af6201ad5, - 0xe615ebcacfb0f075, 0xb8f31f4f68290778, 0x22713ed6ce22d11e, 0x3057c1a72ec3c93b, - 0xcb46acc37c3f1f2f, 0xdbb893fd02aaf50e, 0x331fd92e600b9fcf, 0xa498f96148ea3ad6, - 0xa8d8426e8b6a83ea, 0xa089b274b7735cdc, 0x87f6b3731e524a11, 0x118808e5cbc96749, - 0x9906e4c7b19bd394, 0xafed7f7e9b24a20c, 0x6509eadeeb3644a7, 0x6c1ef1d3e8ef0ede, - 0xb9c97d43e9798fb4, 0xa2f2d784740c28a3, 0x7b8496476197566f, 0x7a5be3e6b65f069d, - 0xf96330ed78be6f10, 0xeee60de77a076a15, 0x2b4bee4aa08b9bd0, 0x6a56a63ec7b8894e, - 0x02121359ba34fef4, 0x4cbf99f8283703fc, 0x398071350caf30c8, 0xd0a77a89f017687a, - 0xf1c1a9eb9e423569, 0x8c7976282dee8199, 0x5d1737a5dd1f7abd, 0x4f53433c09a9fa80, - 0xfa8b0c53df7ca1d9, 0x3fd9dcbc886ccb77, 0xc040917ca91b4720, 0x7dd00142f9d1dcdf, - 0x8476fc1d4f387b58, 0x23f8e7c5f3316503, 0x032a2244e7e37339, 0x5c87a5d750f5a74b, - 0x082b4cc43698992e, 0xdf917becb858f63c, 0x3270b8fc5bf86dda, 0x10ae72bb29b5dd76, - 0x576ac94e7700362b, 0x1ad112dac61efb8f, 0x691bc30ec5faa427, 0xff246311cc327143, - 0x3142368e30e53206, 0x71380e31e02ca396, 0x958d5c960aad76f1, 0xf8d6f430c16da536, - 0xc8ffd13f1be7e1d2, 0x7578ae66004ddbe1, 0x05833f01067be646, 0xbb34b5ad3bfe586d, - 0x095f34c9a12b97f0, 0x247ab64525d60ca8, 0xdcdbc6f3017477d1, 0x4a2e14d4decad24d, - 0xbdb5e6d9be0a1eeb, 0x2a7e70f7794301ab, 0xdef42d8a270540fd, 0x01078ec0a34c22c1, - 0xe5de511af4c16387, 0x7ebb3a52bd9a330a, 0x77697857aa7d6435, 0x004e831603ae4c32, - 0xe7a21020ad78e312, 0x9d41a70c6ab420f2, 0x28e06c18ea1141e6, 0xd2b28cbd984f6b28, - 0x26b75f6c446e9d83, 0xba47568c4d418d7f, 0xd80badbfe6183d8e, 0x0e206d7f5f166044, - 0xe258a43911cbca3e, 0x723a1746b21dc0bc, 0xc7caa854f5d7cdd3, 0x7cac32883d261d9c, - 0x7690c26423ba942c, 0x17e55524478042b8, 0xe0be477656a2389f, 0x4d289b5e67ab2da0, - 0x44862b9c8fbbfd31, 0xb47cc8049d141365, 0x822c1b362b91c793, 0x4eb14655fb13dfd8, - 0x1ecbba0714e2a97b, 0x6143459d5cde5f14, 0x53a8fbf1d5f0ac89, 0x97ea04d81c5e5b00, - 0x622181a8d4fdb3f3, 0xe9bcd341572a1208, 0x1411258643cce58a, 0x9144c5fea4c6e0a4, - 0x0d33d06565cf620f, 0x54a48d489f219ca1, 0xc43e5eac6d63c821, 0xa9728b3a72770daf, - 0xd7934e7b20df87ef, 0xe35503b61a3e86e5, 0xcae321fbc819d504, 0x129a50b3ac60bfa6, - 0xcd5e68ea7e9fb6c3, 0xb01c90199483b1c7, 0x3de93cd5c295376c, 0xaed52edf2ab9ad13, - 0x2e60f512c0a07884, 0xbc3d86a3e36210c9, 0x35269d9b163951ce, 0x0c7d6e2ad0cdb5fa, - 0x59e86297d87f5733, 0x298ef221898db0e7, 0x55000029d1a5aa7e, 0x8bc08ae1b5061b45, - 0xc2c31c2b6c92703a, 0x94cc596baf25ef42, 0x0a1d73db22540456, 0x04b6a0f9d9c4179a, - 0xeffdafa2ae3d3c60, 0xf7c8075bb49496c4, 0x9cc5c7141d1cd4e3, 0x78bd1638218e5534, - 0xb2f11568f850246a, 0xedfabcfa9502bc29, 0x796ce5f2da23051b, 0xaae128b0dc93537c, - 0x3a493da0ee4b29ae, 0xb5df6b2c416895d7, 0xfcabbd25122d7f37, 0x70810b58105dc4b1, - 0xe10fdd37f7882a90, 0x524dcab5518a3f5c, 0x3c9e85878451255b, 0x4029828119bd34e2, - 0x74a05b6f5d3ceccb, 0xb610021542e13eca, 0x0ff979d12f59e2ac, 0x6037da27e4f9cc50, - 0x5e92975a0df1847d, 0xd66de190d3e623fe, 0x5032d6b87b568048, 0x9a36b7ce8235216e, - 0x80272a7a24f64b4a, 0x93efed8b8c6916f7, 0x37ddbff44cce1555, 0x4b95db5d4b99bd25, - 0x92d3fda169812fc0, 0xfb1a4a9a90660bb6, 0x730c196946a4b9b2, 0x81e289aa7f49da68, - 0x64669a0f83b1a05f, 0x27b3ff7d9644f48b, 0xcc6b615c8db675b3, 0x674f20b9bcebbe95, - 0x6f31238275655982, 0x5ae488713e45cf05, 0xbf619f9954c21157, 0xeabac46040a8eae9, - 0x454c6fe9f2c0c1cd, 0x419cf6496412691c, 0xd3dc3bef265b0f70, 0x6d0e60f5c3578a9e, -} - -T4 := [?]u64 { - 0x5b0e608526323c55, 0x1a46c1a9fa1b59f5, 0xa9e245a17c4c8ffa, 0x65ca5159db2955d7, - 0x05db0a76ce35afc2, 0x81eac77ea9113d45, 0x528ef88ab6ac0a0d, 0xa09ea253597be3ff, - 0x430ddfb3ac48cd56, 0xc4b3a67af45ce46f, 0x4ececfd8fbe2d05e, 0x3ef56f10b39935f0, - 0x0b22d6829cd619c6, 0x17fd460a74df2069, 0x6cf8cc8e8510ed40, 0xd6c824bf3a6ecaa7, - 0x61243d581a817049, 0x048bacb6bbc163a2, 0xd9a38ac27d44cc32, 0x7fddff5baaf410ab, - 0xad6d495aa804824b, 0xe1a6a74f2d8c9f94, 0xd4f7851235dee8e3, 0xfd4b7f886540d893, - 0x247c20042aa4bfda, 0x096ea1c517d1327c, 0xd56966b4361a6685, 0x277da5c31221057d, - 0x94d59893a43acff7, 0x64f0c51ccdc02281, 0x3d33bcc4ff6189db, 0xe005cb184ce66af1, - 0xff5ccd1d1db99bea, 0xb0b854a7fe42980f, 0x7bd46a6a718d4b9f, 0xd10fa8cc22a5fd8c, - 0xd31484952be4bd31, 0xc7fa975fcb243847, 0x4886ed1e5846c407, 0x28cddb791eb70b04, - 0xc2b00be2f573417f, 0x5c9590452180f877, 0x7a6bddfff370eb00, 0xce509e38d6d9d6a4, - 0xebeb0f00647fa702, 0x1dcc06cf76606f06, 0xe4d9f28ba286ff0a, 0xd85a305dc918c262, - 0x475b1d8732225f54, 0x2d4fb51668ccb5fe, 0xa679b9d9d72bba20, 0x53841c0d912d43a5, - 0x3b7eaa48bf12a4e8, 0x781e0e47f22f1ddf, 0xeff20ce60ab50973, 0x20d261d19dffb742, - 0x16a12b03062a2e39, 0x1960eb2239650495, 0x251c16fed50eb8b8, 0x9ac0c330f826016e, - 0xed152665953e7671, 0x02d63194a6369570, 0x5074f08394b1c987, 0x70ba598c90b25ce1, - 0x794a15810b9742f6, 0x0d5925e9fcaf8c6c, 0x3067716cd868744e, 0x910ab077e8d7731b, - 0x6a61bbdb5ac42f61, 0x93513efbf0851567, 0xf494724b9e83e9d5, 0xe887e1985c09648d, - 0x34b1d3c675370cfd, 0xdc35e433bc0d255d, 0xd0aab84234131be0, 0x08042a50b48b7eaf, - 0x9997c4ee44a3ab35, 0x829a7b49201799d0, 0x263b8307b7c54441, 0x752f95f4fd6a6ca6, - 0x927217402c08c6e5, 0x2a8ab754a795d9ee, 0xa442f7552f72943d, 0x2c31334e19781208, - 0x4fa98d7ceaee6291, 0x55c3862f665db309, 0xbd0610175d53b1f3, 0x46fe6cb840413f27, - 0x3fe03792df0cfa59, 0xcfe700372eb85e8f, 0xa7be29e7adbce118, 0xe544ee5cde8431dd, - 0x8a781b1b41f1873e, 0xa5c94c78a0d2f0e7, 0x39412e2877b60728, 0xa1265ef3afc9a62c, - 0xbcc2770c6a2506c5, 0x3ab66dd5dce1ce12, 0xe65499d04a675b37, 0x7d8f523481bfd216, - 0x0f6f64fcec15f389, 0x74efbe618b5b13c8, 0xacdc82b714273e1d, 0xdd40bfe003199d17, - 0x37e99257e7e061f8, 0xfa52626904775aaa, 0x8bbbf63a463d56f9, 0xf0013f1543a26e64, - 0xa8307e9f879ec898, 0xcc4c27a4150177cc, 0x1b432f2cca1d3348, 0xde1d1f8f9f6fa013, - 0x606602a047a7ddd6, 0xd237ab64cc1cb2c7, 0x9b938e7225fcd1d3, 0xec4e03708e0ff476, - 0xfeb2fbda3d03c12d, 0xae0bced2ee43889a, 0x22cb8923ebfb4f43, 0x69360d013cf7396d, - 0x855e3602d2d4e022, 0x073805bad01f784c, 0x33e17a133852f546, 0xdf4874058ac7b638, - 0xba92b29c678aa14a, 0x0ce89fc76cfaadcd, 0x5f9d4e0908339e34, 0xf1afe9291f5923b9, - 0x6e3480f60f4a265f, 0xeebf3a2ab29b841c, 0xe21938a88f91b4ad, 0x57dfeff845c6d3c3, - 0x2f006b0bf62caaf2, 0x62f479ef6f75ee78, 0x11a55ad41c8916a9, 0xf229d29084fed453, - 0x42f1c27b16b000e6, 0x2b1f76749823c074, 0x4b76eca3c2745360, 0x8c98f463b91691bd, - 0x14bcc93cf1ade66a, 0x8885213e6d458397, 0x8e177df0274d4711, 0xb49b73b5503f2951, - 0x10168168c3f96b6b, 0x0e3d963b63cab0ae, 0x8dfc4b5655a1db14, 0xf789f1356e14de5c, - 0x683e68af4e51dac1, 0xc9a84f9d8d4b0fd9, 0x3691e03f52a0f9d1, 0x5ed86e46e1878e80, - 0x3c711a0e99d07150, 0x5a0865b20c4e9310, 0x56fbfc1fe4f0682e, 0xea8d5de3105edf9b, - 0x71abfdb12379187a, 0x2eb99de1bee77b9c, 0x21ecc0ea33cf4523, 0x59a4d7521805c7a1, - 0x3896f5eb56ae7c72, 0xaa638f3db18f75dc, 0x9f39358dabe9808e, 0xb7defa91c00b72ac, - 0x6b5541fd62492d92, 0x6dc6dee8f92e4d5b, 0x353f57abc4beea7e, 0x735769d6da5690ce, - 0x0a234aa642391484, 0xf6f9508028f80d9d, 0xb8e319a27ab3f215, 0x31ad9c1151341a4d, - 0x773c22a57bef5805, 0x45c7561a07968633, 0xf913da9e249dbe36, 0xda652d9b78a64c68, - 0x4c27a97f3bc334ef, 0x76621220e66b17f4, 0x967743899acd7d0b, 0xf3ee5bcae0ed6782, - 0x409f753600c879fc, 0x06d09a39b5926db6, 0x6f83aeb0317ac588, 0x01e6ca4a86381f21, - 0x66ff3462d19f3025, 0x72207c24ddfd3bfb, 0x4af6b6d3e2ece2eb, 0x9c994dbec7ea08de, - 0x49ace597b09a8bc4, 0xb38c4766cf0797ba, 0x131b9373c57c2a75, 0xb1822cce61931e58, - 0x9d7555b909ba1c0c, 0x127fafdd937d11d2, 0x29da3badc66d92e4, 0xa2c1d57154c2ecbc, - 0x58c5134d82f6fe24, 0x1c3ae3515b62274f, 0xe907c82e01cb8126, 0xf8ed091913e37fcb, - 0x3249d8f9c80046c9, 0x80cf9bede388fb63, 0x1881539a116cf19e, 0x5103f3f76bd52457, - 0x15b7e6f5ae47f7a8, 0xdbd7c6ded47e9ccf, 0x44e55c410228bb1a, 0xb647d4255edb4e99, - 0x5d11882bb8aafc30, 0xf5098bbb29d3212a, 0x8fb5ea14e90296b3, 0x677b942157dd025a, - 0xfb58e7c0a390acb5, 0x89d3674c83bd4a01, 0x9e2da4df4bf3b93b, 0xfcc41e328cab4829, - 0x03f38c96ba582c52, 0xcad1bdbd7fd85db2, 0xbbb442c16082ae83, 0xb95fe86ba5da9ab0, - 0xb22e04673771a93f, 0x845358c9493152d8, 0xbe2a488697b4541e, 0x95a2dc2dd38e6966, - 0xc02c11ac923c852b, 0x2388b1990df2a87b, 0x7c8008fa1b4f37be, 0x1f70d0c84d54e503, - 0x5490adec7ece57d4, 0x002b3c27d9063a3a, 0x7eaea3848030a2bf, 0xc602326ded2003c0, - 0x83a7287d69a94086, 0xc57a5fcb30f57a8a, 0xb56844e479ebe779, 0xa373b40f05dcbce9, - 0xd71a786e88570ee2, 0x879cbacdbde8f6a0, 0x976ad1bcc164a32f, 0xab21e25e9666d78b, - 0x901063aae5e5c33c, 0x9818b34448698d90, 0xe36487ae3e1e8abb, 0xafbdf931893bdcb4, - 0x6345a0dc5fbbd519, 0x8628fe269b9465ca, 0x1e5d01603f9c51ec, 0x4de44006a15049b7, - 0xbf6c70e5f776cbb1, 0x411218f2ef552bed, 0xcb0c0708705a36a3, 0xe74d14754f986044, - 0xcd56d9430ea8280e, 0xc12591d7535f5065, 0xc83223f1720aef96, 0xc3a0396f7363a51f, -} - -Tiger_Context :: struct { - a: u64, - b: u64, - c: u64, - x: [64]byte, - nx: int, - length: u64, - ver: int, -} - -round :: #force_inline proc "contextless" (a, b, c, x, mul: u64) -> (u64, u64, u64) { - a, b, c := a, b, c - c ~= x - a -= T1[c & 0xff] ~ T2[(c >> 16) & 0xff] ~ T3[(c >> 32) & 0xff] ~ T4[(c >> 48) & 0xff] - b += T4[(c >> 8) & 0xff] ~ T3[(c >> 24) & 0xff] ~ T2[(c >> 40) & 0xff] ~ T1[(c >> 56) & 0xff] - b *= mul - return a, b, c -} - -pass :: #force_inline proc "contextless" (a, b, c: u64, d: []u64, mul: u64) -> (x, y, z: u64) { - x, y, z = round(a, b, c, d[0], mul) - y, z, x = round(y, z, x, d[1], mul) - z, x, y = round(z, x, y, d[2], mul) - x, y, z = round(x, y, z, d[3], mul) - y, z, x = round(y, z, x, d[4], mul) - z, x, y = round(z, x, y, d[5], mul) - x, y, z = round(x, y, z, d[6], mul) - y, z, x = round(y, z, x, d[7], mul) - return -} - -key_schedule :: #force_inline proc "contextless" (x: []u64) { - x[0] -= x[7] ~ 0xa5a5a5a5a5a5a5a5 - x[1] ~= x[0] - x[2] += x[1] - x[3] -= x[2] ~ ((~x[1]) << 19) - x[4] ~= x[3] - x[5] += x[4] - x[6] -= x[5] ~ ((~x[4]) >> 23) - x[7] ~= x[6] - x[0] += x[7] - x[1] -= x[0] ~ ((~x[7]) << 19) - x[2] ~= x[1] - x[3] += x[2] - x[4] -= x[3] ~ ((~x[2]) >> 23) - x[5] ~= x[4] - x[6] += x[5] - x[7] -= x[6] ~ 0x0123456789abcdef -} - -compress :: #force_inline proc "contextless" (ctx: ^Tiger_Context, data: []byte) { - a := ctx.a - b := ctx.b - c := ctx.c - x := util.cast_slice([]u64, data) - ctx.a, ctx.b, ctx.c = pass(ctx.a, ctx.b, ctx.c, x, 5) - key_schedule(x) - ctx.c, ctx.a, ctx.b = pass(ctx.c, ctx.a, ctx.b, x, 7) - key_schedule(x) - ctx.b, ctx.c, ctx.a = pass(ctx.b, ctx.c, ctx.a, x, 9) - ctx.a ~= a - ctx.b -= b - ctx.c += c -} - -init :: proc "contextless" (ctx: ^Tiger_Context) { - ctx.a = 0x0123456789abcdef - ctx.b = 0xfedcba9876543210 - ctx.c = 0xf096a5b4c3b2e187 -} - -update :: proc(ctx: ^Tiger_Context, input: []byte) { - p := make([]byte, len(input)) - copy(p, input) - - length := len(p) - ctx.length += u64(length) - if ctx.nx > 0 { - n := len(p) - if n > 64 - ctx.nx { - n = 64 - ctx.nx - } - copy(ctx.x[ctx.nx:ctx.nx + n], p[:n]) - ctx.nx += n - if ctx.nx == 64 { - compress(ctx, ctx.x[:64 - 1]) - ctx.nx = 0 - } - p = p[n:] - } - for len(p) >= 64 { - compress(ctx, p[:64]) - p = p[64:] - } - if len(p) > 0 { - ctx.nx = copy(ctx.x[:], p) - } -} - -final :: proc(ctx: ^Tiger_Context, hash: []byte) { - length := ctx.length - tmp: [64]byte - if ctx.ver == 1 { - tmp[0] = 0x01 - } else { - tmp[0] = 0x80 - } - - size := length & 0x3f - if size < 56 { - update(ctx, tmp[:56 - size]) - } else { - update(ctx, tmp[:64 + 56 - size]) - } - - length <<= 3 - for i := uint(0); i < 8; i += 1 { - tmp[i] = byte(length >> (8 * i)) - } - update(ctx, tmp[:8]) - - for i := uint(0); i < 8; i += 1 { - tmp[i] = byte(ctx.a >> (8 * i)) - tmp[i + 8] = byte(ctx.b >> (8 * i)) - tmp[i + 16] = byte(ctx.c >> (8 * i)) - } - copy(hash[:], tmp[:len(hash)]) -} \ No newline at end of file diff --git a/core/crypto/aead/aead.odin b/core/crypto/aead/aead.odin new file mode 100644 index 000000000..9b7d810e4 --- /dev/null +++ b/core/crypto/aead/aead.odin @@ -0,0 +1,36 @@ +package aead + +// seal_oneshot encrypts the plaintext and authenticates the aad and ciphertext, +// with the provided algorithm, key, and iv, stores the output in dst and tag. +// +// dst and plaintext MUST alias exactly or not at all. +seal_oneshot :: proc(algo: Algorithm, dst, tag, key, iv, aad, plaintext: []byte, impl: Implementation = nil) { + ctx: Context + init(&ctx, algo, key, impl) + defer reset(&ctx) + seal_ctx(&ctx, dst, tag, iv, aad, plaintext) +} + +// open authenticates the aad and ciphertext, and decrypts the ciphertext, +// with the provided algorithm, key, iv, and tag, and stores the output in dst, +// returning true iff the authentication was successful. If authentication +// fails, the destination buffer will be zeroed. +// +// dst and plaintext MUST alias exactly or not at all. +@(require_results) +open_oneshot :: proc(algo: Algorithm, dst, key, iv, aad, ciphertext, tag: []byte, impl: Implementation = nil) -> bool { + ctx: Context + init(&ctx, algo, key, impl) + defer reset(&ctx) + return open_ctx(&ctx, dst, iv, aad, ciphertext, tag) +} + +seal :: proc { + seal_ctx, + seal_oneshot, +} + +open :: proc { + open_ctx, + open_oneshot, +} diff --git a/core/crypto/aead/doc.odin b/core/crypto/aead/doc.odin new file mode 100644 index 000000000..93be674a0 --- /dev/null +++ b/core/crypto/aead/doc.odin @@ -0,0 +1,57 @@ +/* +package aead provides a generic interface to the supported Authenticated +Encryption with Associated Data algorithms. + +Both a one-shot and context based interface are provided, with similar +usage. If multiple messages are to be sealed/opened via the same key, +the context based interface may be more efficient, depending on the +algorithm. + +WARNING: Reusing the same key + iv to seal (encrypt) multiple messages +results in catastrophic loss of security for most algorithms. + +Example: + package aead_example + + import "core:bytes" + import "core:crypto" + import "core:crypto/aead" + + main :: proc() { + algo := aead.Algorithm.XCHACHA20POLY1305 + + // The example added associated data, and plaintext. + aad_str := "Get your ass in gear boys." + pt_str := "They're immanetizing the Eschaton." + + aad := transmute([]byte)aad_str + plaintext := transmute([]byte)pt_str + pt_len := len(plaintext) + + // Generate a random key for the purposes of illustration. + key := make([]byte, aead.KEY_SIZES[algo]) + defer delete(key) + crypto.rand_bytes(key) + + // `ciphertext || tag`, is a common way data is transmitted, so + // demonstrate that. + buf := make([]byte, pt_len + aead.TAG_SIZES[algo]) + defer delete(buf) + ciphertext, tag := buf[:pt_len], buf[pt_len:] + + // Seal the AAD + Plaintext. + iv := make([]byte, aead.IV_SIZES[algo]) + defer delete(iv) + crypto.rand_bytes(iv) // Random IVs are safe with XChaCha20-Poly1305. + aead.seal(algo, ciphertext, tag, key, iv, aad, plaintext) + + // Open the AAD + Ciphertext. + opened_pt := buf[:pt_len] + if ok := aead.open(algo, opened_pt, key, iv, aad, ciphertext, tag); !ok { + panic("aead example: failed to open") + } + + assert(bytes.equal(opened_pt, plaintext)) + } +*/ +package aead diff --git a/core/crypto/aead/low_level.odin b/core/crypto/aead/low_level.odin new file mode 100644 index 000000000..38a0c84ba --- /dev/null +++ b/core/crypto/aead/low_level.odin @@ -0,0 +1,187 @@ +package aead + +import "core:crypto/aes" +import "core:crypto/chacha20" +import "core:crypto/chacha20poly1305" +import "core:reflect" + +// Implementation is an AEAD implementation. Most callers will not need +// to use this as the package will automatically select the most performant +// implementation available. +Implementation :: union { + aes.Implementation, + chacha20.Implementation, +} + +// MAX_TAG_SIZE is the maximum size tag that can be returned by any of the +// Algorithms supported via this package. +MAX_TAG_SIZE :: 16 + +// Algorithm is the algorithm identifier associated with a given Context. +Algorithm :: enum { + Invalid, + AES_GCM_128, + AES_GCM_192, + AES_GCM_256, + CHACHA20POLY1305, + XCHACHA20POLY1305, +} + +// ALGORITM_NAMES is the Agorithm to algorithm name string. +ALGORITHM_NAMES := [Algorithm]string { + .Invalid = "Invalid", + .AES_GCM_128 = "AES-GCM-128", + .AES_GCM_192 = "AES-GCM-192", + .AES_GCM_256 = "AES-GCM-256", + .CHACHA20POLY1305 = "chacha20poly1305", + .XCHACHA20POLY1305 = "xchacha20poly1305", +} + +// TAG_SIZES is the Algorithm to tag size in bytes. +TAG_SIZES := [Algorithm]int { + .Invalid = 0, + .AES_GCM_128 = aes.GCM_TAG_SIZE, + .AES_GCM_192 = aes.GCM_TAG_SIZE, + .AES_GCM_256 = aes.GCM_TAG_SIZE, + .CHACHA20POLY1305 = chacha20poly1305.TAG_SIZE, + .XCHACHA20POLY1305 = chacha20poly1305.TAG_SIZE, +} + +// KEY_SIZES is the Algorithm to key size in bytes. +KEY_SIZES := [Algorithm]int { + .Invalid = 0, + .AES_GCM_128 = aes.KEY_SIZE_128, + .AES_GCM_192 = aes.KEY_SIZE_192, + .AES_GCM_256 = aes.KEY_SIZE_256, + .CHACHA20POLY1305 = chacha20poly1305.KEY_SIZE, + .XCHACHA20POLY1305 = chacha20poly1305.KEY_SIZE, +} + +// IV_SIZES is the Algorithm to initialization vector size in bytes. +// +// Note: Some algorithms (such as AES-GCM) support variable IV sizes. +IV_SIZES := [Algorithm]int { + .Invalid = 0, + .AES_GCM_128 = aes.GCM_IV_SIZE, + .AES_GCM_192 = aes.GCM_IV_SIZE, + .AES_GCM_256 = aes.GCM_IV_SIZE, + .CHACHA20POLY1305 = chacha20poly1305.IV_SIZE, + .XCHACHA20POLY1305 = chacha20poly1305.XIV_SIZE, +} + +// Context is a concrete instantiation of a specific AEAD algorithm. +Context :: struct { + _algo: Algorithm, + _impl: union { + aes.Context_GCM, + chacha20poly1305.Context, + }, +} + +@(private) +_IMPL_IDS := [Algorithm]typeid { + .Invalid = nil, + .AES_GCM_128 = typeid_of(aes.Context_GCM), + .AES_GCM_192 = typeid_of(aes.Context_GCM), + .AES_GCM_256 = typeid_of(aes.Context_GCM), + .CHACHA20POLY1305 = typeid_of(chacha20poly1305.Context), + .XCHACHA20POLY1305 = typeid_of(chacha20poly1305.Context), +} + +// init initializes a Context with a specific AEAD Algorithm. +init :: proc(ctx: ^Context, algorithm: Algorithm, key: []byte, impl: Implementation = nil) { + if ctx._impl != nil { + reset(ctx) + } + + if len(key) != KEY_SIZES[algorithm] { + panic("crypto/aead: invalid key size") + } + + // Directly specialize the union by setting the type ID (save a copy). + reflect.set_union_variant_typeid( + ctx._impl, + _IMPL_IDS[algorithm], + ) + switch algorithm { + case .AES_GCM_128, .AES_GCM_192, .AES_GCM_256: + impl_ := impl != nil ? impl.(aes.Implementation) : aes.DEFAULT_IMPLEMENTATION + aes.init_gcm(&ctx._impl.(aes.Context_GCM), key, impl_) + case .CHACHA20POLY1305: + impl_ := impl != nil ? impl.(chacha20.Implementation) : chacha20.DEFAULT_IMPLEMENTATION + chacha20poly1305.init(&ctx._impl.(chacha20poly1305.Context), key, impl_) + case .XCHACHA20POLY1305: + impl_ := impl != nil ? impl.(chacha20.Implementation) : chacha20.DEFAULT_IMPLEMENTATION + chacha20poly1305.init_xchacha(&ctx._impl.(chacha20poly1305.Context), key, impl_) + case .Invalid: + panic("crypto/aead: uninitialized algorithm") + case: + panic("crypto/aead: invalid algorithm") + } + + ctx._algo = algorithm +} + +// seal_ctx encrypts the plaintext and authenticates the aad and ciphertext, +// with the provided Context and iv, stores the output in dst and tag. +// +// dst and plaintext MUST alias exactly or not at all. +seal_ctx :: proc(ctx: ^Context, dst, tag, iv, aad, plaintext: []byte) { + switch &impl in ctx._impl { + case aes.Context_GCM: + aes.seal_gcm(&impl, dst, tag, iv, aad, plaintext) + case chacha20poly1305.Context: + chacha20poly1305.seal(&impl, dst, tag, iv, aad, plaintext) + case: + panic("crypto/aead: uninitialized algorithm") + } +} + +// open_ctx authenticates the aad and ciphertext, and decrypts the ciphertext, +// with the provided Context, iv, and tag, and stores the output in dst, +// returning true iff the authentication was successful. If authentication +// fails, the destination buffer will be zeroed. +// +// dst and plaintext MUST alias exactly or not at all. +@(require_results) +open_ctx :: proc(ctx: ^Context, dst, iv, aad, ciphertext, tag: []byte) -> bool { + switch &impl in ctx._impl { + case aes.Context_GCM: + return aes.open_gcm(&impl, dst, iv, aad, ciphertext, tag) + case chacha20poly1305.Context: + return chacha20poly1305.open(&impl, dst, iv, aad, ciphertext, tag) + case: + panic("crypto/aead: uninitialized algorithm") + } +} + +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. +reset :: proc(ctx: ^Context) { + switch &impl in ctx._impl { + case aes.Context_GCM: + aes.reset_gcm(&impl) + case chacha20poly1305.Context: + chacha20poly1305.reset(&impl) + case: + // Calling reset repeatedly is fine. + } + + ctx._algo = .Invalid + ctx._impl = nil +} + +// algorithm returns the Algorithm used by a Context instance. +algorithm :: proc(ctx: ^Context) -> Algorithm { + return ctx._algo +} + +// iv_size returns the IV size of a Context instance in bytes. +iv_size :: proc(ctx: ^Context) -> int { + return IV_SIZES[ctx._algo] +} + +// tag_size returns the tag size of a Context instance in bytes. +tag_size :: proc(ctx: ^Context) -> int { + return TAG_SIZES[ctx._algo] +} diff --git a/core/crypto/aes/aes.odin b/core/crypto/aes/aes.odin new file mode 100644 index 000000000..57f49acf4 --- /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..20b75e57f --- /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 := DEFAULT_IMPLEMENTATION) { + 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") + } + + #no_bounds_check for remaining := len(src); remaining > 0; { + // Process multiple blocks at once + if ctx._off == BLOCK_SIZE { + if nr_blocks := remaining / BLOCK_SIZE; nr_blocks > 0 { + direct_bytes := nr_blocks * BLOCK_SIZE + 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 + #no_bounds_check for remaining := len(dst); remaining > 0; { + // Process multiple blocks at once + if ctx._off == BLOCK_SIZE { + if nr_blocks := remaining / BLOCK_SIZE; nr_blocks > 0 { + direct_bytes := nr_blocks * BLOCK_SIZE + 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..415758b24 --- /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..32476006c --- /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 := DEFAULT_IMPLEMENTATION) { + 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..f1d44a25f --- /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..8616821ce --- /dev/null +++ b/core/crypto/aes/aes_gcm.odin @@ -0,0 +1,270 @@ +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_IV_SIZE is the default size of the GCM IV in bytes. +GCM_IV_SIZE :: 12 +// GCM_IV_SIZE_MAX is the maximum size of the GCM IV in bytes. +GCM_IV_SIZE_MAX :: 0x2000000000000000 // floor((2^64 - 1) / 8) bits +// GCM_TAG_SIZE is the size of a GCM tag in bytes. +GCM_TAG_SIZE :: _aes.GHASH_TAG_SIZE + +@(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 := DEFAULT_IMPLEMENTATION) { + init_impl(&ctx._impl, key, impl) + ctx._is_initialized = true +} + +// seal_gcm encrypts the plaintext and authenticates the aad and ciphertext, +// with the provided Context_GCM and iv, stores the output in dst and tag. +// +// dst and plaintext MUST alias exactly or not at all. +seal_gcm :: proc(ctx: ^Context_GCM, dst, tag, iv, aad, plaintext: []byte) { + assert(ctx._is_initialized) + + gcm_validate_common_slice_sizes(tag, iv, 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, iv, 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, iv) + + // 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, iv, and tag, and stores the output in dst, +// returning true iff the authentication was successful. If authentication +// fails, the destination buffer will be zeroed. +// +// dst and plaintext MUST alias exactly or not at all. +@(require_results) +open_gcm :: proc(ctx: ^Context_GCM, dst, iv, aad, ciphertext, tag: []byte) -> bool { + assert(ctx._is_initialized) + + gcm_validate_common_slice_sizes(tag, iv, 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, iv, aad, ciphertext, tag) + } + + h: [_aes.GHASH_KEY_SIZE]byte + j0: [_aes.GHASH_BLOCK_SIZE]byte + j0_enc: [_aes.GHASH_BLOCK_SIZE]byte + s: [_aes.GHASH_TAG_SIZE]byte + init_ghash_ct64(ctx, &h, &j0, &j0_enc, iv) + + 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_gcm sanitizes the Context_GCM. The Context_GCM must be +// re-initialized to be used again. +reset_gcm :: proc "contextless" (ctx: ^Context_GCM) { + reset_impl(&ctx._impl) + ctx._is_initialized = false +} + +@(private = "file") +gcm_validate_common_slice_sizes :: proc(tag, iv, aad, text: []byte) { + if len(tag) != GCM_TAG_SIZE { + panic("crypto/aes: invalid GCM tag size") + } + + // The specification supports IVs in the range [1, 2^64) bits. + if l := len(iv); l == 0 || u64(l) >= GCM_IV_SIZE_MAX { + panic("crypto/aes: invalid GCM IV size") + } + + if aad_len := u64(len(aad)); aad_len > GCM_A_MAX { + 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, + iv: []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(iv); l == GCM_IV_SIZE { + // if len(IV) = 96, then let J0 = IV || 0^31 || 1 + copy(j0[:], iv) + j0[_aes.GHASH_BLOCK_SIZE - 1] = 1 + } else { + // If len(IV) != 96, then let s = 128 ceil(len(IV)/128) - len(IV), + // and let J0 = GHASHH(IV || 0^(s+64) || ceil(len(IV))^64). + ct64.ghash(j0[:], h[:], iv) + + 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, + iv: ^[_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(iv[GCM_IV_SIZE:]) + 1 + for i in 0 ..< ct64.STRIDE { + // Setup scratch space for the keystream. + blks[i] = tmp2[i][:] + + // Pre-copy the IV to all the counter blocks. + ctrs[i] = tmp[i][:] + copy(ctrs[i], iv[:GCM_IV_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..4cb5ab3b2 --- /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, iv, aad, plaintext: []byte) { + h: [_aes.GHASH_KEY_SIZE]byte + j0: [_aes.GHASH_BLOCK_SIZE]byte + j0_enc: [_aes.GHASH_BLOCK_SIZE]byte + s: [_aes.GHASH_TAG_SIZE]byte + init_ghash_hw(ctx, &h, &j0, &j0_enc, iv) + + // 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, iv, aad, ciphertext, tag: []byte) -> bool { + h: [_aes.GHASH_KEY_SIZE]byte + j0: [_aes.GHASH_BLOCK_SIZE]byte + j0_enc: [_aes.GHASH_BLOCK_SIZE]byte + s: [_aes.GHASH_TAG_SIZE]byte + init_ghash_hw(ctx, &h, &j0, &j0_enc, iv) + + 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, + iv: []byte, +) { + // 1. Let H = CIPH(k, 0^128) + encrypt_block_hw(ctx, h[:], h[:]) + + // Define a block, J0, as follows: + if l := len(iv); l == GCM_IV_SIZE { + // if len(IV) = 96, then let J0 = IV || 0^31 || 1 + copy(j0[:], iv) + j0[_aes.GHASH_BLOCK_SIZE - 1] = 1 + } else { + // If len(IV) != 96, then let s = 128 ceil(len(IV)/128) - len(IV), + // and let J0 = GHASHH(IV || 0^(s+64) || ceil(len(IV))^64). + hw_intel.ghash(j0[:], h[:], iv) + + 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, + iv: ^[_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)(iv)) + ctr := endian.unchecked_get_u32be(iv[GCM_IV_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..f26874809 --- /dev/null +++ b/core/crypto/aes/aes_impl.odin @@ -0,0 +1,45 @@ +package aes + +import "core:crypto/_aes/ct64" +import "core:mem" +import "core:reflect" + +@(private) +Context_Impl :: union { + ct64.Context, + Context_Impl_Hardware, +} + +// DEFAULT_IMPLEMENTATION is the implementation that will be used by +// default if possible. +DEFAULT_IMPLEMENTATION :: Implementation.Hardware + +// Implementation is an AES implementation. Most callers will not need +// to use this as the package will automatically select the most performant +// implementation available (See `is_hardware_accelerated()`). +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..0c9ec6edc --- /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, iv, aad, plaintext: []byte) { + panic(ERR_HW_NOT_SUPPORTED) +} + +@(private) +gcm_open_hw :: proc(ctx: ^Context_Impl_Hardware, dst, iv, aad, ciphertext, tag: []byte) -> bool { + panic(ERR_HW_NOT_SUPPORTED) +} diff --git a/core/crypto/aes/aes_impl_hw_intel.odin b/core/crypto/aes/aes_impl_hw_intel.odin new file mode 100644 index 000000000..0f1fa6143 --- /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/blake/blake.odin b/core/crypto/blake/blake.odin deleted file mode 100644 index 3685109e4..000000000 --- a/core/crypto/blake/blake.odin +++ /dev/null @@ -1,726 +0,0 @@ -package blake - -/* - Copyright 2021 zhibog - Made available under the BSD-3 license. - - List of contributors: - zhibog, dotbmp: Initial implementation. - - Implementation of the BLAKE hashing algorithm, as defined in -*/ - -import "core:os" -import "core:io" - -/* - High level API -*/ - -DIGEST_SIZE_224 :: 28 -DIGEST_SIZE_256 :: 32 -DIGEST_SIZE_384 :: 48 -DIGEST_SIZE_512 :: 64 - -// hash_string_224 will hash the given input and return the -// computed hash -hash_string_224 :: proc "contextless" (data: string) -> [DIGEST_SIZE_224]byte { - return hash_bytes_224(transmute([]byte)(data)) -} - -// hash_bytes_224 will hash the given input and return the -// computed hash -hash_bytes_224 :: proc "contextless" (data: []byte) -> [DIGEST_SIZE_224]byte { - hash: [DIGEST_SIZE_224]byte - ctx: Blake256_Context - ctx.is224 = true - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_224 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_224 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_224(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_224 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_224 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_224, "Size of destination buffer is smaller than the digest size") - ctx: Blake256_Context - ctx.is224 = true - init(&ctx) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_224 will read the stream in chunks and compute a -// hash from its contents -hash_stream_224 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { - hash: [DIGEST_SIZE_224]byte - ctx: Blake256_Context - ctx.is224 = true - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_224 will read the file provided by the given handle -// and compute a hash -hash_file_224 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_224]byte, bool) { - if !load_at_once { - return hash_stream_224(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_224(buf[:]), ok - } - } - return [DIGEST_SIZE_224]byte{}, false -} - -hash_224 :: proc { - hash_stream_224, - hash_file_224, - hash_bytes_224, - hash_string_224, - hash_bytes_to_buffer_224, - hash_string_to_buffer_224, -} - -// hash_string_256 will hash the given input and return the -// computed hash -hash_string_256 :: proc "contextless" (data: string) -> [DIGEST_SIZE_256]byte { - return hash_bytes_256(transmute([]byte)(data)) -} - -// hash_bytes_256 will hash the given input and return the -// computed hash -hash_bytes_256 :: proc "contextless" (data: []byte) -> [DIGEST_SIZE_256]byte { - hash: [DIGEST_SIZE_256]byte - ctx: Blake256_Context - ctx.is224 = false - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_256 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_256 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_256(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_256 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_256 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_256, "Size of destination buffer is smaller than the digest size") - ctx: Blake256_Context - ctx.is224 = false - init(&ctx) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_256 will read the stream in chunks and compute a -// hash from its contents -hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { - hash: [DIGEST_SIZE_256]byte - ctx: Blake256_Context - ctx.is224 = false - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_256 will read the file provided by the given handle -// and compute a hash -hash_file_256 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_256]byte, bool) { - if !load_at_once { - return hash_stream_256(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_256(buf[:]), ok - } - } - return [DIGEST_SIZE_256]byte{}, false -} - -hash_256 :: proc { - hash_stream_256, - hash_file_256, - hash_bytes_256, - hash_string_256, - hash_bytes_to_buffer_256, - hash_string_to_buffer_256, -} - -// hash_string_384 will hash the given input and return the -// computed hash -hash_string_384 :: proc "contextless" (data: string) -> [DIGEST_SIZE_384]byte { - return hash_bytes_384(transmute([]byte)(data)) -} - -// hash_bytes_384 will hash the given input and return the -// computed hash -hash_bytes_384 :: proc "contextless" (data: []byte) -> [DIGEST_SIZE_384]byte { - hash: [DIGEST_SIZE_384]byte - ctx: Blake512_Context - ctx.is384 = true - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_384 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_384 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_384(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_384 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_384 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_384, "Size of destination buffer is smaller than the digest size") - ctx: Blake512_Context - ctx.is384 = true - init(&ctx) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_384 will read the stream in chunks and compute a -// hash from its contents -hash_stream_384 :: proc(s: io.Stream) -> ([DIGEST_SIZE_384]byte, bool) { - hash: [DIGEST_SIZE_384]byte - ctx: Blake512_Context - ctx.is384 = true - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_384 will read the file provided by the given handle -// and compute a hash -hash_file_384 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_384]byte, bool) { - if !load_at_once { - return hash_stream_384(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_384(buf[:]), ok - } - } - return [DIGEST_SIZE_384]byte{}, false -} - -hash_384 :: proc { - hash_stream_384, - hash_file_384, - hash_bytes_384, - hash_string_384, - hash_bytes_to_buffer_384, - hash_string_to_buffer_384, -} - -// hash_string_512 will hash the given input and return the -// computed hash -hash_string_512 :: proc "contextless" (data: string) -> [DIGEST_SIZE_512]byte { - return hash_bytes_512(transmute([]byte)(data)) -} - -// hash_bytes_512 will hash the given input and return the -// computed hash -hash_bytes_512 :: proc "contextless" (data: []byte) -> [DIGEST_SIZE_512]byte { - hash: [DIGEST_SIZE_512]byte - ctx: Blake512_Context - ctx.is384 = false - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_512 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_512 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_512(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_512 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_512 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_512, "Size of destination buffer is smaller than the digest size") - ctx: Blake512_Context - ctx.is384 = false - init(&ctx) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_512 will read the stream in chunks and compute a -// hash from its contents -hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { - hash: [DIGEST_SIZE_512]byte - ctx: Blake512_Context - ctx.is384 = false - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_512 will read the file provided by the given handle -// and compute a hash -hash_file_512 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_512]byte, bool) { - if !load_at_once { - return hash_stream_512(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_512(buf[:]), ok - } - } - return [DIGEST_SIZE_512]byte{}, false -} - -hash_512 :: proc { - hash_stream_512, - hash_file_512, - hash_bytes_512, - hash_string_512, - hash_bytes_to_buffer_512, - hash_string_to_buffer_512, -} - -/* - Low level API -*/ - -init :: proc "contextless" (ctx: ^$T) { - when T == Blake256_Context { - if ctx.is224 { - ctx.h[0] = 0xc1059ed8 - ctx.h[1] = 0x367cd507 - ctx.h[2] = 0x3070dd17 - ctx.h[3] = 0xf70e5939 - ctx.h[4] = 0xffc00b31 - ctx.h[5] = 0x68581511 - ctx.h[6] = 0x64f98fa7 - ctx.h[7] = 0xbefa4fa4 - } else { - ctx.h[0] = 0x6a09e667 - ctx.h[1] = 0xbb67ae85 - ctx.h[2] = 0x3c6ef372 - ctx.h[3] = 0xa54ff53a - ctx.h[4] = 0x510e527f - ctx.h[5] = 0x9b05688c - ctx.h[6] = 0x1f83d9ab - ctx.h[7] = 0x5be0cd19 - } - } else when T == Blake512_Context { - if ctx.is384 { - ctx.h[0] = 0xcbbb9d5dc1059ed8 - ctx.h[1] = 0x629a292a367cd507 - ctx.h[2] = 0x9159015a3070dd17 - ctx.h[3] = 0x152fecd8f70e5939 - ctx.h[4] = 0x67332667ffc00b31 - ctx.h[5] = 0x8eb44a8768581511 - ctx.h[6] = 0xdb0c2e0d64f98fa7 - ctx.h[7] = 0x47b5481dbefa4fa4 - } else { - ctx.h[0] = 0x6a09e667f3bcc908 - ctx.h[1] = 0xbb67ae8584caa73b - ctx.h[2] = 0x3c6ef372fe94f82b - ctx.h[3] = 0xa54ff53a5f1d36f1 - ctx.h[4] = 0x510e527fade682d1 - ctx.h[5] = 0x9b05688c2b3e6c1f - ctx.h[6] = 0x1f83d9abfb41bd6b - ctx.h[7] = 0x5be0cd19137e2179 - } - } -} - -update :: proc "contextless" (ctx: ^$T, data: []byte) { - data := data - when T == Blake256_Context { - if ctx.nx > 0 { - n := copy(ctx.x[ctx.nx:], data) - ctx.nx += n - if ctx.nx == BLOCKSIZE_256 { - block256(ctx, ctx.x[:]) - ctx.nx = 0 - } - data = data[n:] - } - if len(data) >= BLOCKSIZE_256 { - n := len(data) &~ (BLOCKSIZE_256 - 1) - block256(ctx, data[:n]) - data = data[n:] - } - if len(data) > 0 { - ctx.nx = copy(ctx.x[:], data) - } - } else when T == Blake512_Context { - if ctx.nx > 0 { - n := copy(ctx.x[ctx.nx:], data) - ctx.nx += n - if ctx.nx == BLOCKSIZE_512 { - block512(ctx, ctx.x[:]) - ctx.nx = 0 - } - data = data[n:] - } - if len(data) >= BLOCKSIZE_512 { - n := len(data) &~ (BLOCKSIZE_512 - 1) - block512(ctx, data[:n]) - data = data[n:] - } - if len(data) > 0 { - ctx.nx = copy(ctx.x[:], data) - } - } -} - -final :: proc "contextless" (ctx: ^$T, hash: []byte) { - when T == Blake256_Context { - tmp: [65]byte - } else when T == Blake512_Context { - tmp: [129]byte - } - nx := u64(ctx.nx) - tmp[0] = 0x80 - length := (ctx.t + nx) << 3 - - when T == Blake256_Context { - if nx == 55 { - if ctx.is224 { - write_additional(ctx, {0x80}) - } else { - write_additional(ctx, {0x81}) - } - } else { - if nx < 55 { - if nx == 0 { - ctx.nullt = true - } - write_additional(ctx, tmp[0 : 55 - nx]) - } else { - write_additional(ctx, tmp[0 : 64 - nx]) - write_additional(ctx, tmp[1:56]) - ctx.nullt = true - } - if ctx.is224 { - write_additional(ctx, {0x00}) - } else { - write_additional(ctx, {0x01}) - } - } - - for i : uint = 0; i < 8; i += 1 { - tmp[i] = byte(length >> (56 - 8 * i)) - } - write_additional(ctx, tmp[0:8]) - - h := ctx.h[:] - if ctx.is224 { - h = h[0:7] - } - for s, i in h { - hash[i * 4] = byte(s >> 24) - hash[i * 4 + 1] = byte(s >> 16) - hash[i * 4 + 2] = byte(s >> 8) - hash[i * 4 + 3] = byte(s) - } - } else when T == Blake512_Context { - if nx == 111 { - if ctx.is384 { - write_additional(ctx, {0x80}) - } else { - write_additional(ctx, {0x81}) - } - } else { - if nx < 111 { - if nx == 0 { - ctx.nullt = true - } - write_additional(ctx, tmp[0 : 111 - nx]) - } else { - write_additional(ctx, tmp[0 : 128 - nx]) - write_additional(ctx, tmp[1:112]) - ctx.nullt = true - } - if ctx.is384 { - write_additional(ctx, {0x00}) - } else { - write_additional(ctx, {0x01}) - } - } - - for i : uint = 0; i < 16; i += 1 { - tmp[i] = byte(length >> (120 - 8 * i)) - } - write_additional(ctx, tmp[0:16]) - - h := ctx.h[:] - if ctx.is384 { - h = h[0:6] - } - for s, i in h { - hash[i * 8] = byte(s >> 56) - hash[i * 8 + 1] = byte(s >> 48) - hash[i * 8 + 2] = byte(s >> 40) - hash[i * 8 + 3] = byte(s >> 32) - hash[i * 8 + 4] = byte(s >> 24) - hash[i * 8 + 5] = byte(s >> 16) - hash[i * 8 + 6] = byte(s >> 8) - hash[i * 8 + 7] = byte(s) - } - } -} - -SIZE_224 :: 28 -SIZE_256 :: 32 -SIZE_384 :: 48 -SIZE_512 :: 64 -BLOCKSIZE_256 :: 64 -BLOCKSIZE_512 :: 128 - -Blake256_Context :: struct { - h: [8]u32, - s: [4]u32, - t: u64, - x: [64]byte, - nx: int, - is224: bool, - nullt: bool, -} - -Blake512_Context :: struct { - h: [8]u64, - s: [4]u64, - t: u64, - x: [128]byte, - nx: int, - is384: bool, - nullt: bool, -} - -SIGMA := [?]int { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3, - 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4, - 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8, - 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13, - 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9, - 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11, - 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10, - 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5, - 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0, -} - -U256 := [16]u32 { - 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, - 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, - 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, - 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, -} - -U512 := [16]u64 { - 0x243f6a8885a308d3, 0x13198a2e03707344, 0xa4093822299f31d0, 0x082efa98ec4e6c89, - 0x452821e638d01377, 0xbe5466cf34e90c6c, 0xc0ac29b7c97c50dd, 0x3f84d5b5b5470917, - 0x9216d5d98979fb1b, 0xd1310ba698dfb5ac, 0x2ffd72dbd01adfb7, 0xb8e1afed6a267e96, - 0xba7c9045f12c7f99, 0x24a19947b3916cf7, 0x0801f2e2858efc16, 0x636920d871574e69, -} - -G256 :: #force_inline proc "contextless" (a, b, c, d: u32, m: [16]u32, i, j: int) -> (u32, u32, u32, u32) { - a, b, c, d := a, b, c, d - a += m[SIGMA[(i % 10) * 16 + (2 * j)]] ~ U256[SIGMA[(i % 10) * 16 + (2 * j + 1)]] - a += b - d ~= a - d = d << (32 - 16) | d >> 16 - c += d - b ~= c - b = b << (32 - 12) | b >> 12 - a += m[SIGMA[(i % 10) * 16 + (2 * j + 1)]] ~ U256[SIGMA[(i % 10) * 16 + (2 * j)]] - a += b - d ~= a - d = d << (32 - 8) | d >> 8 - c += d - b ~= c - b = b << (32 - 7) | b >> 7 - return a, b, c, d -} - -G512 :: #force_inline proc "contextless" (a, b, c, d: u64, m: [16]u64, i, j: int) -> (u64, u64, u64, u64) { - a, b, c, d := a, b, c, d - a += m[SIGMA[(i % 10) * 16 + (2 * j)]] ~ U512[SIGMA[(i % 10) * 16 + (2 * j + 1)]] - a += b - d ~= a - d = d << (64 - 32) | d >> 32 - c += d - b ~= c - b = b << (64 - 25) | b >> 25 - a += m[SIGMA[(i % 10) * 16 + (2 * j + 1)]] ~ U512[SIGMA[(i % 10) * 16 + (2 * j)]] - a += b - d ~= a - d = d << (64 - 16) | d >> 16 - c += d - b ~= c - b = b << (64 - 11) | b >> 11 - return a, b, c, d -} - -block256 :: proc "contextless" (ctx: ^Blake256_Context, p: []byte) #no_bounds_check { - i, j: int = ---, --- - v, m: [16]u32 = ---, --- - p := p - for len(p) >= BLOCKSIZE_256 { - v[0] = ctx.h[0] - v[1] = ctx.h[1] - v[2] = ctx.h[2] - v[3] = ctx.h[3] - v[4] = ctx.h[4] - v[5] = ctx.h[5] - v[6] = ctx.h[6] - v[7] = ctx.h[7] - v[8] = ctx.s[0] ~ U256[0] - v[9] = ctx.s[1] ~ U256[1] - v[10] = ctx.s[2] ~ U256[2] - v[11] = ctx.s[3] ~ U256[3] - v[12] = U256[4] - v[13] = U256[5] - v[14] = U256[6] - v[15] = U256[7] - - ctx.t += 512 - if !ctx.nullt { - v[12] ~= u32(ctx.t) - v[13] ~= u32(ctx.t) - v[14] ~= u32(ctx.t >> 32) - v[15] ~= u32(ctx.t >> 32) - } - - for i, j = 0, 0; i < 16; i, j = i+1, j+4 { - m[i] = u32(p[j]) << 24 | u32(p[j + 1]) << 16 | u32(p[j + 2]) << 8 | u32(p[j + 3]) - } - - for i = 0; i < 14; i += 1 { - v[0], v[4], v[8], v[12] = G256(v[0], v[4], v[8], v[12], m, i, 0) - v[1], v[5], v[9], v[13] = G256(v[1], v[5], v[9], v[13], m, i, 1) - v[2], v[6], v[10], v[14] = G256(v[2], v[6], v[10], v[14], m, i, 2) - v[3], v[7], v[11], v[15] = G256(v[3], v[7], v[11], v[15], m, i, 3) - v[0], v[5], v[10], v[15] = G256(v[0], v[5], v[10], v[15], m, i, 4) - v[1], v[6], v[11], v[12] = G256(v[1], v[6], v[11], v[12], m, i, 5) - v[2], v[7], v[8], v[13] = G256(v[2], v[7], v[8], v[13], m, i, 6) - v[3], v[4], v[9], v[14] = G256(v[3], v[4], v[9], v[14], m, i, 7) - } - - for i = 0; i < 8; i += 1 { - ctx.h[i] ~= ctx.s[i % 4] ~ v[i] ~ v[i + 8] - } - p = p[BLOCKSIZE_256:] - } -} - -block512 :: proc "contextless" (ctx: ^Blake512_Context, p: []byte) #no_bounds_check { - i, j: int = ---, --- - v, m: [16]u64 = ---, --- - p := p - for len(p) >= BLOCKSIZE_512 { - v[0] = ctx.h[0] - v[1] = ctx.h[1] - v[2] = ctx.h[2] - v[3] = ctx.h[3] - v[4] = ctx.h[4] - v[5] = ctx.h[5] - v[6] = ctx.h[6] - v[7] = ctx.h[7] - v[8] = ctx.s[0] ~ U512[0] - v[9] = ctx.s[1] ~ U512[1] - v[10] = ctx.s[2] ~ U512[2] - v[11] = ctx.s[3] ~ U512[3] - v[12] = U512[4] - v[13] = U512[5] - v[14] = U512[6] - v[15] = U512[7] - - ctx.t += 1024 - if !ctx.nullt { - v[12] ~= ctx.t - v[13] ~= ctx.t - v[14] ~= 0 - v[15] ~= 0 - } - - for i, j = 0, 0; i < 16; i, j = i + 1, j + 8 { - m[i] = u64(p[j]) << 56 | u64(p[j + 1]) << 48 | u64(p[j + 2]) << 40 | u64(p[j + 3]) << 32 | - u64(p[j + 4]) << 24 | u64(p[j + 5]) << 16 | u64(p[j + 6]) << 8 | u64(p[j + 7]) - } - for i = 0; i < 16; i += 1 { - v[0], v[4], v[8], v[12] = G512(v[0], v[4], v[8], v[12], m, i, 0) - v[1], v[5], v[9], v[13] = G512(v[1], v[5], v[9], v[13], m, i, 1) - v[2], v[6], v[10], v[14] = G512(v[2], v[6], v[10], v[14], m, i, 2) - v[3], v[7], v[11], v[15] = G512(v[3], v[7], v[11], v[15], m, i, 3) - v[0], v[5], v[10], v[15] = G512(v[0], v[5], v[10], v[15], m, i, 4) - v[1], v[6], v[11], v[12] = G512(v[1], v[6], v[11], v[12], m, i, 5) - v[2], v[7], v[8], v[13] = G512(v[2], v[7], v[8], v[13], m, i, 6) - v[3], v[4], v[9], v[14] = G512(v[3], v[4], v[9], v[14], m, i, 7) - } - - for i = 0; i < 8; i += 1 { - ctx.h[i] ~= ctx.s[i % 4] ~ v[i] ~ v[i + 8] - } - p = p[BLOCKSIZE_512:] - } -} - -write_additional :: proc "contextless" (ctx: ^$T, data: []byte) { - ctx.t -= u64(len(data)) << 3 - update(ctx, data) -} diff --git a/core/crypto/blake2b/blake2b.odin b/core/crypto/blake2b/blake2b.odin index 8f0770f82..74396b103 100644 --- a/core/crypto/blake2b/blake2b.odin +++ b/core/crypto/blake2b/blake2b.odin @@ -1,3 +1,10 @@ +/* +package blake2b implements the BLAKE2b hash algorithm. + +See: +- [[ https://datatracker.ietf.org/doc/html/rfc7693 ]] +- [[ https://www.blake2.net ]] +*/ package blake2b /* @@ -6,122 +13,47 @@ package blake2b List of contributors: zhibog, dotbmp: Initial implementation. - - Interface for the BLAKE2B hashing algorithm. - BLAKE2B and BLAKE2B share the implementation in the _blake2 package. */ -import "core:os" -import "core:io" - import "../_blake2" -/* - High level API -*/ - +// DIGEST_SIZE is the BLAKE2b digest size in bytes. DIGEST_SIZE :: 64 -// hash_string will hash the given input and return the -// computed hash -hash_string :: proc(data: string) -> [DIGEST_SIZE]byte { - return hash_bytes(transmute([]byte)(data)) +// BLOCK_SIZE is the BLAKE2b block size in bytes. +BLOCK_SIZE :: _blake2.BLAKE2B_BLOCK_SIZE + +// Context is a BLAKE2b instance. +Context :: _blake2.Blake2b_Context + +// init initializes a Context with the default BLAKE2b config. +init :: proc(ctx: ^Context) { + cfg: _blake2.Blake2_Config + cfg.size = _blake2.BLAKE2B_SIZE + _blake2.init(ctx, &cfg) } -// hash_bytes will hash the given input and return the -// computed hash -hash_bytes :: proc(data: []byte) -> [DIGEST_SIZE]byte { - hash: [DIGEST_SIZE]byte - ctx: _blake2.Blake2b_Context - cfg: _blake2.Blake2_Config - cfg.size = _blake2.BLAKE2B_SIZE - ctx.cfg = cfg - _blake2.init(&ctx) - _blake2.update(&ctx, data) - _blake2.final(&ctx, hash[:]) - return hash +// update adds more data to the Context. +update :: proc(ctx: ^Context, data: []byte) { + _blake2.update(ctx, data) } -// hash_string_to_buffer will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer(transmute([]byte)(data), hash) +// final finalizes the Context, writes the digest to hash, and calls +// reset on the Context. +// +// 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) { + _blake2.final(ctx, hash, finalize_clone) } -// hash_bytes_to_buffer will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE, "Size of destination buffer is smaller than the digest size") - ctx: _blake2.Blake2b_Context - cfg: _blake2.Blake2_Config - cfg.size = _blake2.BLAKE2B_SIZE - ctx.cfg = cfg - _blake2.init(&ctx) - _blake2.update(&ctx, data) - _blake2.final(&ctx, hash) +// clone clones the Context other into ctx. +clone :: proc(ctx, other: ^Context) { + _blake2.clone(ctx, other) } - -// hash_stream will read the stream in chunks and compute a -// hash from its contents -hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { - hash: [DIGEST_SIZE]byte - ctx: _blake2.Blake2b_Context - cfg: _blake2.Blake2_Config - cfg.size = _blake2.BLAKE2B_SIZE - ctx.cfg = cfg - _blake2.init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - _blake2.update(&ctx, buf[:read]) - } - } - _blake2.final(&ctx, hash[:]) - return hash, true -} - -// hash_file will read the file provided by the given handle -// and compute a hash -hash_file :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE]byte, bool) { - if !load_at_once { - return hash_stream(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes(buf[:]), ok - } - } - return [DIGEST_SIZE]byte{}, false -} - -hash :: proc { - hash_stream, - hash_file, - hash_bytes, - hash_string, - hash_bytes_to_buffer, - hash_string_to_buffer, -} - -/* - Low level API -*/ - -Blake2b_Context :: _blake2.Blake2b_Context - -init :: proc(ctx: ^_blake2.Blake2b_Context) { - _blake2.init(ctx) -} - -update :: proc "contextless" (ctx: ^_blake2.Blake2b_Context, data: []byte) { - _blake2.update(ctx, data) -} - -final :: proc "contextless" (ctx: ^_blake2.Blake2b_Context, hash: []byte) { - _blake2.final(ctx, hash) +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. +reset :: proc(ctx: ^Context) { + _blake2.reset(ctx) } diff --git a/core/crypto/blake2s/blake2s.odin b/core/crypto/blake2s/blake2s.odin index 6a2d4ab9b..339ddf027 100644 --- a/core/crypto/blake2s/blake2s.odin +++ b/core/crypto/blake2s/blake2s.odin @@ -1,3 +1,10 @@ +/* +package blake2s implements the BLAKE2s hash algorithm. + +See: +- [[ https://datatracker.ietf.org/doc/html/rfc7693 ]] +- [[ https://www.blake2.net/ ]] +*/ package blake2s /* @@ -6,122 +13,47 @@ package blake2s List of contributors: zhibog, dotbmp: Initial implementation. - - Interface for the BLAKE2S hashing algorithm. - BLAKE2B and BLAKE2B share the implementation in the _blake2 package. */ -import "core:os" -import "core:io" - import "../_blake2" -/* - High level API -*/ - +// DIGEST_SIZE is the BLAKE2s digest size in bytes. DIGEST_SIZE :: 32 -// hash_string will hash the given input and return the -// computed hash -hash_string :: proc(data: string) -> [DIGEST_SIZE]byte { - return hash_bytes(transmute([]byte)(data)) +// BLOCK_SIZE is the BLAKE2s block size in bytes. +BLOCK_SIZE :: _blake2.BLAKE2S_BLOCK_SIZE + +// Context is a BLAKE2s instance. +Context :: _blake2.Blake2s_Context + +// init initializes a Context with the default BLAKE2s config. +init :: proc(ctx: ^Context) { + cfg: _blake2.Blake2_Config + cfg.size = _blake2.BLAKE2S_SIZE + _blake2.init(ctx, &cfg) } -// hash_bytes will hash the given input and return the -// computed hash -hash_bytes :: proc(data: []byte) -> [DIGEST_SIZE]byte { - hash: [DIGEST_SIZE]byte - ctx: _blake2.Blake2s_Context - cfg: _blake2.Blake2_Config - cfg.size = _blake2.BLAKE2S_SIZE - ctx.cfg = cfg - _blake2.init(&ctx) - _blake2.update(&ctx, data) - _blake2.final(&ctx, hash[:]) - return hash +// update adds more data to the Context. +update :: proc(ctx: ^Context, data: []byte) { + _blake2.update(ctx, data) } - -// hash_string_to_buffer will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer(transmute([]byte)(data), hash) +// final finalizes the Context, writes the digest to hash, and calls +// reset on the Context. +// +// 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) { + _blake2.final(ctx, hash, finalize_clone) } -// hash_bytes_to_buffer will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE, "Size of destination buffer is smaller than the digest size") - ctx: _blake2.Blake2s_Context - cfg: _blake2.Blake2_Config - cfg.size = _blake2.BLAKE2S_SIZE - ctx.cfg = cfg - _blake2.init(&ctx) - _blake2.update(&ctx, data) - _blake2.final(&ctx, hash) +// clone clones the Context other into ctx. +clone :: proc(ctx, other: ^Context) { + _blake2.clone(ctx, other) } -// hash_stream will read the stream in chunks and compute a -// hash from its contents -hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { - hash: [DIGEST_SIZE]byte - ctx: _blake2.Blake2s_Context - cfg: _blake2.Blake2_Config - cfg.size = _blake2.BLAKE2S_SIZE - ctx.cfg = cfg - _blake2.init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - _blake2.update(&ctx, buf[:read]) - } - } - _blake2.final(&ctx, hash[:]) - return hash, true -} - -// hash_file will read the file provided by the given handle -// and compute a hash -hash_file :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE]byte, bool) { - if !load_at_once { - return hash_stream(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes(buf[:]), ok - } - } - return [DIGEST_SIZE]byte{}, false -} - -hash :: proc { - hash_stream, - hash_file, - hash_bytes, - hash_string, - hash_bytes_to_buffer, - hash_string_to_buffer, -} - -/* - Low level API -*/ - -Blake2s_Context :: _blake2.Blake2b_Context - -init :: proc(ctx: ^_blake2.Blake2s_Context) { - _blake2.init(ctx) -} - -update :: proc "contextless" (ctx: ^_blake2.Blake2s_Context, data: []byte) { - _blake2.update(ctx, data) -} - -final :: proc "contextless" (ctx: ^_blake2.Blake2s_Context, hash: []byte) { - _blake2.final(ctx, hash) +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. +reset :: proc(ctx: ^Context) { + _blake2.reset(ctx) } diff --git a/core/crypto/chacha20/chacha20.odin b/core/crypto/chacha20/chacha20.odin index b29dc1228..dfab2bc65 100644 --- a/core/crypto/chacha20/chacha20.odin +++ b/core/crypto/chacha20/chacha20.odin @@ -1,125 +1,90 @@ +/* +package chacha20 implements the ChaCha20 and XChaCha20 stream ciphers. + +See: +- [[ https://datatracker.ietf.org/doc/html/rfc8439 ]] +- [[ https://datatracker.ietf.org/doc/draft-irtf-cfrg-xchacha/03/ ]] +*/ package chacha20 -import "core:crypto/util" -import "core:math/bits" +import "core:bytes" +import "core:crypto/_chacha20" import "core:mem" -KEY_SIZE :: 32 -NONCE_SIZE :: 12 -XNONCE_SIZE :: 24 - -@(private) -_MAX_CTR_IETF :: 0xffffffff - -@(private) -_BLOCK_SIZE :: 64 -@(private) -_STATE_SIZE_U32 :: 16 -@(private) -_ROUNDS :: 20 - -@(private) -_SIGMA_0 : u32 : 0x61707865 -@(private) -_SIGMA_1 : u32 : 0x3320646e -@(private) -_SIGMA_2 : u32 : 0x79622d32 -@(private) -_SIGMA_3 : u32 : 0x6b206574 +// KEY_SIZE is the (X)ChaCha20 key size in bytes. +KEY_SIZE :: _chacha20.KEY_SIZE +// IV_SIZE is the ChaCha20 IV size in bytes. +IV_SIZE :: _chacha20.IV_SIZE +// XIV_SIZE is the XChaCha20 IV size in bytes. +XIV_SIZE :: _chacha20.XIV_SIZE +// Context is a ChaCha20 or XChaCha20 instance. Context :: struct { - _s: [_STATE_SIZE_U32]u32, - - _buffer: [_BLOCK_SIZE]byte, - _off: int, - - _is_ietf_flavor: bool, - _is_initialized: bool, + _state: _chacha20.Context, + _impl: Implementation, } -init :: proc (ctx: ^Context, key, nonce: []byte) { +// init inititializes a Context for ChaCha20 or XChaCha20 with the provided +// key and iv. +init :: proc(ctx: ^Context, key, iv: []byte, impl := DEFAULT_IMPLEMENTATION) { if len(key) != KEY_SIZE { - panic("crypto/chacha20: invalid ChaCha20 key size") + panic("crypto/chacha20: invalid (X)ChaCha20 key size") } - if n_len := len(nonce); n_len != NONCE_SIZE && n_len != XNONCE_SIZE { - panic("crypto/chacha20: invalid (X)ChaCha20 nonce size") + if l := len(iv); l != IV_SIZE && l != XIV_SIZE { + panic("crypto/chacha20: invalid (X)ChaCha20 IV size") } - k, n := key, nonce + k, n := key, iv - // Derive the XChaCha20 subkey and sub-nonce via HChaCha20. - is_xchacha := len(nonce) == XNONCE_SIZE + init_impl(ctx, impl) + + is_xchacha := len(iv) == XIV_SIZE if is_xchacha { - sub_key := ctx._buffer[:KEY_SIZE] - _hchacha20(sub_key, k, n) + sub_iv: [IV_SIZE]byte + sub_key := ctx._state._buffer[:KEY_SIZE] + hchacha20(sub_key, k, n, ctx._impl) k = sub_key - n = n[16:24] + copy(sub_iv[4:], n[16:]) + n = sub_iv[:] } - ctx._s[0] = _SIGMA_0 - ctx._s[1] = _SIGMA_1 - ctx._s[2] = _SIGMA_2 - ctx._s[3] = _SIGMA_3 - ctx._s[4] = util.U32_LE(k[0:4]) - ctx._s[5] = util.U32_LE(k[4:8]) - ctx._s[6] = util.U32_LE(k[8:12]) - ctx._s[7] = util.U32_LE(k[12:16]) - ctx._s[8] = util.U32_LE(k[16:20]) - ctx._s[9] = util.U32_LE(k[20:24]) - ctx._s[10] = util.U32_LE(k[24:28]) - ctx._s[11] = util.U32_LE(k[28:32]) - ctx._s[12] = 0 - if !is_xchacha { - ctx._s[13] = util.U32_LE(n[0:4]) - ctx._s[14] = util.U32_LE(n[4:8]) - ctx._s[15] = util.U32_LE(n[8:12]) - } else { - ctx._s[13] = 0 - ctx._s[14] = util.U32_LE(n[0:4]) - ctx._s[15] = util.U32_LE(n[4:8]) + _chacha20.init(&ctx._state, k, n, is_xchacha) + if is_xchacha { // The sub-key is stored in the keystream buffer. While // this will be overwritten in most circumstances, explicitly // clear it out early. - mem.zero_explicit(&ctx._buffer, KEY_SIZE) + mem.zero_explicit(&ctx._state._buffer, KEY_SIZE) } - - ctx._off = _BLOCK_SIZE - ctx._is_ietf_flavor = !is_xchacha - ctx._is_initialized = true } -seek :: proc (ctx: ^Context, block_nr: u64) { - assert(ctx._is_initialized) - - if ctx._is_ietf_flavor { - if block_nr > _MAX_CTR_IETF { - panic("crypto/chacha20: attempted to seek past maximum counter") - } - } else { - ctx._s[13] = u32(block_nr >> 32) - } - ctx._s[12] = u32(block_nr) - ctx._off = _BLOCK_SIZE +// seek seeks the (X)ChaCha20 stream counter to the specified block. +seek :: proc(ctx: ^Context, block_nr: u64) { + _chacha20.seek(&ctx._state, block_nr) } -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. +// xor_bytes XORs each byte in src with bytes taken from the (X)ChaCha20 +// keystream, and writes the resulting output to dst. Dst and src MUST +// alias exactly or not at all. +xor_bytes :: proc(ctx: ^Context, dst, src: []byte) { + assert(ctx._state._is_initialized) src, dst := src, dst if dst_len := len(dst); dst_len < len(src) { src = src[:dst_len] } - for remaining := len(src); remaining > 0; { + if bytes.alias_inexactly(dst, src) { + panic("crypto/chacha20: dst and src alias inexactly") + } + + st := &ctx._state + #no_bounds_check for remaining := len(src); remaining > 0; { // Process multiple blocks at once - if ctx._off == _BLOCK_SIZE { - if nr_blocks := remaining / _BLOCK_SIZE; nr_blocks > 0 { - direct_bytes := nr_blocks * _BLOCK_SIZE - _do_blocks(ctx, dst, src, nr_blocks) + if st._off == _chacha20.BLOCK_SIZE { + if nr_blocks := remaining / _chacha20.BLOCK_SIZE; nr_blocks > 0 { + direct_bytes := nr_blocks * _chacha20.BLOCK_SIZE + stream_blocks(ctx, dst, src, nr_blocks) remaining -= direct_bytes if remaining == 0 { return @@ -130,33 +95,34 @@ xor_bytes :: proc (ctx: ^Context, dst, src: []byte) { // If there is a partial block, generate and buffer 1 block // worth of keystream. - _do_blocks(ctx, ctx._buffer[:], nil, 1) - ctx._off = 0 + stream_blocks(ctx, st._buffer[:], nil, 1) + st._off = 0 } // Process partial blocks from the buffered keystream. - to_xor := min(_BLOCK_SIZE - ctx._off, remaining) - buffered_keystream := ctx._buffer[ctx._off:] + to_xor := min(_chacha20.BLOCK_SIZE - st._off, remaining) + buffered_keystream := st._buffer[st._off:] for i := 0; i < to_xor; i = i + 1 { dst[i] = buffered_keystream[i] ~ src[i] } - ctx._off += to_xor + st._off += to_xor dst = dst[to_xor:] src = src[to_xor:] remaining -= to_xor } } -keystream_bytes :: proc (ctx: ^Context, dst: []byte) { - assert(ctx._is_initialized) +// keystream_bytes fills dst with the raw (X)ChaCha20 keystream output. +keystream_bytes :: proc(ctx: ^Context, dst: []byte) { + assert(ctx._state._is_initialized) - dst := dst - for remaining := len(dst); remaining > 0; { + dst, st := dst, &ctx._state + #no_bounds_check for remaining := len(dst); remaining > 0; { // Process multiple blocks at once - if ctx._off == _BLOCK_SIZE { - if nr_blocks := remaining / _BLOCK_SIZE; nr_blocks > 0 { - direct_bytes := nr_blocks * _BLOCK_SIZE - _do_blocks(ctx, dst, nil, nr_blocks) + if st._off == _chacha20.BLOCK_SIZE { + if nr_blocks := remaining / _chacha20.BLOCK_SIZE; nr_blocks > 0 { + direct_bytes := nr_blocks * _chacha20.BLOCK_SIZE + stream_blocks(ctx, dst, nil, nr_blocks) remaining -= direct_bytes if remaining == 0 { return @@ -166,426 +132,22 @@ keystream_bytes :: proc (ctx: ^Context, dst: []byte) { // If there is a partial block, generate and buffer 1 block // worth of keystream. - _do_blocks(ctx, ctx._buffer[:], nil, 1) - ctx._off = 0 + stream_blocks(ctx, st._buffer[:], nil, 1) + st._off = 0 } // Process partial blocks from the buffered keystream. - to_copy := min(_BLOCK_SIZE - ctx._off, remaining) - buffered_keystream := ctx._buffer[ctx._off:] + to_copy := min(_chacha20.BLOCK_SIZE - st._off, remaining) + buffered_keystream := st._buffer[st._off:] copy(dst[:to_copy], buffered_keystream[:to_copy]) - ctx._off += to_copy + st._off += to_copy dst = dst[to_copy:] remaining -= to_copy } } -reset :: proc (ctx: ^Context) { - mem.zero_explicit(&ctx._s, size_of(ctx._s)) - mem.zero_explicit(&ctx._buffer, size_of(ctx._buffer)) - - ctx._is_initialized = false -} - -@(private) -_do_blocks :: proc (ctx: ^Context, dst, src: []byte, nr_blocks: int) { - // Enforce the maximum consumed keystream per nonce. - // - // While all modern "standard" definitions of ChaCha20 use - // the IETF 32-bit counter, for XChaCha20 most common - // implementations allow for a 64-bit counter. - // - // Honestly, the answer here is "use a MRAE primitive", but - // go with common practice in the case of XChaCha20. - if ctx._is_ietf_flavor { - if u64(ctx._s[12]) + u64(nr_blocks) > 0xffffffff { - panic("crypto/chacha20: maximum ChaCha20 keystream per nonce reached") - } - } else { - ctr := (u64(ctx._s[13]) << 32) | u64(ctx._s[12]) - if _, carry := bits.add_u64(ctr, u64(nr_blocks), 0); carry != 0 { - panic("crypto/chacha20: maximum XChaCha20 keystream per nonce reached") - } - } - - dst, src := dst, src - x := &ctx._s - for n := 0; n < nr_blocks; n = n + 1 { - x0, x1, x2, x3 := _SIGMA_0, _SIGMA_1, _SIGMA_2, _SIGMA_3 - x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15 := x[4], x[5], x[6], x[7], x[8], x[9], x[10], x[11], x[12], x[13], x[14], x[15] - - for i := _ROUNDS; i > 0; i = i - 2 { - // Even when forcing inlining manually inlining all of - // these is decently faster. - - // quarterround(x, 0, 4, 8, 12) - x0 += x4 - x12 ~= x0 - x12 = util.ROTL32(x12, 16) - x8 += x12 - x4 ~= x8 - x4 = util.ROTL32(x4, 12) - x0 += x4 - x12 ~= x0 - x12 = util.ROTL32(x12, 8) - x8 += x12 - x4 ~= x8 - x4 = util.ROTL32(x4, 7) - - // quarterround(x, 1, 5, 9, 13) - x1 += x5 - x13 ~= x1 - x13 = util.ROTL32(x13, 16) - x9 += x13 - x5 ~= x9 - x5 = util.ROTL32(x5, 12) - x1 += x5 - x13 ~= x1 - x13 = util.ROTL32(x13, 8) - x9 += x13 - x5 ~= x9 - x5 = util.ROTL32(x5, 7) - - // quarterround(x, 2, 6, 10, 14) - x2 += x6 - x14 ~= x2 - x14 = util.ROTL32(x14, 16) - x10 += x14 - x6 ~= x10 - x6 = util.ROTL32(x6, 12) - x2 += x6 - x14 ~= x2 - x14 = util.ROTL32(x14, 8) - x10 += x14 - x6 ~= x10 - x6 = util.ROTL32(x6, 7) - - // quarterround(x, 3, 7, 11, 15) - x3 += x7 - x15 ~= x3 - x15 = util.ROTL32(x15, 16) - x11 += x15 - x7 ~= x11 - x7 = util.ROTL32(x7, 12) - x3 += x7 - x15 ~= x3 - x15 = util.ROTL32(x15, 8) - x11 += x15 - x7 ~= x11 - x7 = util.ROTL32(x7, 7) - - // quarterround(x, 0, 5, 10, 15) - x0 += x5 - x15 ~= x0 - x15 = util.ROTL32(x15, 16) - x10 += x15 - x5 ~= x10 - x5 = util.ROTL32(x5, 12) - x0 += x5 - x15 ~= x0 - x15 = util.ROTL32(x15, 8) - x10 += x15 - x5 ~= x10 - x5 = util.ROTL32(x5, 7) - - // quarterround(x, 1, 6, 11, 12) - x1 += x6 - x12 ~= x1 - x12 = util.ROTL32(x12, 16) - x11 += x12 - x6 ~= x11 - x6 = util.ROTL32(x6, 12) - x1 += x6 - x12 ~= x1 - x12 = util.ROTL32(x12, 8) - x11 += x12 - x6 ~= x11 - x6 = util.ROTL32(x6, 7) - - // quarterround(x, 2, 7, 8, 13) - x2 += x7 - x13 ~= x2 - x13 = util.ROTL32(x13, 16) - x8 += x13 - x7 ~= x8 - x7 = util.ROTL32(x7, 12) - x2 += x7 - x13 ~= x2 - x13 = util.ROTL32(x13, 8) - x8 += x13 - x7 ~= x8 - x7 = util.ROTL32(x7, 7) - - // quarterround(x, 3, 4, 9, 14) - x3 += x4 - x14 ~= x3 - x14 = util.ROTL32(x14, 16) - x9 += x14 - x4 ~= x9 - x4 = util.ROTL32(x4, 12) - x3 += x4 - x14 ~= x3 - x14 = util.ROTL32(x14, 8) - x9 += x14 - x4 ~= x9 - x4 = util.ROTL32(x4, 7) - } - - x0 += _SIGMA_0 - x1 += _SIGMA_1 - x2 += _SIGMA_2 - x3 += _SIGMA_3 - x4 += x[4] - x5 += x[5] - x6 += x[6] - x7 += x[7] - x8 += x[8] - x9 += x[9] - x10 += x[10] - x11 += x[11] - x12 += x[12] - x13 += x[13] - x14 += x[14] - x15 += x[15] - - // While the "correct" answer to getting more performance out of - // this is "use vector operations", support for that is currently - // a work in progress/to be designed. - // - // Until dedicated assembly can be written leverage the fact that - // the callers of this routine ensure that src/dst are valid. - - when ODIN_ARCH == .i386 || ODIN_ARCH == .amd64 { - // util.PUT_U32_LE/util.U32_LE are not required on little-endian - // systems that also happen to not be strict about aligned - // memory access. - - dst_p := transmute(^[16]u32)(&dst[0]) - if src != nil { - src_p := transmute(^[16]u32)(&src[0]) - dst_p[0] = src_p[0] ~ x0 - dst_p[1] = src_p[1] ~ x1 - dst_p[2] = src_p[2] ~ x2 - dst_p[3] = src_p[3] ~ x3 - dst_p[4] = src_p[4] ~ x4 - dst_p[5] = src_p[5] ~ x5 - dst_p[6] = src_p[6] ~ x6 - dst_p[7] = src_p[7] ~ x7 - dst_p[8] = src_p[8] ~ x8 - dst_p[9] = src_p[9] ~ x9 - dst_p[10] = src_p[10] ~ x10 - dst_p[11] = src_p[11] ~ x11 - dst_p[12] = src_p[12] ~ x12 - dst_p[13] = src_p[13] ~ x13 - dst_p[14] = src_p[14] ~ x14 - dst_p[15] = src_p[15] ~ x15 - src = src[_BLOCK_SIZE:] - } else { - dst_p[0] = x0 - dst_p[1] = x1 - dst_p[2] = x2 - dst_p[3] = x3 - dst_p[4] = x4 - dst_p[5] = x5 - dst_p[6] = x6 - dst_p[7] = x7 - dst_p[8] = x8 - dst_p[9] = x9 - dst_p[10] = x10 - dst_p[11] = x11 - dst_p[12] = x12 - dst_p[13] = x13 - dst_p[14] = x14 - dst_p[15] = x15 - } - dst = dst[_BLOCK_SIZE:] - } else { - #no_bounds_check { - if src != nil { - util.PUT_U32_LE(dst[0:4], util.U32_LE(src[0:4]) ~ x0) - util.PUT_U32_LE(dst[4:8], util.U32_LE(src[4:8]) ~ x1) - util.PUT_U32_LE(dst[8:12], util.U32_LE(src[8:12]) ~ x2) - util.PUT_U32_LE(dst[12:16], util.U32_LE(src[12:16]) ~ x3) - util.PUT_U32_LE(dst[16:20], util.U32_LE(src[16:20]) ~ x4) - util.PUT_U32_LE(dst[20:24], util.U32_LE(src[20:24]) ~ x5) - util.PUT_U32_LE(dst[24:28], util.U32_LE(src[24:28]) ~ x6) - util.PUT_U32_LE(dst[28:32], util.U32_LE(src[28:32]) ~ x7) - util.PUT_U32_LE(dst[32:36], util.U32_LE(src[32:36]) ~ x8) - util.PUT_U32_LE(dst[36:40], util.U32_LE(src[36:40]) ~ x9) - util.PUT_U32_LE(dst[40:44], util.U32_LE(src[40:44]) ~ x10) - util.PUT_U32_LE(dst[44:48], util.U32_LE(src[44:48]) ~ x11) - util.PUT_U32_LE(dst[48:52], util.U32_LE(src[48:52]) ~ x12) - util.PUT_U32_LE(dst[52:56], util.U32_LE(src[52:56]) ~ x13) - util.PUT_U32_LE(dst[56:60], util.U32_LE(src[56:60]) ~ x14) - util.PUT_U32_LE(dst[60:64], util.U32_LE(src[60:64]) ~ x15) - src = src[_BLOCK_SIZE:] - } else { - util.PUT_U32_LE(dst[0:4], x0) - util.PUT_U32_LE(dst[4:8], x1) - util.PUT_U32_LE(dst[8:12], x2) - util.PUT_U32_LE(dst[12:16], x3) - util.PUT_U32_LE(dst[16:20], x4) - util.PUT_U32_LE(dst[20:24], x5) - util.PUT_U32_LE(dst[24:28], x6) - util.PUT_U32_LE(dst[28:32], x7) - util.PUT_U32_LE(dst[32:36], x8) - util.PUT_U32_LE(dst[36:40], x9) - util.PUT_U32_LE(dst[40:44], x10) - util.PUT_U32_LE(dst[44:48], x11) - util.PUT_U32_LE(dst[48:52], x12) - util.PUT_U32_LE(dst[52:56], x13) - util.PUT_U32_LE(dst[56:60], x14) - util.PUT_U32_LE(dst[60:64], x15) - } - dst = dst[_BLOCK_SIZE:] - } - } - - // Increment the counter. Overflow checking is done upon - // entry into the routine, so a 64-bit increment safely - // covers both cases. - new_ctr := ((u64(ctx._s[13]) << 32) | u64(ctx._s[12])) + 1 - x[12] = u32(new_ctr) - x[13] = u32(new_ctr >> 32) - } -} - -@(private) -_hchacha20 :: proc (dst, key, nonce: []byte) { - x0, x1, x2, x3 := _SIGMA_0, _SIGMA_1, _SIGMA_2, _SIGMA_3 - x4 := util.U32_LE(key[0:4]) - x5 := util.U32_LE(key[4:8]) - x6 := util.U32_LE(key[8:12]) - x7 := util.U32_LE(key[12:16]) - x8 := util.U32_LE(key[16:20]) - x9 := util.U32_LE(key[20:24]) - x10 := util.U32_LE(key[24:28]) - x11 := util.U32_LE(key[28:32]) - x12 := util.U32_LE(nonce[0:4]) - x13 := util.U32_LE(nonce[4:8]) - x14 := util.U32_LE(nonce[8:12]) - x15 := util.U32_LE(nonce[12:16]) - - for i := _ROUNDS; i > 0; i = i - 2 { - // quarterround(x, 0, 4, 8, 12) - x0 += x4 - x12 ~= x0 - x12 = util.ROTL32(x12, 16) - x8 += x12 - x4 ~= x8 - x4 = util.ROTL32(x4, 12) - x0 += x4 - x12 ~= x0 - x12 = util.ROTL32(x12, 8) - x8 += x12 - x4 ~= x8 - x4 = util.ROTL32(x4, 7) - - // quarterround(x, 1, 5, 9, 13) - x1 += x5 - x13 ~= x1 - x13 = util.ROTL32(x13, 16) - x9 += x13 - x5 ~= x9 - x5 = util.ROTL32(x5, 12) - x1 += x5 - x13 ~= x1 - x13 = util.ROTL32(x13, 8) - x9 += x13 - x5 ~= x9 - x5 = util.ROTL32(x5, 7) - - // quarterround(x, 2, 6, 10, 14) - x2 += x6 - x14 ~= x2 - x14 = util.ROTL32(x14, 16) - x10 += x14 - x6 ~= x10 - x6 = util.ROTL32(x6, 12) - x2 += x6 - x14 ~= x2 - x14 = util.ROTL32(x14, 8) - x10 += x14 - x6 ~= x10 - x6 = util.ROTL32(x6, 7) - - // quarterround(x, 3, 7, 11, 15) - x3 += x7 - x15 ~= x3 - x15 = util.ROTL32(x15, 16) - x11 += x15 - x7 ~= x11 - x7 = util.ROTL32(x7, 12) - x3 += x7 - x15 ~= x3 - x15 = util.ROTL32(x15, 8) - x11 += x15 - x7 ~= x11 - x7 = util.ROTL32(x7, 7) - - // quarterround(x, 0, 5, 10, 15) - x0 += x5 - x15 ~= x0 - x15 = util.ROTL32(x15, 16) - x10 += x15 - x5 ~= x10 - x5 = util.ROTL32(x5, 12) - x0 += x5 - x15 ~= x0 - x15 = util.ROTL32(x15, 8) - x10 += x15 - x5 ~= x10 - x5 = util.ROTL32(x5, 7) - - // quarterround(x, 1, 6, 11, 12) - x1 += x6 - x12 ~= x1 - x12 = util.ROTL32(x12, 16) - x11 += x12 - x6 ~= x11 - x6 = util.ROTL32(x6, 12) - x1 += x6 - x12 ~= x1 - x12 = util.ROTL32(x12, 8) - x11 += x12 - x6 ~= x11 - x6 = util.ROTL32(x6, 7) - - // quarterround(x, 2, 7, 8, 13) - x2 += x7 - x13 ~= x2 - x13 = util.ROTL32(x13, 16) - x8 += x13 - x7 ~= x8 - x7 = util.ROTL32(x7, 12) - x2 += x7 - x13 ~= x2 - x13 = util.ROTL32(x13, 8) - x8 += x13 - x7 ~= x8 - x7 = util.ROTL32(x7, 7) - - // quarterround(x, 3, 4, 9, 14) - x3 += x4 - x14 ~= x3 - x14 = util.ROTL32(x14, 16) - x9 += x14 - x4 ~= x9 - x4 = util.ROTL32(x4, 12) - x3 += x4 - x14 ~= x3 - x14 = util.ROTL32(x14, 8) - x9 += x14 - x4 ~= x9 - x4 = util.ROTL32(x4, 7) - } - - util.PUT_U32_LE(dst[0:4], x0) - util.PUT_U32_LE(dst[4:8], x1) - util.PUT_U32_LE(dst[8:12], x2) - util.PUT_U32_LE(dst[12:16], x3) - util.PUT_U32_LE(dst[16:20], x12) - util.PUT_U32_LE(dst[20:24], x13) - util.PUT_U32_LE(dst[24:28], x14) - util.PUT_U32_LE(dst[28:32], x15) +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. +reset :: proc(ctx: ^Context) { + _chacha20.reset(&ctx._state) } diff --git a/core/crypto/chacha20/chacha20_impl.odin b/core/crypto/chacha20/chacha20_impl.odin new file mode 100644 index 000000000..be2ee06b4 --- /dev/null +++ b/core/crypto/chacha20/chacha20_impl.odin @@ -0,0 +1,56 @@ +package chacha20 + +import "base:intrinsics" +import "core:crypto/_chacha20/ref" +import "core:crypto/_chacha20/simd128" +import "core:crypto/_chacha20/simd256" + +// DEFAULT_IMPLEMENTATION is the implementation that will be used by +// default if possible. +DEFAULT_IMPLEMENTATION :: Implementation.Simd256 + +// Implementation is a ChaCha20 implementation. Most callers will not need +// to use this as the package will automatically select the most performant +// implementation available. +Implementation :: enum { + Portable, + Simd128, + Simd256, +} + +@(private) +init_impl :: proc(ctx: ^Context, impl: Implementation) { + impl := impl + if impl == .Simd256 && !simd256.is_performant() { + impl = .Simd128 + } + if impl == .Simd128 && !simd128.is_performant() { + impl = .Portable + } + + ctx._impl = impl +} + +@(private) +stream_blocks :: proc(ctx: ^Context, dst, src: []byte, nr_blocks: int) { + switch ctx._impl { + case .Simd256: + simd256.stream_blocks(&ctx._state, dst, src, nr_blocks) + case .Simd128: + simd128.stream_blocks(&ctx._state, dst, src, nr_blocks) + case .Portable: + ref.stream_blocks(&ctx._state, dst, src, nr_blocks) + } +} + +@(private) +hchacha20 :: proc "contextless" (dst, key, iv: []byte, impl: Implementation) { + switch impl { + case .Simd256: + simd256.hchacha20(dst, key, iv) + case .Simd128: + simd128.hchacha20(dst, key, iv) + case .Portable: + ref.hchacha20(dst, key, iv) + } +} diff --git a/core/crypto/chacha20poly1305/chacha20poly1305.odin b/core/crypto/chacha20poly1305/chacha20poly1305.odin index ae395f9e0..3de2532dd 100644 --- a/core/crypto/chacha20poly1305/chacha20poly1305.odin +++ b/core/crypto/chacha20poly1305/chacha20poly1305.odin @@ -1,28 +1,40 @@ +/* +package chacha20poly1305 implements the AEAD_CHACHA20_POLY1305 and +AEAD_XChaCha20_Poly1305 Authenticated Encryption with Additional Data +algorithms. + +See: +- [[ https://www.rfc-editor.org/rfc/rfc8439 ]] +- [[ https://datatracker.ietf.org/doc/html/draft-arciszewski-xchacha-03 ]] +*/ package chacha20poly1305 import "core:crypto" import "core:crypto/chacha20" import "core:crypto/poly1305" -import "core:crypto/util" +import "core:encoding/endian" import "core:mem" +// KEY_SIZE is the chacha20poly1305 key size in bytes. KEY_SIZE :: chacha20.KEY_SIZE -NONCE_SIZE :: chacha20.NONCE_SIZE +// IV_SIZE is the chacha20poly1305 IV size in bytes. +IV_SIZE :: chacha20.IV_SIZE +// XIV_SIZE is the xchacha20poly1305 IV size in bytes. +XIV_SIZE :: chacha20.XIV_SIZE +// TAG_SIZE is the chacha20poly1305 tag size in bytes. TAG_SIZE :: poly1305.TAG_SIZE @(private) _P_MAX :: 64 * 0xffffffff // 64 * (2^32-1) @(private) -_validate_common_slice_sizes :: proc (tag, key, nonce, aad, text: []byte) { +_validate_common_slice_sizes :: proc (tag, iv, aad, text: []byte, is_xchacha: bool) { if len(tag) != TAG_SIZE { panic("crypto/chacha20poly1305: invalid destination tag size") } - if len(key) != KEY_SIZE { - panic("crypto/chacha20poly1305: invalid key size") - } - if len(nonce) != NONCE_SIZE { - panic("crypto/chacha20poly1305: invalid nonce size") + expected_iv_len := is_xchacha ? XIV_SIZE : IV_SIZE + if len(iv) != expected_iv_len { + panic("crypto/chacha20poly1305: invalid IV size") } #assert(size_of(int) == 8 || size_of(int) <= 4) @@ -49,16 +61,52 @@ _update_mac_pad16 :: #force_inline proc (ctx: ^poly1305.Context, x_len: int) { } } -encrypt :: proc (ciphertext, tag, key, nonce, aad, plaintext: []byte) { - _validate_common_slice_sizes(tag, key, nonce, aad, plaintext) +// Context is a keyed (X)Chacha20Poly1305 instance. +Context :: struct { + _key: [KEY_SIZE]byte, + _impl: chacha20.Implementation, + _is_xchacha: bool, + _is_initialized: bool, +} + +// init initializes a Context with the provided key, for AEAD_CHACHA20_POLY1305. +init :: proc(ctx: ^Context, key: []byte, impl := chacha20.DEFAULT_IMPLEMENTATION) { + if len(key) != KEY_SIZE { + panic("crypto/chacha20poly1305: invalid key size") + } + + copy(ctx._key[:], key) + ctx._impl = impl + ctx._is_xchacha = false + ctx._is_initialized = true +} + +// init_xchacha initializes a Context with the provided key, for +// AEAD_XChaCha20_Poly1305. +// +// Note: While there are multiple definitions of XChaCha20-Poly1305 +// this sticks to the IETF draft and uses a 32-bit counter. +init_xchacha :: proc(ctx: ^Context, key: []byte, impl := chacha20.DEFAULT_IMPLEMENTATION) { + init(ctx, key, impl) + ctx._is_xchacha = true +} + +// seal encrypts the plaintext and authenticates the aad and ciphertext, +// with the provided Context and iv, stores the output in dst and tag. +// +// dst and plaintext MUST alias exactly or not at all. +seal :: proc(ctx: ^Context, dst, tag, iv, aad, plaintext: []byte) { + ciphertext := dst + _validate_common_slice_sizes(tag, iv, aad, plaintext, ctx._is_xchacha) if len(ciphertext) != len(plaintext) { panic("crypto/chacha20poly1305: invalid destination ciphertext size") } stream_ctx: chacha20.Context = --- - chacha20.init(&stream_ctx, key, nonce) + chacha20.init(&stream_ctx, ctx._key[:],iv, ctx._impl) + stream_ctx._state._is_ietf_flavor = true - // otk = poly1305_key_gen(key, nonce) + // otk = poly1305_key_gen(key, iv) otk: [poly1305.KEY_SIZE]byte = --- chacha20.keystream_bytes(&stream_ctx, otk[:]) mac_ctx: poly1305.Context = --- @@ -75,7 +123,7 @@ encrypt :: proc (ciphertext, tag, key, nonce, aad, plaintext: []byte) { poly1305.update(&mac_ctx, aad) _update_mac_pad16(&mac_ctx, aad_len) - // ciphertext = chacha20_encrypt(key, 1, nonce, plaintext) + // ciphertext = chacha20_encrypt(key, 1, iv, plaintext) chacha20.seek(&stream_ctx, 1) chacha20.xor_bytes(&stream_ctx, ciphertext, plaintext) chacha20.reset(&stream_ctx) // Don't need the stream context anymore. @@ -87,16 +135,24 @@ encrypt :: proc (ciphertext, tag, key, nonce, aad, plaintext: []byte) { // mac_data |= num_to_8_le_bytes(aad.length) // mac_data |= num_to_8_le_bytes(ciphertext.length) l_buf := otk[0:16] // Reuse the scratch buffer. - util.PUT_U64_LE(l_buf[0:8], u64(aad_len)) - util.PUT_U64_LE(l_buf[8:16], u64(ciphertext_len)) + endian.unchecked_put_u64le(l_buf[0:8], u64(aad_len)) + endian.unchecked_put_u64le(l_buf[8:16], u64(ciphertext_len)) poly1305.update(&mac_ctx, l_buf) // tag = poly1305_mac(mac_data, otk) poly1305.final(&mac_ctx, tag) // Implicitly sanitizes context. } -decrypt :: proc (plaintext, tag, key, nonce, aad, ciphertext: []byte) -> bool { - _validate_common_slice_sizes(tag, key, nonce, aad, ciphertext) +// open authenticates the aad and ciphertext, and decrypts the ciphertext, +// with the provided Context, iv, and tag, and stores the output in dst, +// returning true iff the authentication was successful. If authentication +// fails, the destination buffer will be zeroed. +// +// dst and plaintext MUST alias exactly or not at all. +@(require_results) +open :: proc(ctx: ^Context, dst, iv, aad, ciphertext, tag: []byte) -> bool { + plaintext := dst + _validate_common_slice_sizes(tag, iv, aad, ciphertext, ctx._is_xchacha) if len(ciphertext) != len(plaintext) { panic("crypto/chacha20poly1305: invalid destination plaintext size") } @@ -106,9 +162,10 @@ decrypt :: proc (plaintext, tag, key, nonce, aad, ciphertext: []byte) -> bool { // points where needed. stream_ctx: chacha20.Context = --- - chacha20.init(&stream_ctx, key, nonce) + chacha20.init(&stream_ctx, ctx._key[:], iv, ctx._impl) + stream_ctx._state._is_ietf_flavor = true - // otk = poly1305_key_gen(key, nonce) + // otk = poly1305_key_gen(key, iv) otk: [poly1305.KEY_SIZE]byte = --- chacha20.keystream_bytes(&stream_ctx, otk[:]) defer chacha20.reset(&stream_ctx) @@ -128,8 +185,8 @@ decrypt :: proc (plaintext, tag, key, nonce, aad, ciphertext: []byte) -> bool { poly1305.update(&mac_ctx, ciphertext) _update_mac_pad16(&mac_ctx, ciphertext_len) l_buf := otk[0:16] // Reuse the scratch buffer. - util.PUT_U64_LE(l_buf[0:8], u64(aad_len)) - util.PUT_U64_LE(l_buf[8:16], u64(ciphertext_len)) + endian.unchecked_put_u64le(l_buf[0:8], u64(aad_len)) + endian.unchecked_put_u64le(l_buf[8:16], u64(ciphertext_len)) poly1305.update(&mac_ctx, l_buf) // tag = poly1305_mac(mac_data, otk) @@ -143,9 +200,17 @@ decrypt :: proc (plaintext, tag, key, nonce, aad, ciphertext: []byte) -> bool { return false } - // plaintext = chacha20_decrypt(key, 1, nonce, ciphertext) + // plaintext = chacha20_decrypt(key, 1, iv, ciphertext) chacha20.seek(&stream_ctx, 1) chacha20.xor_bytes(&stream_ctx, plaintext, ciphertext) return true } + +// reset sanitizes the Context. The Context must be +// re-initialized to be used again. +reset :: proc "contextless" (ctx: ^Context) { + mem.zero_explicit(&ctx._key, len(ctx._key)) + ctx._is_xchacha = false + ctx._is_initialized = false +} diff --git a/core/crypto/crypto.odin b/core/crypto/crypto.odin index 6cdcacb9c..323cc45d6 100644 --- a/core/crypto/crypto.odin +++ b/core/crypto/crypto.odin @@ -1,5 +1,10 @@ +/* +package crypto implements a selection of cryptography algorithms and useful +helper routines. +*/ package crypto +import "base:runtime" import "core:mem" // compare_constant_time returns 1 iff a and b are equal, 0 otherwise. @@ -45,9 +50,37 @@ 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)) _rand_bytes(dst) } + +// random_generator returns a `runtime.Random_Generator` backed by the +// system entropy source. +// +// Support for the system entropy source can be checked with the +// `HAS_RAND_BYTES` boolean constant. +random_generator :: proc() -> runtime.Random_Generator { + return { + procedure = proc(data: rawptr, mode: runtime.Random_Generator_Mode, p: []byte) { + 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/ed25519/ed25519.odin b/core/crypto/ed25519/ed25519.odin new file mode 100644 index 000000000..460a19563 --- /dev/null +++ b/core/crypto/ed25519/ed25519.odin @@ -0,0 +1,314 @@ +/* +package ed25519 implements the Ed25519 EdDSA signature algorithm. + +See: +- [[ https://datatracker.ietf.org/doc/html/rfc8032 ]] +- [[ https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf ]] +- [[ https://eprint.iacr.org/2020/1244.pdf ]] +*/ +package ed25519 + +import "core:crypto" +import grp "core:crypto/_edwards25519" +import "core:crypto/sha2" +import "core:mem" + +// PRIVATE_KEY_SIZE is the byte-encoded private key size. +PRIVATE_KEY_SIZE :: 32 +// PUBLIC_KEY_SIZE is the byte-encoded public key size. +PUBLIC_KEY_SIZE :: 32 +// SIGNATURE_SIZE is the byte-encoded signature size. +SIGNATURE_SIZE :: 64 + +@(private) +HDIGEST2_SIZE :: 32 + +// Private_Key is an Ed25519 private key. +Private_Key :: struct { + // WARNING: All of the members are to be treated as internal (ie: + // the Private_Key structure is intended to be opaque). There are + // subtle vulnerabilities that can be introduced if the internal + // values are allowed to be altered. + // + // See: https://github.com/MystenLabs/ed25519-unsafe-libs + _b: [PRIVATE_KEY_SIZE]byte, + _s: grp.Scalar, + _hdigest2: [HDIGEST2_SIZE]byte, + _pub_key: Public_Key, + _is_initialized: bool, +} + +// Public_Key is an Ed25519 public key. +Public_Key :: struct { + // WARNING: All of the members are to be treated as internal (ie: + // the Public_Key structure is intended to be opaque). + _b: [PUBLIC_KEY_SIZE]byte, + _neg_A: grp.Group_Element, + _is_valid: bool, + _is_initialized: bool, +} + +// private_key_set_bytes decodes a byte-encoded private key, and returns +// true iff the operation was successful. +private_key_set_bytes :: proc(priv_key: ^Private_Key, b: []byte) -> bool { + if len(b) != PRIVATE_KEY_SIZE { + return false + } + + // Derive the private key. + ctx: sha2.Context_512 = --- + h_bytes: [sha2.DIGEST_SIZE_512]byte = --- + sha2.init_512(&ctx) + sha2.update(&ctx, b) + sha2.final(&ctx, h_bytes[:]) + + copy(priv_key._b[:], b) + copy(priv_key._hdigest2[:], h_bytes[32:]) + grp.sc_set_bytes_rfc8032(&priv_key._s, h_bytes[:32]) + + // Derive the corresponding public key. + A: grp.Group_Element = --- + grp.ge_scalarmult_basepoint(&A, &priv_key._s) + grp.ge_bytes(&A, priv_key._pub_key._b[:]) + grp.ge_negate(&priv_key._pub_key._neg_A, &A) + priv_key._pub_key._is_valid = !grp.ge_is_small_order(&A) + priv_key._pub_key._is_initialized = true + + priv_key._is_initialized = true + + return true +} + +// private_key_bytes sets dst to byte-encoding of priv_key. +private_key_bytes :: proc(priv_key: ^Private_Key, dst: []byte) { + if !priv_key._is_initialized { + panic("crypto/ed25519: uninitialized private key") + } + if len(dst) != PRIVATE_KEY_SIZE { + panic("crypto/ed25519: invalid destination size") + } + + copy(dst, priv_key._b[:]) +} + +// private_key_clear clears priv_key to the uninitialized state. +private_key_clear :: proc "contextless" (priv_key: ^Private_Key) { + mem.zero_explicit(priv_key, size_of(Private_Key)) +} + +// sign writes the signature by priv_key over msg to sig. +sign :: proc(priv_key: ^Private_Key, msg, sig: []byte) { + if !priv_key._is_initialized { + panic("crypto/ed25519: uninitialized private key") + } + if len(sig) != SIGNATURE_SIZE { + panic("crypto/ed25519: invalid destination size") + } + + // 1. Compute the hash of the private key d, H(d) = (h_0, h_1, ..., h_2b-1) + // using SHA-512 for Ed25519. H(d) may be precomputed. + // + // 2. Using the second half of the digest hdigest2 = hb || ... || h2b-1, + // define: + // + // 2.1 For Ed25519, r = SHA-512(hdigest2 || M); Interpret r as a + // 64-octet little-endian integer. + ctx: sha2.Context_512 = --- + digest_bytes: [sha2.DIGEST_SIZE_512]byte = --- + sha2.init_512(&ctx) + sha2.update(&ctx, priv_key._hdigest2[:]) + sha2.update(&ctx, msg) + sha2.final(&ctx, digest_bytes[:]) + + r: grp.Scalar = --- + grp.sc_set_bytes_wide(&r, &digest_bytes) + + // 3. Compute the point [r]G. The octet string R is the encoding of + // the point [r]G. + R: grp.Group_Element = --- + R_bytes := sig[:32] + grp.ge_scalarmult_basepoint(&R, &r) + grp.ge_bytes(&R, R_bytes) + + // 4. Derive s from H(d) as in the key pair generation algorithm. + // Use octet strings R, Q, and M to define: + // + // 4.1 For Ed25519, digest = SHA-512(R || Q || M). + // Interpret digest as a little-endian integer. + sha2.init_512(&ctx) + sha2.update(&ctx, R_bytes) + sha2.update(&ctx, priv_key._pub_key._b[:]) // Q in NIST terminology. + sha2.update(&ctx, msg) + sha2.final(&ctx, digest_bytes[:]) + + sc: grp.Scalar = --- // `digest` in NIST terminology. + grp.sc_set_bytes_wide(&sc, &digest_bytes) + + // 5. Compute S = (r + digest × s) mod n. The octet string S is the + // encoding of the resultant integer. + grp.sc_mul(&sc, &sc, &priv_key._s) + grp.sc_add(&sc, &sc, &r) + + // 6. Form the signature as the concatenation of the octet strings + // R and S. + grp.sc_bytes(sig[32:], &sc) + + grp.sc_clear(&r) +} + +// public_key_set_bytes decodes a byte-encoded public key, and returns +// true iff the operation was successful. +public_key_set_bytes :: proc "contextless" (pub_key: ^Public_Key, b: []byte) -> bool { + if len(b) != PUBLIC_KEY_SIZE { + return false + } + + A: grp.Group_Element = --- + if !grp.ge_set_bytes(&A, b) { + return false + } + + copy(pub_key._b[:], b) + grp.ge_negate(&pub_key._neg_A, &A) + pub_key._is_valid = !grp.ge_is_small_order(&A) + pub_key._is_initialized = true + + return true +} + +// public_key_set_priv sets pub_key to the public component of priv_key. +public_key_set_priv :: proc(pub_key: ^Public_Key, priv_key: ^Private_Key) { + if !priv_key._is_initialized { + panic("crypto/ed25519: uninitialized public key") + } + + src := &priv_key._pub_key + copy(pub_key._b[:], src._b[:]) + grp.ge_set(&pub_key._neg_A, &src._neg_A) + pub_key._is_valid = src._is_valid + pub_key._is_initialized = src._is_initialized +} + +// public_key_bytes sets dst to byte-encoding of pub_key. +public_key_bytes :: proc(pub_key: ^Public_Key, dst: []byte) { + if !pub_key._is_initialized { + panic("crypto/ed25519: uninitialized public key") + } + if len(dst) != PUBLIC_KEY_SIZE { + panic("crypto/ed25519: invalid destination size") + } + + copy(dst, pub_key._b[:]) +} + +// public_key_equal returns true iff pub_key is equal to other. +public_key_equal :: proc(pub_key, other: ^Public_Key) -> bool { + if !pub_key._is_initialized || !other._is_initialized { + panic("crypto/ed25519: uninitialized public key") + } + + return crypto.compare_constant_time(pub_key._b[:], other._b[:]) == 1 +} + +// verify returns true iff sig is a valid signature by pub_key over msg. +// +// The optional `allow_small_order_A` parameter will make this +// implementation strictly compatible with FIPS 186-5, at the expense of +// SBS-security. Doing so is NOT recommended, and the disallowed +// public keys all have a known discrete-log. +verify :: proc(pub_key: ^Public_Key, msg, sig: []byte, allow_small_order_A := false) -> bool { + switch { + case !pub_key._is_initialized: + return false + case len(sig) != SIGNATURE_SIZE: + return false + } + + // TLDR: Just use ristretto255. + // + // While there are two "standards" for EdDSA, existing implementations + // diverge (sometimes dramatically). This implementation opts for + // "Algorithm 2" from "Taming the Many EdDSAs", which provides the + // strongest notion of security (SUF-CMA + SBS). + // + // The relevant properties are: + // - Reject non-canonical S. + // - Reject non-canonical A/R. + // - Reject small-order A (Extra non-standard check). + // - Cofactored verification equation. + // + // There are 19 possible non-canonical group element encodings of + // which: + // - 2 are small order + // - 10 are mixed order + // - 7 are not on the curve + // + // While historical implementations have been lax about enforcing + // that A/R are canonically encoded, that behavior is mandated by + // both the RFC and FIPS specification. No valid key generation + // or sign implementation will ever produce non-canonically encoded + // public keys or signatures. + // + // There are 8 small-order group elements, 1 which is in the + // prime-order sub-group, and thus the probability that a properly + // generated A is small-order is cryptographically insignificant. + // + // While both the RFC and FIPS standard allow for either the + // cofactored or non-cofactored equation. It is possible to + // artificially produce signatures that are valid for the former + // but not the latter. This will NEVER occur with a valid sign + // implementation. The choice of the latter is to be compatible + // with ABGLSV-Pornin, batch verification, and FROST (among other + // things). + + s_bytes, r_bytes := sig[32:], sig[:32] + + // 1. Reject the signature if S is not in the range [0, L). + s: grp.Scalar = --- + if !grp.sc_set_bytes(&s, s_bytes) { + return false + } + + // 2. Reject the signature if the public key A is one of 8 small + // order points. + // + // As this check is optional and not part of the standard, we allow + // the caller to bypass it if desired. Disabling the check makes + // the scheme NOT SBS-secure. + if !pub_key._is_valid && !allow_small_order_A { + return false + } + + // 3. Reject the signature if A or R are non-canonical. + // + // Note: All initialized public keys are guaranteed to be canonical. + neg_R: grp.Group_Element = --- + if !grp.ge_set_bytes(&neg_R, r_bytes) { + return false + } + grp.ge_negate(&neg_R, &neg_R) + + // 4. Compute the hash SHA512(R||A||M) and reduce it mod L to get a + // scalar h. + ctx: sha2.Context_512 = --- + h_bytes: [sha2.DIGEST_SIZE_512]byte = --- + sha2.init_512(&ctx) + sha2.update(&ctx, r_bytes) + sha2.update(&ctx, pub_key._b[:]) + sha2.update(&ctx, msg) + sha2.final(&ctx, h_bytes[:]) + + h: grp.Scalar = --- + grp.sc_set_bytes_wide(&h, &h_bytes) + + // 5. Accept if 8(s * G) - 8R - 8(h * A) = 0 + // + // > first compute V = SB − R − hA and then accept if V is one of + // > 8 small order points (or alternatively compute 8V with 3 + // > doublings and check against the neutral element) + V: grp.Group_Element = --- + grp.ge_double_scalarmult_basepoint_vartime(&V, &h, &pub_key._neg_A, &s) + grp.ge_add(&V, &V, &neg_R) + + return grp.ge_is_small_order(&V) +} diff --git a/core/crypto/gost/gost.odin b/core/crypto/gost/gost.odin deleted file mode 100644 index 5aca8ce95..000000000 --- a/core/crypto/gost/gost.odin +++ /dev/null @@ -1,382 +0,0 @@ -package gost - -/* - Copyright 2021 zhibog - Made available under the BSD-3 license. - - List of contributors: - zhibog, dotbmp: Initial implementation. - - Implementation of the GOST hashing algorithm, as defined in RFC 5831 -*/ - -import "core:mem" -import "core:os" -import "core:io" - -/* - High level API -*/ - -DIGEST_SIZE :: 32 - -// hash_string will hash the given input and return the -// computed hash -hash_string :: proc(data: string) -> [DIGEST_SIZE]byte { - return hash_bytes(transmute([]byte)(data)) -} - -// hash_bytes will hash the given input and return the -// computed hash -hash_bytes :: proc(data: []byte) -> [DIGEST_SIZE]byte { - hash: [DIGEST_SIZE]byte - ctx: Gost_Context - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE, "Size of destination buffer is smaller than the digest size") - ctx: Gost_Context - init(&ctx) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream will read the stream in chunks and compute a -// hash from its contents -hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { - hash: [DIGEST_SIZE]byte - ctx: Gost_Context - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file will read the file provided by the given handle -// and compute a hash -hash_file :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE]byte, bool) { - if !load_at_once { - return hash_stream(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes(buf[:]), ok - } - } - return [DIGEST_SIZE]byte{}, false -} - -hash :: proc { - hash_stream, - hash_file, - hash_bytes, - hash_string, - hash_bytes_to_buffer, - hash_string_to_buffer, -} - -/* - Low level API -*/ - -init :: proc "contextless" (ctx: ^Gost_Context) { - sbox: [8][16]u32 = { - { 10, 4, 5, 6, 8, 1, 3, 7, 13, 12, 14, 0, 9, 2, 11, 15 }, - { 5, 15, 4, 0, 2, 13, 11, 9, 1, 7, 6, 3, 12, 14, 10, 8 }, - { 7, 15, 12, 14, 9, 4, 1, 0, 3, 11, 5, 2, 6, 10, 8, 13 }, - { 4, 10, 7, 12, 0, 15, 2, 8, 14, 1, 6, 5, 13, 11, 9, 3 }, - { 7, 6, 4, 11, 9, 12, 2, 10, 1, 8, 0, 14, 15, 13, 3, 5 }, - { 7, 6, 2, 4, 13, 9, 15, 0, 10, 1, 5, 11, 8, 14, 12, 3 }, - { 13, 14, 4, 1, 7, 0, 5, 10, 3, 12, 8, 15, 6, 2, 9, 11 }, - { 1, 3, 10, 9, 5, 11, 4, 15, 8, 6, 7, 14, 13, 0, 2, 12 }, - } - - i := 0 - for a := 0; a < 16; a += 1 { - ax := sbox[1][a] << 15 - bx := sbox[3][a] << 23 - cx := sbox[5][a] - cx = (cx >> 1) | (cx << 31) - dx := sbox[7][a] << 7 - for b := 0; b < 16; b, i = b + 1, i + 1 { - SBOX_1[i] = ax | (sbox[0][b] << 11) - SBOX_2[i] = bx | (sbox[2][b] << 19) - SBOX_3[i] = cx | (sbox[4][b] << 27) - SBOX_4[i] = dx | (sbox[6][b] << 3) - } - } -} - -update :: proc(ctx: ^Gost_Context, data: []byte) { - length := byte(len(data)) - j: byte - - i := ctx.partial_bytes - for i < 32 && j < length { - ctx.partial[i] = data[j] - i, j = i + 1, j + 1 - } - - if i < 32 { - ctx.partial_bytes = i - return - } - bytes(ctx, ctx.partial[:], 256) - - for (j + 32) < length { - bytes(ctx, data[j:], 256) - j += 32 - } - - i = 0 - for j < length { - ctx.partial[i] = data[j] - i, j = i + 1, j + 1 - } - ctx.partial_bytes = i -} - -final :: proc(ctx: ^Gost_Context, hash: []byte) { - if ctx.partial_bytes > 0 { - mem.set(&ctx.partial[ctx.partial_bytes], 0, 32 - int(ctx.partial_bytes)) - bytes(ctx, ctx.partial[:], u32(ctx.partial_bytes) << 3) - } - - compress(ctx.hash[:], ctx.len[:]) - compress(ctx.hash[:], ctx.sum[:]) - - for i, j := 0, 0; i < 8; i, j = i + 1, j + 4 { - hash[j] = byte(ctx.hash[i]) - hash[j + 1] = byte(ctx.hash[i] >> 8) - hash[j + 2] = byte(ctx.hash[i] >> 16) - hash[j + 3] = byte(ctx.hash[i] >> 24) - } -} - -/* - GOST implementation -*/ - -Gost_Context :: struct { - sum: [8]u32, - hash: [8]u32, - len: [8]u32, - partial: [32]byte, - partial_bytes: byte, -} - -SBOX_1: [256]u32 -SBOX_2: [256]u32 -SBOX_3: [256]u32 -SBOX_4: [256]u32 - -ENCRYPT_ROUND :: #force_inline proc "contextless" (l, r, t, k1, k2: u32) -> (u32, u32, u32) { - l, r, t := l, r, t - t = (k1) + r - l ~= SBOX_1[t & 0xff] ~ SBOX_2[(t >> 8) & 0xff] ~ SBOX_3[(t >> 16) & 0xff] ~ SBOX_4[t >> 24] - t = (k2) + l - r ~= SBOX_1[t & 0xff] ~ SBOX_2[(t >> 8) & 0xff] ~ SBOX_3[(t >> 16) & 0xff] ~ SBOX_4[t >> 24] - return l, r, t -} - -ENCRYPT :: #force_inline proc "contextless" (a, b, c: u32, key: []u32) -> (l, r, t: u32) { - l, r, t = ENCRYPT_ROUND(a, b, c, key[0], key[1]) - l, r, t = ENCRYPT_ROUND(l, r, t, key[2], key[3]) - l, r, t = ENCRYPT_ROUND(l, r, t, key[4], key[5]) - l, r, t = ENCRYPT_ROUND(l, r, t, key[6], key[7]) - l, r, t = ENCRYPT_ROUND(l, r, t, key[0], key[1]) - l, r, t = ENCRYPT_ROUND(l, r, t, key[2], key[3]) - l, r, t = ENCRYPT_ROUND(l, r, t, key[4], key[5]) - l, r, t = ENCRYPT_ROUND(l, r, t, key[6], key[7]) - l, r, t = ENCRYPT_ROUND(l, r, t, key[0], key[1]) - l, r, t = ENCRYPT_ROUND(l, r, t, key[2], key[3]) - l, r, t = ENCRYPT_ROUND(l, r, t, key[4], key[5]) - l, r, t = ENCRYPT_ROUND(l, r, t, key[6], key[7]) - l, r, t = ENCRYPT_ROUND(l, r, t, key[7], key[6]) - l, r, t = ENCRYPT_ROUND(l, r, t, key[5], key[4]) - l, r, t = ENCRYPT_ROUND(l, r, t, key[3], key[2]) - l, r, t = ENCRYPT_ROUND(l, r, t, key[1], key[0]) - t = r - r = l - l = t - return -} - -bytes :: proc(ctx: ^Gost_Context, buf: []byte, bits: u32) { - a, c: u32 - m: [8]u32 - - for i, j := 0, 0; i < 8; i += 1 { - a = u32(buf[j]) | u32(buf[j + 1]) << 8 | u32(buf[j + 2]) << 16 | u32(buf[j + 3]) << 24 - j += 4 - m[i] = a - c = a + c + ctx.sum[i] - ctx.sum[i] = c - c = c < a ? 1 : 0 - } - - compress(ctx.hash[:], m[:]) - ctx.len[0] += bits - if ctx.len[0] < bits { - ctx.len[1] += 1 - } -} - -compress :: proc(h, m: []u32) { - key, u, v, w, s: [8]u32 - - copy(u[:], h) - copy(v[:], m) - - for i := 0; i < 8; i += 2 { - w[0] = u[0] ~ v[0] - w[1] = u[1] ~ v[1] - w[2] = u[2] ~ v[2] - w[3] = u[3] ~ v[3] - w[4] = u[4] ~ v[4] - w[5] = u[5] ~ v[5] - w[6] = u[6] ~ v[6] - w[7] = u[7] ~ v[7] - - key[0] = (w[0] & 0x000000ff) | (w[2] & 0x000000ff) << 8 | (w[4] & 0x000000ff) << 16 | (w[6] & 0x000000ff) << 24 - key[1] = (w[0] & 0x0000ff00) >> 8 | (w[2] & 0x0000ff00) | (w[4] & 0x0000ff00) << 8 | (w[6] & 0x0000ff00) << 16 - key[2] = (w[0] & 0x00ff0000) >> 16 | (w[2] & 0x00ff0000) >> 8 | (w[4] & 0x00ff0000) | (w[6] & 0x00ff0000) << 8 - key[3] = (w[0] & 0xff000000) >> 24 | (w[2] & 0xff000000) >> 16 | (w[4] & 0xff000000) >> 8 | (w[6] & 0xff000000) - key[4] = (w[1] & 0x000000ff) | (w[3] & 0x000000ff) << 8 | (w[5] & 0x000000ff) << 16 | (w[7] & 0x000000ff) << 24 - key[5] = (w[1] & 0x0000ff00) >> 8 | (w[3] & 0x0000ff00) | (w[5] & 0x0000ff00) << 8 | (w[7] & 0x0000ff00) << 16 - key[6] = (w[1] & 0x00ff0000) >> 16 | (w[3] & 0x00ff0000) >> 8 | (w[5] & 0x00ff0000) | (w[7] & 0x00ff0000) << 8 - key[7] = (w[1] & 0xff000000) >> 24 | (w[3] & 0xff000000) >> 16 | (w[5] & 0xff000000) >> 8 | (w[7] & 0xff000000) - - r := h[i] - l := h[i + 1] - t: u32 - l, r, t = ENCRYPT(l, r, 0, key[:]) - - s[i] = r - s[i + 1] = l - - if i == 6 { - break - } - - l = u[0] ~ u[2] - r = u[1] ~ u[3] - u[0] = u[2] - u[1] = u[3] - u[2] = u[4] - u[3] = u[5] - u[4] = u[6] - u[5] = u[7] - u[6] = l - u[7] = r - - if i == 2 { - u[0] ~= 0xff00ff00 - u[1] ~= 0xff00ff00 - u[2] ~= 0x00ff00ff - u[3] ~= 0x00ff00ff - u[4] ~= 0x00ffff00 - u[5] ~= 0xff0000ff - u[6] ~= 0x000000ff - u[7] ~= 0xff00ffff - } - - l = v[0] - r = v[2] - v[0] = v[4] - v[2] = v[6] - v[4] = l ~ r - v[6] = v[0] ~ r - l = v[1] - r = v[3] - v[1] = v[5] - v[3] = v[7] - v[5] = l ~ r - v[7] = v[1] ~ r - } - - u[0] = m[0] ~ s[6] - u[1] = m[1] ~ s[7] - u[2] = m[2] ~ (s[0] << 16) ~ (s[0] >> 16) ~ (s[0] & 0xffff) ~ - (s[1] & 0xffff) ~ (s[1] >> 16) ~ (s[2] << 16) ~ s[6] ~ (s[6] << 16) ~ - (s[7] & 0xffff0000) ~ (s[7] >> 16) - u[3] = m[3] ~ (s[0] & 0xffff) ~ (s[0] << 16) ~ (s[1] & 0xffff) ~ - (s[1] << 16) ~ (s[1] >> 16) ~ (s[2] << 16) ~ (s[2] >> 16) ~ - (s[3] << 16) ~ s[6] ~ (s[6] << 16) ~ (s[6] >> 16) ~ (s[7] & 0xffff) ~ - (s[7] << 16) ~ (s[7] >> 16) - u[4] = m[4] ~ - (s[0] & 0xffff0000) ~ (s[0] << 16) ~ (s[0] >> 16) ~ - (s[1] & 0xffff0000) ~ (s[1] >> 16) ~ (s[2] << 16) ~ (s[2] >> 16) ~ - (s[3] << 16) ~ (s[3] >> 16) ~ (s[4] << 16) ~ (s[6] << 16) ~ - (s[6] >> 16) ~(s[7] & 0xffff) ~ (s[7] << 16) ~ (s[7] >> 16) - u[5] = m[5] ~ (s[0] << 16) ~ (s[0] >> 16) ~ (s[0] & 0xffff0000) ~ - (s[1] & 0xffff) ~ s[2] ~ (s[2] >> 16) ~ (s[3] << 16) ~ (s[3] >> 16) ~ - (s[4] << 16) ~ (s[4] >> 16) ~ (s[5] << 16) ~ (s[6] << 16) ~ - (s[6] >> 16) ~ (s[7] & 0xffff0000) ~ (s[7] << 16) ~ (s[7] >> 16) - u[6] = m[6] ~ s[0] ~ (s[1] >> 16) ~ (s[2] << 16) ~ s[3] ~ (s[3] >> 16) ~ - (s[4] << 16) ~ (s[4] >> 16) ~ (s[5] << 16) ~ (s[5] >> 16) ~ s[6] ~ - (s[6] << 16) ~ (s[6] >> 16) ~ (s[7] << 16) - u[7] = m[7] ~ (s[0] & 0xffff0000) ~ (s[0] << 16) ~ (s[1] & 0xffff) ~ - (s[1] << 16) ~ (s[2] >> 16) ~ (s[3] << 16) ~ s[4] ~ (s[4] >> 16) ~ - (s[5] << 16) ~ (s[5] >> 16) ~ (s[6] >> 16) ~ (s[7] & 0xffff) ~ - (s[7] << 16) ~ (s[7] >> 16) - - v[0] = h[0] ~ (u[1] << 16) ~ (u[0] >> 16) - v[1] = h[1] ~ (u[2] << 16) ~ (u[1] >> 16) - v[2] = h[2] ~ (u[3] << 16) ~ (u[2] >> 16) - v[3] = h[3] ~ (u[4] << 16) ~ (u[3] >> 16) - v[4] = h[4] ~ (u[5] << 16) ~ (u[4] >> 16) - v[5] = h[5] ~ (u[6] << 16) ~ (u[5] >> 16) - v[6] = h[6] ~ (u[7] << 16) ~ (u[6] >> 16) - v[7] = h[7] ~ (u[0] & 0xffff0000) ~ (u[0] << 16) ~ (u[7] >> 16) ~ (u[1] & 0xffff0000) ~ (u[1] << 16) ~ (u[6] << 16) ~ (u[7] & 0xffff0000) - - h[0] = (v[0] & 0xffff0000) ~ (v[0] << 16) ~ (v[0] >> 16) ~ (v[1] >> 16) ~ - (v[1] & 0xffff0000) ~ (v[2] << 16) ~ (v[3] >> 16) ~ (v[4] << 16) ~ - (v[5] >> 16) ~ v[5] ~ (v[6] >> 16) ~ (v[7] << 16) ~ (v[7] >> 16) ~ - (v[7] & 0xffff) - h[1] = (v[0] << 16) ~ (v[0] >> 16) ~ (v[0] & 0xffff0000) ~ (v[1] & 0xffff) ~ - v[2] ~ (v[2] >> 16) ~ (v[3] << 16) ~ (v[4] >> 16) ~ (v[5] << 16) ~ - (v[6] << 16) ~ v[6] ~ (v[7] & 0xffff0000) ~ (v[7] >> 16) - h[2] = (v[0] & 0xffff) ~ (v[0] << 16) ~ (v[1] << 16) ~ (v[1] >> 16) ~ - (v[1] & 0xffff0000) ~ (v[2] << 16) ~ (v[3] >> 16) ~ v[3] ~ (v[4] << 16) ~ - (v[5] >> 16) ~ v[6] ~ (v[6] >> 16) ~ (v[7] & 0xffff) ~ (v[7] << 16) ~ - (v[7] >> 16) - h[3] = (v[0] << 16) ~ (v[0] >> 16) ~ (v[0] & 0xffff0000) ~ - (v[1] & 0xffff0000) ~ (v[1] >> 16) ~ (v[2] << 16) ~ (v[2] >> 16) ~ v[2] ~ - (v[3] << 16) ~ (v[4] >> 16) ~ v[4] ~ (v[5] << 16) ~ (v[6] << 16) ~ - (v[7] & 0xffff) ~ (v[7] >> 16) - h[4] = (v[0] >> 16) ~ (v[1] << 16) ~ v[1] ~ (v[2] >> 16) ~ v[2] ~ - (v[3] << 16) ~ (v[3] >> 16) ~ v[3] ~ (v[4] << 16) ~ (v[5] >> 16) ~ - v[5] ~ (v[6] << 16) ~ (v[6] >> 16) ~ (v[7] << 16) - h[5] = (v[0] << 16) ~ (v[0] & 0xffff0000) ~ (v[1] << 16) ~ (v[1] >> 16) ~ - (v[1] & 0xffff0000) ~ (v[2] << 16) ~ v[2] ~ (v[3] >> 16) ~ v[3] ~ - (v[4] << 16) ~ (v[4] >> 16) ~ v[4] ~ (v[5] << 16) ~ (v[6] << 16) ~ - (v[6] >> 16) ~ v[6] ~ (v[7] << 16) ~ (v[7] >> 16) ~ (v[7] & 0xffff0000) - h[6] = v[0] ~ v[2] ~ (v[2] >> 16) ~ v[3] ~ (v[3] << 16) ~ v[4] ~ - (v[4] >> 16) ~ (v[5] << 16) ~ (v[5] >> 16) ~ v[5] ~ (v[6] << 16) ~ - (v[6] >> 16) ~ v[6] ~ (v[7] << 16) ~ v[7] - h[7] = v[0] ~ (v[0] >> 16) ~ (v[1] << 16) ~ (v[1] >> 16) ~ (v[2] << 16) ~ - (v[3] >> 16) ~ v[3] ~ (v[4] << 16) ~ v[4] ~ (v[5] >> 16) ~ v[5] ~ - (v[6] << 16) ~ (v[6] >> 16) ~ (v[7] << 16) ~ v[7] -} \ No newline at end of file diff --git a/core/crypto/groestl/groestl.odin b/core/crypto/groestl/groestl.odin deleted file mode 100644 index 61460808f..000000000 --- a/core/crypto/groestl/groestl.odin +++ /dev/null @@ -1,653 +0,0 @@ -package groestl - -/* - Copyright 2021 zhibog - Made available under the BSD-3 license. - - List of contributors: - zhibog, dotbmp: Initial implementation. - - Implementation of the GROESTL hashing algorithm, as defined in -*/ - -import "core:os" -import "core:io" - -/* - High level API -*/ - -DIGEST_SIZE_224 :: 28 -DIGEST_SIZE_256 :: 32 -DIGEST_SIZE_384 :: 48 -DIGEST_SIZE_512 :: 64 - -// hash_string_224 will hash the given input and return the -// computed hash -hash_string_224 :: proc(data: string) -> [DIGEST_SIZE_224]byte { - return hash_bytes_224(transmute([]byte)(data)) -} - -// hash_bytes_224 will hash the given input and return the -// computed hash -hash_bytes_224 :: proc(data: []byte) -> [DIGEST_SIZE_224]byte { - hash: [DIGEST_SIZE_224]byte - ctx: Groestl_Context - ctx.hashbitlen = 224 - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_224 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_224 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_224(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_224 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_224 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_224, "Size of destination buffer is smaller than the digest size") - ctx: Groestl_Context - ctx.hashbitlen = 224 - init(&ctx) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_224 will read the stream in chunks and compute a -// hash from its contents -hash_stream_224 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { - hash: [DIGEST_SIZE_224]byte - ctx: Groestl_Context - ctx.hashbitlen = 224 - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_224 will read the file provided by the given handle -// and compute a hash -hash_file_224 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_224]byte, bool) { - if !load_at_once { - return hash_stream_224(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_224(buf[:]), ok - } - } - return [DIGEST_SIZE_224]byte{}, false -} - -hash_224 :: proc { - hash_stream_224, - hash_file_224, - hash_bytes_224, - hash_string_224, - hash_bytes_to_buffer_224, - hash_string_to_buffer_224, -} - -// hash_string_256 will hash the given input and return the -// computed hash -hash_string_256 :: proc(data: string) -> [DIGEST_SIZE_256]byte { - return hash_bytes_256(transmute([]byte)(data)) -} - -// hash_bytes_256 will hash the given input and return the -// computed hash -hash_bytes_256 :: proc(data: []byte) -> [DIGEST_SIZE_256]byte { - hash: [DIGEST_SIZE_256]byte - ctx: Groestl_Context - ctx.hashbitlen = 256 - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_256 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_256 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_256(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_256 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_256 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_256, "Size of destination buffer is smaller than the digest size") - ctx: Groestl_Context - ctx.hashbitlen = 256 - init(&ctx) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_256 will read the stream in chunks and compute a -// hash from its contents -hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { - hash: [DIGEST_SIZE_256]byte - ctx: Groestl_Context - ctx.hashbitlen = 256 - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_256 will read the file provided by the given handle -// and compute a hash -hash_file_256 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_256]byte, bool) { - if !load_at_once { - return hash_stream_256(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_256(buf[:]), ok - } - } - return [DIGEST_SIZE_256]byte{}, false -} - -hash_256 :: proc { - hash_stream_256, - hash_file_256, - hash_bytes_256, - hash_string_256, - hash_bytes_to_buffer_256, - hash_string_to_buffer_256, -} - -// hash_string_384 will hash the given input and return the -// computed hash -hash_string_384 :: proc(data: string) -> [DIGEST_SIZE_384]byte { - return hash_bytes_384(transmute([]byte)(data)) -} - -// hash_bytes_384 will hash the given input and return the -// computed hash -hash_bytes_384 :: proc(data: []byte) -> [DIGEST_SIZE_384]byte { - hash: [DIGEST_SIZE_384]byte - ctx: Groestl_Context - ctx.hashbitlen = 384 - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_384 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_384 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_384(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_384 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_384 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_384, "Size of destination buffer is smaller than the digest size") - ctx: Groestl_Context - ctx.hashbitlen = 384 - init(&ctx) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_384 will read the stream in chunks and compute a -// hash from its contents -hash_stream_384 :: proc(s: io.Stream) -> ([DIGEST_SIZE_384]byte, bool) { - hash: [DIGEST_SIZE_384]byte - ctx: Groestl_Context - ctx.hashbitlen = 384 - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_384 will read the file provided by the given handle -// and compute a hash -hash_file_384 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_384]byte, bool) { - if !load_at_once { - return hash_stream_384(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_384(buf[:]), ok - } - } - return [DIGEST_SIZE_384]byte{}, false -} - -hash_384 :: proc { - hash_stream_384, - hash_file_384, - hash_bytes_384, - hash_string_384, - hash_bytes_to_buffer_384, - hash_string_to_buffer_384, -} - -// hash_string_512 will hash the given input and return the -// computed hash -hash_string_512 :: proc(data: string) -> [DIGEST_SIZE_512]byte { - return hash_bytes_512(transmute([]byte)(data)) -} - -// hash_bytes_512 will hash the given input and return the -// computed hash -hash_bytes_512 :: proc(data: []byte) -> [DIGEST_SIZE_512]byte { - hash: [DIGEST_SIZE_512]byte - ctx: Groestl_Context - ctx.hashbitlen = 512 - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_512 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_512 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_512(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_512 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_512 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_512, "Size of destination buffer is smaller than the digest size") - ctx: Groestl_Context - ctx.hashbitlen = 512 - init(&ctx) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_512 will read the stream in chunks and compute a -// hash from its contents -hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { - hash: [DIGEST_SIZE_512]byte - ctx: Groestl_Context - ctx.hashbitlen = 512 - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_512 will read the file provided by the given handle -// and compute a hash -hash_file_512 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_512]byte, bool) { - if !load_at_once { - return hash_stream_512(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_512(buf[:]), ok - } - } - return [DIGEST_SIZE_512]byte{}, false -} - -hash_512 :: proc { - hash_stream_512, - hash_file_512, - hash_bytes_512, - hash_string_512, - hash_bytes_to_buffer_512, - hash_string_to_buffer_512, -} - -/* - Low level API -*/ - -init :: proc(ctx: ^Groestl_Context) { - assert(ctx.hashbitlen == 224 || ctx.hashbitlen == 256 || ctx.hashbitlen == 384 || ctx.hashbitlen == 512, "hashbitlen must be set to 224, 256, 384 or 512") - if ctx.hashbitlen <= 256 { - ctx.rounds = 10 - ctx.columns = 8 - ctx.statesize = 64 - } else { - ctx.rounds = 14 - ctx.columns = 16 - ctx.statesize = 128 - } - for i := 8 - size_of(i32); i < 8; i += 1 { - ctx.chaining[i][ctx.columns - 1] = byte(ctx.hashbitlen >> (8 * (7 - uint(i)))) - } -} - -update :: proc(ctx: ^Groestl_Context, data: []byte) { - databitlen := len(data) * 8 - msglen := databitlen / 8 - rem := databitlen % 8 - - i: int - assert(ctx.bits_in_last_byte == 0) - - if ctx.buf_ptr != 0 { - for i = 0; ctx.buf_ptr < ctx.statesize && i < msglen; i, ctx.buf_ptr = i + 1, ctx.buf_ptr + 1 { - ctx.buffer[ctx.buf_ptr] = data[i] - } - - if ctx.buf_ptr < ctx.statesize { - if rem != 0 { - ctx.bits_in_last_byte = rem - ctx.buffer[ctx.buf_ptr] = data[i] - ctx.buf_ptr += 1 - } - return - } - - ctx.buf_ptr = 0 - transform(ctx, ctx.buffer[:], u32(ctx.statesize)) - } - - transform(ctx, data[i:], u32(msglen - i)) - i += ((msglen - i) / ctx.statesize) * ctx.statesize - for i < msglen { - ctx.buffer[ctx.buf_ptr] = data[i] - i, ctx.buf_ptr = i + 1, ctx.buf_ptr + 1 - } - - if rem != 0 { - ctx.bits_in_last_byte = rem - ctx.buffer[ctx.buf_ptr] = data[i] - ctx.buf_ptr += 1 - } -} - -final :: proc(ctx: ^Groestl_Context, hash: []byte) { - hashbytelen := ctx.hashbitlen / 8 - - if ctx.bits_in_last_byte != 0 { - ctx.buffer[ctx.buf_ptr - 1] &= ((1 << uint(ctx.bits_in_last_byte)) - 1) << (8 - uint(ctx.bits_in_last_byte)) - ctx.buffer[ctx.buf_ptr - 1] ~= 0x1 << (7 - uint(ctx.bits_in_last_byte)) - } else { - ctx.buffer[ctx.buf_ptr] = 0x80 - ctx.buf_ptr += 1 - } - - if ctx.buf_ptr > ctx.statesize - 8 { - for ctx.buf_ptr < ctx.statesize { - ctx.buffer[ctx.buf_ptr] = 0 - ctx.buf_ptr += 1 - } - transform(ctx, ctx.buffer[:], u32(ctx.statesize)) - ctx.buf_ptr = 0 - } - - for ctx.buf_ptr < ctx.statesize - 8 { - ctx.buffer[ctx.buf_ptr] = 0 - ctx.buf_ptr += 1 - } - - ctx.block_counter += 1 - ctx.buf_ptr = ctx.statesize - - for ctx.buf_ptr > ctx.statesize - 8 { - ctx.buf_ptr -= 1 - ctx.buffer[ctx.buf_ptr] = byte(ctx.block_counter) - ctx.block_counter >>= 8 - } - - transform(ctx, ctx.buffer[:], u32(ctx.statesize)) - output_transformation(ctx) - - for i, j := ctx.statesize - hashbytelen , 0; i < ctx.statesize; i, j = i + 1, j + 1 { - hash[j] = ctx.chaining[i % 8][i / 8] - } -} - -/* - GROESTL implementation -*/ - -SBOX := [256]byte { - 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, - 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, - 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, - 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, - 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, - 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, - 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, - 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, - 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, - 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, - 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, - 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, - 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, - 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, - 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, - 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, - 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, - 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, - 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, - 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, - 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, - 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, - 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, - 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, - 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, - 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, - 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, - 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, - 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, - 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, - 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, - 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16, -} - -SHIFT := [2][2][8]int { - {{0, 1, 2, 3, 4, 5, 6, 7}, {1, 3, 5, 7, 0, 2, 4, 6}}, - {{0, 1, 2, 3, 4, 5, 6, 11}, {1, 3, 5, 11, 0, 2, 4, 6}}, -} - -Groestl_Context :: struct { - chaining: [8][16]byte, - block_counter: u64, - hashbitlen: int, - buffer: [128]byte, - buf_ptr: int, - bits_in_last_byte: int, - columns: int, - rounds: int, - statesize: int, -} - -Groestl_Variant :: enum { - P512 = 0, - Q512 = 1, - P1024 = 2, - Q1024 = 3, -} - -MUL2 :: #force_inline proc "contextless"(b: byte) -> byte { - return (b >> 7) != 0 ? (b << 1) ~ 0x1b : (b << 1) -} - -MUL3 :: #force_inline proc "contextless"(b: byte) -> byte { - return MUL2(b) ~ b -} - -MUL4 :: #force_inline proc "contextless"(b: byte) -> byte { - return MUL2(MUL2(b)) -} - -MUL5 :: #force_inline proc "contextless"(b: byte) -> byte { - return MUL4(b) ~ b -} - -MUL6 :: #force_inline proc "contextless"(b: byte) -> byte { - return MUL4(b) ~ MUL2(b) -} - -MUL7 :: #force_inline proc "contextless"(b: byte) -> byte { - return MUL4(b) ~ MUL2(b) ~ b -} - -sub_bytes :: #force_inline proc (x: [][16]byte, columns: int) { - for i := 0; i < 8; i += 1 { - for j := 0; j < columns; j += 1 { - x[i][j] = SBOX[x[i][j]] - } - } -} - -shift_bytes :: #force_inline proc (x: [][16]byte, columns: int, v: Groestl_Variant) { - temp: [16]byte - R := &SHIFT[int(v) / 2][int(v) & 1] - - for i := 0; i < 8; i += 1 { - for j := 0; j < columns; j += 1 { - temp[j] = x[i][(j + R[i]) % columns] - } - for j := 0; j < columns; j += 1 { - x[i][j] = temp[j] - } - } -} - -mix_bytes :: #force_inline proc (x: [][16]byte, columns: int) { - temp: [8]byte - - for i := 0; i < columns; i += 1 { - for j := 0; j < 8; j += 1 { - temp[j] = MUL2(x[(j + 0) % 8][i]) ~ - MUL2(x[(j + 1) % 8][i]) ~ - MUL3(x[(j + 2) % 8][i]) ~ - MUL4(x[(j + 3) % 8][i]) ~ - MUL5(x[(j + 4) % 8][i]) ~ - MUL3(x[(j + 5) % 8][i]) ~ - MUL5(x[(j + 6) % 8][i]) ~ - MUL7(x[(j + 7) % 8][i]) - } - for j := 0; j < 8; j += 1 { - x[j][i] = temp[j] - } - } -} - -p :: #force_inline proc (ctx: ^Groestl_Context, x: [][16]byte) { - v := ctx.columns == 8 ? Groestl_Variant.P512 : Groestl_Variant.P1024 - for i := 0; i < ctx.rounds; i += 1 { - add_roundconstant(x, ctx.columns, byte(i), v) - sub_bytes(x, ctx.columns) - shift_bytes(x, ctx.columns, v) - mix_bytes(x, ctx.columns) - } -} - -q :: #force_inline proc (ctx: ^Groestl_Context, x: [][16]byte) { - v := ctx.columns == 8 ? Groestl_Variant.Q512 : Groestl_Variant.Q1024 - for i := 0; i < ctx.rounds; i += 1 { - add_roundconstant(x, ctx.columns, byte(i), v) - sub_bytes(x, ctx.columns) - shift_bytes(x, ctx.columns, v) - mix_bytes(x, ctx.columns) - } -} - -transform :: proc(ctx: ^Groestl_Context, input: []byte, msglen: u32) { - tmp1, tmp2: [8][16]byte - input, msglen := input, msglen - - for msglen >= u32(ctx.statesize) { - for i := 0; i < 8; i += 1 { - for j := 0; j < ctx.columns; j += 1 { - tmp1[i][j] = ctx.chaining[i][j] ~ input[j * 8 + i] - tmp2[i][j] = input[j * 8 + i] - } - } - - p(ctx, tmp1[:]) - q(ctx, tmp2[:]) - - for i := 0; i < 8; i += 1 { - for j := 0; j < ctx.columns; j += 1 { - ctx.chaining[i][j] ~= tmp1[i][j] ~ tmp2[i][j] - } - } - - ctx.block_counter += 1 - msglen -= u32(ctx.statesize) - input = input[ctx.statesize:] - } -} - -output_transformation :: proc(ctx: ^Groestl_Context) { - temp: [8][16]byte - - for i := 0; i < 8; i += 1 { - for j := 0; j < ctx.columns; j += 1 { - temp[i][j] = ctx.chaining[i][j] - } - } - - p(ctx, temp[:]) - - for i := 0; i < 8; i += 1 { - for j := 0; j < ctx.columns; j += 1 { - ctx.chaining[i][j] ~= temp[i][j] - } - } -} - -add_roundconstant :: proc(x: [][16]byte, columns: int, round: byte, v: Groestl_Variant) { - switch (i32(v) & 1) { - case 0: - for i := 0; i < columns; i += 1 { - x[0][i] ~= byte(i << 4) ~ round - } - case 1: - for i := 0; i < columns; i += 1 { - for j := 0; j < 7; j += 1 { - x[j][i] ~= 0xff - } - } - for i := 0; i < columns; i += 1 { - x[7][i] ~= byte(i << 4) ~ 0xff ~ round - } - } -} diff --git a/core/crypto/hash/doc.odin b/core/crypto/hash/doc.odin new file mode 100644 index 000000000..1dfd97de2 --- /dev/null +++ b/core/crypto/hash/doc.odin @@ -0,0 +1,60 @@ +/* +package hash provides a generic interface to the supported hash algorithms. + +A high-level convenience procedure group `hash` is provided to easily +accomplish common tasks. +- `hash_string` - Hash a given string and return the digest. +- `hash_bytes` - Hash a given byte slice and return the digest. +- `hash_string_to_buffer` - Hash a given string and put the digest in + the third parameter. It requires that the destination buffer + is at least as big as the digest size. +- `hash_bytes_to_buffer` - Hash a given string and put the computed + digest in the third parameter. It requires that the destination + buffer is at least as big as the digest size. +- `hash_stream` - Incrementally fully consume a `io.Stream`, and return + the computed digest. +- `hash_file` - Takes a file handle and returns the computed digest. + A third optional boolean parameter controls if the file is streamed + (default), or or read at once. + +Example: + package hash_example + + import "core:crypto/hash" + + main :: proc() { + input := "Feed the fire." + + // Compute the digest, using the high level API. + returned_digest := hash.hash(hash.Algorithm.SHA512_256, input) + defer delete(returned_digest) + + // Variant that takes a destination buffer, instead of returning + // the digest. + digest := make([]byte, hash.DIGEST_SIZES[hash.Algorithm.BLAKE2B]) // @note: Destination buffer has to be at least as big as the digest size of the hash. + defer delete(digest) + hash.hash(hash.Algorithm.BLAKE2B, input, digest) + } + +A generic low level API is provided supporting the init/update/final interface +that is typical with cryptographic hash function implementations. + +Example: + package hash_example + + import "core:crypto/hash" + + main :: proc() { + input := "Let the cinders burn." + + // Compute the digest, using the low level API. + ctx: hash.Context + digest := make([]byte, hash.DIGEST_SIZES[hash.Algorithm.SHA3_512]) + defer delete(digest) + + hash.init(&ctx, hash.Algorithm.SHA3_512) + hash.update(&ctx, transmute([]byte)input) + hash.final(&ctx, digest) + } +*/ +package crypto_hash diff --git a/core/crypto/hash/hash.odin b/core/crypto/hash/hash.odin new file mode 100644 index 000000000..d47f0ab46 --- /dev/null +++ b/core/crypto/hash/hash.odin @@ -0,0 +1,88 @@ +package crypto_hash + +/* + Copyright 2021 zhibog + Made available under the BSD-3 license. + + List of contributors: + zhibog, dotbmp: Initial implementation. +*/ + +import "core:io" +import "core:mem" + +// hash_bytes will hash the given input and return the computed digest +// in a newly allocated slice. +hash_string :: proc(algorithm: Algorithm, data: string, allocator := context.allocator) -> []byte { + return hash_bytes(algorithm, transmute([]byte)(data), allocator) +} + +// hash_bytes will hash the given input and return the computed digest +// in a newly allocated slice. +hash_bytes :: proc(algorithm: Algorithm, data: []byte, allocator := context.allocator) -> []byte { + dst := make([]byte, DIGEST_SIZES[algorithm], allocator) + hash_bytes_to_buffer(algorithm, data, dst) + return dst +} + +// hash_string_to_buffer will hash the given input and assign the +// computed digest to the third parameter. It requires that the +// destination buffer is at least as big as the digest size. The +// provided destination buffer is returned to match the behavior of +// `hash_string`. +hash_string_to_buffer :: proc(algorithm: Algorithm, data: string, hash: []byte) -> []byte { + return hash_bytes_to_buffer(algorithm, transmute([]byte)(data), hash) +} + +// hash_bytes_to_buffer will hash the given input and write the +// computed digest into the third parameter. It requires that the +// destination buffer is at least as big as the digest size. The +// provided destination buffer is returned to match the behavior of +// `hash_bytes`. +hash_bytes_to_buffer :: proc(algorithm: Algorithm, data, hash: []byte) -> []byte { + ctx: Context + + init(&ctx, algorithm) + update(&ctx, data) + final(&ctx, hash) + + return hash +} + +// hash_stream will incrementally fully consume a stream, and return the +// computed digest in a newly allocated slice. +hash_stream :: proc( + algorithm: Algorithm, + s: io.Stream, + allocator := context.allocator, +) -> ( + []byte, + io.Error, +) { + ctx: Context + + buf: [MAX_BLOCK_SIZE * 4]byte + defer mem.zero_explicit(&buf, size_of(buf)) + + init(&ctx, algorithm) + + loop: for { + n, err := io.read(s, buf[:]) + if n > 0 { + // XXX/yawning: Can io.read return n > 0 and EOF? + update(&ctx, buf[:n]) + } + #partial switch err { + case .None: + case .EOF: + break loop + case: + return nil, err + } + } + + dst := make([]byte, DIGEST_SIZES[algorithm], allocator) + final(&ctx, dst) + + return dst, io.Error.None +} diff --git a/core/crypto/hash/hash_freestanding.odin b/core/crypto/hash/hash_freestanding.odin new file mode 100644 index 000000000..bec3c4eee --- /dev/null +++ b/core/crypto/hash/hash_freestanding.odin @@ -0,0 +1,10 @@ +#+build freestanding +package crypto_hash + +hash :: proc { + hash_stream, + hash_bytes, + hash_string, + hash_bytes_to_buffer, + hash_string_to_buffer, +} diff --git a/core/crypto/hash/hash_os.odin b/core/crypto/hash/hash_os.odin new file mode 100644 index 000000000..d54e657ad --- /dev/null +++ b/core/crypto/hash/hash_os.odin @@ -0,0 +1,38 @@ +#+build !freestanding +package crypto_hash + +import "core:io" +import "core:os" + +// hash_file will read the file provided by the given handle and return the +// computed digest in a newly allocated slice. +hash_file :: proc( + algorithm: Algorithm, + hd: os.Handle, + load_at_once := false, + allocator := context.allocator, +) -> ( + []byte, + io.Error, +) { + if !load_at_once { + return hash_stream(algorithm, os.stream_from_handle(hd), allocator) + } + + buf, ok := os.read_entire_file(hd, allocator) + if !ok { + return nil, io.Error.Unknown + } + defer delete(buf, allocator) + + return hash_bytes(algorithm, buf, allocator), io.Error.None +} + +hash :: proc { + hash_stream, + hash_file, + hash_bytes, + hash_string, + hash_bytes_to_buffer, + hash_string_to_buffer, +} diff --git a/core/crypto/hash/low_level.odin b/core/crypto/hash/low_level.odin new file mode 100644 index 000000000..242eadd5f --- /dev/null +++ b/core/crypto/hash/low_level.odin @@ -0,0 +1,353 @@ +package crypto_hash + +import "core:crypto/blake2b" +import "core:crypto/blake2s" +import "core:crypto/sha2" +import "core:crypto/sha3" +import "core:crypto/sm3" +import "core:crypto/legacy/keccak" +import "core:crypto/legacy/md5" +import "core:crypto/legacy/sha1" + +import "core:reflect" + +// MAX_DIGEST_SIZE is the maximum size digest that can be returned by any +// of the Algorithms supported via this package. +MAX_DIGEST_SIZE :: 64 +// MAX_BLOCK_SIZE is the maximum block size used by any of Algorithms +// supported by this package. +MAX_BLOCK_SIZE :: sha3.BLOCK_SIZE_224 + +// Algorithm is the algorithm identifier associated with a given Context. +Algorithm :: enum { + Invalid, + BLAKE2B, + BLAKE2S, + SHA224, + SHA256, + SHA384, + SHA512, + SHA512_256, + SHA3_224, + SHA3_256, + SHA3_384, + SHA3_512, + SM3, + Legacy_KECCAK_224, + Legacy_KECCAK_256, + Legacy_KECCAK_384, + Legacy_KECCAK_512, + Insecure_MD5, + Insecure_SHA1, +} + +// ALGORITHM_NAMES is the Algorithm to algorithm name string. +ALGORITHM_NAMES := [Algorithm]string { + .Invalid = "Invalid", + .BLAKE2B = "BLAKE2b", + .BLAKE2S = "BLAKE2s", + .SHA224 = "SHA-224", + .SHA256 = "SHA-256", + .SHA384 = "SHA-384", + .SHA512 = "SHA-512", + .SHA512_256 = "SHA-512/256", + .SHA3_224 = "SHA3-224", + .SHA3_256 = "SHA3-256", + .SHA3_384 = "SHA3-384", + .SHA3_512 = "SHA3-512", + .SM3 = "SM3", + .Legacy_KECCAK_224 = "Keccak-224", + .Legacy_KECCAK_256 = "Keccak-256", + .Legacy_KECCAK_384 = "Keccak-384", + .Legacy_KECCAK_512 = "Keccak-512", + .Insecure_MD5 = "MD5", + .Insecure_SHA1 = "SHA-1", +} + +// DIGEST_SIZES is the Algorithm to digest size in bytes. +DIGEST_SIZES := [Algorithm]int { + .Invalid = 0, + .BLAKE2B = blake2b.DIGEST_SIZE, + .BLAKE2S = blake2s.DIGEST_SIZE, + .SHA224 = sha2.DIGEST_SIZE_224, + .SHA256 = sha2.DIGEST_SIZE_256, + .SHA384 = sha2.DIGEST_SIZE_384, + .SHA512 = sha2.DIGEST_SIZE_512, + .SHA512_256 = sha2.DIGEST_SIZE_512_256, + .SHA3_224 = sha3.DIGEST_SIZE_224, + .SHA3_256 = sha3.DIGEST_SIZE_256, + .SHA3_384 = sha3.DIGEST_SIZE_384, + .SHA3_512 = sha3.DIGEST_SIZE_512, + .SM3 = sm3.DIGEST_SIZE, + .Legacy_KECCAK_224 = keccak.DIGEST_SIZE_224, + .Legacy_KECCAK_256 = keccak.DIGEST_SIZE_256, + .Legacy_KECCAK_384 = keccak.DIGEST_SIZE_384, + .Legacy_KECCAK_512 = keccak.DIGEST_SIZE_512, + .Insecure_MD5 = md5.DIGEST_SIZE, + .Insecure_SHA1 = sha1.DIGEST_SIZE, +} + +// BLOCK_SIZES is the Algoritm to block size in bytes. +BLOCK_SIZES := [Algorithm]int { + .Invalid = 0, + .BLAKE2B = blake2b.BLOCK_SIZE, + .BLAKE2S = blake2s.BLOCK_SIZE, + .SHA224 = sha2.BLOCK_SIZE_256, + .SHA256 = sha2.BLOCK_SIZE_256, + .SHA384 = sha2.BLOCK_SIZE_512, + .SHA512 = sha2.BLOCK_SIZE_512, + .SHA512_256 = sha2.BLOCK_SIZE_512, + .SHA3_224 = sha3.BLOCK_SIZE_224, + .SHA3_256 = sha3.BLOCK_SIZE_256, + .SHA3_384 = sha3.BLOCK_SIZE_384, + .SHA3_512 = sha3.BLOCK_SIZE_512, + .SM3 = sm3.BLOCK_SIZE, + .Legacy_KECCAK_224 = keccak.BLOCK_SIZE_224, + .Legacy_KECCAK_256 = keccak.BLOCK_SIZE_256, + .Legacy_KECCAK_384 = keccak.BLOCK_SIZE_384, + .Legacy_KECCAK_512 = keccak.BLOCK_SIZE_512, + .Insecure_MD5 = md5.BLOCK_SIZE, + .Insecure_SHA1 = sha1.BLOCK_SIZE, +} + +// Context is a concrete instantiation of a specific hash algorithm. +Context :: struct { + _algo: Algorithm, + _impl: union { + blake2b.Context, + blake2s.Context, + sha2.Context_256, + sha2.Context_512, + sha3.Context, + sm3.Context, + keccak.Context, + md5.Context, + sha1.Context, + }, +} + +@(private) +_IMPL_IDS := [Algorithm]typeid { + .Invalid = nil, + .BLAKE2B = typeid_of(blake2b.Context), + .BLAKE2S = typeid_of(blake2s.Context), + .SHA224 = typeid_of(sha2.Context_256), + .SHA256 = typeid_of(sha2.Context_256), + .SHA384 = typeid_of(sha2.Context_512), + .SHA512 = typeid_of(sha2.Context_512), + .SHA512_256 = typeid_of(sha2.Context_512), + .SHA3_224 = typeid_of(sha3.Context), + .SHA3_256 = typeid_of(sha3.Context), + .SHA3_384 = typeid_of(sha3.Context), + .SHA3_512 = typeid_of(sha3.Context), + .SM3 = typeid_of(sm3.Context), + .Legacy_KECCAK_224 = typeid_of(keccak.Context), + .Legacy_KECCAK_256 = typeid_of(keccak.Context), + .Legacy_KECCAK_384 = typeid_of(keccak.Context), + .Legacy_KECCAK_512 = typeid_of(keccak.Context), + .Insecure_MD5 = typeid_of(md5.Context), + .Insecure_SHA1 = typeid_of(sha1.Context), +} + +// init initializes a Context with a specific hash Algorithm. +init :: proc(ctx: ^Context, algorithm: Algorithm) { + if ctx._impl != nil { + reset(ctx) + } + + // Directly specialize the union by setting the type ID (save a copy). + reflect.set_union_variant_typeid( + ctx._impl, + _IMPL_IDS[algorithm], + ) + switch algorithm { + case .BLAKE2B: + blake2b.init(&ctx._impl.(blake2b.Context)) + case .BLAKE2S: + blake2s.init(&ctx._impl.(blake2s.Context)) + case .SHA224: + sha2.init_224(&ctx._impl.(sha2.Context_256)) + case .SHA256: + sha2.init_256(&ctx._impl.(sha2.Context_256)) + case .SHA384: + sha2.init_384(&ctx._impl.(sha2.Context_512)) + case .SHA512: + sha2.init_512(&ctx._impl.(sha2.Context_512)) + case .SHA512_256: + sha2.init_512_256(&ctx._impl.(sha2.Context_512)) + case .SHA3_224: + sha3.init_224(&ctx._impl.(sha3.Context)) + case .SHA3_256: + sha3.init_256(&ctx._impl.(sha3.Context)) + case .SHA3_384: + sha3.init_384(&ctx._impl.(sha3.Context)) + case .SHA3_512: + sha3.init_512(&ctx._impl.(sha3.Context)) + case .SM3: + sm3.init(&ctx._impl.(sm3.Context)) + case .Legacy_KECCAK_224: + keccak.init_224(&ctx._impl.(keccak.Context)) + case .Legacy_KECCAK_256: + keccak.init_256(&ctx._impl.(keccak.Context)) + case .Legacy_KECCAK_384: + keccak.init_384(&ctx._impl.(keccak.Context)) + case .Legacy_KECCAK_512: + keccak.init_512(&ctx._impl.(keccak.Context)) + case .Insecure_MD5: + md5.init(&ctx._impl.(md5.Context)) + case .Insecure_SHA1: + sha1.init(&ctx._impl.(sha1.Context)) + case .Invalid: + panic("crypto/hash: uninitialized algorithm") + case: + panic("crypto/hash: invalid algorithm") + } + + ctx._algo = algorithm +} + +// update adds more data to the Context. +update :: proc(ctx: ^Context, data: []byte) { + switch &impl in ctx._impl { + case blake2b.Context: + blake2b.update(&impl, data) + case blake2s.Context: + blake2s.update(&impl, data) + case sha2.Context_256: + sha2.update(&impl, data) + case sha2.Context_512: + sha2.update(&impl, data) + case sha3.Context: + sha3.update(&impl, data) + case sm3.Context: + sm3.update(&impl, data) + case keccak.Context: + keccak.update(&impl, data) + case md5.Context: + md5.update(&impl, data) + case sha1.Context: + sha1.update(&impl, data) + case: + panic("crypto/hash: uninitialized algorithm") + } +} + +// final finalizes the Context, writes the digest to hash, and calls +// reset on the Context. +// +// 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) { + switch &impl in ctx._impl { + case blake2b.Context: + blake2b.final(&impl, hash, finalize_clone) + case blake2s.Context: + blake2s.final(&impl, hash, finalize_clone) + case sha2.Context_256: + sha2.final(&impl, hash, finalize_clone) + case sha2.Context_512: + sha2.final(&impl, hash, finalize_clone) + case sha3.Context: + sha3.final(&impl, hash, finalize_clone) + case sm3.Context: + sm3.final(&impl, hash, finalize_clone) + case keccak.Context: + keccak.final(&impl, hash, finalize_clone) + case md5.Context: + md5.final(&impl, hash, finalize_clone) + case sha1.Context: + sha1.final(&impl, hash, finalize_clone) + case: + panic("crypto/hash: uninitialized algorithm") + } + + if !finalize_clone { + reset(ctx) + } +} + +// clone clones the Context other into ctx. +clone :: proc(ctx, other: ^Context) { + // XXX/yawning: Maybe these cases should panic, because both cases, + // are probably bugs. + if ctx == other { + return + } + if ctx._impl != nil { + reset(ctx) + } + + ctx._algo = other._algo + + reflect.set_union_variant_typeid( + ctx._impl, + reflect.union_variant_typeid(other._impl), + ) + switch &src_impl in other._impl { + case blake2b.Context: + blake2b.clone(&ctx._impl.(blake2b.Context), &src_impl) + case blake2s.Context: + blake2s.clone(&ctx._impl.(blake2s.Context), &src_impl) + case sha2.Context_256: + sha2.clone(&ctx._impl.(sha2.Context_256), &src_impl) + case sha2.Context_512: + sha2.clone(&ctx._impl.(sha2.Context_512), &src_impl) + case sha3.Context: + sha3.clone(&ctx._impl.(sha3.Context), &src_impl) + case sm3.Context: + sm3.clone(&ctx._impl.(sm3.Context), &src_impl) + case keccak.Context: + keccak.clone(&ctx._impl.(keccak.Context), &src_impl) + case md5.Context: + md5.clone(&ctx._impl.(md5.Context), &src_impl) + case sha1.Context: + sha1.clone(&ctx._impl.(sha1.Context), &src_impl) + case: + panic("crypto/hash: uninitialized algorithm") + } +} + +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. +reset :: proc(ctx: ^Context) { + switch &impl in ctx._impl { + case blake2b.Context: + blake2b.reset(&impl) + case blake2s.Context: + blake2s.reset(&impl) + case sha2.Context_256: + sha2.reset(&impl) + case sha2.Context_512: + sha2.reset(&impl) + case sha3.Context: + sha3.reset(&impl) + case sm3.Context: + sm3.reset(&impl) + case keccak.Context: + keccak.reset(&impl) + case md5.Context: + md5.reset(&impl) + case sha1.Context: + sha1.reset(&impl) + case: + // Unlike clone, calling reset repeatedly is fine. + } + + ctx._algo = .Invalid + ctx._impl = nil +} + +// algorithm returns the Algorithm used by a Context instance. +algorithm :: proc(ctx: ^Context) -> Algorithm { + return ctx._algo +} + +// digest_size returns the digest size of a Context instance in bytes. +digest_size :: proc(ctx: ^Context) -> int { + return DIGEST_SIZES[ctx._algo] +} + +// block_size returns the block size of a Context instance in bytes. +block_size :: proc(ctx: ^Context) -> int { + return BLOCK_SIZES[ctx._algo] +} diff --git a/core/crypto/haval/haval.odin b/core/crypto/haval/haval.odin deleted file mode 100644 index b98facb33..000000000 --- a/core/crypto/haval/haval.odin +++ /dev/null @@ -1,1814 +0,0 @@ -package haval - -/* - Copyright 2021 zhibog - Made available under the BSD-3 license. - - List of contributors: - zhibog, dotbmp: Initial implementation. - - Implementation for the HAVAL hashing algorithm as defined in -*/ - -import "core:mem" -import "core:os" -import "core:io" - -import "../util" - -/* - High level API -*/ - -DIGEST_SIZE_128 :: 16 -DIGEST_SIZE_160 :: 20 -DIGEST_SIZE_192 :: 24 -DIGEST_SIZE_224 :: 28 -DIGEST_SIZE_256 :: 32 - -// hash_string_128_3 will hash the given input and return the -// computed hash -hash_string_128_3 :: proc(data: string) -> [DIGEST_SIZE_128]byte { - return hash_bytes_128_3(transmute([]byte)(data)) -} - -// hash_bytes_128_3 will hash the given input and return the -// computed hash -hash_bytes_128_3 :: proc(data: []byte) -> [DIGEST_SIZE_128]byte { - hash: [DIGEST_SIZE_128]byte - ctx: Haval_Context - ctx.hashbitlen = 128 - ctx.rounds = 3 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_128_3 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_128_3 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_128_3(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_128_3 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_128_3 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_128, "Size of destination buffer is smaller than the digest size") - ctx: Haval_Context - ctx.hashbitlen = 128 - ctx.rounds = 3 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_128_3 will read the stream in chunks and compute a -// hash from its contents -hash_stream_128_3 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { - hash: [DIGEST_SIZE_128]byte - ctx: Haval_Context - ctx.hashbitlen = 128 - ctx.rounds = 3 - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - ctx.str_len = u32(len(buf[:read])) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_128_3 will read the file provided by the given handle -// and compute a hash -hash_file_128_3 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_128]byte, bool) { - if !load_at_once { - return hash_stream_128_3(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_128_3(buf[:]), ok - } - } - return [DIGEST_SIZE_128]byte{}, false -} - -hash_128_3 :: proc { - hash_stream_128_3, - hash_file_128_3, - hash_bytes_128_3, - hash_string_128_3, - hash_bytes_to_buffer_128_3, - hash_string_to_buffer_128_3, -} - -// hash_string_128_4 will hash the given input and return the -// computed hash -hash_string_128_4 :: proc(data: string) -> [DIGEST_SIZE_128]byte { - return hash_bytes_128_4(transmute([]byte)(data)) -} - -// hash_bytes_128_4 will hash the given input and return the -// computed hash -hash_bytes_128_4 :: proc(data: []byte) -> [DIGEST_SIZE_128]byte { - hash: [DIGEST_SIZE_128]byte - ctx: Haval_Context - ctx.hashbitlen = 128 - ctx.rounds = 4 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_128_4 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_128_4 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_128_4(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_128_4 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_128_4 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_128, "Size of destination buffer is smaller than the digest size") - ctx: Haval_Context - ctx.hashbitlen = 128 - ctx.rounds = 4 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_128_4 will read the stream in chunks and compute a -// hash from its contents -hash_stream_128_4 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { - hash: [DIGEST_SIZE_128]byte - ctx: Haval_Context - ctx.hashbitlen = 128 - ctx.rounds = 4 - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - ctx.str_len = u32(len(buf[:read])) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_128_4 will read the file provided by the given handle -// and compute a hash -hash_file_128_4 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_128]byte, bool) { - if !load_at_once { - return hash_stream_128_4(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_128_4(buf[:]), ok - } - } - return [DIGEST_SIZE_128]byte{}, false -} - -hash_128_4 :: proc { - hash_stream_128_4, - hash_file_128_4, - hash_bytes_128_4, - hash_string_128_4, - hash_bytes_to_buffer_128_4, - hash_string_to_buffer_128_4, -} - -// hash_string_128_5 will hash the given input and return the -// computed hash -hash_string_128_5 :: proc(data: string) -> [DIGEST_SIZE_128]byte { - return hash_bytes_128_5(transmute([]byte)(data)) -} - -// hash_bytes_128_5 will hash the given input and return the -// computed hash -hash_bytes_128_5 :: proc(data: []byte) -> [DIGEST_SIZE_128]byte { - hash: [DIGEST_SIZE_128]byte - ctx: Haval_Context - ctx.hashbitlen = 128 - ctx.rounds = 5 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_128_5 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_128_5 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_128_5(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_128_5 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_128_5 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_128, "Size of destination buffer is smaller than the digest size") - ctx: Haval_Context - ctx.hashbitlen = 128 - ctx.rounds = 5 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_128_5 will read the stream in chunks and compute a -// hash from its contents -hash_stream_128_5 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { - hash: [DIGEST_SIZE_128]byte - ctx: Haval_Context - ctx.hashbitlen = 128 - ctx.rounds = 5 - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - ctx.str_len = u32(len(buf[:read])) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_128_5 will read the file provided by the given handle -// and compute a hash -hash_file_128_5 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_128]byte, bool) { - if !load_at_once { - return hash_stream_128_5(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_128_5(buf[:]), ok - } - } - return [DIGEST_SIZE_128]byte{}, false -} - -hash_128_5 :: proc { - hash_stream_128_5, - hash_file_128_5, - hash_bytes_128_5, - hash_string_128_5, - hash_bytes_to_buffer_128_5, - hash_string_to_buffer_128_5, -} - -// hash_string_160_3 will hash the given input and return the -// computed hash -hash_string_160_3 :: proc(data: string) -> [DIGEST_SIZE_160]byte { - return hash_bytes_160_3(transmute([]byte)(data)) -} - -// hash_bytes_160_3 will hash the given input and return the -// computed hash -hash_bytes_160_3 :: proc(data: []byte) -> [DIGEST_SIZE_160]byte { - hash: [DIGEST_SIZE_160]byte - ctx: Haval_Context - ctx.hashbitlen = 160 - ctx.rounds = 3 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_160_3 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_160_3 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_160_3(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_160_3 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_160_3 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_160, "Size of destination buffer is smaller than the digest size") - ctx: Haval_Context - ctx.hashbitlen = 160 - ctx.rounds = 3 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_160_3 will read the stream in chunks and compute a -// hash from its contents -hash_stream_160_3 :: proc(s: io.Stream) -> ([DIGEST_SIZE_160]byte, bool) { - hash: [DIGEST_SIZE_160]byte - ctx: Haval_Context - ctx.hashbitlen = 160 - ctx.rounds = 3 - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - ctx.str_len = u32(len(buf[:read])) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_160_3 will read the file provided by the given handle -// and compute a hash -hash_file_160_3 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_160]byte, bool) { - if !load_at_once { - return hash_stream_160_3(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_160_3(buf[:]), ok - } - } - return [DIGEST_SIZE_160]byte{}, false -} - -hash_160_3 :: proc { - hash_stream_160_3, - hash_file_160_3, - hash_bytes_160_3, - hash_string_160_3, - hash_bytes_to_buffer_160_3, - hash_string_to_buffer_160_3, -} - -// hash_string_160_4 will hash the given input and return the -// computed hash -hash_string_160_4 :: proc(data: string) -> [DIGEST_SIZE_160]byte { - return hash_bytes_160_4(transmute([]byte)(data)) -} - -// hash_bytes_160_4 will hash the given input and return the -// computed hash -hash_bytes_160_4 :: proc(data: []byte) -> [DIGEST_SIZE_160]byte { - hash: [DIGEST_SIZE_160]byte - ctx: Haval_Context - ctx.hashbitlen = 160 - ctx.rounds = 4 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_160_4 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_160_4 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_160_4(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_160_4 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_160_4 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_160, "Size of destination buffer is smaller than the digest size") - ctx: Haval_Context - ctx.hashbitlen = 160 - ctx.rounds = 4 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_160_4 will read the stream in chunks and compute a -// hash from its contents -hash_stream_160_4 :: proc(s: io.Stream) -> ([DIGEST_SIZE_160]byte, bool) { - hash: [DIGEST_SIZE_160]byte - ctx: Haval_Context - ctx.hashbitlen = 160 - ctx.rounds = 4 - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - ctx.str_len = u32(len(buf[:read])) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_160_4 will read the file provided by the given handle -// and compute a hash -hash_file_160_4 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_160]byte, bool) { - if !load_at_once { - return hash_stream_160_4(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_160_4(buf[:]), ok - } - } - return [DIGEST_SIZE_160]byte{}, false -} - -hash_160_4 :: proc { - hash_stream_160_4, - hash_file_160_4, - hash_bytes_160_4, - hash_string_160_4, - hash_bytes_to_buffer_160_4, - hash_string_to_buffer_160_4, -} - -// hash_string_160_5 will hash the given input and return the -// computed hash -hash_string_160_5 :: proc(data: string) -> [DIGEST_SIZE_160]byte { - return hash_bytes_160_5(transmute([]byte)(data)) -} - -// hash_bytes_160_5 will hash the given input and return the -// computed hash -hash_bytes_160_5 :: proc(data: []byte) -> [DIGEST_SIZE_160]byte { - hash: [DIGEST_SIZE_160]byte - ctx: Haval_Context - ctx.hashbitlen = 160 - ctx.rounds = 5 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_160_5 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_160_5 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_160_5(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_160_5 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_160_5 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_160, "Size of destination buffer is smaller than the digest size") - ctx: Haval_Context - ctx.hashbitlen = 160 - ctx.rounds = 5 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_160_5 will read the stream in chunks and compute a -// hash from its contents -hash_stream_160_5 :: proc(s: io.Stream) -> ([DIGEST_SIZE_160]byte, bool) { - hash: [DIGEST_SIZE_160]byte - ctx: Haval_Context - ctx.hashbitlen = 160 - ctx.rounds = 5 - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - ctx.str_len = u32(len(buf[:read])) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_160_5 will read the file provided by the given handle -// and compute a hash -hash_file_160_5 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_160]byte, bool) { - if !load_at_once { - return hash_stream_160_5(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_160_5(buf[:]), ok - } - } - return [DIGEST_SIZE_160]byte{}, false -} - -hash_160_5 :: proc { - hash_stream_160_5, - hash_file_160_5, - hash_bytes_160_5, - hash_string_160_5, - hash_bytes_to_buffer_160_5, - hash_string_to_buffer_160_5, -} - -// hash_string_192_3 will hash the given input and return the -// computed hash -hash_string_192_3 :: proc(data: string) -> [DIGEST_SIZE_192]byte { - return hash_bytes_192_3(transmute([]byte)(data)) -} - -// hash_bytes_192_3 will hash the given input and return the -// computed hash -hash_bytes_192_3 :: proc(data: []byte) -> [DIGEST_SIZE_192]byte { - hash: [DIGEST_SIZE_192]byte - ctx: Haval_Context - ctx.hashbitlen = 192 - ctx.rounds = 3 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_192_3 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_192_3 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_192_3(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_192_3 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_192_3 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_192, "Size of destination buffer is smaller than the digest size") - ctx: Haval_Context - ctx.hashbitlen = 192 - ctx.rounds = 3 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_192_3 will read the stream in chunks and compute a -// hash from its contents -hash_stream_192_3 :: proc(s: io.Stream) -> ([DIGEST_SIZE_192]byte, bool) { - hash: [DIGEST_SIZE_192]byte - ctx: Haval_Context - ctx.hashbitlen = 192 - ctx.rounds = 3 - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - ctx.str_len = u32(len(buf[:read])) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_192_3 will read the file provided by the given handle -// and compute a hash -hash_file_192_3 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_192]byte, bool) { - if !load_at_once { - return hash_stream_192_3(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_192_3(buf[:]), ok - } - } - return [DIGEST_SIZE_192]byte{}, false -} - -hash_192_3 :: proc { - hash_stream_192_3, - hash_file_192_3, - hash_bytes_192_3, - hash_string_192_3, - hash_bytes_to_buffer_192_3, - hash_string_to_buffer_192_3, -} - -// hash_string_192_4 will hash the given input and return the -// computed hash -hash_string_192_4 :: proc(data: string) -> [DIGEST_SIZE_192]byte { - return hash_bytes_192_4(transmute([]byte)(data)) -} - -// hash_bytes_192_4 will hash the given input and return the -// computed hash -hash_bytes_192_4 :: proc(data: []byte) -> [DIGEST_SIZE_192]byte { - hash: [DIGEST_SIZE_192]byte - ctx: Haval_Context - ctx.hashbitlen = 192 - ctx.rounds = 4 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_192_4 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_192_4 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_192_4(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_192_4 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_192_4 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_192, "Size of destination buffer is smaller than the digest size") - ctx: Haval_Context - ctx.hashbitlen = 192 - ctx.rounds = 4 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_192_4 will read the stream in chunks and compute a -// hash from its contents -hash_stream_192_4 :: proc(s: io.Stream) -> ([DIGEST_SIZE_192]byte, bool) { - hash: [DIGEST_SIZE_192]byte - ctx: Haval_Context - ctx.hashbitlen = 192 - ctx.rounds = 4 - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - ctx.str_len = u32(len(buf[:read])) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_192_4 will read the file provided by the given handle -// and compute a hash -hash_file_192_4 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_192]byte, bool) { - if !load_at_once { - return hash_stream_192_4(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_192_4(buf[:]), ok - } - } - return [DIGEST_SIZE_192]byte{}, false -} - -hash_192_4 :: proc { - hash_stream_192_4, - hash_file_192_4, - hash_bytes_192_4, - hash_string_192_4, - hash_bytes_to_buffer_192_4, - hash_string_to_buffer_192_4, -} - -// hash_string_192_5 will hash the given input and return the -// computed hash -hash_string_192_5 :: proc(data: string) -> [DIGEST_SIZE_192]byte { - return hash_bytes_192_5(transmute([]byte)(data)) -} - -// hash_bytes_2DIGEST_SIZE_192_5 will hash the given input and return the -// computed hash -hash_bytes_192_5 :: proc(data: []byte) -> [DIGEST_SIZE_192]byte { - hash: [DIGEST_SIZE_192]byte - ctx: Haval_Context - ctx.hashbitlen = 192 - ctx.rounds = 5 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_192_5 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_192_5 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_192_5(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_192_5 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_192_5 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_192, "Size of destination buffer is smaller than the digest size") - ctx: Haval_Context - ctx.hashbitlen = 192 - ctx.rounds = 5 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_192_5 will read the stream in chunks and compute a -// hash from its contents -hash_stream_192_5 :: proc(s: io.Stream) -> ([DIGEST_SIZE_192]byte, bool) { - hash: [DIGEST_SIZE_192]byte - ctx: Haval_Context - ctx.hashbitlen = 192 - ctx.rounds = 5 - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - ctx.str_len = u32(len(buf[:read])) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_192_5 will read the file provided by the given handle -// and compute a hash -hash_file_192_5 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_192]byte, bool) { - if !load_at_once { - return hash_stream_192_5(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_192_5(buf[:]), ok - } - } - return [DIGEST_SIZE_192]byte{}, false -} - -hash_192_5 :: proc { - hash_stream_192_5, - hash_file_192_5, - hash_bytes_192_5, - hash_string_192_5, - hash_bytes_to_buffer_192_5, - hash_string_to_buffer_192_5, -} - -// hash_string_224_3 will hash the given input and return the -// computed hash -hash_string_224_3 :: proc(data: string) -> [DIGEST_SIZE_224]byte { - return hash_bytes_224_3(transmute([]byte)(data)) -} - -// hash_bytes_224_3 will hash the given input and return the -// computed hash -hash_bytes_224_3 :: proc(data: []byte) -> [DIGEST_SIZE_224]byte { - hash: [DIGEST_SIZE_224]byte - ctx: Haval_Context - ctx.hashbitlen = 224 - ctx.rounds = 3 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_224_3 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_224_3 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_224_3(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_224_3 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_224_3 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_224, "Size of destination buffer is smaller than the digest size") - ctx: Haval_Context - ctx.hashbitlen = 224 - ctx.rounds = 3 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_224_3 will read the stream in chunks and compute a -// hash from its contents -hash_stream_224_3 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { - hash: [DIGEST_SIZE_224]byte - ctx: Haval_Context - ctx.hashbitlen = 224 - ctx.rounds = 3 - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - ctx.str_len = u32(len(buf[:read])) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_224_3 will read the file provided by the given handle -// and compute a hash -hash_file_224_3 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_224]byte, bool) { - if !load_at_once { - return hash_stream_224_3(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_224_3(buf[:]), ok - } - } - return [DIGEST_SIZE_224]byte{}, false -} - -hash_224_3 :: proc { - hash_stream_224_3, - hash_file_224_3, - hash_bytes_224_3, - hash_string_224_3, - hash_bytes_to_buffer_224_3, - hash_string_to_buffer_224_3, -} - -// hash_string_224_4 will hash the given input and return the -// computed hash -hash_string_224_4 :: proc(data: string) -> [DIGEST_SIZE_224]byte { - return hash_bytes_224_4(transmute([]byte)(data)) -} - -// hash_bytes_224_4 will hash the given input and return the -// computed hash -hash_bytes_224_4 :: proc(data: []byte) -> [DIGEST_SIZE_224]byte { - hash: [DIGEST_SIZE_224]byte - ctx: Haval_Context - ctx.hashbitlen = 224 - ctx.rounds = 4 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_224_4 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_224_4 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_224_4(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_224_4 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_224_4 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_224, "Size of destination buffer is smaller than the digest size") - ctx: Haval_Context - ctx.hashbitlen = 224 - ctx.rounds = 4 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_224_4 will read the stream in chunks and compute a -// hash from its contents -hash_stream_224_4 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { - hash: [DIGEST_SIZE_224]byte - ctx: Haval_Context - ctx.hashbitlen = 224 - ctx.rounds = 4 - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - ctx.str_len = u32(len(buf[:read])) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_224_4 will read the file provided by the given handle -// and compute a hash -hash_file_224_4 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_224]byte, bool) { - if !load_at_once { - return hash_stream_224_4(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_224_4(buf[:]), ok - } - } - return [DIGEST_SIZE_224]byte{}, false -} - -hash_224_4 :: proc { - hash_stream_224_4, - hash_file_224_4, - hash_bytes_224_4, - hash_string_224_4, - hash_bytes_to_buffer_224_4, - hash_string_to_buffer_224_4, -} - -// hash_string_224_5 will hash the given input and return the -// computed hash -hash_string_224_5 :: proc(data: string) -> [DIGEST_SIZE_224]byte { - return hash_bytes_224_5(transmute([]byte)(data)) -} - -// hash_bytes_224_5 will hash the given input and return the -// computed hash -hash_bytes_224_5 :: proc(data: []byte) -> [DIGEST_SIZE_224]byte { - hash: [DIGEST_SIZE_224]byte - ctx: Haval_Context - ctx.hashbitlen = 224 - ctx.rounds = 5 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_224_5 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_224_5 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_224_5(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_224_5 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_224_5 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_224, "Size of destination buffer is smaller than the digest size") - ctx: Haval_Context - ctx.hashbitlen = 224 - ctx.rounds = 5 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_224_5 will read the stream in chunks and compute a -// hash from its contents -hash_stream_224_5 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { - hash: [DIGEST_SIZE_224]byte - ctx: Haval_Context - ctx.hashbitlen = 224 - ctx.rounds = 5 - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - ctx.str_len = u32(len(buf[:read])) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_224_5 will read the file provided by the given handle -// and compute a hash -hash_file_224_5 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_224]byte, bool) { - if !load_at_once { - return hash_stream_224_5(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_224_5(buf[:]), ok - } - } - return [DIGEST_SIZE_224]byte{}, false -} - -hash_224_5 :: proc { - hash_stream_224_5, - hash_file_224_5, - hash_bytes_224_5, - hash_string_224_5, - hash_bytes_to_buffer_224_5, - hash_string_to_buffer_224_5, -} - -// hash_string_256_3 will hash the given input and return the -// computed hash -hash_string_256_3 :: proc(data: string) -> [DIGEST_SIZE_256]byte { - return hash_bytes_256_3(transmute([]byte)(data)) -} - -// hash_bytes_256_3 will hash the given input and return the -// computed hash -hash_bytes_256_3 :: proc(data: []byte) -> [DIGEST_SIZE_256]byte { - hash: [DIGEST_SIZE_256]byte - ctx: Haval_Context - ctx.hashbitlen = 256 - ctx.rounds = 3 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_256_3 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_256_3 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_256_3(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_256_3 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_256_3 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_256, "Size of destination buffer is smaller than the digest size") - ctx: Haval_Context - ctx.hashbitlen = 256 - ctx.rounds = 3 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_256_3 will read the stream in chunks and compute a -// hash from its contents -hash_stream_256_3 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { - hash: [DIGEST_SIZE_256]byte - ctx: Haval_Context - ctx.hashbitlen = 256 - ctx.rounds = 3 - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - ctx.str_len = u32(len(buf[:read])) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_256_3 will read the file provided by the given handle -// and compute a hash -hash_file_256_3 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_256]byte, bool) { - if !load_at_once { - return hash_stream_256_3(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_256_3(buf[:]), ok - } - } - return [DIGEST_SIZE_256]byte{}, false -} - -hash_256_3 :: proc { - hash_stream_256_3, - hash_file_256_3, - hash_bytes_256_3, - hash_string_256_3, - hash_bytes_to_buffer_256_3, - hash_string_to_buffer_256_3, -} - -// hash_string_256_4 will hash the given input and return the -// computed hash -hash_string_256_4 :: proc(data: string) -> [DIGEST_SIZE_256]byte { - return hash_bytes_256_4(transmute([]byte)(data)) -} - -// hash_bytes_256_4 will hash the given input and return the -// computed hash -hash_bytes_256_4 :: proc(data: []byte) -> [DIGEST_SIZE_256]byte { - hash: [DIGEST_SIZE_256]byte - ctx: Haval_Context - ctx.hashbitlen = 256 - ctx.rounds = 4 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_256_4 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_256_4 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_256_4(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_256_4 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_256_4 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_256, "Size of destination buffer is smaller than the digest size") - ctx: Haval_Context - ctx.hashbitlen = 256 - ctx.rounds = 4 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_256_4 will read the stream in chunks and compute a -// hash from its contents -hash_stream_256_4 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { - hash: [DIGEST_SIZE_256]byte - ctx: Haval_Context - ctx.hashbitlen = 256 - ctx.rounds = 4 - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - ctx.str_len = u32(len(buf[:read])) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_256_4 will read the file provided by the given handle -// and compute a hash -hash_file_256_4 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_256]byte, bool) { - if !load_at_once { - return hash_stream_256_4(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_256_4(buf[:]), ok - } - } - return [DIGEST_SIZE_256]byte{}, false -} - -hash_256_4 :: proc { - hash_stream_256_4, - hash_file_256_4, - hash_bytes_256_4, - hash_string_256_4, - hash_bytes_to_buffer_256_4, - hash_string_to_buffer_256_4, -} - -// hash_string_256_5 will hash the given input and return the -// computed hash -hash_string_256_5 :: proc(data: string) -> [DIGEST_SIZE_256]byte { - return hash_bytes_256_5(transmute([]byte)(data)) -} - -// hash_bytes_256_5 will hash the given input and return the -// computed hash -hash_bytes_256_5 :: proc(data: []byte) -> [DIGEST_SIZE_256]byte { - hash: [DIGEST_SIZE_256]byte - ctx: Haval_Context - ctx.hashbitlen = 256 - ctx.rounds = 5 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_256_5 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_256_5 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_256_5(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_256_5 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_256_5 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_256, "Size of destination buffer is smaller than the digest size") - ctx: Haval_Context - ctx.hashbitlen = 256 - ctx.rounds = 5 - init(&ctx) - ctx.str_len = u32(len(data)) - update(&ctx, data) - final(&ctx, hash) -} - - -// hash_stream_256_5 will read the stream in chunks and compute a -// hash from its contents -hash_stream_256_5 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { - hash: [DIGEST_SIZE_256]byte - ctx: Haval_Context - ctx.hashbitlen = 256 - ctx.rounds = 5 - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - ctx.str_len = u32(len(buf[:read])) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_256_5 will read the file provided by the given handle -// and compute a hash -hash_file_256_5 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_256]byte, bool) { - if !load_at_once { - return hash_stream_256_5(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_256_5(buf[:]), ok - } - } - return [DIGEST_SIZE_256]byte{}, false -} - -hash_256_5 :: proc { - hash_stream_256_5, - hash_file_256_5, - hash_bytes_256_5, - hash_string_256_5, - hash_bytes_to_buffer_256_5, - hash_string_to_buffer_256_5, -} - -/* - Low level API -*/ - -init :: proc(ctx: ^Haval_Context) { - assert(ctx.hashbitlen == 128 || ctx.hashbitlen == 160 || ctx.hashbitlen == 192 || ctx.hashbitlen == 224 || ctx.hashbitlen == 256, "hashbitlen must be set to 128, 160, 192, 224 or 256") - assert(ctx.rounds == 3 || ctx.rounds == 4 || ctx.rounds == 5, "rounds must be set to 3, 4 or 5") - ctx.fingerprint[0] = 0x243f6a88 - ctx.fingerprint[1] = 0x85a308d3 - ctx.fingerprint[2] = 0x13198a2e - ctx.fingerprint[3] = 0x03707344 - ctx.fingerprint[4] = 0xa4093822 - ctx.fingerprint[5] = 0x299f31d0 - ctx.fingerprint[6] = 0x082efa98 - ctx.fingerprint[7] = 0xec4e6c89 -} - -// @note(zh): Make sure to set ctx.str_len to the remaining buffer size before calling this proc - e.g. ctx.str_len = u32(len(data)) -update :: proc(ctx: ^Haval_Context, data: []byte) { - i: u32 - rmd_len := u32((ctx.count[0] >> 3) & 0x7f) - fill_len := 128 - rmd_len - str_len := ctx.str_len - - ctx.count[0] += str_len << 3 - if ctx.count[0] < (str_len << 3) { - ctx.count[1] += 1 - } - ctx.count[1] += str_len >> 29 - - when ODIN_ENDIAN == .Little { - if rmd_len + str_len >= 128 { - copy(util.slice_to_bytes(ctx.block[:])[rmd_len:], data[:fill_len]) - block(ctx, ctx.rounds) - for i = fill_len; i + 127 < str_len; i += 128 { - copy(util.slice_to_bytes(ctx.block[:]), data[i:128]) - block(ctx, ctx.rounds) - } - rmd_len = 0 - } else { - i = 0 - } - copy(util.slice_to_bytes(ctx.block[:])[rmd_len:], data[i:]) - } else { - if rmd_len + str_len >= 128 { - copy(ctx.remainder[rmd_len:], data[:fill_len]) - CH2UINT(ctx.remainder[:], ctx.block[:]) - block(ctx, ctx.rounds) - for i = fill_len; i + 127 < str_len; i += 128 { - copy(ctx.remainder[:], data[i:128]) - CH2UINT(ctx.remainder[:], ctx.block[:]) - block(ctx, ctx.rounds) - } - rmd_len = 0 - } else { - i = 0 - } - copy(ctx.remainder[rmd_len:], data[i:]) - } -} - -final :: proc(ctx: ^Haval_Context, hash: []byte) { - pad_len: u32 - tail: [10]byte - - tail[0] = byte(ctx.hashbitlen & 0x3) << 6 | byte(ctx.rounds & 0x7) << 3 | (VERSION & 0x7) - tail[1] = byte(ctx.hashbitlen >> 2) & 0xff - - UINT2CH(ctx.count[:], util.slice_to_bytes(tail[2:]), 2) - rmd_len := (ctx.count[0] >> 3) & 0x7f - if rmd_len < 118 { - pad_len = 118 - rmd_len - } else { - pad_len = 246 - rmd_len - } - - ctx.str_len = pad_len - update(ctx, PADDING[:]) - ctx.str_len = 10 - update(ctx, tail[:]) - tailor(ctx, ctx.hashbitlen) - UINT2CH(ctx.fingerprint[:], hash, ctx.hashbitlen >> 5) - - mem.set(ctx, 0, size_of(ctx)) -} - -/* - HAVAL implementation -*/ - -VERSION :: 1 - -Haval_Context :: struct { - count: [2]u32, - fingerprint: [8]u32, - block: [32]u32, - remainder: [128]byte, - rounds: u32, - hashbitlen: u32, - str_len: u32, -} - -PADDING := [128]byte { - 0x01, 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, -} - -F_1 :: #force_inline proc "contextless" (x6, x5, x4, x3, x2, x1, x0: u32) -> u32 { - return ((x1) & ((x0) ~ (x4)) ~ (x2) & (x5) ~ (x3) & (x6) ~ (x0)) -} - -F_2 :: #force_inline proc "contextless" (x6, x5, x4, x3, x2, x1, x0: u32) -> u32 { - return ((x2) & ((x1) & ~(x3) ~ (x4) & (x5) ~ (x6) ~ (x0)) ~ (x4) & ((x1) ~ (x5)) ~ (x3) & (x5) ~ (x0)) -} - -F_3 :: #force_inline proc "contextless" (x6, x5, x4, x3, x2, x1, x0: u32) -> u32 { - return ((x3) & ((x1) & (x2) ~ (x6) ~ (x0)) ~ (x1) & (x4) ~ (x2) & (x5) ~ (x0)) -} - -F_4 :: #force_inline proc "contextless" (x6, x5, x4, x3, x2, x1, x0: u32) -> u32 { - return ((x4) & ((x5) & ~(x2) ~ (x3) & ~(x6) ~ (x1) ~ (x6) ~ (x0)) ~ (x3) & ((x1) & (x2) ~ (x5) ~ (x6)) ~ (x2) & (x6) ~ (x0)) -} - -F_5 :: #force_inline proc "contextless" (x6, x5, x4, x3, x2, x1, x0: u32) -> u32 { - return ((x0) & ((x1) & (x2) & (x3) ~ ~(x5)) ~ (x1) & (x4) ~ (x2) & (x5) ~ (x3) & (x6)) -} - -FPHI_1 :: #force_inline proc(x6, x5, x4, x3, x2, x1, x0, rounds: u32) -> u32 { - switch rounds { - case 3: return F_1(x1, x0, x3, x5, x6, x2, x4) - case 4: return F_1(x2, x6, x1, x4, x5, x3, x0) - case 5: return F_1(x3, x4, x1, x0, x5, x2, x6) - case: assert(rounds < 3 || rounds > 5, "Rounds count not supported!") - } - return 0 -} - -FPHI_2 :: #force_inline proc(x6, x5, x4, x3, x2, x1, x0, rounds: u32) -> u32 { - switch rounds { - case 3: return F_2(x4, x2, x1, x0, x5, x3, x6) - case 4: return F_2(x3, x5, x2, x0, x1, x6, x4) - case 5: return F_2(x6, x2, x1, x0, x3, x4, x5) - case: assert(rounds < 3 || rounds > 5, "Rounds count not supported!") - } - return 0 -} - -FPHI_3 :: #force_inline proc(x6, x5, x4, x3, x2, x1, x0, rounds: u32) -> u32 { - switch rounds { - case 3: return F_3(x6, x1, x2, x3, x4, x5, x0) - case 4: return F_3(x1, x4, x3, x6, x0, x2, x5) - case 5: return F_3(x2, x6, x0, x4, x3, x1, x5) - case: assert(rounds < 3 || rounds > 5, "Rounds count not supported!") - } - return 0 -} - -FPHI_4 :: #force_inline proc(x6, x5, x4, x3, x2, x1, x0, rounds: u32) -> u32 { - switch rounds { - case 4: return F_4(x6, x4, x0, x5, x2, x1, x3) - case 5: return F_4(x1, x5, x3, x2, x0, x4, x6) - case: assert(rounds < 4 || rounds > 5, "Rounds count not supported!") - } - return 0 -} - -FPHI_5 :: #force_inline proc(x6, x5, x4, x3, x2, x1, x0, rounds: u32) -> u32 { - switch rounds { - case 5: return F_5(x2, x5, x0, x6, x4, x3, x1) - case: assert(rounds != 5, "Rounds count not supported!") - } - return 0 -} - -FF_1 :: #force_inline proc(x7, x6, x5, x4, x3, x2, x1, x0, w, rounds: u32) -> u32 { - tmp := FPHI_1(x6, x5, x4, x3, x2, x1, x0, rounds) - x8 := util.ROTR32(tmp, 7) + util.ROTR32(x7, 11) + w - return x8 -} - -FF_2 :: #force_inline proc(x7, x6, x5, x4, x3, x2, x1, x0, w, c, rounds: u32) -> u32 { - tmp := FPHI_2(x6, x5, x4, x3, x2, x1, x0, rounds) - x8 := util.ROTR32(tmp, 7) + util.ROTR32(x7, 11) + w + c - return x8 -} - -FF_3 :: #force_inline proc(x7, x6, x5, x4, x3, x2, x1, x0, w, c, rounds: u32) -> u32 { - tmp := FPHI_3(x6, x5, x4, x3, x2, x1, x0, rounds) - x8 := util.ROTR32(tmp, 7) + util.ROTR32(x7, 11) + w + c - return x8 -} - -FF_4 :: #force_inline proc(x7, x6, x5, x4, x3, x2, x1, x0, w, c, rounds: u32) -> u32 { - tmp := FPHI_4(x6, x5, x4, x3, x2, x1, x0, rounds) - x8 := util.ROTR32(tmp, 7) + util.ROTR32(x7, 11) + w + c - return x8 -} - -FF_5 :: #force_inline proc(x7, x6, x5, x4, x3, x2, x1, x0, w, c, rounds: u32) -> u32 { - tmp := FPHI_5(x6, x5, x4, x3, x2, x1, x0, rounds) - x8 := util.ROTR32(tmp, 7) + util.ROTR32(x7, 11) + w + c - return x8 -} - -CH2UINT :: #force_inline proc "contextless" (str: []byte, word: []u32) { - for _, i in word[:32] { - word[i] = u32(str[i*4+0]) << 0 | u32(str[i*4+1]) << 8 | u32(str[i*4+2]) << 16 | u32(str[i*4+3]) << 24 - } -} - -UINT2CH :: #force_inline proc "contextless" (word: []u32, str: []byte, wlen: u32) { - for _, i in word[:wlen] { - str[i*4+0] = byte(word[i] >> 0) & 0xff - str[i*4+1] = byte(word[i] >> 8) & 0xff - str[i*4+2] = byte(word[i] >> 16) & 0xff - str[i*4+3] = byte(word[i] >> 24) & 0xff - } -} - -block :: proc(ctx: ^Haval_Context, rounds: u32) { - t0, t1, t2, t3 := ctx.fingerprint[0], ctx.fingerprint[1], ctx.fingerprint[2], ctx.fingerprint[3] - t4, t5, t6, t7 := ctx.fingerprint[4], ctx.fingerprint[5], ctx.fingerprint[6], ctx.fingerprint[7] - w := ctx.block - - t7 = FF_1(t7, t6, t5, t4, t3, t2, t1, t0, w[ 0], rounds) - t6 = FF_1(t6, t5, t4, t3, t2, t1, t0, t7, w[ 1], rounds) - t5 = FF_1(t5, t4, t3, t2, t1, t0, t7, t6, w[ 2], rounds) - t4 = FF_1(t4, t3, t2, t1, t0, t7, t6, t5, w[ 3], rounds) - t3 = FF_1(t3, t2, t1, t0, t7, t6, t5, t4, w[ 4], rounds) - t2 = FF_1(t2, t1, t0, t7, t6, t5, t4, t3, w[ 5], rounds) - t1 = FF_1(t1, t0, t7, t6, t5, t4, t3, t2, w[ 6], rounds) - t0 = FF_1(t0, t7, t6, t5, t4, t3, t2, t1, w[ 7], rounds) - - t7 = FF_1(t7, t6, t5, t4, t3, t2, t1, t0, w[ 8], rounds) - t6 = FF_1(t6, t5, t4, t3, t2, t1, t0, t7, w[ 9], rounds) - t5 = FF_1(t5, t4, t3, t2, t1, t0, t7, t6, w[10], rounds) - t4 = FF_1(t4, t3, t2, t1, t0, t7, t6, t5, w[11], rounds) - t3 = FF_1(t3, t2, t1, t0, t7, t6, t5, t4, w[12], rounds) - t2 = FF_1(t2, t1, t0, t7, t6, t5, t4, t3, w[13], rounds) - t1 = FF_1(t1, t0, t7, t6, t5, t4, t3, t2, w[14], rounds) - t0 = FF_1(t0, t7, t6, t5, t4, t3, t2, t1, w[15], rounds) - - t7 = FF_1(t7, t6, t5, t4, t3, t2, t1, t0, w[16], rounds) - t6 = FF_1(t6, t5, t4, t3, t2, t1, t0, t7, w[17], rounds) - t5 = FF_1(t5, t4, t3, t2, t1, t0, t7, t6, w[18], rounds) - t4 = FF_1(t4, t3, t2, t1, t0, t7, t6, t5, w[19], rounds) - t3 = FF_1(t3, t2, t1, t0, t7, t6, t5, t4, w[20], rounds) - t2 = FF_1(t2, t1, t0, t7, t6, t5, t4, t3, w[21], rounds) - t1 = FF_1(t1, t0, t7, t6, t5, t4, t3, t2, w[22], rounds) - t0 = FF_1(t0, t7, t6, t5, t4, t3, t2, t1, w[23], rounds) - - t7 = FF_1(t7, t6, t5, t4, t3, t2, t1, t0, w[24], rounds) - t6 = FF_1(t6, t5, t4, t3, t2, t1, t0, t7, w[25], rounds) - t5 = FF_1(t5, t4, t3, t2, t1, t0, t7, t6, w[26], rounds) - t4 = FF_1(t4, t3, t2, t1, t0, t7, t6, t5, w[27], rounds) - t3 = FF_1(t3, t2, t1, t0, t7, t6, t5, t4, w[28], rounds) - t2 = FF_1(t2, t1, t0, t7, t6, t5, t4, t3, w[29], rounds) - t1 = FF_1(t1, t0, t7, t6, t5, t4, t3, t2, w[30], rounds) - t0 = FF_1(t0, t7, t6, t5, t4, t3, t2, t1, w[31], rounds) - - t7 = FF_2(t7, t6, t5, t4, t3, t2, t1, t0, w[ 5], 0x452821e6, rounds) - t6 = FF_2(t6, t5, t4, t3, t2, t1, t0, t7, w[14], 0x38d01377, rounds) - t5 = FF_2(t5, t4, t3, t2, t1, t0, t7, t6, w[26], 0xbe5466cf, rounds) - t4 = FF_2(t4, t3, t2, t1, t0, t7, t6, t5, w[18], 0x34e90c6c, rounds) - t3 = FF_2(t3, t2, t1, t0, t7, t6, t5, t4, w[11], 0xc0ac29b7, rounds) - t2 = FF_2(t2, t1, t0, t7, t6, t5, t4, t3, w[28], 0xc97c50dd, rounds) - t1 = FF_2(t1, t0, t7, t6, t5, t4, t3, t2, w[ 7], 0x3f84d5b5, rounds) - t0 = FF_2(t0, t7, t6, t5, t4, t3, t2, t1, w[16], 0xb5470917, rounds) - - t7 = FF_2(t7, t6, t5, t4, t3, t2, t1, t0, w[ 0], 0x9216d5d9, rounds) - t6 = FF_2(t6, t5, t4, t3, t2, t1, t0, t7, w[23], 0x8979fb1b, rounds) - t5 = FF_2(t5, t4, t3, t2, t1, t0, t7, t6, w[20], 0xd1310ba6, rounds) - t4 = FF_2(t4, t3, t2, t1, t0, t7, t6, t5, w[22], 0x98dfb5ac, rounds) - t3 = FF_2(t3, t2, t1, t0, t7, t6, t5, t4, w[ 1], 0x2ffd72db, rounds) - t2 = FF_2(t2, t1, t0, t7, t6, t5, t4, t3, w[10], 0xd01adfb7, rounds) - t1 = FF_2(t1, t0, t7, t6, t5, t4, t3, t2, w[ 4], 0xb8e1afed, rounds) - t0 = FF_2(t0, t7, t6, t5, t4, t3, t2, t1, w[ 8], 0x6a267e96, rounds) - - t7 = FF_2(t7, t6, t5, t4, t3, t2, t1, t0, w[30], 0xba7c9045, rounds) - t6 = FF_2(t6, t5, t4, t3, t2, t1, t0, t7, w[ 3], 0xf12c7f99, rounds) - t5 = FF_2(t5, t4, t3, t2, t1, t0, t7, t6, w[21], 0x24a19947, rounds) - t4 = FF_2(t4, t3, t2, t1, t0, t7, t6, t5, w[ 9], 0xb3916cf7, rounds) - t3 = FF_2(t3, t2, t1, t0, t7, t6, t5, t4, w[17], 0x0801f2e2, rounds) - t2 = FF_2(t2, t1, t0, t7, t6, t5, t4, t3, w[24], 0x858efc16, rounds) - t1 = FF_2(t1, t0, t7, t6, t5, t4, t3, t2, w[29], 0x636920d8, rounds) - t0 = FF_2(t0, t7, t6, t5, t4, t3, t2, t1, w[ 6], 0x71574e69, rounds) - - t7 = FF_2(t7, t6, t5, t4, t3, t2, t1, t0, w[19], 0xa458fea3, rounds) - t6 = FF_2(t6, t5, t4, t3, t2, t1, t0, t7, w[12], 0xf4933d7e, rounds) - t5 = FF_2(t5, t4, t3, t2, t1, t0, t7, t6, w[15], 0x0d95748f, rounds) - t4 = FF_2(t4, t3, t2, t1, t0, t7, t6, t5, w[13], 0x728eb658, rounds) - t3 = FF_2(t3, t2, t1, t0, t7, t6, t5, t4, w[ 2], 0x718bcd58, rounds) - t2 = FF_2(t2, t1, t0, t7, t6, t5, t4, t3, w[25], 0x82154aee, rounds) - t1 = FF_2(t1, t0, t7, t6, t5, t4, t3, t2, w[31], 0x7b54a41d, rounds) - t0 = FF_2(t0, t7, t6, t5, t4, t3, t2, t1, w[27], 0xc25a59b5, rounds) - - t7 = FF_3(t7, t6, t5, t4, t3, t2, t1, t0, w[19], 0x9c30d539, rounds) - t6 = FF_3(t6, t5, t4, t3, t2, t1, t0, t7, w[ 9], 0x2af26013, rounds) - t5 = FF_3(t5, t4, t3, t2, t1, t0, t7, t6, w[ 4], 0xc5d1b023, rounds) - t4 = FF_3(t4, t3, t2, t1, t0, t7, t6, t5, w[20], 0x286085f0, rounds) - t3 = FF_3(t3, t2, t1, t0, t7, t6, t5, t4, w[28], 0xca417918, rounds) - t2 = FF_3(t2, t1, t0, t7, t6, t5, t4, t3, w[17], 0xb8db38ef, rounds) - t1 = FF_3(t1, t0, t7, t6, t5, t4, t3, t2, w[ 8], 0x8e79dcb0, rounds) - t0 = FF_3(t0, t7, t6, t5, t4, t3, t2, t1, w[22], 0x603a180e, rounds) - - t7 = FF_3(t7, t6, t5, t4, t3, t2, t1, t0, w[29], 0x6c9e0e8b, rounds) - t6 = FF_3(t6, t5, t4, t3, t2, t1, t0, t7, w[14], 0xb01e8a3e, rounds) - t5 = FF_3(t5, t4, t3, t2, t1, t0, t7, t6, w[25], 0xd71577c1, rounds) - t4 = FF_3(t4, t3, t2, t1, t0, t7, t6, t5, w[12], 0xbd314b27, rounds) - t3 = FF_3(t3, t2, t1, t0, t7, t6, t5, t4, w[24], 0x78af2fda, rounds) - t2 = FF_3(t2, t1, t0, t7, t6, t5, t4, t3, w[30], 0x55605c60, rounds) - t1 = FF_3(t1, t0, t7, t6, t5, t4, t3, t2, w[16], 0xe65525f3, rounds) - t0 = FF_3(t0, t7, t6, t5, t4, t3, t2, t1, w[26], 0xaa55ab94, rounds) - - t7 = FF_3(t7, t6, t5, t4, t3, t2, t1, t0, w[31], 0x57489862, rounds) - t6 = FF_3(t6, t5, t4, t3, t2, t1, t0, t7, w[15], 0x63e81440, rounds) - t5 = FF_3(t5, t4, t3, t2, t1, t0, t7, t6, w[ 7], 0x55ca396a, rounds) - t4 = FF_3(t4, t3, t2, t1, t0, t7, t6, t5, w[ 3], 0x2aab10b6, rounds) - t3 = FF_3(t3, t2, t1, t0, t7, t6, t5, t4, w[ 1], 0xb4cc5c34, rounds) - t2 = FF_3(t2, t1, t0, t7, t6, t5, t4, t3, w[ 0], 0x1141e8ce, rounds) - t1 = FF_3(t1, t0, t7, t6, t5, t4, t3, t2, w[18], 0xa15486af, rounds) - t0 = FF_3(t0, t7, t6, t5, t4, t3, t2, t1, w[27], 0x7c72e993, rounds) - - t7 = FF_3(t7, t6, t5, t4, t3, t2, t1, t0, w[13], 0xb3ee1411, rounds) - t6 = FF_3(t6, t5, t4, t3, t2, t1, t0, t7, w[ 6], 0x636fbc2a, rounds) - t5 = FF_3(t5, t4, t3, t2, t1, t0, t7, t6, w[21], 0x2ba9c55d, rounds) - t4 = FF_3(t4, t3, t2, t1, t0, t7, t6, t5, w[10], 0x741831f6, rounds) - t3 = FF_3(t3, t2, t1, t0, t7, t6, t5, t4, w[23], 0xce5c3e16, rounds) - t2 = FF_3(t2, t1, t0, t7, t6, t5, t4, t3, w[11], 0x9b87931e, rounds) - t1 = FF_3(t1, t0, t7, t6, t5, t4, t3, t2, w[ 5], 0xafd6ba33, rounds) - t0 = FF_3(t0, t7, t6, t5, t4, t3, t2, t1, w[ 2], 0x6c24cf5c, rounds) - - if rounds >= 4 { - t7 = FF_4(t7, t6, t5, t4, t3, t2, t1, t0, w[24], 0x7a325381, rounds) - t6 = FF_4(t6, t5, t4, t3, t2, t1, t0, t7, w[ 4], 0x28958677, rounds) - t5 = FF_4(t5, t4, t3, t2, t1, t0, t7, t6, w[ 0], 0x3b8f4898, rounds) - t4 = FF_4(t4, t3, t2, t1, t0, t7, t6, t5, w[14], 0x6b4bb9af, rounds) - t3 = FF_4(t3, t2, t1, t0, t7, t6, t5, t4, w[ 2], 0xc4bfe81b, rounds) - t2 = FF_4(t2, t1, t0, t7, t6, t5, t4, t3, w[ 7], 0x66282193, rounds) - t1 = FF_4(t1, t0, t7, t6, t5, t4, t3, t2, w[28], 0x61d809cc, rounds) - t0 = FF_4(t0, t7, t6, t5, t4, t3, t2, t1, w[23], 0xfb21a991, rounds) - - t7 = FF_4(t7, t6, t5, t4, t3, t2, t1, t0, w[26], 0x487cac60, rounds) - t6 = FF_4(t6, t5, t4, t3, t2, t1, t0, t7, w[ 6], 0x5dec8032, rounds) - t5 = FF_4(t5, t4, t3, t2, t1, t0, t7, t6, w[30], 0xef845d5d, rounds) - t4 = FF_4(t4, t3, t2, t1, t0, t7, t6, t5, w[20], 0xe98575b1, rounds) - t3 = FF_4(t3, t2, t1, t0, t7, t6, t5, t4, w[18], 0xdc262302, rounds) - t2 = FF_4(t2, t1, t0, t7, t6, t5, t4, t3, w[25], 0xeb651b88, rounds) - t1 = FF_4(t1, t0, t7, t6, t5, t4, t3, t2, w[19], 0x23893e81, rounds) - t0 = FF_4(t0, t7, t6, t5, t4, t3, t2, t1, w[ 3], 0xd396acc5, rounds) - - t7 = FF_4(t7, t6, t5, t4, t3, t2, t1, t0, w[22], 0x0f6d6ff3, rounds) - t6 = FF_4(t6, t5, t4, t3, t2, t1, t0, t7, w[11], 0x83f44239, rounds) - t5 = FF_4(t5, t4, t3, t2, t1, t0, t7, t6, w[31], 0x2e0b4482, rounds) - t4 = FF_4(t4, t3, t2, t1, t0, t7, t6, t5, w[21], 0xa4842004, rounds) - t3 = FF_4(t3, t2, t1, t0, t7, t6, t5, t4, w[ 8], 0x69c8f04a, rounds) - t2 = FF_4(t2, t1, t0, t7, t6, t5, t4, t3, w[27], 0x9e1f9b5e, rounds) - t1 = FF_4(t1, t0, t7, t6, t5, t4, t3, t2, w[12], 0x21c66842, rounds) - t0 = FF_4(t0, t7, t6, t5, t4, t3, t2, t1, w[ 9], 0xf6e96c9a, rounds) - - t7 = FF_4(t7, t6, t5, t4, t3, t2, t1, t0, w[ 1], 0x670c9c61, rounds) - t6 = FF_4(t6, t5, t4, t3, t2, t1, t0, t7, w[29], 0xabd388f0, rounds) - t5 = FF_4(t5, t4, t3, t2, t1, t0, t7, t6, w[ 5], 0x6a51a0d2, rounds) - t4 = FF_4(t4, t3, t2, t1, t0, t7, t6, t5, w[15], 0xd8542f68, rounds) - t3 = FF_4(t3, t2, t1, t0, t7, t6, t5, t4, w[17], 0x960fa728, rounds) - t2 = FF_4(t2, t1, t0, t7, t6, t5, t4, t3, w[10], 0xab5133a3, rounds) - t1 = FF_4(t1, t0, t7, t6, t5, t4, t3, t2, w[16], 0x6eef0b6c, rounds) - t0 = FF_4(t0, t7, t6, t5, t4, t3, t2, t1, w[13], 0x137a3be4, rounds) - } - - if rounds == 5 { - t7 = FF_5(t7, t6, t5, t4, t3, t2, t1, t0, w[27], 0xba3bf050, rounds) - t6 = FF_5(t6, t5, t4, t3, t2, t1, t0, t7, w[ 3], 0x7efb2a98, rounds) - t5 = FF_5(t5, t4, t3, t2, t1, t0, t7, t6, w[21], 0xa1f1651d, rounds) - t4 = FF_5(t4, t3, t2, t1, t0, t7, t6, t5, w[26], 0x39af0176, rounds) - t3 = FF_5(t3, t2, t1, t0, t7, t6, t5, t4, w[17], 0x66ca593e, rounds) - t2 = FF_5(t2, t1, t0, t7, t6, t5, t4, t3, w[11], 0x82430e88, rounds) - t1 = FF_5(t1, t0, t7, t6, t5, t4, t3, t2, w[20], 0x8cee8619, rounds) - t0 = FF_5(t0, t7, t6, t5, t4, t3, t2, t1, w[29], 0x456f9fb4, rounds) - - t7 = FF_5(t7, t6, t5, t4, t3, t2, t1, t0, w[19], 0x7d84a5c3, rounds) - t6 = FF_5(t6, t5, t4, t3, t2, t1, t0, t7, w[ 0], 0x3b8b5ebe, rounds) - t5 = FF_5(t5, t4, t3, t2, t1, t0, t7, t6, w[12], 0xe06f75d8, rounds) - t4 = FF_5(t4, t3, t2, t1, t0, t7, t6, t5, w[ 7], 0x85c12073, rounds) - t3 = FF_5(t3, t2, t1, t0, t7, t6, t5, t4, w[13], 0x401a449f, rounds) - t2 = FF_5(t2, t1, t0, t7, t6, t5, t4, t3, w[ 8], 0x56c16aa6, rounds) - t1 = FF_5(t1, t0, t7, t6, t5, t4, t3, t2, w[31], 0x4ed3aa62, rounds) - t0 = FF_5(t0, t7, t6, t5, t4, t3, t2, t1, w[10], 0x363f7706, rounds) - - t7 = FF_5(t7, t6, t5, t4, t3, t2, t1, t0, w[ 5], 0x1bfedf72, rounds) - t6 = FF_5(t6, t5, t4, t3, t2, t1, t0, t7, w[ 9], 0x429b023d, rounds) - t5 = FF_5(t5, t4, t3, t2, t1, t0, t7, t6, w[14], 0x37d0d724, rounds) - t4 = FF_5(t4, t3, t2, t1, t0, t7, t6, t5, w[30], 0xd00a1248, rounds) - t3 = FF_5(t3, t2, t1, t0, t7, t6, t5, t4, w[18], 0xdb0fead3, rounds) - t2 = FF_5(t2, t1, t0, t7, t6, t5, t4, t3, w[ 6], 0x49f1c09b, rounds) - t1 = FF_5(t1, t0, t7, t6, t5, t4, t3, t2, w[28], 0x075372c9, rounds) - t0 = FF_5(t0, t7, t6, t5, t4, t3, t2, t1, w[24], 0x80991b7b, rounds) - - t7 = FF_5(t7, t6, t5, t4, t3, t2, t1, t0, w[ 2], 0x25d479d8, rounds) - t6 = FF_5(t6, t5, t4, t3, t2, t1, t0, t7, w[23], 0xf6e8def7, rounds) - t5 = FF_5(t5, t4, t3, t2, t1, t0, t7, t6, w[16], 0xe3fe501a, rounds) - t4 = FF_5(t4, t3, t2, t1, t0, t7, t6, t5, w[22], 0xb6794c3b, rounds) - t3 = FF_5(t3, t2, t1, t0, t7, t6, t5, t4, w[ 4], 0x976ce0bd, rounds) - t2 = FF_5(t2, t1, t0, t7, t6, t5, t4, t3, w[ 1], 0x04c006ba, rounds) - t1 = FF_5(t1, t0, t7, t6, t5, t4, t3, t2, w[25], 0xc1a94fb6, rounds) - t0 = FF_5(t0, t7, t6, t5, t4, t3, t2, t1, w[15], 0x409f60c4, rounds) - } - - ctx.fingerprint[0] += t0 - ctx.fingerprint[1] += t1 - ctx.fingerprint[2] += t2 - ctx.fingerprint[3] += t3 - ctx.fingerprint[4] += t4 - ctx.fingerprint[5] += t5 - ctx.fingerprint[6] += t6 - ctx.fingerprint[7] += t7 -} - -tailor :: proc(ctx: ^Haval_Context, size: u32) { - temp: u32 - switch size { - case 128: - temp = (ctx.fingerprint[7] & 0x000000ff) | - (ctx.fingerprint[6] & 0xff000000) | - (ctx.fingerprint[5] & 0x00ff0000) | - (ctx.fingerprint[4] & 0x0000ff00) - ctx.fingerprint[0] += util.ROTR32(temp, 8) - - temp = (ctx.fingerprint[7] & 0x0000ff00) | - (ctx.fingerprint[6] & 0x000000ff) | - (ctx.fingerprint[5] & 0xff000000) | - (ctx.fingerprint[4] & 0x00ff0000) - ctx.fingerprint[1] += util.ROTR32(temp, 16) - - temp = (ctx.fingerprint[7] & 0x00ff0000) | - (ctx.fingerprint[6] & 0x0000ff00) | - (ctx.fingerprint[5] & 0x000000ff) | - (ctx.fingerprint[4] & 0xff000000) - ctx.fingerprint[2] += util.ROTR32(temp, 24) - - temp = (ctx.fingerprint[7] & 0xff000000) | - (ctx.fingerprint[6] & 0x00ff0000) | - (ctx.fingerprint[5] & 0x0000ff00) | - (ctx.fingerprint[4] & 0x000000ff) - ctx.fingerprint[3] += temp - case 160: - temp = (ctx.fingerprint[7] & u32(0x3f)) | - (ctx.fingerprint[6] & u32(0x7f << 25)) | - (ctx.fingerprint[5] & u32(0x3f << 19)) - ctx.fingerprint[0] += util.ROTR32(temp, 19) - - temp = (ctx.fingerprint[7] & u32(0x3f << 6)) | - (ctx.fingerprint[6] & u32(0x3f)) | - (ctx.fingerprint[5] & u32(0x7f << 25)) - ctx.fingerprint[1] += util.ROTR32(temp, 25) - - temp = (ctx.fingerprint[7] & u32(0x7f << 12)) | - (ctx.fingerprint[6] & u32(0x3f << 6)) | - (ctx.fingerprint[5] & u32(0x3f)) - ctx.fingerprint[2] += temp - - temp = (ctx.fingerprint[7] & u32(0x3f << 19)) | - (ctx.fingerprint[6] & u32(0x7f << 12)) | - (ctx.fingerprint[5] & u32(0x3f << 6)) - ctx.fingerprint[3] += temp >> 6 - - temp = (ctx.fingerprint[7] & u32(0x7f << 25)) | - (ctx.fingerprint[6] & u32(0x3f << 19)) | - (ctx.fingerprint[5] & u32(0x7f << 12)) - ctx.fingerprint[4] += temp >> 12 - case 192: - temp = (ctx.fingerprint[7] & u32(0x1f)) | - (ctx.fingerprint[6] & u32(0x3f << 26)) - ctx.fingerprint[0] += util.ROTR32(temp, 26) - - temp = (ctx.fingerprint[7] & u32(0x1f << 5)) | - (ctx.fingerprint[6] & u32(0x1f)) - ctx.fingerprint[1] += temp - - temp = (ctx.fingerprint[7] & u32(0x3f << 10)) | - (ctx.fingerprint[6] & u32(0x1f << 5)) - ctx.fingerprint[2] += temp >> 5 - - temp = (ctx.fingerprint[7] & u32(0x1f << 16)) | - (ctx.fingerprint[6] & u32(0x3f << 10)) - ctx.fingerprint[3] += temp >> 10 - - temp = (ctx.fingerprint[7] & u32(0x1f << 21)) | - (ctx.fingerprint[6] & u32(0x1f << 16)) - ctx.fingerprint[4] += temp >> 16 - - temp = (ctx.fingerprint[7] & u32(0x3f << 26)) | - (ctx.fingerprint[6] & u32(0x1f << 21)) - ctx.fingerprint[5] += temp >> 21 - case 224: - ctx.fingerprint[0] += (ctx.fingerprint[7] >> 27) & 0x1f - ctx.fingerprint[1] += (ctx.fingerprint[7] >> 22) & 0x1f - ctx.fingerprint[2] += (ctx.fingerprint[7] >> 18) & 0x0f - ctx.fingerprint[3] += (ctx.fingerprint[7] >> 13) & 0x1f - ctx.fingerprint[4] += (ctx.fingerprint[7] >> 9) & 0x0f - ctx.fingerprint[5] += (ctx.fingerprint[7] >> 4) & 0x1f - ctx.fingerprint[6] += ctx.fingerprint[7] & 0x0f - } -} diff --git a/core/crypto/hkdf/hkdf.odin b/core/crypto/hkdf/hkdf.odin new file mode 100644 index 000000000..bffe09eff --- /dev/null +++ b/core/crypto/hkdf/hkdf.odin @@ -0,0 +1,103 @@ +/* +package hkdf implements the HKDF HMAC-based Extract-and-Expand Key +Derivation Function. + +See: [[ https://www.rfc-editor.org/rfc/rfc5869 ]] +*/ +package hkdf + +import "core:crypto/hash" +import "core:crypto/hmac" +import "core:mem" + +// extract_and_expand derives output keying material (OKM) via the +// HKDF-Extract and HKDF-Expand algorithms, with the specified has +// function, salt, input keying material (IKM), and optional info. +// The dst buffer must be less-than-or-equal to 255 HMAC tags. +extract_and_expand :: proc(algorithm: hash.Algorithm, salt, ikm, info, dst: []byte) { + h_len := hash.DIGEST_SIZES[algorithm] + + tmp: [hash.MAX_DIGEST_SIZE]byte + prk := tmp[:h_len] + defer mem.zero_explicit(raw_data(prk), h_len) + + extract(algorithm, salt, ikm, prk) + expand(algorithm, prk, info, dst) +} + +// extract derives a pseudorandom key (PRK) via the HKDF-Extract algorithm, +// with the specified hash function, salt, and input keying material (IKM). +// It requires that the dst buffer be the HMAC tag size for the specified +// hash function. +extract :: proc(algorithm: hash.Algorithm, salt, ikm, dst: []byte) { + // PRK = HMAC-Hash(salt, IKM) + hmac.sum(algorithm, dst, ikm, salt) +} + +// expand derives output keying material (OKM) via the HKDF-Expand algorithm, +// with the specified hash function, pseudorandom key (PRK), and optional +// info. The dst buffer must be less-than-or-equal to 255 HMAC tags. +expand :: proc(algorithm: hash.Algorithm, prk, info, dst: []byte) { + h_len := hash.DIGEST_SIZES[algorithm] + + // (<= 255*HashLen) + dk_len := len(dst) + switch { + case dk_len == 0: + return + case dk_len > h_len * 255: + panic("crypto/hkdf: derived key too long") + case: + } + + // The output OKM is calculated as follows: + // + // N = ceil(L/HashLen) + // T = T(1) | T(2) | T(3) | ... | T(N) + // OKM = first L octets of T + // + // where: + // T(0) = empty string (zero length) + // T(1) = HMAC-Hash(PRK, T(0) | info | 0x01) + // T(2) = HMAC-Hash(PRK, T(1) | info | 0x02) + // T(3) = HMAC-Hash(PRK, T(2) | info | 0x03) + // ... + + n := dk_len / h_len + r := dk_len % h_len + + base: hmac.Context + defer hmac.reset(&base) + + hmac.init(&base, algorithm, prk) + + dst_blk := dst + prev: []byte + + for i in 1 ..= n { + _F(&base, prev, info, i, dst_blk[:h_len]) + + prev = dst_blk[:h_len] + dst_blk = dst_blk[h_len:] + } + + if r > 0 { + tmp: [hash.MAX_DIGEST_SIZE]byte + blk := tmp[:h_len] + defer mem.zero_explicit(raw_data(blk), h_len) + + _F(&base, prev, info, n + 1, blk) + copy(dst_blk, blk) + } +} + +@(private) +_F :: proc(base: ^hmac.Context, prev, info: []byte, i: int, dst_blk: []byte) { + prf: hmac.Context + + hmac.clone(&prf, base) + hmac.update(&prf, prev) + hmac.update(&prf, info) + hmac.update(&prf, []byte{u8(i)}) + hmac.final(&prf, dst_blk) +} diff --git a/core/crypto/hmac/hmac.odin b/core/crypto/hmac/hmac.odin new file mode 100644 index 000000000..4813a9938 --- /dev/null +++ b/core/crypto/hmac/hmac.odin @@ -0,0 +1,174 @@ +/* +package hmac implements the HMAC MAC algorithm. + +See: +- [[ https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.198-1.pdf ]] +*/ +package hmac + +import "core:crypto" +import "core:crypto/hash" +import "core:mem" + +// sum will compute the HMAC with the specified algorithm and key +// over msg, and write the computed tag to dst. It requires that +// the dst buffer is the tag size. +sum :: proc(algorithm: hash.Algorithm, dst, msg, key: []byte) { + ctx: Context + + init(&ctx, algorithm, key) + update(&ctx, msg) + final(&ctx, dst) +} + +// verify will verify the HMAC tag computed with the specified algorithm +// and key over msg and return true iff the tag is valid. It requires +// that the tag is correctly sized. +verify :: proc(algorithm: hash.Algorithm, tag, msg, key: []byte) -> bool { + tag_buf: [hash.MAX_DIGEST_SIZE]byte + + derived_tag := tag_buf[:hash.DIGEST_SIZES[algorithm]] + sum(algorithm, derived_tag, msg, key) + + return crypto.compare_constant_time(derived_tag, tag) == 1 +} + +// Context is a concrete instantiation of HMAC with a specific hash +// algorithm. +Context :: struct { + _o_hash: hash.Context, // H(k ^ ipad) (not finalized) + _i_hash: hash.Context, // H(k ^ opad) (not finalized) + _tag_sz: int, + _is_initialized: bool, +} + +// init initializes a Context with a specific hash Algorithm and key. +init :: proc(ctx: ^Context, algorithm: hash.Algorithm, key: []byte) { + if ctx._is_initialized { + reset(ctx) + } + + _init_hashes(ctx, algorithm, key) + + ctx._tag_sz = hash.DIGEST_SIZES[algorithm] + ctx._is_initialized = true +} + +// update adds more data to the Context. +update :: proc(ctx: ^Context, data: []byte) { + assert(ctx._is_initialized) + + hash.update(&ctx._i_hash, data) +} + +// final finalizes the Context, writes the tag to dst, and calls +// reset on the Context. +final :: proc(ctx: ^Context, dst: []byte) { + assert(ctx._is_initialized) + + defer (reset(ctx)) + + if len(dst) != ctx._tag_sz { + panic("crypto/hmac: invalid destination tag size") + } + + hash.final(&ctx._i_hash, dst) // H((k ^ ipad) || text) + + hash.update(&ctx._o_hash, dst) // H((k ^ opad) || H((k ^ ipad) || text)) + hash.final(&ctx._o_hash, dst) +} + +// clone clones the Context other into ctx. +clone :: proc(ctx, other: ^Context) { + if ctx == other { + return + } + + hash.clone(&ctx._o_hash, &other._o_hash) + hash.clone(&ctx._i_hash, &other._i_hash) + ctx._tag_sz = other._tag_sz + ctx._is_initialized = other._is_initialized +} + +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. +reset :: proc(ctx: ^Context) { + if !ctx._is_initialized { + return + } + + hash.reset(&ctx._o_hash) + hash.reset(&ctx._i_hash) + ctx._tag_sz = 0 + ctx._is_initialized = false +} + +// algorithm returns the Algorithm used by a Context instance. +algorithm :: proc(ctx: ^Context) -> hash.Algorithm { + assert(ctx._is_initialized) + + return hash.algorithm(&ctx._i_hash) +} + +// tag_size returns the tag size of a Context instance in bytes. +tag_size :: proc(ctx: ^Context) -> int { + assert(ctx._is_initialized) + + return ctx._tag_sz +} + +@(private) +_I_PAD :: 0x36 +_O_PAD :: 0x5c + +@(private) +_init_hashes :: proc(ctx: ^Context, algorithm: hash.Algorithm, key: []byte) { + K0_buf: [hash.MAX_BLOCK_SIZE]byte + kPad_buf: [hash.MAX_BLOCK_SIZE]byte + + kLen := len(key) + B := hash.BLOCK_SIZES[algorithm] + K0 := K0_buf[:B] + defer mem.zero_explicit(raw_data(K0), B) + + switch { + case kLen == B, kLen < B: + // If the length of K = B: set K0 = K. + // + // If the length of K < B: append zeros to the end of K to + // create a B-byte string K0 (e.g., if K is 20 bytes in + // length and B = 64, then K will be appended with 44 zero + // bytes x’00’). + // + // K0 is zero-initialized, so the copy handles both cases. + copy(K0, key) + case kLen > B: + // If the length of K > B: hash K to obtain an L byte string, + // then append (B-L) zeros to create a B-byte string K0 + // (i.e., K0 = H(K) || 00...00). + tmpCtx := &ctx._o_hash // Saves allocating a hash.Context. + hash.init(tmpCtx, algorithm) + hash.update(tmpCtx, key) + hash.final(tmpCtx, K0) + } + + // Initialize the hashes, and write the padded keys: + // - ctx._i_hash -> H(K0 ^ ipad) + // - ctx._o_hash -> H(K0 ^ opad) + + hash.init(&ctx._o_hash, algorithm) + hash.init(&ctx._i_hash, algorithm) + + kPad := kPad_buf[:B] + defer mem.zero_explicit(raw_data(kPad), B) + + for v, i in K0 { + kPad[i] = v ~ _I_PAD + } + hash.update(&ctx._i_hash, kPad) + + for v, i in K0 { + kPad[i] = v ~ _O_PAD + } + hash.update(&ctx._o_hash, kPad) +} diff --git a/core/crypto/jh/jh.odin b/core/crypto/jh/jh.odin deleted file mode 100644 index 5dc6c4e6b..000000000 --- a/core/crypto/jh/jh.odin +++ /dev/null @@ -1,584 +0,0 @@ -package jh - -/* - Copyright 2021 zhibog - Made available under the BSD-3 license. - - List of contributors: - zhibog, dotbmp: Initial implementation. - - Implementation of the JH hashing algorithm, as defined in -*/ - -import "core:os" -import "core:io" - -/* - High level API -*/ - -DIGEST_SIZE_224 :: 28 -DIGEST_SIZE_256 :: 32 -DIGEST_SIZE_384 :: 48 -DIGEST_SIZE_512 :: 64 - -// hash_string_224 will hash the given input and return the -// computed hash -hash_string_224 :: proc(data: string) -> [DIGEST_SIZE_224]byte { - return hash_bytes_224(transmute([]byte)(data)) -} - -// hash_bytes_224 will hash the given input and return the -// computed hash -hash_bytes_224 :: proc(data: []byte) -> [DIGEST_SIZE_224]byte { - hash: [DIGEST_SIZE_224]byte - ctx: Jh_Context - ctx.hashbitlen = 224 - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_224 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_224 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_224(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_224 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_224 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_224, "Size of destination buffer is smaller than the digest size") - ctx: Jh_Context - ctx.hashbitlen = 224 - init(&ctx) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_224 will read the stream in chunks and compute a -// hash from its contents -hash_stream_224 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { - hash: [DIGEST_SIZE_224]byte - ctx: Jh_Context - ctx.hashbitlen = 224 - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_224 will read the file provided by the given handle -// and compute a hash -hash_file_224 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_224]byte, bool) { - if !load_at_once { - return hash_stream_224(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_224(buf[:]), ok - } - } - return [DIGEST_SIZE_224]byte{}, false -} - -hash_224 :: proc { - hash_stream_224, - hash_file_224, - hash_bytes_224, - hash_string_224, - hash_bytes_to_buffer_224, - hash_string_to_buffer_224, -} - -// hash_string_256 will hash the given input and return the -// computed hash -hash_string_256 :: proc(data: string) -> [DIGEST_SIZE_256]byte { - return hash_bytes_256(transmute([]byte)(data)) -} - -// hash_bytes_256 will hash the given input and return the -// computed hash -hash_bytes_256 :: proc(data: []byte) -> [DIGEST_SIZE_256]byte { - hash: [DIGEST_SIZE_256]byte - ctx: Jh_Context - ctx.hashbitlen = 256 - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_256 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_256 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_256(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_256 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_256 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_256, "Size of destination buffer is smaller than the digest size") - ctx: Jh_Context - ctx.hashbitlen = 256 - init(&ctx) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_256 will read the stream in chunks and compute a -// hash from its contents -hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { - hash: [DIGEST_SIZE_256]byte - ctx: Jh_Context - ctx.hashbitlen = 256 - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_256 will read the file provided by the given handle -// and compute a hash -hash_file_256 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_256]byte, bool) { - if !load_at_once { - return hash_stream_256(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_256(buf[:]), ok - } - } - return [DIGEST_SIZE_256]byte{}, false -} - -hash_256 :: proc { - hash_stream_256, - hash_file_256, - hash_bytes_256, - hash_string_256, - hash_bytes_to_buffer_256, - hash_string_to_buffer_256, -} - -// hash_string_384 will hash the given input and return the -// computed hash -hash_string_384 :: proc(data: string) -> [DIGEST_SIZE_384]byte { - return hash_bytes_384(transmute([]byte)(data)) -} - -// hash_bytes_384 will hash the given input and return the -// computed hash -hash_bytes_384 :: proc(data: []byte) -> [DIGEST_SIZE_384]byte { - hash: [DIGEST_SIZE_384]byte - ctx: Jh_Context - ctx.hashbitlen = 384 - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_384 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_384 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_384(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_384 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_384 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_384, "Size of destination buffer is smaller than the digest size") - ctx: Jh_Context - ctx.hashbitlen = 384 - init(&ctx) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_384 will read the stream in chunks and compute a -// hash from its contents -hash_stream_384 :: proc(s: io.Stream) -> ([DIGEST_SIZE_384]byte, bool) { - hash: [DIGEST_SIZE_384]byte - ctx: Jh_Context - ctx.hashbitlen = 384 - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_384 will read the file provided by the given handle -// and compute a hash -hash_file_384 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_384]byte, bool) { - if !load_at_once { - return hash_stream_384(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_384(buf[:]), ok - } - } - return [DIGEST_SIZE_384]byte{}, false -} - -hash_384 :: proc { - hash_stream_384, - hash_file_384, - hash_bytes_384, - hash_string_384, - hash_bytes_to_buffer_384, - hash_string_to_buffer_384, -} - -// hash_string_512 will hash the given input and return the -// computed hash -hash_string_512 :: proc(data: string) -> [DIGEST_SIZE_512]byte { - return hash_bytes_512(transmute([]byte)(data)) -} - -// hash_bytes_512 will hash the given input and return the -// computed hash -hash_bytes_512 :: proc(data: []byte) -> [DIGEST_SIZE_512]byte { - hash: [DIGEST_SIZE_512]byte - ctx: Jh_Context - ctx.hashbitlen = 512 - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_512 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_512 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_512(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_512 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_512 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_512, "Size of destination buffer is smaller than the digest size") - ctx: Jh_Context - ctx.hashbitlen = 512 - init(&ctx) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_512 will read the stream in chunks and compute a -// hash from its contents -hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { - hash: [DIGEST_SIZE_512]byte - ctx: Jh_Context - ctx.hashbitlen = 512 - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_512 will read the file provided by the given handle -// and compute a hash -hash_file_512 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_512]byte, bool) { - if !load_at_once { - return hash_stream_512(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_512(buf[:]), ok - } - } - return [DIGEST_SIZE_512]byte{}, false -} - -hash_512 :: proc { - hash_stream_512, - hash_file_512, - hash_bytes_512, - hash_string_512, - hash_bytes_to_buffer_512, - hash_string_to_buffer_512, -} - -/* - Low level API -*/ - -init :: proc(ctx: ^Jh_Context) { - assert(ctx.hashbitlen == 224 || ctx.hashbitlen == 256 || ctx.hashbitlen == 384 || ctx.hashbitlen == 512, "hashbitlen must be set to 224, 256, 384 or 512") - ctx.H[1] = byte(ctx.hashbitlen) & 0xff - ctx.H[0] = byte(ctx.hashbitlen >> 8) & 0xff - F8(ctx) -} - -update :: proc(ctx: ^Jh_Context, data: []byte) { - databitlen := u64(len(data)) * 8 - ctx.databitlen += databitlen - i := u64(0) - - if (ctx.buffer_size > 0) && ((ctx.buffer_size + databitlen) < 512) { - if (databitlen & 7) == 0 { - copy(ctx.buffer[ctx.buffer_size >> 3:], data[:64 - (ctx.buffer_size >> 3)]) - } else { - copy(ctx.buffer[ctx.buffer_size >> 3:], data[:64 - (ctx.buffer_size >> 3) + 1]) - } - ctx.buffer_size += databitlen - databitlen = 0 - } - - if (ctx.buffer_size > 0 ) && ((ctx.buffer_size + databitlen) >= 512) { - copy(ctx.buffer[ctx.buffer_size >> 3:], data[:64 - (ctx.buffer_size >> 3)]) - i = 64 - (ctx.buffer_size >> 3) - databitlen = databitlen - (512 - ctx.buffer_size) - F8(ctx) - ctx.buffer_size = 0 - } - - for databitlen >= 512 { - copy(ctx.buffer[:], data[i:i + 64]) - F8(ctx) - i += 64 - databitlen -= 512 - } - - if databitlen > 0 { - if (databitlen & 7) == 0 { - copy(ctx.buffer[:], data[i:i + ((databitlen & 0x1ff) >> 3)]) - } else { - copy(ctx.buffer[:], data[i:i + ((databitlen & 0x1ff) >> 3) + 1]) - } - ctx.buffer_size = databitlen - } -} - -final :: proc(ctx: ^Jh_Context, hash: []byte) { - if ctx.databitlen & 0x1ff == 0 { - for i := 0; i < 64; i += 1 { - ctx.buffer[i] = 0 - } - ctx.buffer[0] = 0x80 - ctx.buffer[63] = byte(ctx.databitlen) & 0xff - ctx.buffer[62] = byte(ctx.databitlen >> 8) & 0xff - ctx.buffer[61] = byte(ctx.databitlen >> 16) & 0xff - ctx.buffer[60] = byte(ctx.databitlen >> 24) & 0xff - ctx.buffer[59] = byte(ctx.databitlen >> 32) & 0xff - ctx.buffer[58] = byte(ctx.databitlen >> 40) & 0xff - ctx.buffer[57] = byte(ctx.databitlen >> 48) & 0xff - ctx.buffer[56] = byte(ctx.databitlen >> 56) & 0xff - F8(ctx) - } else { - if ctx.buffer_size & 7 == 0 { - for i := (ctx.databitlen & 0x1ff) >> 3; i < 64; i += 1 { - ctx.buffer[i] = 0 - } - } else { - for i := ((ctx.databitlen & 0x1ff) >> 3) + 1; i < 64; i += 1 { - ctx.buffer[i] = 0 - } - } - ctx.buffer[(ctx.databitlen & 0x1ff) >> 3] |= 1 << (7 - (ctx.databitlen & 7)) - F8(ctx) - for i := 0; i < 64; i += 1 { - ctx.buffer[i] = 0 - } - ctx.buffer[63] = byte(ctx.databitlen) & 0xff - ctx.buffer[62] = byte(ctx.databitlen >> 8) & 0xff - ctx.buffer[61] = byte(ctx.databitlen >> 16) & 0xff - ctx.buffer[60] = byte(ctx.databitlen >> 24) & 0xff - ctx.buffer[59] = byte(ctx.databitlen >> 32) & 0xff - ctx.buffer[58] = byte(ctx.databitlen >> 40) & 0xff - ctx.buffer[57] = byte(ctx.databitlen >> 48) & 0xff - ctx.buffer[56] = byte(ctx.databitlen >> 56) & 0xff - F8(ctx) - } - switch ctx.hashbitlen { - case 224: copy(hash[:], ctx.H[100:128]) - case 256: copy(hash[:], ctx.H[96:128]) - case 384: copy(hash[:], ctx.H[80:128]) - case 512: copy(hash[:], ctx.H[64:128]) - } -} - -/* - JH implementation -*/ - -ROUNDCONSTANT_ZERO := [64]byte { - 0x6, 0xa, 0x0, 0x9, 0xe, 0x6, 0x6, 0x7, - 0xf, 0x3, 0xb, 0xc, 0xc, 0x9, 0x0, 0x8, - 0xb, 0x2, 0xf, 0xb, 0x1, 0x3, 0x6, 0x6, - 0xe, 0xa, 0x9, 0x5, 0x7, 0xd, 0x3, 0xe, - 0x3, 0xa, 0xd, 0xe, 0xc, 0x1, 0x7, 0x5, - 0x1, 0x2, 0x7, 0x7, 0x5, 0x0, 0x9, 0x9, - 0xd, 0xa, 0x2, 0xf, 0x5, 0x9, 0x0, 0xb, - 0x0, 0x6, 0x6, 0x7, 0x3, 0x2, 0x2, 0xa, -} - -SBOX := [2][16]byte { - {9, 0, 4, 11, 13, 12, 3, 15, 1, 10, 2, 6, 7, 5, 8, 14}, - {3, 12, 6, 13, 5, 7, 1, 9, 15, 2, 0, 4, 11, 10, 14, 8}, -} - -Jh_Context :: struct { - hashbitlen: int, - databitlen: u64, - buffer_size: u64, - H: [128]byte, - A: [256]byte, - roundconstant: [64]byte, - buffer: [64]byte, -} - -E8_finaldegroup :: proc(ctx: ^Jh_Context) { - t0,t1,t2,t3: byte - tem: [256]byte - for i := 0; i < 128; i += 1 { - tem[i] = ctx.A[i << 1] - tem[i + 128] = ctx.A[(i << 1) + 1] - } - for i := 0; i < 128; i += 1 { - ctx.H[i] = 0 - } - for i := 0; i < 256; i += 1 { - t0 = (tem[i] >> 3) & 1 - t1 = (tem[i] >> 2) & 1 - t2 = (tem[i] >> 1) & 1 - t3 = (tem[i] >> 0) & 1 - - ctx.H[uint(i) >> 3] |= t0 << (7 - (uint(i) & 7)) - ctx.H[(uint(i) + 256) >> 3] |= t1 << (7 - (uint(i) & 7)) - ctx.H[(uint(i) + 512) >> 3] |= t2 << (7 - (uint(i) & 7)) - ctx.H[(uint(i) + 768) >> 3] |= t3 << (7 - (uint(i) & 7)) - } -} - -update_roundconstant :: proc(ctx: ^Jh_Context) { - tem: [64]byte - t: byte - for i := 0; i < 64; i += 1 { - tem[i] = SBOX[0][ctx.roundconstant[i]] - } - for i := 0; i < 64; i += 2 { - tem[i + 1] ~= ((tem[i] << 1) ~ (tem[i] >> 3) ~ ((tem[i] >> 2) & 2)) & 0xf - tem[i] ~= ((tem[i + 1] << 1) ~ (tem[i + 1] >> 3) ~ ((tem[i + 1] >> 2) & 2)) & 0xf - } - for i := 0; i < 64; i += 4 { - t = tem[i + 2] - tem[i + 2] = tem[i + 3] - tem[i + 3] = t - } - for i := 0; i < 32; i += 1 { - ctx.roundconstant[i] = tem[i << 1] - ctx.roundconstant[i + 32] = tem[(i << 1) + 1] - } - for i := 32; i < 64; i += 2 { - t = ctx.roundconstant[i] - ctx.roundconstant[i] = ctx.roundconstant[i + 1] - ctx.roundconstant[i + 1] = t - } -} - -R8 :: proc(ctx: ^Jh_Context) { - t: byte - tem, roundconstant_expanded: [256]byte - for i := u32(0); i < 256; i += 1 { - roundconstant_expanded[i] = (ctx.roundconstant[i >> 2] >> (3 - (i & 3)) ) & 1 - } - for i := 0; i < 256; i += 1 { - tem[i] = SBOX[roundconstant_expanded[i]][ctx.A[i]] - } - for i := 0; i < 256; i += 2 { - tem[i+1] ~= ((tem[i] << 1) ~ (tem[i] >> 3) ~ ((tem[i] >> 2) & 2)) & 0xf - tem[i] ~= ((tem[i + 1] << 1) ~ (tem[i + 1] >> 3) ~ ((tem[i + 1] >> 2) & 2)) & 0xf - } - for i := 0; i < 256; i += 4 { - t = tem[i + 2] - tem[i+2] = tem[i + 3] - tem[i+3] = t - } - for i := 0; i < 128; i += 1 { - ctx.A[i] = tem[i << 1] - ctx.A[i + 128] = tem[(i << 1) + 1] - } - for i := 128; i < 256; i += 2 { - t = ctx.A[i] - ctx.A[i] = ctx.A[i + 1] - ctx.A[i + 1] = t - } -} - -E8_initialgroup :: proc(ctx: ^Jh_Context) { - t0, t1, t2, t3: byte - tem: [256]byte - for i := u32(0); i < 256; i += 1 { - t0 = (ctx.H[i >> 3] >> (7 - (i & 7))) & 1 - t1 = (ctx.H[(i + 256) >> 3] >> (7 - (i & 7))) & 1 - t2 = (ctx.H[(i + 512) >> 3] >> (7 - (i & 7))) & 1 - t3 = (ctx.H[(i + 768) >> 3] >> (7 - (i & 7))) & 1 - tem[i] = (t0 << 3) | (t1 << 2) | (t2 << 1) | (t3 << 0) - } - for i := 0; i < 128; i += 1 { - ctx.A[i << 1] = tem[i] - ctx.A[(i << 1) + 1] = tem[i + 128] - } -} - -E8 :: proc(ctx: ^Jh_Context) { - for i := 0; i < 64; i += 1 { - ctx.roundconstant[i] = ROUNDCONSTANT_ZERO[i] - } - E8_initialgroup(ctx) - for i := 0; i < 42; i += 1 { - R8(ctx) - update_roundconstant(ctx) - } - E8_finaldegroup(ctx) -} - -F8 :: proc(ctx: ^Jh_Context) { - for i := 0; i < 64; i += 1 { - ctx.H[i] ~= ctx.buffer[i] - } - E8(ctx) - for i := 0; i < 64; i += 1 { - ctx.H[i + 64] ~= ctx.buffer[i] - } -} diff --git a/core/crypto/keccak/keccak.odin b/core/crypto/keccak/keccak.odin deleted file mode 100644 index 4c74858d2..000000000 --- a/core/crypto/keccak/keccak.odin +++ /dev/null @@ -1,374 +0,0 @@ -package keccak - -/* - Copyright 2021 zhibog - Made available under the BSD-3 license. - - List of contributors: - zhibog, dotbmp: Initial implementation. - - Interface for the Keccak hashing algorithm. - This is done because the padding in the SHA3 standard was changed by the NIST, resulting in a different output. -*/ - -import "core:os" -import "core:io" - -import "../_sha3" - - -/* - High level API -*/ - -DIGEST_SIZE_224 :: 28 -DIGEST_SIZE_256 :: 32 -DIGEST_SIZE_384 :: 48 -DIGEST_SIZE_512 :: 64 - -// hash_string_224 will hash the given input and return the -// computed hash -hash_string_224 :: proc(data: string) -> [DIGEST_SIZE_224]byte { - return hash_bytes_224(transmute([]byte)(data)) -} - -// hash_bytes_224 will hash the given input and return the -// computed hash -hash_bytes_224 :: proc(data: []byte) -> [DIGEST_SIZE_224]byte { - hash: [DIGEST_SIZE_224]byte - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_224 - ctx.is_keccak = true - _sha3.init(&ctx) - _sha3.update(&ctx, data) - _sha3.final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_224 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_224 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_224(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_224 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_224 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_224, "Size of destination buffer is smaller than the digest size") - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_224 - ctx.is_keccak = true - _sha3.init(&ctx) - _sha3.update(&ctx, data) - _sha3.final(&ctx, hash) -} - -// hash_stream_224 will read the stream in chunks and compute a -// hash from its contents -hash_stream_224 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { - hash: [DIGEST_SIZE_224]byte - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_224 - ctx.is_keccak = true - _sha3.init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - _sha3.update(&ctx, buf[:read]) - } - } - _sha3.final(&ctx, hash[:]) - return hash, true -} - -// hash_file_224 will read the file provided by the given handle -// and compute a hash -hash_file_224 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_224]byte, bool) { - if !load_at_once { - return hash_stream_224(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_224(buf[:]), ok - } - } - return [DIGEST_SIZE_224]byte{}, false -} - -hash_224 :: proc { - hash_stream_224, - hash_file_224, - hash_bytes_224, - hash_string_224, - hash_bytes_to_buffer_224, - hash_string_to_buffer_224, -} - -// hash_string_256 will hash the given input and return the -// computed hash -hash_string_256 :: proc(data: string) -> [DIGEST_SIZE_256]byte { - return hash_bytes_256(transmute([]byte)(data)) -} - -// hash_bytes_256 will hash the given input and return the -// computed hash -hash_bytes_256 :: proc(data: []byte) -> [DIGEST_SIZE_256]byte { - hash: [DIGEST_SIZE_256]byte - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_256 - ctx.is_keccak = true - _sha3.init(&ctx) - _sha3.update(&ctx, data) - _sha3.final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_256 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_256 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_256(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_256 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_256 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_256, "Size of destination buffer is smaller than the digest size") - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_256 - ctx.is_keccak = true - _sha3.init(&ctx) - _sha3.update(&ctx, data) - _sha3.final(&ctx, hash) -} - -// hash_stream_256 will read the stream in chunks and compute a -// hash from its contents -hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { - hash: [DIGEST_SIZE_256]byte - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_256 - ctx.is_keccak = true - _sha3.init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - _sha3.update(&ctx, buf[:read]) - } - } - _sha3.final(&ctx, hash[:]) - return hash, true -} - -// hash_file_256 will read the file provided by the given handle -// and compute a hash -hash_file_256 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_256]byte, bool) { - if !load_at_once { - return hash_stream_256(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_256(buf[:]), ok - } - } - return [DIGEST_SIZE_256]byte{}, false -} - -hash_256 :: proc { - hash_stream_256, - hash_file_256, - hash_bytes_256, - hash_string_256, - hash_bytes_to_buffer_256, - hash_string_to_buffer_256, -} - -// hash_string_384 will hash the given input and return the -// computed hash -hash_string_384 :: proc(data: string) -> [DIGEST_SIZE_384]byte { - return hash_bytes_384(transmute([]byte)(data)) -} - -// hash_bytes_384 will hash the given input and return the -// computed hash -hash_bytes_384 :: proc(data: []byte) -> [DIGEST_SIZE_384]byte { - hash: [DIGEST_SIZE_384]byte - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_384 - ctx.is_keccak = true - _sha3.init(&ctx) - _sha3.update(&ctx, data) - _sha3.final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_384 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_384 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_384(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_384 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_384 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_384, "Size of destination buffer is smaller than the digest size") - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_384 - ctx.is_keccak = true - _sha3.init(&ctx) - _sha3.update(&ctx, data) - _sha3.final(&ctx, hash) -} - -// hash_stream_384 will read the stream in chunks and compute a -// hash from its contents -hash_stream_384 :: proc(s: io.Stream) -> ([DIGEST_SIZE_384]byte, bool) { - hash: [DIGEST_SIZE_384]byte - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_384 - ctx.is_keccak = true - _sha3.init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - _sha3.update(&ctx, buf[:read]) - } - } - _sha3.final(&ctx, hash[:]) - return hash, true -} - -// hash_file_384 will read the file provided by the given handle -// and compute a hash -hash_file_384 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_384]byte, bool) { - if !load_at_once { - return hash_stream_384(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_384(buf[:]), ok - } - } - return [DIGEST_SIZE_384]byte{}, false -} - -hash_384 :: proc { - hash_stream_384, - hash_file_384, - hash_bytes_384, - hash_string_384, - hash_bytes_to_buffer_384, - hash_string_to_buffer_384, -} - -// hash_string_512 will hash the given input and return the -// computed hash -hash_string_512 :: proc(data: string) -> [DIGEST_SIZE_512]byte { - return hash_bytes_512(transmute([]byte)(data)) -} - -// hash_bytes_512 will hash the given input and return the -// computed hash -hash_bytes_512 :: proc(data: []byte) -> [DIGEST_SIZE_512]byte { - hash: [DIGEST_SIZE_512]byte - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_512 - ctx.is_keccak = true - _sha3.init(&ctx) - _sha3.update(&ctx, data) - _sha3.final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_512 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_512 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_512(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_512 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_512 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_512, "Size of destination buffer is smaller than the digest size") - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_512 - ctx.is_keccak = true - _sha3.init(&ctx) - _sha3.update(&ctx, data) - _sha3.final(&ctx, hash) -} - -// hash_stream_512 will read the stream in chunks and compute a -// hash from its contents -hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { - hash: [DIGEST_SIZE_512]byte - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_512 - ctx.is_keccak = true - _sha3.init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - _sha3.update(&ctx, buf[:read]) - } - } - _sha3.final(&ctx, hash[:]) - return hash, true -} - -// hash_file_512 will read the file provided by the given handle -// and compute a hash -hash_file_512 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_512]byte, bool) { - if !load_at_once { - return hash_stream_512(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_512(buf[:]), ok - } - } - return [DIGEST_SIZE_512]byte{}, false -} - -hash_512 :: proc { - hash_stream_512, - hash_file_512, - hash_bytes_512, - hash_string_512, - hash_bytes_to_buffer_512, - hash_string_to_buffer_512, -} - -/* - Low level API -*/ - -Keccak_Context :: _sha3.Sha3_Context - -init :: proc(ctx: ^_sha3.Sha3_Context) { - ctx.is_keccak = true - _sha3.init(ctx) -} - -update :: proc "contextless" (ctx: ^_sha3.Sha3_Context, data: []byte) { - _sha3.update(ctx, data) -} - -final :: proc "contextless" (ctx: ^_sha3.Sha3_Context, hash: []byte) { - _sha3.final(ctx, hash) -} diff --git a/core/crypto/kmac/kmac.odin b/core/crypto/kmac/kmac.odin new file mode 100644 index 000000000..e8bf42946 --- /dev/null +++ b/core/crypto/kmac/kmac.odin @@ -0,0 +1,116 @@ +/* +package kmac implements the KMAC MAC algorithm. + +See: +- [[ https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-185.pdf ]] +*/ +package kmac + +import "../_sha3" +import "core:crypto" +import "core:crypto/shake" + +// MIN_KEY_SIZE_128 is the minimum key size for KMAC128 in bytes. +MIN_KEY_SIZE_128 :: 128 / 8 +// MIN_KEY_SIZE_256 is the minimum key size for KMAC256 in bytes. +MIN_KEY_SIZE_256 :: 256 / 8 + +// MIN_TAG_SIZE is the absolute minimum tag size for KMAC in bytes (8.4.2). +// Most callers SHOULD use at least 128-bits if not 256-bits for the tag +// size. +MIN_TAG_SIZE :: 32 / 8 + +// sum will compute the KMAC with the specified security strength, +// key, and domain separator over msg, and write the computed digest to +// dst. +sum :: proc(sec_strength: int, dst, msg, key, domain_sep: []byte) { + ctx: Context + + _init_kmac(&ctx, key, domain_sep, sec_strength) + update(&ctx, msg) + final(&ctx, dst) +} + +// verify will verify the KMAC tag computed with the specified security +// strength, key and domain separator over msg and return true iff the +// tag is valid. +verify :: proc(sec_strength: int, tag, msg, key, domain_sep: []byte, allocator := context.temp_allocator) -> bool { + derived_tag := make([]byte, len(tag), allocator) + + sum(sec_strength, derived_tag, msg, key, domain_sep) + + return crypto.compare_constant_time(derived_tag, tag) == 1 +} + +// Context is a KMAC instance. +Context :: distinct shake.Context + +// init_128 initializes a Context for KMAC28. This routine will panic if +// the key length is less than MIN_KEY_SIZE_128. +init_128 :: proc(ctx: ^Context, key, domain_sep: []byte) { + _init_kmac(ctx, key, domain_sep, 128) +} + +// init_256 initializes a Context for KMAC256. This routine will panic if +// the key length is less than MIN_KEY_SIZE_256. +init_256 :: proc(ctx: ^Context, key, domain_sep: []byte) { + _init_kmac(ctx, key, domain_sep, 256) +} + +// update adds more data to the Context. +update :: proc(ctx: ^Context, data: []byte) { + assert(ctx.is_initialized) + + shake.write((^shake.Context)(ctx), data) +} + +// final finalizes the Context, writes the tag to dst, and calls reset +// on the Context. This routine will panic if the dst length is less than +// MIN_TAG_SIZE. +final :: proc(ctx: ^Context, dst: []byte) { + assert(ctx.is_initialized) + defer reset(ctx) + + if len(dst) < MIN_TAG_SIZE { + panic("crypto/kmac: invalid KMAC tag_size, too short") + } + + _sha3.final_cshake((^_sha3.Context)(ctx), dst) +} + +// clone clones the Context other into ctx. +clone :: proc(ctx, other: ^Context) { + if ctx == other { + return + } + + shake.clone((^shake.Context)(ctx), (^shake.Context)(other)) +} + +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. +reset :: proc(ctx: ^Context) { + if !ctx.is_initialized { + return + } + + shake.reset((^shake.Context)(ctx)) +} + +@(private) +_init_kmac :: proc(ctx: ^Context, key, s: []byte, sec_strength: int) { + if ctx.is_initialized { + reset(ctx) + } + + if len(key) < sec_strength / 8 { + panic("crypto/kmac: invalid KMAC key, too short") + } + + ctx_ := (^_sha3.Context)(ctx) + _sha3.init_cshake(ctx_, N_KMAC, s, sec_strength) + _sha3.bytepad(ctx_, [][]byte{key}, _sha3.rate_cshake(sec_strength)) +} + +@(private) +N_KMAC := []byte{'K', 'M', 'A', 'C'} diff --git a/core/crypto/legacy/README.md b/core/crypto/legacy/README.md new file mode 100644 index 000000000..e1ba6f54b --- /dev/null +++ b/core/crypto/legacy/README.md @@ -0,0 +1,10 @@ +# crypto/legacy + +These are algorithms that are shipped solely for the purpose of +interoperability with legacy systems. The use of these packages in +any other capacity is discouraged, especially those that are known +to be broken. + +- keccak - The draft version of the algorithm that became SHA-3 +- MD5 - Broken (https://eprint.iacr.org/2005/075) +- SHA-1 - Broken (https://eprint.iacr.org/2017/190) diff --git a/core/crypto/legacy/keccak/keccak.odin b/core/crypto/legacy/keccak/keccak.odin new file mode 100644 index 000000000..6ca66b7ca --- /dev/null +++ b/core/crypto/legacy/keccak/keccak.odin @@ -0,0 +1,95 @@ +/* +package keccak implements the Keccak hash algorithm family. + +During the SHA-3 standardization process, the padding scheme was changed +thus Keccac and SHA-3 produce different outputs. Most users should use +SHA-3 and/or SHAKE instead, however the legacy algorithm is provided for +backward compatibility purposes. +*/ +package keccak + +/* + Copyright 2021 zhibog + Made available under the BSD-3 license. + + List of contributors: + zhibog, dotbmp: Initial implementation. +*/ + +import "../../_sha3" + +// DIGEST_SIZE_224 is the Keccak-224 digest size. +DIGEST_SIZE_224 :: 28 +// DIGEST_SIZE_256 is the Keccak-256 digest size. +DIGEST_SIZE_256 :: 32 +// DIGEST_SIZE_384 is the Keccak-384 digest size. +DIGEST_SIZE_384 :: 48 +// DIGEST_SIZE_512 is the Keccak-512 digest size. +DIGEST_SIZE_512 :: 64 + +// BLOCK_SIZE_224 is the Keccak-224 block size in bytes. +BLOCK_SIZE_224 :: _sha3.RATE_224 +// BLOCK_SIZE_256 is the Keccak-256 block size in bytes. +BLOCK_SIZE_256 :: _sha3.RATE_256 +// BLOCK_SIZE_384 is the Keccak-384 block size in bytes. +BLOCK_SIZE_384 :: _sha3.RATE_384 +// BLOCK_SIZE_512 is the Keccak-512 block size in bytes. +BLOCK_SIZE_512 :: _sha3.RATE_512 + +// Context is a Keccak instance. +Context :: distinct _sha3.Context + +// init_224 initializes a Context for Keccak-224. +init_224 :: proc(ctx: ^Context) { + ctx.mdlen = DIGEST_SIZE_224 + _init(ctx) +} + +// init_256 initializes a Context for Keccak-256. +init_256 :: proc(ctx: ^Context) { + ctx.mdlen = DIGEST_SIZE_256 + _init(ctx) +} + +// init_384 initializes a Context for Keccak-384. +init_384 :: proc(ctx: ^Context) { + ctx.mdlen = DIGEST_SIZE_384 + _init(ctx) +} + +// init_512 initializes a Context for Keccak-512. +init_512 :: proc(ctx: ^Context) { + ctx.mdlen = DIGEST_SIZE_512 + _init(ctx) +} + +@(private) +_init :: proc(ctx: ^Context) { + ctx.dsbyte = _sha3.DS_KECCAK + _sha3.init((^_sha3.Context)(ctx)) +} + +// update adds more data to the Context. +update :: proc(ctx: ^Context, data: []byte) { + _sha3.update((^_sha3.Context)(ctx), data) +} + +// final finalizes the Context, writes the digest to hash, and calls +// reset on the Context. +// +// 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((^_sha3.Context)(ctx), hash, finalize_clone) +} + +// clone clones the Context other into ctx. +clone :: proc(ctx, other: ^Context) { + _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((^_sha3.Context)(ctx)) +} diff --git a/core/crypto/legacy/md5/md5.odin b/core/crypto/legacy/md5/md5.odin new file mode 100644 index 000000000..28b47e0b3 --- /dev/null +++ b/core/crypto/legacy/md5/md5.odin @@ -0,0 +1,247 @@ +/* +package md5 implements the MD5 hash algorithm. + +WARNING: The MD5 algorithm is known to be insecure and should only be +used for interoperating with legacy applications. + +See: +- [[ https://eprint.iacr.org/2005/075 ]] +- [[ https://datatracker.ietf.org/doc/html/rfc1321 ]] +*/ +package md5 + +/* + Copyright 2021 zhibog + Made available under the BSD-3 license. + + List of contributors: + zhibog, dotbmp: Initial implementation. +*/ + +import "core:encoding/endian" +import "core:math/bits" +import "core:mem" + +// DIGEST_SIZE is the MD5 digest size in bytes. +DIGEST_SIZE :: 16 + +// BLOCK_SIZE is the MD5 block size in bytes. +BLOCK_SIZE :: 64 + +// Context is a MD5 instance. +Context :: struct { + data: [BLOCK_SIZE]byte, + state: [4]u32, + bitlen: u64, + datalen: u32, + + is_initialized: bool, +} + +// init initializes a Context. +init :: proc(ctx: ^Context) { + ctx.state[0] = 0x67452301 + ctx.state[1] = 0xefcdab89 + ctx.state[2] = 0x98badcfe + ctx.state[3] = 0x10325476 + + ctx.bitlen = 0 + ctx.datalen = 0 + + ctx.is_initialized = true +} + +// update adds more data to the Context. +update :: proc(ctx: ^Context, data: []byte) { + assert(ctx.is_initialized) + + for i := 0; i < len(data); i += 1 { + ctx.data[ctx.datalen] = data[i] + ctx.datalen += 1 + if (ctx.datalen == BLOCK_SIZE) { + transform(ctx, ctx.data[:]) + ctx.bitlen += 512 + ctx.datalen = 0 + } + } +} + +// final finalizes the Context, writes the digest to hash, and calls +// reset on the Context. +// +// 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) { + assert(ctx.is_initialized) + + if len(hash) < DIGEST_SIZE { + panic("crypto/md5: invalid destination digest size") + } + + ctx := ctx + if finalize_clone { + tmp_ctx: Context + clone(&tmp_ctx, ctx) + ctx = &tmp_ctx + } + defer(reset(ctx)) + + i := ctx.datalen + + if ctx.datalen < 56 { + ctx.data[i] = 0x80 + i += 1 + for i < 56 { + ctx.data[i] = 0x00 + i += 1 + } + } else if ctx.datalen >= 56 { + ctx.data[i] = 0x80 + i += 1 + for i < BLOCK_SIZE { + ctx.data[i] = 0x00 + i += 1 + } + transform(ctx, ctx.data[:]) + mem.set(&ctx.data, 0, 56) + } + + ctx.bitlen += u64(ctx.datalen * 8) + endian.unchecked_put_u64le(ctx.data[56:], ctx.bitlen) + transform(ctx, ctx.data[:]) + + for i = 0; i < DIGEST_SIZE / 4; i += 1 { + endian.unchecked_put_u32le(hash[i * 4:], ctx.state[i]) + } +} + +// clone clones the Context other into ctx. +clone :: proc(ctx, other: ^$T) { + ctx^ = other^ +} + +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. +reset :: proc(ctx: ^$T) { + if !ctx.is_initialized { + return + } + + mem.zero_explicit(ctx, size_of(ctx^)) +} + +/* + MD5 implementation +*/ + +/* + @note(zh): F, G, H and I, as mentioned in the RFC, have been inlined into FF, GG, HH + and II respectively, instead of declaring them separately. +*/ + +@(private) +FF :: #force_inline proc "contextless" (a, b, c, d, m: u32, s: int, t: u32) -> u32 { + return b + bits.rotate_left32(a + ((b & c) | (~b & d)) + m + t, s) +} + +@(private) +GG :: #force_inline proc "contextless" (a, b, c, d, m: u32, s: int, t: u32) -> u32 { + return b + bits.rotate_left32(a + ((b & d) | (c & ~d)) + m + t, s) +} + +@(private) +HH :: #force_inline proc "contextless" (a, b, c, d, m: u32, s: int, t: u32) -> u32 { + return b + bits.rotate_left32(a + (b ~ c ~ d) + m + t, s) +} + +@(private) +II :: #force_inline proc "contextless" (a, b, c, d, m: u32, s: int, t: u32) -> u32 { + return b + bits.rotate_left32(a + (c ~ (b | ~d)) + m + t, s) +} + +@(private) +transform :: proc "contextless" (ctx: ^Context, data: []byte) { + m: [DIGEST_SIZE]u32 + + for i := 0; i < DIGEST_SIZE; i += 1 { + m[i] = endian.unchecked_get_u32le(data[i * 4:]) + } + + a := ctx.state[0] + b := ctx.state[1] + c := ctx.state[2] + d := ctx.state[3] + + a = FF(a, b, c, d, m[0], 7, 0xd76aa478) + d = FF(d, a, b, c, m[1], 12, 0xe8c7b756) + c = FF(c, d, a, b, m[2], 17, 0x242070db) + b = FF(b, c, d, a, m[3], 22, 0xc1bdceee) + a = FF(a, b, c, d, m[4], 7, 0xf57c0faf) + d = FF(d, a, b, c, m[5], 12, 0x4787c62a) + c = FF(c, d, a, b, m[6], 17, 0xa8304613) + b = FF(b, c, d, a, m[7], 22, 0xfd469501) + a = FF(a, b, c, d, m[8], 7, 0x698098d8) + d = FF(d, a, b, c, m[9], 12, 0x8b44f7af) + c = FF(c, d, a, b, m[10], 17, 0xffff5bb1) + b = FF(b, c, d, a, m[11], 22, 0x895cd7be) + a = FF(a, b, c, d, m[12], 7, 0x6b901122) + d = FF(d, a, b, c, m[13], 12, 0xfd987193) + c = FF(c, d, a, b, m[14], 17, 0xa679438e) + b = FF(b, c, d, a, m[15], 22, 0x49b40821) + + a = GG(a, b, c, d, m[1], 5, 0xf61e2562) + d = GG(d, a, b, c, m[6], 9, 0xc040b340) + c = GG(c, d, a, b, m[11], 14, 0x265e5a51) + b = GG(b, c, d, a, m[0], 20, 0xe9b6c7aa) + a = GG(a, b, c, d, m[5], 5, 0xd62f105d) + d = GG(d, a, b, c, m[10], 9, 0x02441453) + c = GG(c, d, a, b, m[15], 14, 0xd8a1e681) + b = GG(b, c, d, a, m[4], 20, 0xe7d3fbc8) + a = GG(a, b, c, d, m[9], 5, 0x21e1cde6) + d = GG(d, a, b, c, m[14], 9, 0xc33707d6) + c = GG(c, d, a, b, m[3], 14, 0xf4d50d87) + b = GG(b, c, d, a, m[8], 20, 0x455a14ed) + a = GG(a, b, c, d, m[13], 5, 0xa9e3e905) + d = GG(d, a, b, c, m[2], 9, 0xfcefa3f8) + c = GG(c, d, a, b, m[7], 14, 0x676f02d9) + b = GG(b, c, d, a, m[12], 20, 0x8d2a4c8a) + + a = HH(a, b, c, d, m[5], 4, 0xfffa3942) + d = HH(d, a, b, c, m[8], 11, 0x8771f681) + c = HH(c, d, a, b, m[11], 16, 0x6d9d6122) + b = HH(b, c, d, a, m[14], 23, 0xfde5380c) + a = HH(a, b, c, d, m[1], 4, 0xa4beea44) + d = HH(d, a, b, c, m[4], 11, 0x4bdecfa9) + c = HH(c, d, a, b, m[7], 16, 0xf6bb4b60) + b = HH(b, c, d, a, m[10], 23, 0xbebfbc70) + a = HH(a, b, c, d, m[13], 4, 0x289b7ec6) + d = HH(d, a, b, c, m[0], 11, 0xeaa127fa) + c = HH(c, d, a, b, m[3], 16, 0xd4ef3085) + b = HH(b, c, d, a, m[6], 23, 0x04881d05) + a = HH(a, b, c, d, m[9], 4, 0xd9d4d039) + d = HH(d, a, b, c, m[12], 11, 0xe6db99e5) + c = HH(c, d, a, b, m[15], 16, 0x1fa27cf8) + b = HH(b, c, d, a, m[2], 23, 0xc4ac5665) + + a = II(a, b, c, d, m[0], 6, 0xf4292244) + d = II(d, a, b, c, m[7], 10, 0x432aff97) + c = II(c, d, a, b, m[14], 15, 0xab9423a7) + b = II(b, c, d, a, m[5], 21, 0xfc93a039) + a = II(a, b, c, d, m[12], 6, 0x655b59c3) + d = II(d, a, b, c, m[3], 10, 0x8f0ccc92) + c = II(c, d, a, b, m[10], 15, 0xffeff47d) + b = II(b, c, d, a, m[1], 21, 0x85845dd1) + a = II(a, b, c, d, m[8], 6, 0x6fa87e4f) + d = II(d, a, b, c, m[15], 10, 0xfe2ce6e0) + c = II(c, d, a, b, m[6], 15, 0xa3014314) + b = II(b, c, d, a, m[13], 21, 0x4e0811a1) + a = II(a, b, c, d, m[4], 6, 0xf7537e82) + d = II(d, a, b, c, m[11], 10, 0xbd3af235) + c = II(c, d, a, b, m[2], 15, 0x2ad7d2bb) + b = II(b, c, d, a, m[9], 21, 0xeb86d391) + + ctx.state[0] += a + ctx.state[1] += b + ctx.state[2] += c + ctx.state[3] += d +} diff --git a/core/crypto/legacy/sha1/sha1.odin b/core/crypto/legacy/sha1/sha1.odin new file mode 100644 index 000000000..1025ecb5b --- /dev/null +++ b/core/crypto/legacy/sha1/sha1.odin @@ -0,0 +1,205 @@ +/* +package sha1 implements the SHA1 hash algorithm. + +WARNING: The SHA1 algorithm is known to be insecure and should only be +used for interoperating with legacy applications. + +See: +- [[ https://eprint.iacr.org/2017/190 ]] +- [[ https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf ]] +- [[ https://datatracker.ietf.org/doc/html/rfc3174 ]] +*/ +package sha1 + +/* + Copyright 2021 zhibog + Made available under the BSD-3 license. + + List of contributors: + zhibog, dotbmp: Initial implementation. +*/ + +import "core:encoding/endian" +import "core:math/bits" +import "core:mem" + +// DIGEST_SIZE is the SHA1 digest size in bytes. +DIGEST_SIZE :: 20 + +// BLOCK_SIZE is the SHA1 block size in bytes. +BLOCK_SIZE :: 64 + +// Context is a SHA1 instance. +Context :: struct { + data: [BLOCK_SIZE]byte, + state: [5]u32, + k: [4]u32, + bitlen: u64, + datalen: u32, + + is_initialized: bool, +} + +// init initializes a Context. +init :: proc(ctx: ^Context) { + ctx.state[0] = 0x67452301 + ctx.state[1] = 0xefcdab89 + ctx.state[2] = 0x98badcfe + ctx.state[3] = 0x10325476 + ctx.state[4] = 0xc3d2e1f0 + ctx.k[0] = 0x5a827999 + ctx.k[1] = 0x6ed9eba1 + ctx.k[2] = 0x8f1bbcdc + ctx.k[3] = 0xca62c1d6 + + ctx.datalen = 0 + ctx.bitlen = 0 + + ctx.is_initialized = true +} + +// update adds more data to the Context. +update :: proc(ctx: ^Context, data: []byte) { + assert(ctx.is_initialized) + + for i := 0; i < len(data); i += 1 { + ctx.data[ctx.datalen] = data[i] + ctx.datalen += 1 + if (ctx.datalen == BLOCK_SIZE) { + transform(ctx, ctx.data[:]) + ctx.bitlen += 512 + ctx.datalen = 0 + } + } +} + +// final finalizes the Context, writes the digest to hash, and calls +// reset on the Context. +// +// 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) { + assert(ctx.is_initialized) + + if len(hash) < DIGEST_SIZE { + panic("crypto/sha1: invalid destination digest size") + } + + ctx := ctx + if finalize_clone { + tmp_ctx: Context + clone(&tmp_ctx, ctx) + ctx = &tmp_ctx + } + defer(reset(ctx)) + + i := ctx.datalen + + if ctx.datalen < 56 { + ctx.data[i] = 0x80 + i += 1 + for i < 56 { + ctx.data[i] = 0x00 + i += 1 + } + } else { + ctx.data[i] = 0x80 + i += 1 + for i < BLOCK_SIZE { + ctx.data[i] = 0x00 + i += 1 + } + transform(ctx, ctx.data[:]) + mem.set(&ctx.data, 0, 56) + } + + ctx.bitlen += u64(ctx.datalen * 8) + endian.unchecked_put_u64be(ctx.data[56:], ctx.bitlen) + transform(ctx, ctx.data[:]) + + for i = 0; i < DIGEST_SIZE / 4; i += 1 { + endian.unchecked_put_u32be(hash[i * 4:], ctx.state[i]) + } +} + +// clone clones the Context other into ctx. +clone :: proc(ctx, other: ^$T) { + ctx^ = other^ +} + +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. +reset :: proc(ctx: ^$T) { + if !ctx.is_initialized { + return + } + + mem.zero_explicit(ctx, size_of(ctx^)) +} + +/* + SHA1 implementation +*/ + +@(private) +transform :: proc "contextless" (ctx: ^Context, data: []byte) { + a, b, c, d, e, i, t: u32 + m: [80]u32 + + for i = 0; i < 16; i += 1 { + m[i] = endian.unchecked_get_u32be(data[i * 4:]) + } + for i < 80 { + m[i] = (m[i - 3] ~ m[i - 8] ~ m[i - 14] ~ m[i - 16]) + m[i] = (m[i] << 1) | (m[i] >> 31) + i += 1 + } + + a = ctx.state[0] + b = ctx.state[1] + c = ctx.state[2] + d = ctx.state[3] + e = ctx.state[4] + + for i = 0; i < 20; i += 1 { + t = bits.rotate_left32(a, 5) + ((b & c) ~ (~b & d)) + e + ctx.k[0] + m[i] + e = d + d = c + c = bits.rotate_left32(b, 30) + b = a + a = t + } + for i < 40 { + t = bits.rotate_left32(a, 5) + (b ~ c ~ d) + e + ctx.k[1] + m[i] + e = d + d = c + c = bits.rotate_left32(b, 30) + b = a + a = t + i += 1 + } + for i < 60 { + t = bits.rotate_left32(a, 5) + ((b & c) ~ (b & d) ~ (c & d)) + e + ctx.k[2] + m[i] + e = d + d = c + c = bits.rotate_left32(b, 30) + b = a + a = t + i += 1 + } + for i < 80 { + t = bits.rotate_left32(a, 5) + (b ~ c ~ d) + e + ctx.k[3] + m[i] + e = d + d = c + c = bits.rotate_left32(b, 30) + b = a + a = t + i += 1 + } + + ctx.state[0] += a + ctx.state[1] += b + ctx.state[2] += c + ctx.state[3] += d + ctx.state[4] += e +} diff --git a/core/crypto/md2/md2.odin b/core/crypto/md2/md2.odin deleted file mode 100644 index 4942183e1..000000000 --- a/core/crypto/md2/md2.odin +++ /dev/null @@ -1,182 +0,0 @@ -package md2 - -/* - Copyright 2021 zhibog - Made available under the BSD-3 license. - - List of contributors: - zhibog, dotbmp: Initial implementation. - - Implementation of the MD2 hashing algorithm, as defined in RFC 1319 -*/ - -import "core:os" -import "core:io" - -/* - High level API -*/ - -DIGEST_SIZE :: 16 - -// hash_string will hash the given input and return the -// computed hash -hash_string :: proc(data: string) -> [DIGEST_SIZE]byte { - return hash_bytes(transmute([]byte)(data)) -} - -// hash_bytes will hash the given input and return the -// computed hash -hash_bytes :: proc(data: []byte) -> [DIGEST_SIZE]byte { - hash: [DIGEST_SIZE]byte - ctx: Md2_Context - // init(&ctx) No-op - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE, "Size of destination buffer is smaller than the digest size") - ctx: Md2_Context - // init(&ctx) No-op - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream will read the stream in chunks and compute a -// hash from its contents -hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { - hash: [DIGEST_SIZE]byte - ctx: Md2_Context - // init(&ctx) No-op - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file will read the file provided by the given handle -// and compute a hash -hash_file :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE]byte, bool) { - if !load_at_once { - return hash_stream(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes(buf[:]), ok - } - } - return [DIGEST_SIZE]byte{}, false -} - -hash :: proc { - hash_stream, - hash_file, - hash_bytes, - hash_string, - hash_bytes_to_buffer, - hash_string_to_buffer, -} - -/* - Low level API -*/ - -@(warning="Init is a no-op for MD2") -init :: proc(ctx: ^Md2_Context) { - // No action needed here -} - -update :: proc(ctx: ^Md2_Context, data: []byte) { - for i := 0; i < len(data); i += 1 { - ctx.data[ctx.datalen] = data[i] - ctx.datalen += 1 - if (ctx.datalen == DIGEST_SIZE) { - transform(ctx, ctx.data[:]) - ctx.datalen = 0 - } - } -} - -final :: proc(ctx: ^Md2_Context, hash: []byte) { - to_pad := byte(DIGEST_SIZE - ctx.datalen) - for ctx.datalen < DIGEST_SIZE { - ctx.data[ctx.datalen] = to_pad - ctx.datalen += 1 - } - transform(ctx, ctx.data[:]) - transform(ctx, ctx.checksum[:]) - for i := 0; i < DIGEST_SIZE; i += 1 { - hash[i] = ctx.state[i] - } -} - -/* - MD2 implementation -*/ - -Md2_Context :: struct { - data: [DIGEST_SIZE]byte, - state: [DIGEST_SIZE * 3]byte, - checksum: [DIGEST_SIZE]byte, - datalen: int, -} - -PI_TABLE := [?]byte { - 41, 46, 67, 201, 162, 216, 124, 1, 61, 54, 84, 161, 236, 240, 6, - 19, 98, 167, 5, 243, 192, 199, 115, 140, 152, 147, 43, 217, 188, 76, - 130, 202, 30, 155, 87, 60, 253, 212, 224, 22, 103, 66, 111, 24, 138, - 23, 229, 18, 190, 78, 196, 214, 218, 158, 222, 73, 160, 251, 245, 142, - 187, 47, 238, 122, 169, 104, 121, 145, 21, 178, 7, 63, 148, 194, 16, - 137, 11, 34, 95, 33, 128, 127, 93, 154, 90, 144, 50, 39, 53, 62, - 204, 231, 191, 247, 151, 3, 255, 25, 48, 179, 72, 165, 181, 209, 215, - 94, 146, 42, 172, 86, 170, 198, 79, 184, 56, 210, 150, 164, 125, 182, - 118, 252, 107, 226, 156, 116, 4, 241, 69, 157, 112, 89, 100, 113, 135, - 32, 134, 91, 207, 101, 230, 45, 168, 2, 27, 96, 37, 173, 174, 176, - 185, 246, 28, 70, 97, 105, 52, 64, 126, 15, 85, 71, 163, 35, 221, - 81, 175, 58, 195, 92, 249, 206, 186, 197, 234, 38, 44, 83, 13, 110, - 133, 40, 132, 9, 211, 223, 205, 244, 65, 129, 77, 82, 106, 220, 55, - 200, 108, 193, 171, 250, 36, 225, 123, 8, 12, 189, 177, 74, 120, 136, - 149, 139, 227, 99, 232, 109, 233, 203, 213, 254, 59, 0, 29, 57, 242, - 239, 183, 14, 102, 88, 208, 228, 166, 119, 114, 248, 235, 117, 75, 10, - 49, 68, 80, 180, 143, 237, 31, 26, 219, 153, 141, 51, 159, 17, 131, - 20, -} - -transform :: proc(ctx: ^Md2_Context, data: []byte) { - j,k,t: byte - for j = 0; j < DIGEST_SIZE; j += 1 { - ctx.state[j + DIGEST_SIZE] = data[j] - ctx.state[j + DIGEST_SIZE * 2] = (ctx.state[j + DIGEST_SIZE] ~ ctx.state[j]) - } - t = 0 - for j = 0; j < DIGEST_SIZE + 2; j += 1 { - for k = 0; k < DIGEST_SIZE * 3; k += 1 { - ctx.state[k] ~= PI_TABLE[t] - t = ctx.state[k] - } - t = (t + j) & 0xff - } - t = ctx.checksum[DIGEST_SIZE - 1] - for j = 0; j < DIGEST_SIZE; j += 1 { - ctx.checksum[j] ~= PI_TABLE[data[j] ~ t] - t = ctx.checksum[j] - } -} diff --git a/core/crypto/md4/md4.odin b/core/crypto/md4/md4.odin deleted file mode 100644 index 8efdbb5a5..000000000 --- a/core/crypto/md4/md4.odin +++ /dev/null @@ -1,263 +0,0 @@ -package md4 - -/* - Copyright 2021 zhibog - Made available under the BSD-3 license. - - List of contributors: - zhibog, dotbmp: Initial implementation. - Jeroen van Rijn: Context design to be able to change from Odin implementation to bindings. - - Implementation of the MD4 hashing algorithm, as defined in RFC 1320 -*/ - -import "core:mem" -import "core:os" -import "core:io" - -import "../util" - -/* - High level API -*/ - -DIGEST_SIZE :: 16 - -// hash_string will hash the given input and return the -// computed hash -hash_string :: proc(data: string) -> [DIGEST_SIZE]byte { - return hash_bytes(transmute([]byte)(data)) -} - -// hash_bytes will hash the given input and return the -// computed hash -hash_bytes :: proc(data: []byte) -> [DIGEST_SIZE]byte { - hash: [DIGEST_SIZE]byte - ctx: Md4_Context - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE, "Size of destination buffer is smaller than the digest size") - ctx: Md4_Context - init(&ctx) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream will read the stream in chunks and compute a -// hash from its contents -hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { - hash: [DIGEST_SIZE]byte - ctx: Md4_Context - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file will read the file provided by the given handle -// and compute a hash -hash_file :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE]byte, bool) { - if !load_at_once { - return hash_stream(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes(buf[:]), ok - } - } - return [DIGEST_SIZE]byte{}, false -} - -hash :: proc { - hash_stream, - hash_file, - hash_bytes, - hash_string, - hash_bytes_to_buffer, - hash_string_to_buffer, -} - -/* - Low level API -*/ - -init :: proc(ctx: ^Md4_Context) { - ctx.state[0] = 0x67452301 - ctx.state[1] = 0xefcdab89 - ctx.state[2] = 0x98badcfe - ctx.state[3] = 0x10325476 -} - -update :: proc(ctx: ^Md4_Context, data: []byte) { - for i := 0; i < len(data); i += 1 { - ctx.data[ctx.datalen] = data[i] - ctx.datalen += 1 - if(ctx.datalen == BLOCK_SIZE) { - transform(ctx, ctx.data[:]) - ctx.bitlen += 512 - ctx.datalen = 0 - } - } -} - -final :: proc(ctx: ^Md4_Context, hash: []byte) { - i := ctx.datalen - if ctx.datalen < 56 { - ctx.data[i] = 0x80 - i += 1 - for i < 56 { - ctx.data[i] = 0x00 - i += 1 - } - } else if ctx.datalen >= 56 { - ctx.data[i] = 0x80 - i += 1 - for i < BLOCK_SIZE { - ctx.data[i] = 0x00 - i += 1 - } - transform(ctx, ctx.data[:]) - mem.set(&ctx.data, 0, 56) - } - - ctx.bitlen += u64(ctx.datalen * 8) - ctx.data[56] = byte(ctx.bitlen) - ctx.data[57] = byte(ctx.bitlen >> 8) - ctx.data[58] = byte(ctx.bitlen >> 16) - ctx.data[59] = byte(ctx.bitlen >> 24) - ctx.data[60] = byte(ctx.bitlen >> 32) - ctx.data[61] = byte(ctx.bitlen >> 40) - ctx.data[62] = byte(ctx.bitlen >> 48) - ctx.data[63] = byte(ctx.bitlen >> 56) - transform(ctx, ctx.data[:]) - - for i = 0; i < 4; i += 1 { - hash[i] = byte(ctx.state[0] >> (i * 8)) & 0x000000ff - hash[i + 4] = byte(ctx.state[1] >> (i * 8)) & 0x000000ff - hash[i + 8] = byte(ctx.state[2] >> (i * 8)) & 0x000000ff - hash[i + 12] = byte(ctx.state[3] >> (i * 8)) & 0x000000ff - } -} - -/* - MD4 implementation -*/ - -BLOCK_SIZE :: 64 - -Md4_Context :: struct { - data: [64]byte, - state: [4]u32, - bitlen: u64, - datalen: u32, -} - -/* - @note(zh): F, G and H, as mentioned in the RFC, have been inlined into FF, GG - and HH respectively, instead of declaring them separately. -*/ - -FF :: #force_inline proc "contextless"(a, b, c, d, x: u32, s : int) -> u32 { - return util.ROTL32(a + ((b & c) | (~b & d)) + x, s) -} - -GG :: #force_inline proc "contextless"(a, b, c, d, x: u32, s : int) -> u32 { - return util.ROTL32(a + ((b & c) | (b & d) | (c & d)) + x + 0x5a827999, s) -} - -HH :: #force_inline proc "contextless"(a, b, c, d, x: u32, s : int) -> u32 { - return util.ROTL32(a + (b ~ c ~ d) + x + 0x6ed9eba1, s) -} - -transform :: proc(ctx: ^Md4_Context, data: []byte) { - a, b, c, d, i, j: u32 - m: [DIGEST_SIZE]u32 - - for i, j = 0, 0; i < DIGEST_SIZE; i += 1 { - m[i] = u32(data[j]) | (u32(data[j + 1]) << 8) | (u32(data[j + 2]) << 16) | (u32(data[j + 3]) << 24) - j += 4 - } - - a = ctx.state[0] - b = ctx.state[1] - c = ctx.state[2] - d = ctx.state[3] - - a = FF(a, b, c, d, m[0], 3) - d = FF(d, a, b, c, m[1], 7) - c = FF(c, d, a, b, m[2], 11) - b = FF(b, c, d, a, m[3], 19) - a = FF(a, b, c, d, m[4], 3) - d = FF(d, a, b, c, m[5], 7) - c = FF(c, d, a, b, m[6], 11) - b = FF(b, c, d, a, m[7], 19) - a = FF(a, b, c, d, m[8], 3) - d = FF(d, a, b, c, m[9], 7) - c = FF(c, d, a, b, m[10], 11) - b = FF(b, c, d, a, m[11], 19) - a = FF(a, b, c, d, m[12], 3) - d = FF(d, a, b, c, m[13], 7) - c = FF(c, d, a, b, m[14], 11) - b = FF(b, c, d, a, m[15], 19) - - a = GG(a, b, c, d, m[0], 3) - d = GG(d, a, b, c, m[4], 5) - c = GG(c, d, a, b, m[8], 9) - b = GG(b, c, d, a, m[12], 13) - a = GG(a, b, c, d, m[1], 3) - d = GG(d, a, b, c, m[5], 5) - c = GG(c, d, a, b, m[9], 9) - b = GG(b, c, d, a, m[13], 13) - a = GG(a, b, c, d, m[2], 3) - d = GG(d, a, b, c, m[6], 5) - c = GG(c, d, a, b, m[10], 9) - b = GG(b, c, d, a, m[14], 13) - a = GG(a, b, c, d, m[3], 3) - d = GG(d, a, b, c, m[7], 5) - c = GG(c, d, a, b, m[11], 9) - b = GG(b, c, d, a, m[15], 13) - - a = HH(a, b, c, d, m[0], 3) - d = HH(d, a, b, c, m[8], 9) - c = HH(c, d, a, b, m[4], 11) - b = HH(b, c, d, a, m[12], 15) - a = HH(a, b, c, d, m[2], 3) - d = HH(d, a, b, c, m[10], 9) - c = HH(c, d, a, b, m[6], 11) - b = HH(b, c, d, a, m[14], 15) - a = HH(a, b, c, d, m[1], 3) - d = HH(d, a, b, c, m[9], 9) - c = HH(c, d, a, b, m[5], 11) - b = HH(b, c, d, a, m[13], 15) - a = HH(a, b, c, d, m[3], 3) - d = HH(d, a, b, c, m[11], 9) - c = HH(c, d, a, b, m[7], 11) - b = HH(b, c, d, a, m[15], 15) - - ctx.state[0] += a - ctx.state[1] += b - ctx.state[2] += c - ctx.state[3] += d -} diff --git a/core/crypto/md5/md5.odin b/core/crypto/md5/md5.odin deleted file mode 100644 index 858480b04..000000000 --- a/core/crypto/md5/md5.odin +++ /dev/null @@ -1,285 +0,0 @@ -package md5 - -/* - Copyright 2021 zhibog - Made available under the BSD-3 license. - - List of contributors: - zhibog, dotbmp: Initial implementation. - - Implementation of the MD5 hashing algorithm, as defined in RFC 1321 -*/ - -import "core:mem" -import "core:os" -import "core:io" - -import "../util" - -/* - High level API -*/ - -DIGEST_SIZE :: 16 - -// hash_string will hash the given input and return the -// computed hash -hash_string :: proc(data: string) -> [DIGEST_SIZE]byte { - return hash_bytes(transmute([]byte)(data)) -} - -// hash_bytes will hash the given input and return the -// computed hash -hash_bytes :: proc(data: []byte) -> [DIGEST_SIZE]byte { - hash: [DIGEST_SIZE]byte - ctx: Md5_Context - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE, "Size of destination buffer is smaller than the digest size") - ctx: Md5_Context - init(&ctx) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream will read the stream in chunks and compute a -// hash from its contents -hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { - hash: [DIGEST_SIZE]byte - ctx: Md5_Context - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file will read the file provided by the given handle -// and compute a hash -hash_file :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE]byte, bool) { - if !load_at_once { - return hash_stream(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes(buf[:]), ok - } - } - return [DIGEST_SIZE]byte{}, false -} - -hash :: proc { - hash_stream, - hash_file, - hash_bytes, - hash_string, - hash_bytes_to_buffer, - hash_string_to_buffer, -} - -/* - Low level API -*/ - -init :: proc(ctx: ^Md5_Context) { - ctx.state[0] = 0x67452301 - ctx.state[1] = 0xefcdab89 - ctx.state[2] = 0x98badcfe - ctx.state[3] = 0x10325476 -} - -update :: proc(ctx: ^Md5_Context, data: []byte) { - for i := 0; i < len(data); i += 1 { - ctx.data[ctx.datalen] = data[i] - ctx.datalen += 1 - if(ctx.datalen == BLOCK_SIZE) { - transform(ctx, ctx.data[:]) - ctx.bitlen += 512 - ctx.datalen = 0 - } - } -} - -final :: proc(ctx: ^Md5_Context, hash: []byte){ - i : u32 - i = ctx.datalen - - if ctx.datalen < 56 { - ctx.data[i] = 0x80 - i += 1 - for i < 56 { - ctx.data[i] = 0x00 - i += 1 - } - } else if ctx.datalen >= 56 { - ctx.data[i] = 0x80 - i += 1 - for i < BLOCK_SIZE { - ctx.data[i] = 0x00 - i += 1 - } - transform(ctx, ctx.data[:]) - mem.set(&ctx.data, 0, 56) - } - - ctx.bitlen += u64(ctx.datalen * 8) - ctx.data[56] = byte(ctx.bitlen) - ctx.data[57] = byte(ctx.bitlen >> 8) - ctx.data[58] = byte(ctx.bitlen >> 16) - ctx.data[59] = byte(ctx.bitlen >> 24) - ctx.data[60] = byte(ctx.bitlen >> 32) - ctx.data[61] = byte(ctx.bitlen >> 40) - ctx.data[62] = byte(ctx.bitlen >> 48) - ctx.data[63] = byte(ctx.bitlen >> 56) - transform(ctx, ctx.data[:]) - - for i = 0; i < 4; i += 1 { - hash[i] = byte(ctx.state[0] >> (i * 8)) & 0x000000ff - hash[i + 4] = byte(ctx.state[1] >> (i * 8)) & 0x000000ff - hash[i + 8] = byte(ctx.state[2] >> (i * 8)) & 0x000000ff - hash[i + 12] = byte(ctx.state[3] >> (i * 8)) & 0x000000ff - } -} - -/* - MD4 implementation -*/ - -BLOCK_SIZE :: 64 - -Md5_Context :: struct { - data: [BLOCK_SIZE]byte, - state: [4]u32, - bitlen: u64, - datalen: u32, -} - -/* - @note(zh): F, G, H and I, as mentioned in the RFC, have been inlined into FF, GG, HH - and II respectively, instead of declaring them separately. -*/ - -FF :: #force_inline proc "contextless" (a, b, c, d, m: u32, s: int, t: u32) -> u32 { - return b + util.ROTL32(a + ((b & c) | (~b & d)) + m + t, s) -} - -GG :: #force_inline proc "contextless" (a, b, c, d, m: u32, s: int, t: u32) -> u32 { - return b + util.ROTL32(a + ((b & d) | (c & ~d)) + m + t, s) -} - -HH :: #force_inline proc "contextless" (a, b, c, d, m: u32, s: int, t: u32) -> u32 { - return b + util.ROTL32(a + (b ~ c ~ d) + m + t, s) -} - -II :: #force_inline proc "contextless" (a, b, c, d, m: u32, s: int, t: u32) -> u32 { - return b + util.ROTL32(a + (c ~ (b | ~d)) + m + t, s) -} - -transform :: proc(ctx: ^Md5_Context, data: []byte) { - i, j: u32 - m: [DIGEST_SIZE]u32 - - for i, j = 0, 0; i < DIGEST_SIZE; i+=1 { - m[i] = u32(data[j]) + u32(data[j + 1]) << 8 + u32(data[j + 2]) << 16 + u32(data[j + 3]) << 24 - j += 4 - } - - a := ctx.state[0] - b := ctx.state[1] - c := ctx.state[2] - d := ctx.state[3] - - a = FF(a, b, c, d, m[0], 7, 0xd76aa478) - d = FF(d, a, b, c, m[1], 12, 0xe8c7b756) - c = FF(c, d, a, b, m[2], 17, 0x242070db) - b = FF(b, c, d, a, m[3], 22, 0xc1bdceee) - a = FF(a, b, c, d, m[4], 7, 0xf57c0faf) - d = FF(d, a, b, c, m[5], 12, 0x4787c62a) - c = FF(c, d, a, b, m[6], 17, 0xa8304613) - b = FF(b, c, d, a, m[7], 22, 0xfd469501) - a = FF(a, b, c, d, m[8], 7, 0x698098d8) - d = FF(d, a, b, c, m[9], 12, 0x8b44f7af) - c = FF(c, d, a, b, m[10], 17, 0xffff5bb1) - b = FF(b, c, d, a, m[11], 22, 0x895cd7be) - a = FF(a, b, c, d, m[12], 7, 0x6b901122) - d = FF(d, a, b, c, m[13], 12, 0xfd987193) - c = FF(c, d, a, b, m[14], 17, 0xa679438e) - b = FF(b, c, d, a, m[15], 22, 0x49b40821) - - a = GG(a, b, c, d, m[1], 5, 0xf61e2562) - d = GG(d, a, b, c, m[6], 9, 0xc040b340) - c = GG(c, d, a, b, m[11], 14, 0x265e5a51) - b = GG(b, c, d, a, m[0], 20, 0xe9b6c7aa) - a = GG(a, b, c, d, m[5], 5, 0xd62f105d) - d = GG(d, a, b, c, m[10], 9, 0x02441453) - c = GG(c, d, a, b, m[15], 14, 0xd8a1e681) - b = GG(b, c, d, a, m[4], 20, 0xe7d3fbc8) - a = GG(a, b, c, d, m[9], 5, 0x21e1cde6) - d = GG(d, a, b, c, m[14], 9, 0xc33707d6) - c = GG(c, d, a, b, m[3], 14, 0xf4d50d87) - b = GG(b, c, d, a, m[8], 20, 0x455a14ed) - a = GG(a, b, c, d, m[13], 5, 0xa9e3e905) - d = GG(d, a, b, c, m[2], 9, 0xfcefa3f8) - c = GG(c, d, a, b, m[7], 14, 0x676f02d9) - b = GG(b, c, d, a, m[12], 20, 0x8d2a4c8a) - - a = HH(a, b, c, d, m[5], 4, 0xfffa3942) - d = HH(d, a, b, c, m[8], 11, 0x8771f681) - c = HH(c, d, a, b, m[11], 16, 0x6d9d6122) - b = HH(b, c, d, a, m[14], 23, 0xfde5380c) - a = HH(a, b, c, d, m[1], 4, 0xa4beea44) - d = HH(d, a, b, c, m[4], 11, 0x4bdecfa9) - c = HH(c, d, a, b, m[7], 16, 0xf6bb4b60) - b = HH(b, c, d, a, m[10], 23, 0xbebfbc70) - a = HH(a, b, c, d, m[13], 4, 0x289b7ec6) - d = HH(d, a, b, c, m[0], 11, 0xeaa127fa) - c = HH(c, d, a, b, m[3], 16, 0xd4ef3085) - b = HH(b, c, d, a, m[6], 23, 0x04881d05) - a = HH(a, b, c, d, m[9], 4, 0xd9d4d039) - d = HH(d, a, b, c, m[12], 11, 0xe6db99e5) - c = HH(c, d, a, b, m[15], 16, 0x1fa27cf8) - b = HH(b, c, d, a, m[2], 23, 0xc4ac5665) - - a = II(a, b, c, d, m[0], 6, 0xf4292244) - d = II(d, a, b, c, m[7], 10, 0x432aff97) - c = II(c, d, a, b, m[14], 15, 0xab9423a7) - b = II(b, c, d, a, m[5], 21, 0xfc93a039) - a = II(a, b, c, d, m[12], 6, 0x655b59c3) - d = II(d, a, b, c, m[3], 10, 0x8f0ccc92) - c = II(c, d, a, b, m[10], 15, 0xffeff47d) - b = II(b, c, d, a, m[1], 21, 0x85845dd1) - a = II(a, b, c, d, m[8], 6, 0x6fa87e4f) - d = II(d, a, b, c, m[15], 10, 0xfe2ce6e0) - c = II(c, d, a, b, m[6], 15, 0xa3014314) - b = II(b, c, d, a, m[13], 21, 0x4e0811a1) - a = II(a, b, c, d, m[4], 6, 0xf7537e82) - d = II(d, a, b, c, m[11], 10, 0xbd3af235) - c = II(c, d, a, b, m[2], 15, 0x2ad7d2bb) - b = II(b, c, d, a, m[9], 21, 0xeb86d391) - - ctx.state[0] += a - ctx.state[1] += b - ctx.state[2] += c - ctx.state[3] += d -} diff --git a/core/crypto/pbkdf2/pbkdf2.odin b/core/crypto/pbkdf2/pbkdf2.odin new file mode 100644 index 000000000..8bb5cb73e --- /dev/null +++ b/core/crypto/pbkdf2/pbkdf2.odin @@ -0,0 +1,122 @@ +/* +package pbkdf2 implements the PBKDF2 password-based key derivation function. + +See: [[ https://www.rfc-editor.org/rfc/rfc2898 ]] +*/ +package pbkdf2 + +import "core:crypto/hash" +import "core:crypto/hmac" +import "core:encoding/endian" +import "core:mem" + +// derive invokes PBKDF2-HMAC with the specified hash algorithm, password, +// salt, iteration count, and outputs the derived key to dst. +derive :: proc( + hmac_hash: hash.Algorithm, + password: []byte, + salt: []byte, + iterations: u32, + dst: []byte, +) { + h_len := hash.DIGEST_SIZES[hmac_hash] + + // 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long" + // and stop. + + dk_len := len(dst) + switch { + case dk_len == 0: + return + case u64(dk_len) > u64(max(u32)) * u64(h_len): + // This is so beyond anything that is practical or reasonable, + // so just panic instead of returning an error. + panic("crypto/pbkdf2: derived key too long") + case: + } + + // 2. Let l be the number of hLen-octet blocks in the derived key, + // rounding up, and let r be the number of octets in the last block. + + l := dk_len / h_len // Don't need to round up. + r := dk_len % h_len + + // 3. For each block of the derived key apply the function F defined + // below to the password P, the salt S, the iteration count c, and + // the block index to compute the block. + // + // 4. Concatenate the blocks and extract the first dkLen octets to + // produce a derived key DK. + // + // 5. Output the derived key DK. + + // Each iteration of F is always `PRF (P, ...)`, so instantiate the + // PRF, and clone since memcpy is faster than having to re-initialize + // HMAC repeatedly. + + base: hmac.Context + defer hmac.reset(&base) + + hmac.init(&base, hmac_hash, password) + + // Process all of the blocks that will be written directly to dst. + dst_blk := dst + for i in 1 ..= l { // F expects i starting at 1. + _F(&base, salt, iterations, u32(i), dst_blk[:h_len]) + dst_blk = dst_blk[h_len:] + } + + // Instead of rounding l up, just proceass the one extra block iff + // r != 0. + if r > 0 { + tmp: [hash.MAX_DIGEST_SIZE]byte + blk := tmp[:h_len] + defer mem.zero_explicit(raw_data(blk), h_len) + + _F(&base, salt, iterations, u32(l + 1), blk) + copy(dst_blk, blk) + } +} + +@(private) +_F :: proc(base: ^hmac.Context, salt: []byte, c: u32, i: u32, dst_blk: []byte) { + h_len := len(dst_blk) + + tmp: [hash.MAX_DIGEST_SIZE]byte + u := tmp[:h_len] + defer mem.zero_explicit(raw_data(u), h_len) + + // F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c + // + // where + // + // U_1 = PRF (P, S || INT (i)) , + // U_2 = PRF (P, U_1) , + // ... + // U_c = PRF (P, U_{c-1}) . + // + // Here, INT (i) is a four-octet encoding of the integer i, most + // significant octet first. + + prf: hmac.Context + + // U_1: PRF (P, S || INT (i)) + hmac.clone(&prf, base) + hmac.update(&prf, salt) + endian.unchecked_put_u32be(u, i) // Use u as scratch space. + hmac.update(&prf, u[:4]) + hmac.final(&prf, u) + copy(dst_blk, u) + + // U_2 ... U_c: U_n = PRF (P, U_(n-1)) + for _ in 1 ..< c { + hmac.clone(&prf, base) + hmac.update(&prf, u) + hmac.final(&prf, u) + + // XOR dst_blk and u. + for v, i in u { + dst_blk[i] ~= v + } + } +} diff --git a/core/crypto/poly1305/poly1305.odin b/core/crypto/poly1305/poly1305.odin index ab320c80c..ea0e6c907 100644 --- a/core/crypto/poly1305/poly1305.odin +++ b/core/crypto/poly1305/poly1305.odin @@ -1,17 +1,31 @@ +/* +package poly1305 implements the Poly1305 one-time MAC algorithm. + +See: +- [[ https://datatracker.ietf.org/doc/html/rfc8439 ]] +*/ package poly1305 import "core:crypto" -import "core:crypto/util" import field "core:crypto/_fiat/field_poly1305" +import "core:encoding/endian" +import "core:math/bits" import "core:mem" +// KEY_SIZE is the Poly1305 key size in bytes. KEY_SIZE :: 32 +// TAG_SIZE is the Poly1305 tag size in bytes. TAG_SIZE :: 16 @(private) _BLOCK_SIZE :: 16 -sum :: proc (dst, msg, key: []byte) { +// sum will compute the Poly1305 MAC with the key over msg, and write +// the computed tag to dst. It requires that the dst buffer is the tag +// size. +// +// The key SHOULD be unique and MUST be unpredictable for each invocation. +sum :: proc(dst, msg, key: []byte) { ctx: Context = --- init(&ctx, key) @@ -19,13 +33,12 @@ sum :: proc (dst, msg, key: []byte) { final(&ctx, dst) } -verify :: proc (tag, msg, key: []byte) -> bool { +// verify will verify the Poly1305 tag computed with the key over msg and +// return true iff the tag is valid. It requires that the tag is correctly +// sized. +verify :: proc(tag, msg, key: []byte) -> bool { ctx: Context = --- - derived_tag: [16]byte = --- - - if len(tag) != TAG_SIZE { - panic("crypto/poly1305: invalid tag size") - } + derived_tag: [TAG_SIZE]byte = --- init(&ctx, key) update(&ctx, msg) @@ -34,30 +47,32 @@ verify :: proc (tag, msg, key: []byte) -> bool { return crypto.compare_constant_time(derived_tag[:], tag) == 1 } +// Context is a Poly1305 instance. Context :: struct { - _r: field.Tight_Field_Element, - _a: field.Tight_Field_Element, - _s: field.Tight_Field_Element, - - _buffer: [_BLOCK_SIZE]byte, - _leftover: int, - + _r: field.Tight_Field_Element, + _a: field.Tight_Field_Element, + _s: [2]u64, + _buffer: [_BLOCK_SIZE]byte, + _leftover: int, _is_initialized: bool, } -init :: proc (ctx: ^Context, key: []byte) { +// init initializes a Context with the specified key. The key SHOULD be +// unique and MUST be unpredictable for each invocation. +init :: proc(ctx: ^Context, key: []byte) { if len(key) != KEY_SIZE { panic("crypto/poly1305: invalid key size") } // r = le_bytes_to_num(key[0..15]) // r = clamp(r) (r &= 0xffffffc0ffffffc0ffffffc0fffffff) - tmp_lo := util.U64_LE(key[0:8]) & 0x0ffffffc0fffffff - tmp_hi := util.U64_LE(key[8:16]) & 0xffffffc0ffffffc + tmp_lo := endian.unchecked_get_u64le(key[0:]) & 0x0ffffffc0fffffff + tmp_hi := endian.unchecked_get_u64le(key[8:]) & 0x0ffffffc0ffffffc field.fe_from_u64s(&ctx._r, tmp_lo, tmp_hi) // s = le_bytes_to_num(key[16..31]) - field.fe_from_bytes(&ctx._s, key[16:32], 0) + ctx._s[0] = endian.unchecked_get_u64le(key[16:]) + ctx._s[1] = endian.unchecked_get_u64le(key[24:]) // a = 0 field.fe_zero(&ctx._a) @@ -68,7 +83,8 @@ init :: proc (ctx: ^Context, key: []byte) { ctx._is_initialized = true } -update :: proc (ctx: ^Context, data: []byte) { +// update adds more data to the Context. +update :: proc(ctx: ^Context, data: []byte) { assert(ctx._is_initialized) msg := data @@ -105,8 +121,11 @@ update :: proc (ctx: ^Context, data: []byte) { } } -final :: proc (ctx: ^Context, dst: []byte) { +// final finalizes the Context, writes the tag to dst, and calls +// reset on the Context. +final :: proc(ctx: ^Context, dst: []byte) { assert(ctx._is_initialized) + defer reset(ctx) if len(dst) != TAG_SIZE { panic("poly1305: invalid destination tag size") @@ -121,19 +140,25 @@ final :: proc (ctx: ^Context, dst: []byte) { _blocks(ctx, ctx._buffer[:], true) } - // a += s - field.fe_add(field.fe_relax_cast(&ctx._a), &ctx._a, &ctx._s) // _a unreduced - field.fe_carry(&ctx._a, field.fe_relax_cast(&ctx._a)) // _a reduced - - // return num_to_16_le_bytes(a) + // a += s (NOT mod p) tmp: [32]byte = --- field.fe_to_bytes(&tmp, &ctx._a) - copy_slice(dst, tmp[0:16]) - reset(ctx) + c: u64 + lo := endian.unchecked_get_u64le(tmp[0:]) + hi := endian.unchecked_get_u64le(tmp[8:]) + + lo, c = bits.add_u64(lo, ctx._s[0], 0) + hi, _ = bits.add_u64(hi, ctx._s[1], c) + + // return num_to_16_le_bytes(a) + endian.unchecked_put_u64le(dst[0:], lo) + endian.unchecked_put_u64le(dst[8:], hi) } -reset :: proc (ctx: ^Context) { +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. +reset :: proc(ctx: ^Context) { mem.zero_explicit(&ctx._r, size_of(ctx._r)) mem.zero_explicit(&ctx._a, size_of(ctx._a)) mem.zero_explicit(&ctx._s, size_of(ctx._s)) @@ -143,7 +168,7 @@ reset :: proc (ctx: ^Context) { } @(private) -_blocks :: proc (ctx: ^Context, msg: []byte, final := false) { +_blocks :: proc "contextless" (ctx: ^Context, msg: []byte, final := false) { n: field.Tight_Field_Element = --- final_byte := byte(!final) @@ -151,7 +176,7 @@ _blocks :: proc (ctx: ^Context, msg: []byte, final := false) { data_len := len(data) for data_len >= _BLOCK_SIZE { // n = le_bytes_to_num(msg[((i-1)*16)..*i*16] | [0x01]) - field.fe_from_bytes(&n, data[:_BLOCK_SIZE], final_byte, false) + field.fe_from_bytes(&n, data[:_BLOCK_SIZE], final_byte) // a += n field.fe_add(field.fe_relax_cast(&ctx._a), &ctx._a, &n) // _a unreduced diff --git a/core/crypto/rand_bsd.odin b/core/crypto/rand_bsd.odin new file mode 100644 index 000000000..78a6fcaaf --- /dev/null +++ b/core/crypto/rand_bsd.odin @@ -0,0 +1,15 @@ +#+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)) +} diff --git a/core/crypto/rand_darwin.odin b/core/crypto/rand_darwin.odin new file mode 100644 index 000000000..df474bc4c --- /dev/null +++ b/core/crypto/rand_darwin.odin @@ -0,0 +1,17 @@ +package crypto + +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)) + fmt.panicf("crypto/rand_bytes: SecRandomCopyBytes returned non-zero result: %v %s", err, msg) + } +} diff --git a/core/crypto/rand_generic.odin b/core/crypto/rand_generic.odin index fde91f85a..ef578f5c0 100644 --- a/core/crypto/rand_generic.odin +++ b/core/crypto/rand_generic.odin @@ -1,7 +1,15 @@ +#+build !linux +#+build !windows +#+build !openbsd +#+build !freebsd +#+build !netbsd +#+build !darwin +#+build !js package crypto -when ODIN_OS != .Linux && ODIN_OS != .OpenBSD && ODIN_OS != .Windows && ODIN_OS != .JS { - _rand_bytes :: proc(dst: []byte) { - unimplemented("crypto: rand_bytes not supported on this OS") - } +HAS_RAND_BYTES :: false + +@(private) +_rand_bytes :: proc(dst: []byte) { + unimplemented("crypto: rand_bytes not supported on this OS") } diff --git a/core/crypto/rand_js.odin b/core/crypto/rand_js.odin index 353b1e6b9..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 diff --git a/core/crypto/rand_linux.odin b/core/crypto/rand_linux.odin index 86fc425d6..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,7 +32,7 @@ _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:] diff --git a/core/crypto/rand_openbsd.odin b/core/crypto/rand_openbsd.odin deleted file mode 100644 index bae97e8f0..000000000 --- a/core/crypto/rand_openbsd.odin +++ /dev/null @@ -1,12 +0,0 @@ -package crypto - -import "core:c" - -foreign import libc "system:c" -foreign libc { - arc4random_buf :: proc "c" (buf: rawptr, nbytes: c.size_t) --- -} - -_rand_bytes :: proc (dst: []byte) { - arc4random_buf(raw_data(dst), len(dst)) -} diff --git a/core/crypto/rand_windows.odin b/core/crypto/rand_windows.odin index 53b58c776..83a976e38 100644 --- a/core/crypto/rand_windows.odin +++ b/core/crypto/rand_windows.odin @@ -4,20 +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)) + ret := os.Platform_Error(win32.BCryptGenRandom(nil, raw_data(dst), u32(len(dst)), win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG)) + if ret != nil { + #partial switch ret { + case os.ERROR_INVALID_HANDLE: + // The handle to the first parameter is invalid. + // This should not happen here, since we explicitly pass nil to it + 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) } } } diff --git a/core/crypto/ripemd/ripemd.odin b/core/crypto/ripemd/ripemd.odin deleted file mode 100644 index f9edb121b..000000000 --- a/core/crypto/ripemd/ripemd.odin +++ /dev/null @@ -1,919 +0,0 @@ -package ripemd - -/* - Copyright 2021 zhibog - Made available under the BSD-3 license. - - List of contributors: - zhibog, dotbmp: Initial implementation. - - Implementation for the RIPEMD hashing algorithm as defined in -*/ - -import "core:os" -import "core:io" - -import "../util" - -/* - High level API -*/ - -DIGEST_SIZE_128 :: 16 -DIGEST_SIZE_160 :: 20 -DIGEST_SIZE_256 :: 32 -DIGEST_SIZE_320 :: 40 - -// hash_string_128 will hash the given input and return the -// computed hash -hash_string_128 :: proc(data: string) -> [DIGEST_SIZE_128]byte { - return hash_bytes_128(transmute([]byte)(data)) -} - -// hash_bytes_128 will hash the given input and return the -// computed hash -hash_bytes_128 :: proc(data: []byte) -> [DIGEST_SIZE_128]byte { - hash: [DIGEST_SIZE_128]byte - ctx: Ripemd128_Context - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_128 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_128 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_128(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_128 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_128 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_128, "Size of destination buffer is smaller than the digest size") - ctx: Ripemd128_Context - init(&ctx) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_128 will read the stream in chunks and compute a -// hash from its contents -hash_stream_128 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { - hash: [DIGEST_SIZE_128]byte - ctx: Ripemd128_Context - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_128 will read the file provided by the given handle -// and compute a hash -hash_file_128 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_128]byte, bool) { - if !load_at_once { - return hash_stream_128(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_128(buf[:]), ok - } - } - return [DIGEST_SIZE_128]byte{}, false -} - -hash_128 :: proc { - hash_stream_128, - hash_file_128, - hash_bytes_128, - hash_string_128, - hash_bytes_to_buffer_128, - hash_string_to_buffer_128, -} - -// hash_string_160 will hash the given input and return the -// computed hash -hash_string_160 :: proc(data: string) -> [DIGEST_SIZE_160]byte { - return hash_bytes_160(transmute([]byte)(data)) -} - -// hash_bytes_160 will hash the given input and return the -// computed hash -hash_bytes_160 :: proc(data: []byte) -> [DIGEST_SIZE_160]byte { - hash: [DIGEST_SIZE_160]byte - ctx: Ripemd160_Context - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_160 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_160 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_160(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_160 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_160 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_160, "Size of destination buffer is smaller than the digest size") - ctx: Ripemd160_Context - init(&ctx) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_160 will read the stream in chunks and compute a -// hash from its contents -hash_stream_160 :: proc(s: io.Stream) -> ([DIGEST_SIZE_160]byte, bool) { - hash: [DIGEST_SIZE_160]byte - ctx: Ripemd160_Context - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_160 will read the file provided by the given handle -// and compute a hash -hash_file_160 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_160]byte, bool) { - if !load_at_once { - return hash_stream_160(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_160(buf[:]), ok - } - } - return [DIGEST_SIZE_160]byte{}, false -} - -hash_160 :: proc { - hash_stream_160, - hash_file_160, - hash_bytes_160, - hash_string_160, - hash_bytes_to_buffer_160, - hash_string_to_buffer_160, -} - -// hash_string_256 will hash the given input and return the -// computed hash -hash_string_256 :: proc(data: string) -> [DIGEST_SIZE_256]byte { - return hash_bytes_256(transmute([]byte)(data)) -} - -// hash_bytes_256 will hash the given input and return the -// computed hash -hash_bytes_256 :: proc(data: []byte) -> [DIGEST_SIZE_256]byte { - hash: [DIGEST_SIZE_256]byte - ctx: Ripemd256_Context - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_256 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_256 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_256(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_256 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_256 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_256, "Size of destination buffer is smaller than the digest size") - ctx: Ripemd256_Context - init(&ctx) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_256 will read the stream in chunks and compute a -// hash from its contents -hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { - hash: [DIGEST_SIZE_256]byte - ctx: Ripemd256_Context - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_256 will read the file provided by the given handle -// and compute a hash -hash_file_256 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_256]byte, bool) { - if !load_at_once { - return hash_stream_256(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_256(buf[:]), ok - } - } - return [DIGEST_SIZE_256]byte{}, false -} - -hash_256 :: proc { - hash_stream_256, - hash_file_256, - hash_bytes_256, - hash_string_256, - hash_bytes_to_buffer_256, - hash_string_to_buffer_256, -} - -// hash_string_320 will hash the given input and return the -// computed hash -hash_string_320 :: proc(data: string) -> [DIGEST_SIZE_320]byte { - return hash_bytes_320(transmute([]byte)(data)) -} - -// hash_bytes_320 will hash the given input and return the -// computed hash -hash_bytes_320 :: proc(data: []byte) -> [DIGEST_SIZE_320]byte { - hash: [DIGEST_SIZE_320]byte - ctx: Ripemd320_Context - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_320 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_320 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_320(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_320 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_320 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_320, "Size of destination buffer is smaller than the digest size") - ctx: Ripemd320_Context - init(&ctx) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_320 will read the stream in chunks and compute a -// hash from its contents -hash_stream_320 :: proc(s: io.Stream) -> ([DIGEST_SIZE_320]byte, bool) { - hash: [DIGEST_SIZE_320]byte - ctx: Ripemd320_Context - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_320 will read the file provided by the given handle -// and compute a hash -hash_file_320 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_320]byte, bool) { - if !load_at_once { - return hash_stream_320(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_320(buf[:]), ok - } - } - return [DIGEST_SIZE_320]byte{}, false -} - -hash_320 :: proc { - hash_stream_320, - hash_file_320, - hash_bytes_320, - hash_string_320, - hash_bytes_to_buffer_320, - hash_string_to_buffer_320, -} - -/* - Low level API -*/ - -init :: proc(ctx: ^$T) { - when T == Ripemd128_Context { - ctx.s[0], ctx.s[1], ctx.s[2], ctx.s[3] = S0, S1, S2, S3 - } else when T == Ripemd160_Context { - ctx.s[0], ctx.s[1], ctx.s[2], ctx.s[3], ctx.s[4] = S0, S1, S2, S3, S4 - } else when T == Ripemd256_Context { - ctx.s[0], ctx.s[1], ctx.s[2], ctx.s[3] = S0, S1, S2, S3 - ctx.s[4], ctx.s[5], ctx.s[6], ctx.s[7] = S5, S6, S7, S8 - } else when T == Ripemd320_Context { - ctx.s[0], ctx.s[1], ctx.s[2], ctx.s[3], ctx.s[4] = S0, S1, S2, S3, S4 - ctx.s[5], ctx.s[6], ctx.s[7], ctx.s[8], ctx.s[9] = S5, S6, S7, S8, S9 - } -} - -update :: proc(ctx: ^$T, data: []byte) { - ctx.tc += u64(len(data)) - data := data - if ctx.nx > 0 { - n := len(data) - - when T == Ripemd128_Context { - if n > RIPEMD_128_BLOCK_SIZE - ctx.nx { - n = RIPEMD_128_BLOCK_SIZE - ctx.nx - } - } else when T == Ripemd160_Context { - if n > RIPEMD_160_BLOCK_SIZE - ctx.nx { - n = RIPEMD_160_BLOCK_SIZE - ctx.nx - } - } else when T == Ripemd256_Context{ - if n > RIPEMD_256_BLOCK_SIZE - ctx.nx { - n = RIPEMD_256_BLOCK_SIZE - ctx.nx - } - } else when T == Ripemd320_Context{ - if n > RIPEMD_320_BLOCK_SIZE - ctx.nx { - n = RIPEMD_320_BLOCK_SIZE - ctx.nx - } - } - - for i := 0; i < n; i += 1 { - ctx.x[ctx.nx + i] = data[i] - } - - ctx.nx += n - when T == Ripemd128_Context { - if ctx.nx == RIPEMD_128_BLOCK_SIZE { - block(ctx, ctx.x[0:]) - ctx.nx = 0 - } - } else when T == Ripemd160_Context { - if ctx.nx == RIPEMD_160_BLOCK_SIZE { - block(ctx, ctx.x[0:]) - ctx.nx = 0 - } - } else when T == Ripemd256_Context{ - if ctx.nx == RIPEMD_256_BLOCK_SIZE { - block(ctx, ctx.x[0:]) - ctx.nx = 0 - } - } else when T == Ripemd320_Context{ - if ctx.nx == RIPEMD_320_BLOCK_SIZE { - block(ctx, ctx.x[0:]) - ctx.nx = 0 - } - } - data = data[n:] - } - n := block(ctx, data) - data = data[n:] - if len(data) > 0 { - ctx.nx = copy(ctx.x[:], data) - } -} - -final :: proc(ctx: ^$T, hash: []byte) { - d := ctx - tc := d.tc - tmp: [64]byte - tmp[0] = 0x80 - - if tc % 64 < 56 { - update(d, tmp[0:56 - tc % 64]) - } else { - update(d, tmp[0:64 + 56 - tc % 64]) - } - - tc <<= 3 - for i : u32 = 0; i < 8; i += 1 { - tmp[i] = byte(tc >> (8 * i)) - } - - update(d, tmp[0:8]) - - when T == Ripemd128_Context { - size :: RIPEMD_128_SIZE - } else when T == Ripemd160_Context { - size :: RIPEMD_160_SIZE - } else when T == Ripemd256_Context{ - size :: RIPEMD_256_SIZE - } else when T == Ripemd320_Context{ - size :: RIPEMD_320_SIZE - } - - digest: [size]byte - for s, i in d.s { - digest[i * 4] = byte(s) - digest[i * 4 + 1] = byte(s >> 8) - digest[i * 4 + 2] = byte(s >> 16) - digest[i * 4 + 3] = byte(s >> 24) - } - copy(hash[:], digest[:]) -} - - -/* - RIPEMD implementation -*/ - -Ripemd128_Context :: struct { - s: [4]u32, - x: [RIPEMD_128_BLOCK_SIZE]byte, - nx: int, - tc: u64, -} - -Ripemd160_Context :: struct { - s: [5]u32, - x: [RIPEMD_160_BLOCK_SIZE]byte, - nx: int, - tc: u64, -} - -Ripemd256_Context :: struct { - s: [8]u32, - x: [RIPEMD_256_BLOCK_SIZE]byte, - nx: int, - tc: u64, -} - -Ripemd320_Context :: struct { - s: [10]u32, - x: [RIPEMD_320_BLOCK_SIZE]byte, - nx: int, - tc: u64, -} - -RIPEMD_128_SIZE :: 16 -RIPEMD_128_BLOCK_SIZE :: 64 -RIPEMD_160_SIZE :: 20 -RIPEMD_160_BLOCK_SIZE :: 64 -RIPEMD_256_SIZE :: 32 -RIPEMD_256_BLOCK_SIZE :: 64 -RIPEMD_320_SIZE :: 40 -RIPEMD_320_BLOCK_SIZE :: 64 - -S0 :: 0x67452301 -S1 :: 0xefcdab89 -S2 :: 0x98badcfe -S3 :: 0x10325476 -S4 :: 0xc3d2e1f0 -S5 :: 0x76543210 -S6 :: 0xfedcba98 -S7 :: 0x89abcdef -S8 :: 0x01234567 -S9 :: 0x3c2d1e0f - -RIPEMD_128_N0 := [64]uint { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, - 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, - 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, -} - -RIPEMD_128_R0 := [64]uint { - 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, - 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, - 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, - 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, -} - -RIPEMD_128_N1 := [64]uint { - 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, - 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, - 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, - 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, -} - -RIPEMD_128_R1 := [64]uint { - 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, - 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, - 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, - 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, -} - -RIPEMD_160_N0 := [80]uint { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, - 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, - 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, - 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13, -} - -RIPEMD_160_R0 := [80]uint { - 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, - 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, - 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, - 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, - 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6, -} - -RIPEMD_160_N1 := [80]uint { - 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, - 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, - 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, - 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, - 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11, -} - -RIPEMD_160_R1 := [80]uint { - 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, - 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, - 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, - 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, - 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11, -} - -block :: #force_inline proc (ctx: ^$T, p: []byte) -> int { - when T == Ripemd128_Context { - return ripemd_128_block(ctx, p) - } - else when T == Ripemd160_Context { - return ripemd_160_block(ctx, p) - } - else when T == Ripemd256_Context { - return ripemd_256_block(ctx, p) - } - else when T == Ripemd320_Context { - return ripemd_320_block(ctx, p) - } -} - -ripemd_128_block :: proc(ctx: ^$T, p: []byte) -> int { - n := 0 - x: [16]u32 = --- - alpha: u32 = --- - p := p - for len(p) >= RIPEMD_128_BLOCK_SIZE { - a, b, c, d := ctx.s[0], ctx.s[1], ctx.s[2], ctx.s[3] - aa, bb, cc, dd := a, b, c, d - for i,j := 0, 0; i < 16; i, j = i+1, j+4 { - x[i] = u32(p[j]) | u32(p[j+1])<<8 | u32(p[j+2])<<16 | u32(p[j+3])<<24 - } - i := 0 - for i < 16 { - alpha = a + (b ~ c ~ d) + x[RIPEMD_128_N0[i]] - s := int(RIPEMD_128_R0[i]) - alpha = util.ROTL32(alpha, s) - a, b, c, d = d, alpha, b, c - alpha = aa + (bb & dd | cc &~ dd) + x[RIPEMD_128_N1[i]] + 0x50a28be6 - s = int(RIPEMD_128_R1[i]) - alpha = util.ROTL32(alpha, s) - aa, bb, cc, dd= dd, alpha, bb, cc - i += 1 - } - for i < 32 { - alpha = a + (d ~ (b & (c~d))) + x[RIPEMD_128_N0[i]] + 0x5a827999 - s := int(RIPEMD_128_R0[i]) - alpha = util.ROTL32(alpha, s) - a, b, c, d = d, alpha, b, c - alpha = aa + (dd ~ (bb | ~cc)) + x[RIPEMD_128_N1[i]] + 0x5c4dd124 - s = int(RIPEMD_128_R1[i]) - alpha = util.ROTL32(alpha, s) - aa, bb, cc, dd = dd, alpha, bb, cc - i += 1 - } - for i < 48 { - alpha = a + (d ~ (b | ~c)) + x[RIPEMD_128_N0[i]] + 0x6ed9eba1 - s := int(RIPEMD_128_R0[i]) - alpha = util.ROTL32(alpha, s) - a, b, c, d = d, alpha, b, c - alpha = aa + (dd ~ (bb & (cc~dd))) + x[RIPEMD_128_N1[i]] + 0x6d703ef3 - s = int(RIPEMD_128_R1[i]) - alpha = util.ROTL32(alpha, s) - aa, bb, cc, dd = dd, alpha, bb, cc - i += 1 - } - for i < 64 { - alpha = a + (c ~ (d & (b~c))) + x[RIPEMD_128_N0[i]] + 0x8f1bbcdc - s := int(RIPEMD_128_R0[i]) - alpha = util.ROTL32(alpha, s) - a, b, c, d = d, alpha, b, c - alpha = aa + (bb ~ cc ~ dd) + x[RIPEMD_128_N1[i]] - s = int(RIPEMD_128_R1[i]) - alpha = util.ROTL32(alpha, s) - aa, bb, cc, dd = dd, alpha, bb, cc - i += 1 - } - c = ctx.s[1] + c + dd - ctx.s[1] = ctx.s[2] + d + aa - ctx.s[2] = ctx.s[3] + a + bb - ctx.s[3] = ctx.s[0] + b + cc - ctx.s[0] = c - p = p[RIPEMD_128_BLOCK_SIZE:] - n += RIPEMD_128_BLOCK_SIZE - } - return n -} - -ripemd_160_block :: proc(ctx: ^$T, p: []byte) -> int { - n := 0 - x: [16]u32 = --- - alpha, beta: u32 = ---, --- - p := p - for len(p) >= RIPEMD_160_BLOCK_SIZE { - a, b, c, d, e := ctx.s[0], ctx.s[1], ctx.s[2], ctx.s[3], ctx.s[4] - aa, bb, cc, dd, ee := a, b, c, d, e - for i,j := 0, 0; i < 16; i, j = i+1, j+4 { - x[i] = u32(p[j]) | u32(p[j+1])<<8 | u32(p[j+2])<<16 | u32(p[j+3])<<24 - } - i := 0 - for i < 16 { - alpha = a + (b ~ c ~ d) + x[RIPEMD_160_N0[i]] - s := int(RIPEMD_160_R0[i]) - alpha = util.ROTL32(alpha, s) + e - beta = util.ROTL32(c, 10) - a, b, c, d, e = e, alpha, b, beta, d - alpha = aa + (bb ~ (cc | ~dd)) + x[RIPEMD_160_N1[i]] + 0x50a28be6 - s = int(RIPEMD_160_R1[i]) - alpha = util.ROTL32(alpha, s) + ee - beta = util.ROTL32(cc, 10) - aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd - i += 1 - } - for i < 32 { - alpha = a + (b&c | ~b&d) + x[RIPEMD_160_N0[i]] + 0x5a827999 - s := int(RIPEMD_160_R0[i]) - alpha = util.ROTL32(alpha, s) + e - beta = util.ROTL32(c, 10) - a, b, c, d, e = e, alpha, b, beta, d - alpha = aa + (bb&dd | cc&~dd) + x[RIPEMD_160_N1[i]] + 0x5c4dd124 - s = int(RIPEMD_160_R1[i]) - alpha = util.ROTL32(alpha, s) + ee - beta = util.ROTL32(cc, 10) - aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd - i += 1 - } - for i < 48 { - alpha = a + (b | ~c ~ d) + x[RIPEMD_160_N0[i]] + 0x6ed9eba1 - s := int(RIPEMD_160_R0[i]) - alpha = util.ROTL32(alpha, s) + e - beta = util.ROTL32(c, 10) - a, b, c, d, e = e, alpha, b, beta, d - alpha = aa + (bb | ~cc ~ dd) + x[RIPEMD_160_N1[i]] + 0x6d703ef3 - s = int(RIPEMD_160_R1[i]) - alpha = util.ROTL32(alpha, s) + ee - beta = util.ROTL32(cc, 10) - aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd - i += 1 - } - for i < 64 { - alpha = a + (b&d | c&~d) + x[RIPEMD_160_N0[i]] + 0x8f1bbcdc - s := int(RIPEMD_160_R0[i]) - alpha = util.ROTL32(alpha, s) + e - beta = util.ROTL32(c, 10) - a, b, c, d, e = e, alpha, b, beta, d - alpha = aa + (bb&cc | ~bb&dd) + x[RIPEMD_160_N1[i]] + 0x7a6d76e9 - s = int(RIPEMD_160_R1[i]) - alpha = util.ROTL32(alpha, s) + ee - beta = util.ROTL32(cc, 10) - aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd - i += 1 - } - for i < 80 { - alpha = a + (b ~ (c | ~d)) + x[RIPEMD_160_N0[i]] + 0xa953fd4e - s := int(RIPEMD_160_R0[i]) - alpha = util.ROTL32(alpha, s) + e - beta = util.ROTL32(c, 10) - a, b, c, d, e = e, alpha, b, beta, d - alpha = aa + (bb ~ cc ~ dd) + x[RIPEMD_160_N1[i]] - s = int(RIPEMD_160_R1[i]) - alpha = util.ROTL32(alpha, s) + ee - beta = util.ROTL32(cc, 10) - aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd - i += 1 - } - dd += c + ctx.s[1] - ctx.s[1] = ctx.s[2] + d + ee - ctx.s[2] = ctx.s[3] + e + aa - ctx.s[3] = ctx.s[4] + a + bb - ctx.s[4] = ctx.s[0] + b + cc - ctx.s[0] = dd - p = p[RIPEMD_160_BLOCK_SIZE:] - n += RIPEMD_160_BLOCK_SIZE - } - return n -} - -ripemd_256_block :: proc(ctx: ^$T, p: []byte) -> int { - n := 0 - x: [16]u32 = --- - alpha: u32 = --- - p := p - for len(p) >= RIPEMD_256_BLOCK_SIZE { - a, b, c, d := ctx.s[0], ctx.s[1], ctx.s[2], ctx.s[3] - aa, bb, cc, dd := ctx.s[4], ctx.s[5], ctx.s[6], ctx.s[7] - for i,j := 0, 0; i < 16; i, j = i+1, j+4 { - x[i] = u32(p[j]) | u32(p[j+1])<<8 | u32(p[j+2])<<16 | u32(p[j+3])<<24 - } - i := 0 - for i < 16 { - alpha = a + (b ~ c ~ d) + x[RIPEMD_128_N0[i]] - s := int(RIPEMD_128_R0[i]) - alpha = util.ROTL32(alpha, s) - a, b, c, d = d, alpha, b, c - alpha = aa + (bb & dd | cc &~ dd) + x[RIPEMD_128_N1[i]] + 0x50a28be6 - s = int(RIPEMD_128_R1[i]) - alpha = util.ROTL32(alpha, s) - aa, bb, cc, dd= dd, alpha, bb, cc - i += 1 - } - t := a - a = aa - aa = t - for i < 32 { - alpha = a + (d ~ (b & (c~d))) + x[RIPEMD_128_N0[i]] + 0x5a827999 - s := int(RIPEMD_128_R0[i]) - alpha = util.ROTL32(alpha, s) - a, b, c, d = d, alpha, b, c - alpha = aa + (dd ~ (bb | ~cc)) + x[RIPEMD_128_N1[i]] + 0x5c4dd124 - s = int(RIPEMD_128_R1[i]) - alpha = util.ROTL32(alpha, s) - aa, bb, cc, dd = dd, alpha, bb, cc - i += 1 - } - t = b - b = bb - bb = t - for i < 48 { - alpha = a + (d ~ (b | ~c)) + x[RIPEMD_128_N0[i]] + 0x6ed9eba1 - s := int(RIPEMD_128_R0[i]) - alpha = util.ROTL32(alpha, s) - a, b, c, d = d, alpha, b, c - alpha = aa + (dd ~ (bb & (cc~dd))) + x[RIPEMD_128_N1[i]] + 0x6d703ef3 - s = int(RIPEMD_128_R1[i]) - alpha = util.ROTL32(alpha, s) - aa, bb, cc, dd = dd, alpha, bb, cc - i += 1 - } - t = c - c = cc - cc = t - for i < 64 { - alpha = a + (c ~ (d & (b~c))) + x[RIPEMD_128_N0[i]] + 0x8f1bbcdc - s := int(RIPEMD_128_R0[i]) - alpha = util.ROTL32(alpha, s) - a, b, c, d = d, alpha, b, c - alpha = aa + (bb ~ cc ~ dd) + x[RIPEMD_128_N1[i]] - s = int(RIPEMD_128_R1[i]) - alpha = util.ROTL32(alpha, s) - aa, bb, cc, dd = dd, alpha, bb, cc - i += 1 - } - t = d - d = dd - dd = t - ctx.s[0] += a - ctx.s[1] += b - ctx.s[2] += c - ctx.s[3] += d - ctx.s[4] += aa - ctx.s[5] += bb - ctx.s[6] += cc - ctx.s[7] += dd - p = p[RIPEMD_256_BLOCK_SIZE:] - n += RIPEMD_256_BLOCK_SIZE - } - return n -} - -ripemd_320_block :: proc(ctx: ^$T, p: []byte) -> int { - n := 0 - x: [16]u32 = --- - alpha, beta: u32 = ---, --- - p := p - for len(p) >= RIPEMD_320_BLOCK_SIZE { - a, b, c, d, e := ctx.s[0], ctx.s[1], ctx.s[2], ctx.s[3], ctx.s[4] - aa, bb, cc, dd, ee := ctx.s[5], ctx.s[6], ctx.s[7], ctx.s[8], ctx.s[9] - for i,j := 0, 0; i < 16; i, j = i+1, j+4 { - x[i] = u32(p[j]) | u32(p[j+1])<<8 | u32(p[j+2])<<16 | u32(p[j+3])<<24 - } - i := 0 - for i < 16 { - alpha = a + (b ~ c ~ d) + x[RIPEMD_160_N0[i]] - s := int(RIPEMD_160_R0[i]) - alpha = util.ROTL32(alpha, s) + e - beta = util.ROTL32(c, 10) - a, b, c, d, e = e, alpha, b, beta, d - alpha = aa + (bb ~ (cc | ~dd)) + x[RIPEMD_160_N1[i]] + 0x50a28be6 - s = int(RIPEMD_160_R1[i]) - alpha = util.ROTL32(alpha, s) + ee - beta = util.ROTL32(cc, 10) - aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd - i += 1 - } - t := b - b = bb - bb = t - for i < 32 { - alpha = a + (b&c | ~b&d) + x[RIPEMD_160_N0[i]] + 0x5a827999 - s := int(RIPEMD_160_R0[i]) - alpha = util.ROTL32(alpha, s) + e - beta = util.ROTL32(c, 10) - a, b, c, d, e = e, alpha, b, beta, d - alpha = aa + (bb&dd | cc&~dd) + x[RIPEMD_160_N1[i]] + 0x5c4dd124 - s = int(RIPEMD_160_R1[i]) - alpha = util.ROTL32(alpha, s) + ee - beta = util.ROTL32(cc, 10) - aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd - i += 1 - } - t = d - d = dd - dd = t - for i < 48 { - alpha = a + (b | ~c ~ d) + x[RIPEMD_160_N0[i]] + 0x6ed9eba1 - s := int(RIPEMD_160_R0[i]) - alpha = util.ROTL32(alpha, s) + e - beta = util.ROTL32(c, 10) - a, b, c, d, e = e, alpha, b, beta, d - alpha = aa + (bb | ~cc ~ dd) + x[RIPEMD_160_N1[i]] + 0x6d703ef3 - s = int(RIPEMD_160_R1[i]) - alpha = util.ROTL32(alpha, s) + ee - beta = util.ROTL32(cc, 10) - aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd - i += 1 - } - t = a - a = aa - aa = t - for i < 64 { - alpha = a + (b&d | c&~d) + x[RIPEMD_160_N0[i]] + 0x8f1bbcdc - s := int(RIPEMD_160_R0[i]) - alpha = util.ROTL32(alpha, s) + e - beta = util.ROTL32(c, 10) - a, b, c, d, e = e, alpha, b, beta, d - alpha = aa + (bb&cc | ~bb&dd) + x[RIPEMD_160_N1[i]] + 0x7a6d76e9 - s = int(RIPEMD_160_R1[i]) - alpha = util.ROTL32(alpha, s) + ee - beta = util.ROTL32(cc, 10) - aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd - i += 1 - } - t = c - c = cc - cc = t - for i < 80 { - alpha = a + (b ~ (c | ~d)) + x[RIPEMD_160_N0[i]] + 0xa953fd4e - s := int(RIPEMD_160_R0[i]) - alpha = util.ROTL32(alpha, s) + e - beta = util.ROTL32(c, 10) - a, b, c, d, e = e, alpha, b, beta, d - alpha = aa + (bb ~ cc ~ dd) + x[RIPEMD_160_N1[i]] - s = int(RIPEMD_160_R1[i]) - alpha = util.ROTL32(alpha, s) + ee - beta = util.ROTL32(cc, 10) - aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd - i += 1 - } - t = e - e = ee - ee = t - ctx.s[0] += a - ctx.s[1] += b - ctx.s[2] += c - ctx.s[3] += d - ctx.s[4] += e - ctx.s[5] += aa - ctx.s[6] += bb - ctx.s[7] += cc - ctx.s[8] += dd - ctx.s[9] += ee - p = p[RIPEMD_320_BLOCK_SIZE:] - n += RIPEMD_320_BLOCK_SIZE - } - return n -} diff --git a/core/crypto/ristretto255/ristretto255.odin b/core/crypto/ristretto255/ristretto255.odin new file mode 100644 index 000000000..7b0944e33 --- /dev/null +++ b/core/crypto/ristretto255/ristretto255.odin @@ -0,0 +1,510 @@ +/* +package ristretto255 implement the ristretto255 prime-order group. + +See: +- [[ https://www.rfc-editor.org/rfc/rfc9496 ]] +*/ +package ristretto255 + +import grp "core:crypto/_edwards25519" +import field "core:crypto/_fiat/field_curve25519" +import "core:mem" + +// ELEMENT_SIZE is the size of a byte-encoded ristretto255 group element. +ELEMENT_SIZE :: 32 +// WIDE_ELEMENT_SIZE is the side of a wide byte-encoded ristretto255 +// group element. +WIDE_ELEMENT_SIZE :: 64 + +@(private) +FE_NEG_ONE := field.Tight_Field_Element { + 2251799813685228, + 2251799813685247, + 2251799813685247, + 2251799813685247, + 2251799813685247, +} +@(private) +FE_INVSQRT_A_MINUS_D := field.Tight_Field_Element { + 278908739862762, + 821645201101625, + 8113234426968, + 1777959178193151, + 2118520810568447, +} +@(private) +FE_ONE_MINUS_D_SQ := field.Tight_Field_Element { + 1136626929484150, + 1998550399581263, + 496427632559748, + 118527312129759, + 45110755273534, +} +@(private) +FE_D_MINUS_ONE_SQUARED := field.Tight_Field_Element { + 1507062230895904, + 1572317787530805, + 683053064812840, + 317374165784489, + 1572899562415810, +} +@(private) +FE_SQRT_AD_MINUS_ONE := field.Tight_Field_Element { + 2241493124984347, + 425987919032274, + 2207028919301688, + 1220490630685848, + 974799131293748, +} +@(private) +GE_IDENTITY := Group_Element{grp.GE_IDENTITY, true} + +// Group_Element is a ristretto255 group element. The zero-initialized +// value is invalid. +Group_Element :: struct { + // WARNING: While the internal representation is an Edwards25519 + // group element, this is not guaranteed to always be the case, + // and your code *WILL* break if you mess with `_p`. + _p: grp.Group_Element, + _is_initialized: bool, +} + +// ge_clear clears ge to the uninitialized state. +ge_clear :: proc "contextless" (ge: ^Group_Element) { + mem.zero_explicit(ge, size_of(Group_Element)) +} + +// ge_set sets `ge = a`. +ge_set :: proc(ge, a: ^Group_Element) { + _ge_assert_initialized([]^Group_Element{a}) + + grp.ge_set(&ge._p, &a._p) + ge._is_initialized = true +} + +// ge_identity sets ge to the identity (neutral) element. +ge_identity :: proc "contextless" (ge: ^Group_Element) { + grp.ge_identity(&ge._p) + ge._is_initialized = true +} + +// ge_generator sets ge to the group generator. +ge_generator :: proc "contextless" (ge: ^Group_Element) { + grp.ge_generator(&ge._p) + ge._is_initialized = true +} + +// ge_set_bytes sets ge to the result of decoding b as a ristretto255 +// group element, and returns true on success. +@(require_results) +ge_set_bytes :: proc "contextless" (ge: ^Group_Element, b: []byte) -> bool { + // 1. Interpret the string as an unsigned integer s in little-endian + // representation. If the length of the string is not 32 bytes or + // if the resulting value is >= p, decoding fails. + // + // 2. If IS_NEGATIVE(s) returns TRUE, decoding fails. + + if len(b) != ELEMENT_SIZE { + return false + } + if b[31] & 128 != 0 || b[0] & 1 != 0 { + // Fail early if b is clearly > p, or negative. + return false + } + + b_ := (^[32]byte)(raw_data(b)) + + s: field.Tight_Field_Element = --- + defer field.fe_clear(&s) + + field.fe_from_bytes(&s, b_) + if field.fe_equal_bytes(&s, b_) != 1 { + // Reject non-canonical encodings of s. + return false + } + + // 3. Process s as follows: + v, u1, u2: field.Loose_Field_Element = ---, ---, --- + tmp, u2_sqr: field.Tight_Field_Element = ---, --- + + // ss = s^2 + // u1 = 1 - ss + // u2 = 1 + ss + // u2_sqr = u2^2 + field.fe_carry_square(&tmp, field.fe_relax_cast(&s)) + field.fe_sub(&u1, &field.FE_ONE, &tmp) + field.fe_add(&u2, &field.FE_ONE, &tmp) + field.fe_carry_square(&u2_sqr, &u2) + + // v = -(D * u1^2) - u2_sqr + field.fe_carry_square(&tmp, &u1) + field.fe_carry_mul(&tmp, field.fe_relax_cast(&grp.FE_D), field.fe_relax_cast(&tmp)) + field.fe_carry_add(&tmp, &tmp, &u2_sqr) + field.fe_opp(&v, &tmp) + + // (was_square, invsqrt) = SQRT_RATIO_M1(1, v * u2_sqr) + field.fe_carry_mul(&tmp, &v, field.fe_relax_cast(&u2_sqr)) + was_square := field.fe_carry_sqrt_ratio_m1( + &tmp, + field.fe_relax_cast(&field.FE_ONE), + field.fe_relax_cast(&tmp), + ) + + // den_x = invsqrt * u2 + // den_y = invsqrt * den_x * v + x, y, t: field.Tight_Field_Element = ---, ---, --- + field.fe_carry_mul(&x, field.fe_relax_cast(&tmp), &u2) + field.fe_carry_mul(&y, field.fe_relax_cast(&tmp), field.fe_relax_cast(&x)) + field.fe_carry_mul(&y, field.fe_relax_cast(&y), &v) + + // x = CT_ABS(2 * s * den_x) + field.fe_carry_mul(&x, field.fe_relax_cast(&s), field.fe_relax_cast(&x)) + field.fe_carry_add(&x, &x, &x) + field.fe_carry_abs(&x, &x) + + // y = u1 * den_y + field.fe_carry_mul(&y, &u1, field.fe_relax_cast(&y)) + + // t = x * y + field.fe_carry_mul(&t, field.fe_relax_cast(&x), field.fe_relax_cast(&y)) + + field.fe_clear_vec([]^field.Loose_Field_Element{&v, &u1, &u2}) + field.fe_clear_vec([]^field.Tight_Field_Element{&tmp, &u2_sqr}) + defer field.fe_clear_vec([]^field.Tight_Field_Element{&x, &y, &t}) + + // 4. If was_square is FALSE, IS_NEGATIVE(t) returns TRUE, or y = 0, + // decoding fails. Otherwise, return the group element represented + // by the internal representation (x, y, 1, t) as the result of + // decoding. + + switch { + case was_square == 0: + // Not sure why the RFC doesn't have this just fail early. + return false + case field.fe_is_negative(&t) != 0: + return false + case field.fe_equal(&y, &field.FE_ZERO) != 0: + return false + } + + field.fe_set(&ge._p.x, &x) + field.fe_set(&ge._p.y, &y) + field.fe_one(&ge._p.z) + field.fe_set(&ge._p.t, &t) + ge._is_initialized = true + + return true +} + +// ge_set_wide_bytes sets ge to the result of deriving a ristretto255 +// group element, from a wide (512-bit) byte string. +ge_set_wide_bytes :: proc(ge: ^Group_Element, b: []byte) { + if len(b) != WIDE_ELEMENT_SIZE { + panic("crypto/ristretto255: invalid wide input size") + } + + // The element derivation function on an input string b proceeds as + // follows: + // + // 1. Compute P1 as MAP(b[0:32]). + // 2. Compute P2 as MAP(b[32:64]). + // 3. Return P1 + P2. + + p1, p2: Group_Element = ---, --- + ge_map(&p1, b[0:32]) + ge_map(&p2, b[32:64]) + + ge_add(ge, &p1, &p2) + + ge_clear(&p1) + ge_clear(&p2) +} + +// ge_bytes sets dst to the canonical encoding of ge. +ge_bytes :: proc(ge: ^Group_Element, dst: []byte) { + _ge_assert_initialized([]^Group_Element{ge}) + if len(dst) != ELEMENT_SIZE { + panic("crypto/ristretto255: invalid destination size") + } + + x0, y0, z0, t0 := &ge._p.x, &ge._p.y, &ge._p.z, &ge._p.t + + // 1. Process the internal representation into a field element s as + // follows: + + // u1 = (z0 + y0) * (z0 - y0) + // u2 = x0 * y0 + u1, u2: field.Tight_Field_Element = ---, --- + tmp1, tmp2: field.Loose_Field_Element = ---, --- + field.fe_add(&tmp1, z0, y0) + field.fe_sub(&tmp2, z0, y0) + field.fe_carry_mul(&u1, &tmp1, &tmp2) + field.fe_carry_mul(&u2, field.fe_relax_cast(x0), field.fe_relax_cast(y0)) + + // Ignore was_square since this is always square. + // (_, invsqrt) = SQRT_RATIO_M1(1, u1 * u2^2) + tmp: field.Tight_Field_Element = --- + field.fe_carry_square(&tmp, field.fe_relax_cast(&u2)) + field.fe_carry_mul(&tmp, field.fe_relax_cast(&u1), field.fe_relax_cast(&tmp)) + _ = field.fe_carry_sqrt_ratio_m1( + &tmp, + field.fe_relax_cast(&field.FE_ONE), + field.fe_relax_cast(&tmp), + ) + + // den1 = invsqrt * u1 + // den2 = invsqrt * u2 + // z_inv = den1 * den2 * t0 + den1, den2 := &u1, &u2 + z_inv: field.Tight_Field_Element = --- + field.fe_carry_mul(den1, field.fe_relax_cast(&tmp), field.fe_relax_cast(&u1)) + field.fe_carry_mul(den2, field.fe_relax_cast(&tmp), field.fe_relax_cast(&u2)) + field.fe_carry_mul(&z_inv, field.fe_relax_cast(den1), field.fe_relax_cast(den2)) + field.fe_carry_mul(&z_inv, field.fe_relax_cast(&z_inv), field.fe_relax_cast(t0)) + + // rotate = IS_NEGATIVE(t0 * z_inv) + // Note: Reordered from the RFC because invsqrt is no longer needed. + field.fe_carry_mul(&tmp, field.fe_relax_cast(t0), field.fe_relax_cast(&z_inv)) + rotate := field.fe_is_negative(&tmp) + + // ix0 = x0 * SQRT_M1 + // iy0 = y0 * SQRT_M1 + // enchanted_denominator = den1 * INVSQRT_A_MINUS_D + ix0, iy0: field.Tight_Field_Element = ---, --- + field.fe_carry_mul(&ix0, field.fe_relax_cast(x0), field.fe_relax_cast(&field.FE_SQRT_M1)) + field.fe_carry_mul(&iy0, field.fe_relax_cast(y0), field.fe_relax_cast(&field.FE_SQRT_M1)) + field.fe_carry_mul(&tmp, field.fe_relax_cast(den1), field.fe_relax_cast(&FE_INVSQRT_A_MINUS_D)) + + // Conditionally rotate x and y. + // x = CT_SELECT(iy0 IF rotate ELSE x0) + // y = CT_SELECT(ix0 IF rotate ELSE y0) + // z = z0 + // den_inv = CT_SELECT(enchanted_denominator IF rotate ELSE den2) + x, y: field.Tight_Field_Element = ---, --- + field.fe_cond_select(&x, x0, &iy0, rotate) + field.fe_cond_select(&y, y0, &ix0, rotate) + field.fe_cond_select(&tmp, den2, &tmp, rotate) + + // y = CT_SELECT(-y IF IS_NEGATIVE(x * z_inv) ELSE y) + field.fe_carry_mul(&x, field.fe_relax_cast(&x), field.fe_relax_cast(&z_inv)) + field.fe_cond_negate(&y, &y, field.fe_is_negative(&x)) + + // s = CT_ABS(den_inv * (z - y)) + field.fe_sub(&tmp1, z0, &y) + field.fe_carry_mul(&tmp, field.fe_relax_cast(&tmp), &tmp1) + field.fe_carry_abs(&tmp, &tmp) + + // 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_ := (^[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}) + field.fe_clear_vec([]^field.Loose_Field_Element{&tmp1, &tmp2}) +} + +// ge_add sets `ge = a + b`. +ge_add :: proc(ge, a, b: ^Group_Element) { + _ge_assert_initialized([]^Group_Element{a, b}) + + grp.ge_add(&ge._p, &a._p, &b._p) + ge._is_initialized = true +} + +// ge_double sets `ge = a + a`. +ge_double :: proc(ge, a: ^Group_Element) { + _ge_assert_initialized([]^Group_Element{a}) + + grp.ge_double(&ge._p, &a._p) + ge._is_initialized = true +} + +// ge_negate sets `ge = -a`. +ge_negate :: proc(ge, a: ^Group_Element) { + _ge_assert_initialized([]^Group_Element{a}) + + grp.ge_negate(&ge._p, &a._p) + ge._is_initialized = true +} + +// ge_scalarmult sets `ge = A * sc`. +ge_scalarmult :: proc(ge, A: ^Group_Element, sc: ^Scalar) { + _ge_assert_initialized([]^Group_Element{A}) + + grp.ge_scalarmult(&ge._p, &A._p, sc) + ge._is_initialized = true +} + +// ge_scalarmult_generator sets `ge = G * sc` +ge_scalarmult_generator :: proc "contextless" (ge: ^Group_Element, sc: ^Scalar) { + grp.ge_scalarmult_basepoint(&ge._p, sc) + ge._is_initialized = true +} + +// ge_scalarmult_vartime sets `ge = A * sc` in variable time. +ge_scalarmult_vartime :: proc(ge, A: ^Group_Element, sc: ^Scalar) { + _ge_assert_initialized([]^Group_Element{A}) + + grp.ge_scalarmult_vartime(&ge._p, &A._p, sc) + ge._is_initialized = true +} + +// ge_double_scalarmult_generator_vartime sets `ge = A * a + G * b` in variable +// time. +ge_double_scalarmult_generator_vartime :: proc( + ge: ^Group_Element, + a: ^Scalar, + A: ^Group_Element, + b: ^Scalar, +) { + _ge_assert_initialized([]^Group_Element{A}) + + grp.ge_double_scalarmult_basepoint_vartime(&ge._p, a, &A._p, b) + ge._is_initialized = true +} + +// ge_cond_negate sets `ge = a` iff `ctrl == 0` and `ge = -a` iff `ctrl == 1`. +// Behavior for all other values of ctrl are undefined, +ge_cond_negate :: proc(ge, a: ^Group_Element, ctrl: int) { + _ge_assert_initialized([]^Group_Element{a}) + + grp.ge_cond_negate(&ge._p, &a._p, ctrl) + ge._is_initialized = true +} + +// ge_cond_assign sets `ge = ge` iff `ctrl == 0` and `ge = a` iff `ctrl == 1`. +// Behavior for all other values of ctrl are undefined, +ge_cond_assign :: proc(ge, a: ^Group_Element, ctrl: int) { + _ge_assert_initialized([]^Group_Element{ge, a}) + + grp.ge_cond_assign(&ge._p, &a._p, ctrl) +} + +// ge_cond_select sets `ge = a` iff `ctrl == 0` and `ge = b` iff `ctrl == 1`. +// Behavior for all other values of ctrl are undefined, +ge_cond_select :: proc(ge, a, b: ^Group_Element, ctrl: int) { + _ge_assert_initialized([]^Group_Element{a, b}) + + grp.ge_cond_select(&ge._p, &a._p, &b._p, ctrl) + ge._is_initialized = true +} + +// ge_equal returns 1 iff `a == b`, and 0 otherwise. +@(require_results) +ge_equal :: proc(a, b: ^Group_Element) -> int { + _ge_assert_initialized([]^Group_Element{a, b}) + + // CT_EQ(x1 * y2, y1 * x2) | CT_EQ(y1 * y2, x1 * x2) + ax_by, ay_bx, ay_by, ax_bx: field.Tight_Field_Element = ---, ---, ---, --- + field.fe_carry_mul(&ax_by, field.fe_relax_cast(&a._p.x), field.fe_relax_cast(&b._p.y)) + field.fe_carry_mul(&ay_bx, field.fe_relax_cast(&a._p.y), field.fe_relax_cast(&b._p.x)) + field.fe_carry_mul(&ay_by, field.fe_relax_cast(&a._p.y), field.fe_relax_cast(&b._p.y)) + field.fe_carry_mul(&ax_bx, field.fe_relax_cast(&a._p.x), field.fe_relax_cast(&b._p.x)) + + ret := field.fe_equal(&ax_by, &ay_bx) | field.fe_equal(&ay_by, &ax_bx) + + field.fe_clear_vec([]^field.Tight_Field_Element{&ax_by, &ay_bx, &ay_by, &ax_bx}) + + return ret +} + +// ge_is_identity returns 1 iff `ge` is the identity element, and 0 otherwise. +@(require_results) +ge_is_identity :: proc(ge: ^Group_Element) -> int { + return ge_equal(ge, &GE_IDENTITY) +} + +@(private) +ge_map :: proc "contextless" (ge: ^Group_Element, b: []byte) { + b_ := (^[32]byte)(raw_data(b)) + + // The MAP function is defined on 32-byte strings as: + // + // 1. Mask the most significant bit in the final byte of the string, + // and interpret the string as an unsigned integer r in little- + // endian representation. Reduce r modulo p to obtain a field + // element t. + // * Masking the most significant bit is equivalent to interpreting + // the whole string as an unsigned integer in little-endian + // representation and then reducing it modulo 2^255. + t: field.Tight_Field_Element = --- + field.fe_from_bytes(&t, b_) + + // 2. Process t as follows: + // + // r = SQRT_M1 * t^2 + // u = (r + 1) * ONE_MINUS_D_SQ + // v = (-1 - r*D) * (r + D) + tmp1: field.Loose_Field_Element = --- + r, u, v: field.Tight_Field_Element = ---, ---, --- + + field.fe_carry_square(&r, field.fe_relax_cast(&t)) + field.fe_carry_mul(&r, field.fe_relax_cast(&field.FE_SQRT_M1), field.fe_relax_cast(&r)) + + field.fe_add(&tmp1, &field.FE_ONE, &r) + field.fe_carry_mul(&u, &tmp1, field.fe_relax_cast(&FE_ONE_MINUS_D_SQ)) + + field.fe_carry_mul(&v, field.fe_relax_cast(&r), field.fe_relax_cast(&grp.FE_D)) + field.fe_carry_add(&v, &field.FE_ONE, &v) + field.fe_carry_opp(&v, &v) + field.fe_add(&tmp1, &r, &grp.FE_D) + field.fe_carry_mul(&v, field.fe_relax_cast(&v), &tmp1) + + // (was_square, s) = SQRT_RATIO_M1(u, v) + // s_prime = -CT_ABS(s*t) + // s = CT_SELECT(s IF was_square ELSE s_prime) + // c = CT_SELECT(-1 IF was_square ELSE r) + s, s_prime, c: field.Tight_Field_Element = ---, ---, --- + was_square := field.fe_carry_sqrt_ratio_m1( + &s, + field.fe_relax_cast(&u), + field.fe_relax_cast(&v), + ) + field.fe_carry_mul(&s_prime, field.fe_relax_cast(&s), field.fe_relax_cast(&t)) + field.fe_carry_abs(&s_prime, &s_prime) + field.fe_carry_opp(&s_prime, &s_prime) + field.fe_cond_select(&s, &s_prime, &s, was_square) + field.fe_cond_select(&c, &r, &FE_NEG_ONE, was_square) + + // N = c * (r - 1) * D_MINUS_ONE_SQ - v + N: field.Tight_Field_Element = --- + field.fe_sub(&tmp1, &r, &field.FE_ONE) + field.fe_carry_mul(&N, field.fe_relax_cast(&c), &tmp1) + field.fe_carry_mul(&N, field.fe_relax_cast(&N), field.fe_relax_cast(&FE_D_MINUS_ONE_SQUARED)) + field.fe_carry_sub(&N, &N, &v) + + // w0 = 2 * s * v + // w1 = N * SQRT_AD_MINUS_ONE + // w2 = 1 - s^2 + // w3 = 1 + s^2 + w0, w1: field.Tight_Field_Element = ---, --- + w2, w3: field.Loose_Field_Element = ---, --- + field.fe_carry_mul(&w0, field.fe_relax_cast(&s), field.fe_relax_cast(&v)) + field.fe_carry_add(&w0, &w0, &w0) + field.fe_carry_mul(&w1, field.fe_relax_cast(&N), field.fe_relax_cast(&FE_SQRT_AD_MINUS_ONE)) + field.fe_carry_square(&s, field.fe_relax_cast(&s)) + field.fe_sub(&w2, &field.FE_ONE, &s) + field.fe_add(&w3, &field.FE_ONE, &s) + + // 3. Return the group element represented by the internal + // representation (w0*w3, w2*w1, w1*w3, w0*w2). + + field.fe_carry_mul(&ge._p.x, field.fe_relax_cast(&w0), &w3) + field.fe_carry_mul(&ge._p.y, &w2, field.fe_relax_cast(&w1)) + field.fe_carry_mul(&ge._p.z, field.fe_relax_cast(&w1), &w3) + field.fe_carry_mul(&ge._p.t, field.fe_relax_cast(&w0), &w2) + ge._is_initialized = true + + field.fe_clear_vec([]^field.Tight_Field_Element{&r, &u, &v, &s, &s_prime, &c, &N, &w0, &w1}) + field.fe_clear_vec([]^field.Loose_Field_Element{&tmp1, &w2, &w3}) +} + +@(private) +_ge_assert_initialized :: proc(ges: []^Group_Element) { + for ge in ges { + if !ge._is_initialized { + panic("crypto/ristretto255: uninitialized group element") + } + } +} diff --git a/core/crypto/ristretto255/ristretto255_scalar.odin b/core/crypto/ristretto255/ristretto255_scalar.odin new file mode 100644 index 000000000..1ecb490e0 --- /dev/null +++ b/core/crypto/ristretto255/ristretto255_scalar.odin @@ -0,0 +1,97 @@ +package ristretto255 + +import grp "core:crypto/_edwards25519" + +// SCALAR_SIZE is the size of a byte-encoded ristretto255 scalar. +SCALAR_SIZE :: 32 +// WIDE_SCALAR_SIZE is the size of a wide byte-encoded ristretto255 +// scalar. +WIDE_SCALAR_SIZE :: 64 + +// Scalar is a ristretto255 scalar. The zero-initialized value is valid, +// and represents `0`. +Scalar :: grp.Scalar + +// sc_clear clears sc to the uninitialized state. +sc_clear :: proc "contextless" (sc: ^Scalar) { + grp.sc_clear(sc) +} + +// sc_set sets `sc = a`. +sc_set :: proc "contextless" (sc, a: ^Scalar) { + grp.sc_set(sc, a) +} + +// sc_set_u64 sets `sc = i`. +sc_set_u64 :: proc "contextless" (sc: ^Scalar, i: u64) { + grp.sc_set_u64(sc, i) +} + +// sc_set_bytes sets sc to the result of decoding b as a ristretto255 +// scalar, and returns true on success. +@(require_results) +sc_set_bytes :: proc(sc: ^Scalar, b: []byte) -> bool { + if len(b) != SCALAR_SIZE { + return false + } + + return grp.sc_set_bytes(sc, b) +} + +// sc_set_wide_bytes sets sc to the result of deriving a ristretto255 +// scalar, from a wide (512-bit) byte string by interpreting b as a +// little-endian value, and reducing it mod the group order. +sc_set_bytes_wide :: proc(sc: ^Scalar, b: []byte) { + if len(b) != WIDE_SCALAR_SIZE { + panic("crypto/ristretto255: invalid wide input size") + } + + b_ := (^[WIDE_SCALAR_SIZE]byte)(raw_data(b)) + grp.sc_set_bytes_wide(sc, b_) +} + +// sc_bytes sets dst to the canonical encoding of sc. +sc_bytes :: proc(sc: ^Scalar, dst: []byte) { + if len(dst) != SCALAR_SIZE { + panic("crypto/ristretto255: invalid destination size") + } + + grp.sc_bytes(dst, sc) +} + +// sc_add sets `sc = a + b`. +sc_add :: proc "contextless" (sc, a, b: ^Scalar) { + grp.sc_add(sc, a, b) +} + +// sc_sub sets `sc = a - b`. +sc_sub :: proc "contextless" (sc, a, b: ^Scalar) { + grp.sc_sub(sc, a, b) +} + +// sc_negate sets `sc = -a`. +sc_negate :: proc "contextless" (sc, a: ^Scalar) { + grp.sc_negate(sc, a) +} + +// sc_mul sets `sc = a * b`. +sc_mul :: proc "contextless" (sc, a, b: ^Scalar) { + grp.sc_mul(sc, a, b) +} + +// sc_square sets `sc = a^2`. +sc_square :: proc "contextless" (sc, a: ^Scalar) { + grp.sc_square(sc, a) +} + +// sc_cond_assign sets `sc = sc` iff `ctrl == 0` and `sc = a` iff `ctrl == 1`. +// Behavior for all other values of ctrl are undefined, +sc_cond_assign :: proc(sc, a: ^Scalar, ctrl: int) { + grp.sc_cond_assign(sc, a, ctrl) +} + +// sc_equal returns 1 iff `a == b`, and 0 otherwise. +@(require_results) +sc_equal :: proc(a, b: ^Scalar) -> int { + return grp.sc_equal(a, b) +} diff --git a/core/crypto/sha1/sha1.odin b/core/crypto/sha1/sha1.odin deleted file mode 100644 index 599d1791e..000000000 --- a/core/crypto/sha1/sha1.odin +++ /dev/null @@ -1,246 +0,0 @@ -package sha1 - -/* - Copyright 2021 zhibog - Made available under the BSD-3 license. - - List of contributors: - zhibog, dotbmp: Initial implementation. - - Implementation of the SHA1 hashing algorithm, as defined in RFC 3174 -*/ - -import "core:mem" -import "core:os" -import "core:io" - -import "../util" - -/* - High level API -*/ - -DIGEST_SIZE :: 20 - -// hash_string will hash the given input and return the -// computed hash -hash_string :: proc(data: string) -> [DIGEST_SIZE]byte { - return hash_bytes(transmute([]byte)(data)) -} - -// hash_bytes will hash the given input and return the -// computed hash -hash_bytes :: proc(data: []byte) -> [DIGEST_SIZE]byte { - hash: [DIGEST_SIZE]byte - ctx: Sha1_Context - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE, "Size of destination buffer is smaller than the digest size") - ctx: Sha1_Context - init(&ctx) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream will read the stream in chunks and compute a -// hash from its contents -hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { - hash: [DIGEST_SIZE]byte - ctx: Sha1_Context - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file will read the file provided by the given handle -// and compute a hash -hash_file :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE]byte, bool) { - if !load_at_once { - return hash_stream(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes(buf[:]), ok - } - } - return [DIGEST_SIZE]byte{}, false -} - -hash :: proc { - hash_stream, - hash_file, - hash_bytes, - hash_string, - hash_bytes_to_buffer, - hash_string_to_buffer, -} - -/* - Low level API -*/ - -init :: proc(ctx: ^Sha1_Context) { - ctx.state[0] = 0x67452301 - ctx.state[1] = 0xefcdab89 - ctx.state[2] = 0x98badcfe - ctx.state[3] = 0x10325476 - ctx.state[4] = 0xc3d2e1f0 - ctx.k[0] = 0x5a827999 - ctx.k[1] = 0x6ed9eba1 - ctx.k[2] = 0x8f1bbcdc - ctx.k[3] = 0xca62c1d6 -} - -update :: proc(ctx: ^Sha1_Context, data: []byte) { - for i := 0; i < len(data); i += 1 { - ctx.data[ctx.datalen] = data[i] - ctx.datalen += 1 - if (ctx.datalen == BLOCK_SIZE) { - transform(ctx, ctx.data[:]) - ctx.bitlen += 512 - ctx.datalen = 0 - } - } -} - -final :: proc(ctx: ^Sha1_Context, hash: []byte) { - i := ctx.datalen - - if ctx.datalen < 56 { - ctx.data[i] = 0x80 - i += 1 - for i < 56 { - ctx.data[i] = 0x00 - i += 1 - } - } - else { - ctx.data[i] = 0x80 - i += 1 - for i < BLOCK_SIZE { - ctx.data[i] = 0x00 - i += 1 - } - transform(ctx, ctx.data[:]) - mem.set(&ctx.data, 0, 56) - } - - ctx.bitlen += u64(ctx.datalen * 8) - ctx.data[63] = u8(ctx.bitlen) - ctx.data[62] = u8(ctx.bitlen >> 8) - ctx.data[61] = u8(ctx.bitlen >> 16) - ctx.data[60] = u8(ctx.bitlen >> 24) - ctx.data[59] = u8(ctx.bitlen >> 32) - ctx.data[58] = u8(ctx.bitlen >> 40) - ctx.data[57] = u8(ctx.bitlen >> 48) - ctx.data[56] = u8(ctx.bitlen >> 56) - transform(ctx, ctx.data[:]) - - for j: u32 = 0; j < 4; j += 1 { - hash[j] = u8(ctx.state[0] >> (24 - j * 8)) & 0x000000ff - hash[j + 4] = u8(ctx.state[1] >> (24 - j * 8)) & 0x000000ff - hash[j + 8] = u8(ctx.state[2] >> (24 - j * 8)) & 0x000000ff - hash[j + 12] = u8(ctx.state[3] >> (24 - j * 8)) & 0x000000ff - hash[j + 16] = u8(ctx.state[4] >> (24 - j * 8)) & 0x000000ff - } -} - -/* - SHA1 implementation -*/ - -BLOCK_SIZE :: 64 - -Sha1_Context :: struct { - data: [BLOCK_SIZE]byte, - datalen: u32, - bitlen: u64, - state: [5]u32, - k: [4]u32, -} - -transform :: proc(ctx: ^Sha1_Context, data: []byte) { - a, b, c, d, e, i, j, t: u32 - m: [80]u32 - - for i, j = 0, 0; i < 16; i += 1 { - m[i] = u32(data[j]) << 24 + u32(data[j + 1]) << 16 + u32(data[j + 2]) << 8 + u32(data[j + 3]) - j += 4 - } - for i < 80 { - m[i] = (m[i - 3] ~ m[i - 8] ~ m[i - 14] ~ m[i - 16]) - m[i] = (m[i] << 1) | (m[i] >> 31) - i += 1 - } - - a = ctx.state[0] - b = ctx.state[1] - c = ctx.state[2] - d = ctx.state[3] - e = ctx.state[4] - - for i = 0; i < 20; i += 1 { - t = util.ROTL32(a, 5) + ((b & c) ~ (~b & d)) + e + ctx.k[0] + m[i] - e = d - d = c - c = util.ROTL32(b, 30) - b = a - a = t - } - for i < 40 { - t = util.ROTL32(a, 5) + (b ~ c ~ d) + e + ctx.k[1] + m[i] - e = d - d = c - c = util.ROTL32(b, 30) - b = a - a = t - i += 1 - } - for i < 60 { - t = util.ROTL32(a, 5) + ((b & c) ~ (b & d) ~ (c & d)) + e + ctx.k[2] + m[i] - e = d - d = c - c = util.ROTL32(b, 30) - b = a - a = t - i += 1 - } - for i < 80 { - t = util.ROTL32(a, 5) + (b ~ c ~ d) + e + ctx.k[3] + m[i] - e = d - d = c - c = util.ROTL32(b, 30) - b = a - a = t - i += 1 - } - - ctx.state[0] += a - ctx.state[1] += b - ctx.state[2] += c - ctx.state[3] += d - ctx.state[4] += e -} diff --git a/core/crypto/sha2/sha2.odin b/core/crypto/sha2/sha2.odin index 0f55c4be1..4230851ab 100644 --- a/core/crypto/sha2/sha2.odin +++ b/core/crypto/sha2/sha2.odin @@ -1,3 +1,10 @@ +/* +package sha2 implements the SHA2 hash algorithm family. + +See: +- [[ https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf ]] +- [[ https://datatracker.ietf.org/doc/html/rfc3874 ]] +*/ package sha2 /* @@ -6,652 +13,428 @@ package sha2 List of contributors: zhibog, dotbmp: Initial implementation. - - Implementation of the SHA2 hashing algorithm, as defined in - and in RFC 3874 */ +import "core:encoding/endian" +import "core:math/bits" import "core:mem" -import "core:os" -import "core:io" - -import "../util" - -/* - High level API -*/ +// DIGEST_SIZE_224 is the SHA-224 digest size in bytes. DIGEST_SIZE_224 :: 28 +// DIGEST_SIZE_256 is the SHA-256 digest size in bytes. DIGEST_SIZE_256 :: 32 +// DIGEST_SIZE_384 is the SHA-384 digest size in bytes. DIGEST_SIZE_384 :: 48 +// DIGEST_SIZE_512 is the SHA-512 digest size in bytes. DIGEST_SIZE_512 :: 64 +// DIGEST_SIZE_512_256 is the SHA-512/256 digest size in bytes. +DIGEST_SIZE_512_256 :: 32 -// hash_string_224 will hash the given input and return the -// computed hash -hash_string_224 :: proc(data: string) -> [DIGEST_SIZE_224]byte { - return hash_bytes_224(transmute([]byte)(data)) +// BLOCK_SIZE_256 is the SHA-224 and SHA-256 block size in bytes. +BLOCK_SIZE_256 :: 64 +// BLOCK_SIZE_512 is the SHA-384, SHA-512, and SHA-512/256 block size +// in bytes. +BLOCK_SIZE_512 :: 128 + +// Context_256 is a SHA-224 or SHA-256 instance. +Context_256 :: struct { + block: [BLOCK_SIZE_256]byte, + h: [8]u32, + bitlength: u64, + length: u64, + md_bits: int, + + is_initialized: bool, } -// hash_bytes_224 will hash the given input and return the -// computed hash -hash_bytes_224 :: proc(data: []byte) -> [DIGEST_SIZE_224]byte { - hash: [DIGEST_SIZE_224]byte - ctx: Sha256_Context - ctx.is224 = true - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash +// Context_512 is a SHA-384, SHA-512 or SHA-512/256 instance. +Context_512 :: struct { + block: [BLOCK_SIZE_512]byte, + h: [8]u64, + bitlength: u64, + length: u64, + md_bits: int, + + is_initialized: bool, } -// hash_string_to_buffer_224 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_224 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_224(transmute([]byte)(data), hash) +// init_224 initializes a Context_256 for SHA-224. +init_224 :: proc(ctx: ^Context_256) { + ctx.md_bits = 224 + _init(ctx) } -// hash_bytes_to_buffer_224 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_224 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_224, "Size of destination buffer is smaller than the digest size") - ctx: Sha256_Context - ctx.is224 = true - init(&ctx) - update(&ctx, data) - final(&ctx, hash) +// init_256 initializes a Context_256 for SHA-256. +init_256 :: proc(ctx: ^Context_256) { + ctx.md_bits = 256 + _init(ctx) } -// hash_stream_224 will read the stream in chunks and compute a -// hash from its contents -hash_stream_224 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { - hash: [DIGEST_SIZE_224]byte - ctx: Sha512_Context - ctx.is384 = false - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true +// init_384 initializes a Context_512 for SHA-384. +init_384 :: proc(ctx: ^Context_512) { + ctx.md_bits = 384 + _init(ctx) } -// hash_file_224 will read the file provided by the given handle -// and compute a hash -hash_file_224 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_224]byte, bool) { - if !load_at_once { - return hash_stream_224(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_224(buf[:]), ok - } - } - return [DIGEST_SIZE_224]byte{}, false +// init_512 initializes a Context_512 for SHA-512. +init_512 :: proc(ctx: ^Context_512) { + ctx.md_bits = 512 + _init(ctx) } -hash_224 :: proc { - hash_stream_224, - hash_file_224, - hash_bytes_224, - hash_string_224, - hash_bytes_to_buffer_224, - hash_string_to_buffer_224, +// init_512_256 initializes a Context_512 for SHA-512/256. +init_512_256 :: proc(ctx: ^Context_512) { + ctx.md_bits = 256 + _init(ctx) } -// hash_string_256 will hash the given input and return the -// computed hash -hash_string_256 :: proc(data: string) -> [DIGEST_SIZE_256]byte { - return hash_bytes_256(transmute([]byte)(data)) -} - -// hash_bytes_256 will hash the given input and return the -// computed hash -hash_bytes_256 :: proc(data: []byte) -> [DIGEST_SIZE_256]byte { - hash: [DIGEST_SIZE_256]byte - ctx: Sha256_Context - ctx.is224 = false - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_256 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_256 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_256(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_256 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_256 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_256, "Size of destination buffer is smaller than the digest size") - ctx: Sha256_Context - ctx.is224 = false - init(&ctx) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_256 will read the stream in chunks and compute a -// hash from its contents -hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { - hash: [DIGEST_SIZE_256]byte - ctx: Sha512_Context - ctx.is384 = false - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_256 will read the file provided by the given handle -// and compute a hash -hash_file_256 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_256]byte, bool) { - if !load_at_once { - return hash_stream_256(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_256(buf[:]), ok - } - } - return [DIGEST_SIZE_256]byte{}, false -} - -hash_256 :: proc { - hash_stream_256, - hash_file_256, - hash_bytes_256, - hash_string_256, - hash_bytes_to_buffer_256, - hash_string_to_buffer_256, -} - -// hash_string_384 will hash the given input and return the -// computed hash -hash_string_384 :: proc(data: string) -> [DIGEST_SIZE_384]byte { - return hash_bytes_384(transmute([]byte)(data)) -} - -// hash_bytes_384 will hash the given input and return the -// computed hash -hash_bytes_384 :: proc(data: []byte) -> [DIGEST_SIZE_384]byte { - hash: [DIGEST_SIZE_384]byte - ctx: Sha512_Context - ctx.is384 = true - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_384 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_384 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_384(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_384 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_384 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_384, "Size of destination buffer is smaller than the digest size") - ctx: Sha512_Context - ctx.is384 = true - init(&ctx) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_384 will read the stream in chunks and compute a -// hash from its contents -hash_stream_384 :: proc(s: io.Stream) -> ([DIGEST_SIZE_384]byte, bool) { - hash: [DIGEST_SIZE_384]byte - ctx: Sha512_Context - ctx.is384 = true - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_384 will read the file provided by the given handle -// and compute a hash -hash_file_384 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_384]byte, bool) { - if !load_at_once { - return hash_stream_384(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_384(buf[:]), ok - } - } - return [DIGEST_SIZE_384]byte{}, false -} - -hash_384 :: proc { - hash_stream_384, - hash_file_384, - hash_bytes_384, - hash_string_384, - hash_bytes_to_buffer_384, - hash_string_to_buffer_384, -} - -// hash_string_512 will hash the given input and return the -// computed hash -hash_string_512 :: proc(data: string) -> [DIGEST_SIZE_512]byte { - return hash_bytes_512(transmute([]byte)(data)) -} - -// hash_bytes_512 will hash the given input and return the -// computed hash -hash_bytes_512 :: proc(data: []byte) -> [DIGEST_SIZE_512]byte { - hash: [DIGEST_SIZE_512]byte - ctx: Sha512_Context - ctx.is384 = false - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_512 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_512 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_512(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_512 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_512 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_512, "Size of destination buffer is smaller than the digest size") - ctx: Sha512_Context - ctx.is384 = false - init(&ctx) - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream_512 will read the stream in chunks and compute a -// hash from its contents -hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { - hash: [DIGEST_SIZE_512]byte - ctx: Sha512_Context - ctx.is384 = false - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_512 will read the file provided by the given handle -// and compute a hash -hash_file_512 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_512]byte, bool) { - if !load_at_once { - return hash_stream_512(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_512(buf[:]), ok - } - } - return [DIGEST_SIZE_512]byte{}, false -} - -hash_512 :: proc { - hash_stream_512, - hash_file_512, - hash_bytes_512, - hash_string_512, - hash_bytes_to_buffer_512, - hash_string_to_buffer_512, -} - -/* - Low level API -*/ - -init :: proc(ctx: ^$T) { - when T == Sha256_Context { - if ctx.is224 { - ctx.h[0] = 0xc1059ed8 - ctx.h[1] = 0x367cd507 - ctx.h[2] = 0x3070dd17 - ctx.h[3] = 0xf70e5939 - ctx.h[4] = 0xffc00b31 - ctx.h[5] = 0x68581511 - ctx.h[6] = 0x64f98fa7 - ctx.h[7] = 0xbefa4fa4 - } else { - ctx.h[0] = 0x6a09e667 - ctx.h[1] = 0xbb67ae85 - ctx.h[2] = 0x3c6ef372 - ctx.h[3] = 0xa54ff53a - ctx.h[4] = 0x510e527f - ctx.h[5] = 0x9b05688c - ctx.h[6] = 0x1f83d9ab - ctx.h[7] = 0x5be0cd19 - } - } else when T == Sha512_Context { - if ctx.is384 { - ctx.h[0] = 0xcbbb9d5dc1059ed8 - ctx.h[1] = 0x629a292a367cd507 - ctx.h[2] = 0x9159015a3070dd17 - ctx.h[3] = 0x152fecd8f70e5939 - ctx.h[4] = 0x67332667ffc00b31 - ctx.h[5] = 0x8eb44a8768581511 - ctx.h[6] = 0xdb0c2e0d64f98fa7 - ctx.h[7] = 0x47b5481dbefa4fa4 - } else { - ctx.h[0] = 0x6a09e667f3bcc908 - ctx.h[1] = 0xbb67ae8584caa73b - ctx.h[2] = 0x3c6ef372fe94f82b - ctx.h[3] = 0xa54ff53a5f1d36f1 - ctx.h[4] = 0x510e527fade682d1 - ctx.h[5] = 0x9b05688c2b3e6c1f - ctx.h[6] = 0x1f83d9abfb41bd6b - ctx.h[7] = 0x5be0cd19137e2179 - } - } +@(private) +_init :: proc(ctx: ^$T) { + when T == Context_256 { + switch ctx.md_bits { + case 224: + ctx.h[0] = 0xc1059ed8 + ctx.h[1] = 0x367cd507 + ctx.h[2] = 0x3070dd17 + ctx.h[3] = 0xf70e5939 + ctx.h[4] = 0xffc00b31 + ctx.h[5] = 0x68581511 + ctx.h[6] = 0x64f98fa7 + ctx.h[7] = 0xbefa4fa4 + case 256: + ctx.h[0] = 0x6a09e667 + ctx.h[1] = 0xbb67ae85 + ctx.h[2] = 0x3c6ef372 + ctx.h[3] = 0xa54ff53a + ctx.h[4] = 0x510e527f + ctx.h[5] = 0x9b05688c + ctx.h[6] = 0x1f83d9ab + ctx.h[7] = 0x5be0cd19 + case: + panic("crypto/sha2: invalid digest output length") + } + } else when T == Context_512 { + switch ctx.md_bits { + case 256: + // SHA-512/256 + ctx.h[0] = 0x22312194fc2bf72c + ctx.h[1] = 0x9f555fa3c84c64c2 + ctx.h[2] = 0x2393b86b6f53b151 + ctx.h[3] = 0x963877195940eabd + ctx.h[4] = 0x96283ee2a88effe3 + ctx.h[5] = 0xbe5e1e2553863992 + ctx.h[6] = 0x2b0199fc2c85b8aa + ctx.h[7] = 0x0eb72ddc81c52ca2 + case 384: + // SHA-384 + ctx.h[0] = 0xcbbb9d5dc1059ed8 + ctx.h[1] = 0x629a292a367cd507 + ctx.h[2] = 0x9159015a3070dd17 + ctx.h[3] = 0x152fecd8f70e5939 + ctx.h[4] = 0x67332667ffc00b31 + ctx.h[5] = 0x8eb44a8768581511 + ctx.h[6] = 0xdb0c2e0d64f98fa7 + ctx.h[7] = 0x47b5481dbefa4fa4 + case 512: + // SHA-512 + ctx.h[0] = 0x6a09e667f3bcc908 + ctx.h[1] = 0xbb67ae8584caa73b + ctx.h[2] = 0x3c6ef372fe94f82b + ctx.h[3] = 0xa54ff53a5f1d36f1 + ctx.h[4] = 0x510e527fade682d1 + ctx.h[5] = 0x9b05688c2b3e6c1f + ctx.h[6] = 0x1f83d9abfb41bd6b + ctx.h[7] = 0x5be0cd19137e2179 + case: + panic("crypto/sha2: invalid digest output length") + } + } + + ctx.length = 0 + ctx.bitlength = 0 + + ctx.is_initialized = true } +// update adds more data to the Context. update :: proc(ctx: ^$T, data: []byte) { - length := uint(len(data)) - block_nb: uint - new_len, rem_len, tmp_len: uint - shifted_message := make([]byte, length) + assert(ctx.is_initialized) - when T == Sha256_Context { - CURR_BLOCK_SIZE :: SHA256_BLOCK_SIZE - } else when T == Sha512_Context { - CURR_BLOCK_SIZE :: SHA512_BLOCK_SIZE - } + when T == Context_256 { + CURR_BLOCK_SIZE :: BLOCK_SIZE_256 + } else when T == Context_512 { + CURR_BLOCK_SIZE :: BLOCK_SIZE_512 + } - tmp_len = CURR_BLOCK_SIZE - ctx.length - rem_len = length < tmp_len ? length : tmp_len - copy(ctx.block[ctx.length:], data[:rem_len]) + data := data + ctx.length += u64(len(data)) - if ctx.length + length < CURR_BLOCK_SIZE { - ctx.length += length - return - } - - new_len = length - rem_len - block_nb = new_len / CURR_BLOCK_SIZE - shifted_message = data[rem_len:] - - sha2_transf(ctx, ctx.block[:], 1) - sha2_transf(ctx, shifted_message, block_nb) - - rem_len = new_len % CURR_BLOCK_SIZE - if rem_len > 0 { - when T == Sha256_Context {copy(ctx.block[:], shifted_message[block_nb << 6:rem_len])} - else when T == Sha512_Context {copy(ctx.block[:], shifted_message[block_nb << 7:rem_len])} - } - - ctx.length = rem_len - when T == Sha256_Context {ctx.tot_len += (block_nb + 1) << 6} - else when T == Sha512_Context {ctx.tot_len += (block_nb + 1) << 7} + if ctx.bitlength > 0 { + n := copy(ctx.block[ctx.bitlength:], data[:]) + ctx.bitlength += u64(n) + if ctx.bitlength == CURR_BLOCK_SIZE { + sha2_transf(ctx, ctx.block[:]) + ctx.bitlength = 0 + } + data = data[n:] + } + if len(data) >= CURR_BLOCK_SIZE { + n := len(data) &~ (CURR_BLOCK_SIZE - 1) + sha2_transf(ctx, data[:n]) + data = data[n:] + } + if len(data) > 0 { + ctx.bitlength = u64(copy(ctx.block[:], data[:])) + } } -final :: proc(ctx: ^$T, hash: []byte) { - block_nb, pm_len, len_b: u32 - i: i32 +// final finalizes the Context, writes the digest to hash, and calls +// reset on the Context. +// +// 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: ^$T, hash: []byte, finalize_clone: bool = false) { + assert(ctx.is_initialized) - when T == Sha256_Context {CURR_BLOCK_SIZE :: SHA256_BLOCK_SIZE} - else when T == Sha512_Context {CURR_BLOCK_SIZE :: SHA512_BLOCK_SIZE} + if len(hash) * 8 < ctx.md_bits { + panic("crypto/sha2: invalid destination digest size") + } - when T == Sha256_Context {block_nb = 1 + ((CURR_BLOCK_SIZE - 9) < (ctx.length % CURR_BLOCK_SIZE) ? 1 : 0)} - else when T == Sha512_Context {block_nb = 1 + ((CURR_BLOCK_SIZE - 17) < (ctx.length % CURR_BLOCK_SIZE) ? 1 : 0)} + ctx := ctx + if finalize_clone { + tmp_ctx: T + clone(&tmp_ctx, ctx) + ctx = &tmp_ctx + } + defer(reset(ctx)) - len_b = u32(ctx.tot_len + ctx.length) << 3 - when T == Sha256_Context {pm_len = block_nb << 6} - else when T == Sha512_Context {pm_len = block_nb << 7} + length := ctx.length - mem.set(rawptr(&(ctx.block[ctx.length:])[0]), 0, int(uint(pm_len) - ctx.length)) - ctx.block[ctx.length] = 0x80 + raw_pad: [BLOCK_SIZE_512]byte + when T == Context_256 { + CURR_BLOCK_SIZE :: BLOCK_SIZE_256 + pm_len := 8 // 64-bits for length + } else when T == Context_512 { + CURR_BLOCK_SIZE :: BLOCK_SIZE_512 + pm_len := 16 // 128-bits for length + } + pad := raw_pad[:CURR_BLOCK_SIZE] + pad_len := u64(CURR_BLOCK_SIZE - pm_len) - util.PUT_U32_BE(ctx.block[pm_len - 4:], len_b) + pad[0] = 0x80 + if length % CURR_BLOCK_SIZE < pad_len { + update(ctx, pad[0:pad_len - length % CURR_BLOCK_SIZE]) + } else { + update(ctx, pad[0:CURR_BLOCK_SIZE + pad_len - length % CURR_BLOCK_SIZE]) + } - sha2_transf(ctx, ctx.block[:], uint(block_nb)) + length_hi, length_lo := bits.mul_u64(length, 8) // Length in bits + when T == Context_256 { + _ = length_hi + endian.unchecked_put_u64be(pad[:], length_lo) + update(ctx, pad[:8]) + } else when T == Context_512 { + endian.unchecked_put_u64be(pad[:], length_hi) + endian.unchecked_put_u64be(pad[8:], length_lo) + update(ctx, pad[0:16]) + } + assert(ctx.bitlength == 0) - when T == Sha256_Context { - if ctx.is224 { - for i = 0; i < 7; i += 1 {util.PUT_U32_BE(hash[i << 2:], ctx.h[i])} - } else { - for i = 0; i < 8; i += 1 {util.PUT_U32_BE(hash[i << 2:], ctx.h[i])} - } - } else when T == Sha512_Context { - if ctx.is384 { - for i = 0; i < 6; i += 1 {util.PUT_U64_BE(hash[i << 3:], ctx.h[i])} - } else { - for i = 0; i < 8; i += 1 {util.PUT_U64_BE(hash[i << 3:], ctx.h[i])} - } - } + when T == Context_256 { + for i := 0; i < ctx.md_bits / 32; i += 1 { + endian.unchecked_put_u32be(hash[i * 4:], ctx.h[i]) + } + } else when T == Context_512 { + for i := 0; i < ctx.md_bits / 64; i += 1 { + endian.unchecked_put_u64be(hash[i * 8:], ctx.h[i]) + } + } +} + +// clone clones the Context other into ctx. +clone :: proc(ctx, other: ^$T) { + ctx^ = other^ +} + +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. +reset :: proc(ctx: ^$T) { + if !ctx.is_initialized { + return + } + + mem.zero_explicit(ctx, size_of(ctx^)) } /* SHA2 implementation */ -SHA256_BLOCK_SIZE :: 64 -SHA512_BLOCK_SIZE :: 128 - -Sha256_Context :: struct { - tot_len: uint, - length: uint, - block: [128]byte, - h: [8]u32, - is224: bool, -} - -Sha512_Context :: struct { - tot_len: uint, - length: uint, - block: [256]byte, - h: [8]u64, - is384: bool, -} - +@(private) sha256_k := [64]u32 { - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, - 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, - 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, - 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, - 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, - 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, - 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, - 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, - 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, } +@(private) sha512_k := [80]u64 { - 0x428a2f98d728ae22, 0x7137449123ef65cd, - 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, - 0x3956c25bf348b538, 0x59f111f1b605d019, - 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, - 0xd807aa98a3030242, 0x12835b0145706fbe, - 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, - 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, - 0x9bdc06a725c71235, 0xc19bf174cf692694, - 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, - 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, - 0x2de92c6f592b0275, 0x4a7484aa6ea6e483, - 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, - 0x983e5152ee66dfab, 0xa831c66d2db43210, - 0xb00327c898fb213f, 0xbf597fc7beef0ee4, - 0xc6e00bf33da88fc2, 0xd5a79147930aa725, - 0x06ca6351e003826f, 0x142929670a0e6e70, - 0x27b70a8546d22ffc, 0x2e1b21385c26c926, - 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df, - 0x650a73548baf63de, 0x766a0abb3c77b2a8, - 0x81c2c92e47edaee6, 0x92722c851482353b, - 0xa2bfe8a14cf10364, 0xa81a664bbc423001, - 0xc24b8b70d0f89791, 0xc76c51a30654be30, - 0xd192e819d6ef5218, 0xd69906245565a910, - 0xf40e35855771202a, 0x106aa07032bbd1b8, - 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, - 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, - 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, - 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3, - 0x748f82ee5defb2fc, 0x78a5636f43172f60, - 0x84c87814a1f0ab72, 0x8cc702081a6439ec, - 0x90befffa23631e28, 0xa4506cebde82bde9, - 0xbef9a3f7b2c67915, 0xc67178f2e372532b, - 0xca273eceea26619c, 0xd186b8c721c0c207, - 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, - 0x06f067aa72176fba, 0x0a637dc5a2c898a6, - 0x113f9804bef90dae, 0x1b710b35131c471b, - 0x28db77f523047d84, 0x32caab7b40c72493, - 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c, - 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, - 0x5fcb6fab3ad6faec, 0x6c44198c4a475817, + 0x428a2f98d728ae22, 0x7137449123ef65cd, + 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, + 0x3956c25bf348b538, 0x59f111f1b605d019, + 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, + 0xd807aa98a3030242, 0x12835b0145706fbe, + 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, + 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, + 0x9bdc06a725c71235, 0xc19bf174cf692694, + 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, + 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, + 0x2de92c6f592b0275, 0x4a7484aa6ea6e483, + 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, + 0x983e5152ee66dfab, 0xa831c66d2db43210, + 0xb00327c898fb213f, 0xbf597fc7beef0ee4, + 0xc6e00bf33da88fc2, 0xd5a79147930aa725, + 0x06ca6351e003826f, 0x142929670a0e6e70, + 0x27b70a8546d22ffc, 0x2e1b21385c26c926, + 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df, + 0x650a73548baf63de, 0x766a0abb3c77b2a8, + 0x81c2c92e47edaee6, 0x92722c851482353b, + 0xa2bfe8a14cf10364, 0xa81a664bbc423001, + 0xc24b8b70d0f89791, 0xc76c51a30654be30, + 0xd192e819d6ef5218, 0xd69906245565a910, + 0xf40e35855771202a, 0x106aa07032bbd1b8, + 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, + 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, + 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, + 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3, + 0x748f82ee5defb2fc, 0x78a5636f43172f60, + 0x84c87814a1f0ab72, 0x8cc702081a6439ec, + 0x90befffa23631e28, 0xa4506cebde82bde9, + 0xbef9a3f7b2c67915, 0xc67178f2e372532b, + 0xca273eceea26619c, 0xd186b8c721c0c207, + 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, + 0x06f067aa72176fba, 0x0a637dc5a2c898a6, + 0x113f9804bef90dae, 0x1b710b35131c471b, + 0x28db77f523047d84, 0x32caab7b40c72493, + 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c, + 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, + 0x5fcb6fab3ad6faec, 0x6c44198c4a475817, } -SHA256_CH :: #force_inline proc "contextless"(x, y, z: u32) -> u32 { - return (x & y) ~ (~x & z) +@(private) +SHA256_CH :: #force_inline proc "contextless" (x, y, z: u32) -> u32 { + return (x & y) ~ (~x & z) } -SHA256_MAJ :: #force_inline proc "contextless"(x, y, z: u32) -> u32 { - return (x & y) ~ (x & z) ~ (y & z) +@(private) +SHA256_MAJ :: #force_inline proc "contextless" (x, y, z: u32) -> u32 { + return (x & y) ~ (x & z) ~ (y & z) } -SHA512_CH :: #force_inline proc "contextless"(x, y, z: u64) -> u64 { - return (x & y) ~ (~x & z) +@(private) +SHA512_CH :: #force_inline proc "contextless" (x, y, z: u64) -> u64 { + return (x & y) ~ (~x & z) } -SHA512_MAJ :: #force_inline proc "contextless"(x, y, z: u64) -> u64 { - return (x & y) ~ (x & z) ~ (y & z) +@(private) +SHA512_MAJ :: #force_inline proc "contextless" (x, y, z: u64) -> u64 { + return (x & y) ~ (x & z) ~ (y & z) } -SHA256_F1 :: #force_inline proc "contextless"(x: u32) -> u32 { - return util.ROTR32(x, 2) ~ util.ROTR32(x, 13) ~ util.ROTR32(x, 22) +@(private) +SHA256_F1 :: #force_inline proc "contextless" (x: u32) -> u32 { + return bits.rotate_left32(x, 30) ~ bits.rotate_left32(x, 19) ~ bits.rotate_left32(x, 10) } -SHA256_F2 :: #force_inline proc "contextless"(x: u32) -> u32 { - return util.ROTR32(x, 6) ~ util.ROTR32(x, 11) ~ util.ROTR32(x, 25) +@(private) +SHA256_F2 :: #force_inline proc "contextless" (x: u32) -> u32 { + return bits.rotate_left32(x, 26) ~ bits.rotate_left32(x, 21) ~ bits.rotate_left32(x, 7) } -SHA256_F3 :: #force_inline proc "contextless"(x: u32) -> u32 { - return util.ROTR32(x, 7) ~ util.ROTR32(x, 18) ~ (x >> 3) +@(private) +SHA256_F3 :: #force_inline proc "contextless" (x: u32) -> u32 { + return bits.rotate_left32(x, 25) ~ bits.rotate_left32(x, 14) ~ (x >> 3) } -SHA256_F4 :: #force_inline proc "contextless"(x: u32) -> u32 { - return util.ROTR32(x, 17) ~ util.ROTR32(x, 19) ~ (x >> 10) +@(private) +SHA256_F4 :: #force_inline proc "contextless" (x: u32) -> u32 { + return bits.rotate_left32(x, 15) ~ bits.rotate_left32(x, 13) ~ (x >> 10) } -SHA512_F1 :: #force_inline proc "contextless"(x: u64) -> u64 { - return util.ROTR64(x, 28) ~ util.ROTR64(x, 34) ~ util.ROTR64(x, 39) +@(private) +SHA512_F1 :: #force_inline proc "contextless" (x: u64) -> u64 { + return bits.rotate_left64(x, 36) ~ bits.rotate_left64(x, 30) ~ bits.rotate_left64(x, 25) } -SHA512_F2 :: #force_inline proc "contextless"(x: u64) -> u64 { - return util.ROTR64(x, 14) ~ util.ROTR64(x, 18) ~ util.ROTR64(x, 41) +@(private) +SHA512_F2 :: #force_inline proc "contextless" (x: u64) -> u64 { + return bits.rotate_left64(x, 50) ~ bits.rotate_left64(x, 46) ~ bits.rotate_left64(x, 23) } -SHA512_F3 :: #force_inline proc "contextless"(x: u64) -> u64 { - return util.ROTR64(x, 1) ~ util.ROTR64(x, 8) ~ (x >> 7) +@(private) +SHA512_F3 :: #force_inline proc "contextless" (x: u64) -> u64 { + return bits.rotate_left64(x, 63) ~ bits.rotate_left64(x, 56) ~ (x >> 7) } -SHA512_F4 :: #force_inline proc "contextless"(x: u64) -> u64 { - return util.ROTR64(x, 19) ~ util.ROTR64(x, 61) ~ (x >> 6) +@(private) +SHA512_F4 :: #force_inline proc "contextless" (x: u64) -> u64 { + return bits.rotate_left64(x, 45) ~ bits.rotate_left64(x, 3) ~ (x >> 6) } -PACK32 :: #force_inline proc "contextless"(b: []byte, x: ^u32) { - x^ = u32(b[3]) | u32(b[2]) << 8 | u32(b[1]) << 16 | u32(b[0]) << 24 -} - -PACK64 :: #force_inline proc "contextless"(b: []byte, x: ^u64) { - x^ = u64(b[7]) | u64(b[6]) << 8 | u64(b[5]) << 16 | u64(b[4]) << 24 | u64(b[3]) << 32 | u64(b[2]) << 40 | u64(b[1]) << 48 | u64(b[0]) << 56 -} - -sha2_transf :: proc(ctx: ^$T, data: []byte, block_nb: uint) { - when T == Sha256_Context { +@(private) +sha2_transf :: proc "contextless" (ctx: ^$T, data: []byte) { + when T == Context_256 { w: [64]u32 wv: [8]u32 t1, t2: u32 - } else when T == Sha512_Context { + CURR_BLOCK_SIZE :: BLOCK_SIZE_256 + } else when T == Context_512 { w: [80]u64 wv: [8]u64 t1, t2: u64 + CURR_BLOCK_SIZE :: BLOCK_SIZE_512 } - sub_block := make([]byte, len(data)) - i, j: i32 - - for i = 0; i < i32(block_nb); i += 1 { - when T == Sha256_Context { - sub_block = data[i << 6:] - } else when T == Sha512_Context { - sub_block = data[i << 7:] - } - - for j = 0; j < 16; j += 1 { - when T == Sha256_Context { - PACK32(sub_block[j << 2:], &w[j]) - } else when T == Sha512_Context { - PACK64(sub_block[j << 3:], &w[j]) + data := data + for len(data) >= CURR_BLOCK_SIZE { + for i := 0; i < 16; i += 1 { + when T == Context_256 { + w[i] = endian.unchecked_get_u32be(data[i * 4:]) + } else when T == Context_512 { + w[i] = endian.unchecked_get_u64be(data[i * 8:]) } } - when T == Sha256_Context { - for j = 16; j < 64; j += 1 { - w[j] = SHA256_F4(w[j - 2]) + w[j - 7] + SHA256_F3(w[j - 15]) + w[j - 16] + when T == Context_256 { + for i := 16; i < 64; i += 1 { + w[i] = SHA256_F4(w[i - 2]) + w[i - 7] + SHA256_F3(w[i - 15]) + w[i - 16] } - } else when T == Sha512_Context { - for j = 16; j < 80; j += 1 { - w[j] = SHA512_F4(w[j - 2]) + w[j - 7] + SHA512_F3(w[j - 15]) + w[j - 16] + } else when T == Context_512 { + for i := 16; i < 80; i += 1 { + w[i] = SHA512_F4(w[i - 2]) + w[i - 7] + SHA512_F3(w[i - 15]) + w[i - 16] } } - for j = 0; j < 8; j += 1 { - wv[j] = ctx.h[j] + for i := 0; i < 8; i += 1 { + wv[i] = ctx.h[i] } - when T == Sha256_Context { - for j = 0; j < 64; j += 1 { - t1 = wv[7] + SHA256_F2(wv[4]) + SHA256_CH(wv[4], wv[5], wv[6]) + sha256_k[j] + w[j] + when T == Context_256 { + for i := 0; i < 64; i += 1 { + t1 = wv[7] + SHA256_F2(wv[4]) + SHA256_CH(wv[4], wv[5], wv[6]) + sha256_k[i] + w[i] t2 = SHA256_F1(wv[0]) + SHA256_MAJ(wv[0], wv[1], wv[2]) wv[7] = wv[6] wv[6] = wv[5] @@ -662,9 +445,9 @@ sha2_transf :: proc(ctx: ^$T, data: []byte, block_nb: uint) { wv[1] = wv[0] wv[0] = t1 + t2 } - } else when T == Sha512_Context { - for j = 0; j < 80; j += 1 { - t1 = wv[7] + SHA512_F2(wv[4]) + SHA512_CH(wv[4], wv[5], wv[6]) + sha512_k[j] + w[j] + } else when T == Context_512 { + for i := 0; i < 80; i += 1 { + t1 = wv[7] + SHA512_F2(wv[4]) + SHA512_CH(wv[4], wv[5], wv[6]) + sha512_k[i] + w[i] t2 = SHA512_F1(wv[0]) + SHA512_MAJ(wv[0], wv[1], wv[2]) wv[7] = wv[6] wv[6] = wv[5] @@ -677,8 +460,10 @@ sha2_transf :: proc(ctx: ^$T, data: []byte, block_nb: uint) { } } - for j = 0; j < 8; j += 1 { - ctx.h[j] += wv[j] + for i := 0; i < 8; i += 1 { + ctx.h[i] += wv[i] } + + data = data[CURR_BLOCK_SIZE:] } } diff --git a/core/crypto/sha3/sha3.odin b/core/crypto/sha3/sha3.odin index 5d8ad2106..3b7bdedd7 100644 --- a/core/crypto/sha3/sha3.odin +++ b/core/crypto/sha3/sha3.odin @@ -1,3 +1,13 @@ +/* +package sha3 implements the SHA3 hash algorithm family. + +The SHAKE XOF can be found in crypto/shake. While discouraged if the +pre-standardization Keccak algorithm is required, it can be found in +crypto/legacy/keccak. + +See: +- [[ https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.202.pdf ]] +*/ package sha3 /* @@ -6,355 +16,82 @@ package sha3 List of contributors: zhibog, dotbmp: Initial implementation. - - Interface for the SHA3 hashing algorithm. The SHAKE functionality can be found in package shake. - If you wish to compute a Keccak hash, you can use the keccak package, it will use the original padding. */ -import "core:os" -import "core:io" - import "../_sha3" -/* - High level API -*/ - +// DIGEST_SIZE_224 is the SHA3-224 digest size. DIGEST_SIZE_224 :: 28 +// DIGEST_SIZE_256 is the SHA3-256 digest size. DIGEST_SIZE_256 :: 32 +// DIGEST_SIZE_384 is the SHA3-384 digest size. DIGEST_SIZE_384 :: 48 +// DIGEST_SIZE_512 is the SHA3-512 digest size. DIGEST_SIZE_512 :: 64 -// hash_string_224 will hash the given input and return the -// computed hash -hash_string_224 :: proc(data: string) -> [DIGEST_SIZE_224]byte { - return hash_bytes_224(transmute([]byte)(data)) +// BLOCK_SIZE_224 is the SHA3-224 block size in bytes. +BLOCK_SIZE_224 :: _sha3.RATE_224 +// BLOCK_SIZE_256 is the SHA3-256 block size in bytes. +BLOCK_SIZE_256 :: _sha3.RATE_256 +// BLOCK_SIZE_384 is the SHA3-384 block size in bytes. +BLOCK_SIZE_384 :: _sha3.RATE_384 +// BLOCK_SIZE_512 is the SHA3-512 block size in bytes. +BLOCK_SIZE_512 :: _sha3.RATE_512 + +// Context is a SHA3 instance. +Context :: distinct _sha3.Context + +// init_224 initializes a Context for SHA3-224. +init_224 :: proc(ctx: ^Context) { + ctx.mdlen = DIGEST_SIZE_224 + _init(ctx) } -// hash_bytes_224 will hash the given input and return the -// computed hash -hash_bytes_224 :: proc(data: []byte) -> [DIGEST_SIZE_224]byte { - hash: [DIGEST_SIZE_224]byte - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_224 - _sha3.init(&ctx) - _sha3.update(&ctx, data) - _sha3.final(&ctx, hash[:]) - return hash +// init_256 initializes a Context for SHA3-256. +init_256 :: proc(ctx: ^Context) { + ctx.mdlen = DIGEST_SIZE_256 + _init(ctx) } -// hash_string_to_buffer_224 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_224 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_224(transmute([]byte)(data), hash) +// init_384 initializes a Context for SHA3-384. +init_384 :: proc(ctx: ^Context) { + ctx.mdlen = DIGEST_SIZE_384 + _init(ctx) } -// hash_bytes_to_buffer_224 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_224 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_224, "Size of destination buffer is smaller than the digest size") - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_224 - _sha3.init(&ctx) - _sha3.update(&ctx, data) - _sha3.final(&ctx, hash) +// init_512 initializes a Context for SHA3-512. +init_512 :: proc(ctx: ^Context) { + ctx.mdlen = DIGEST_SIZE_512 + _init(ctx) } -// hash_stream_224 will read the stream in chunks and compute a -// hash from its contents -hash_stream_224 :: proc(s: io.Stream) -> ([DIGEST_SIZE_224]byte, bool) { - hash: [DIGEST_SIZE_224]byte - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_224 - _sha3.init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - _sha3.update(&ctx, buf[:read]) - } - } - _sha3.final(&ctx, hash[:]) - return hash, true +@(private) +_init :: proc(ctx: ^Context) { + ctx.dsbyte = _sha3.DS_SHA3 + _sha3.init((^_sha3.Context)(ctx)) } -// hash_file_224 will read the file provided by the given handle -// and compute a hash -hash_file_224 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_224]byte, bool) { - if !load_at_once { - return hash_stream_224(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_224(buf[:]), ok - } - } - return [DIGEST_SIZE_224]byte{}, false +// update adds more data to the Context. +update :: proc(ctx: ^Context, data: []byte) { + _sha3.update((^_sha3.Context)(ctx), data) } -hash_224 :: proc { - hash_stream_224, - hash_file_224, - hash_bytes_224, - hash_string_224, - hash_bytes_to_buffer_224, - hash_string_to_buffer_224, +// final finalizes the Context, writes the digest to hash, and calls +// reset on the Context. +// +// 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((^_sha3.Context)(ctx), hash, finalize_clone) } -// hash_string_256 will hash the given input and return the -// computed hash -hash_string_256 :: proc(data: string) -> [DIGEST_SIZE_256]byte { - return hash_bytes_256(transmute([]byte)(data)) +// clone clones the Context other into ctx. +clone :: proc(ctx, other: ^Context) { + _sha3.clone((^_sha3.Context)(ctx), (^_sha3.Context)(other)) } -// hash_bytes_256 will hash the given input and return the -// computed hash -hash_bytes_256 :: proc(data: []byte) -> [DIGEST_SIZE_256]byte { - hash: [DIGEST_SIZE_256]byte - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_256 - _sha3.init(&ctx) - _sha3.update(&ctx, data) - _sha3.final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_256 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_256 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_256(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_256 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_256 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_256, "Size of destination buffer is smaller than the digest size") - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_256 - _sha3.init(&ctx) - _sha3.update(&ctx, data) - _sha3.final(&ctx, hash) -} - -// hash_stream_256 will read the stream in chunks and compute a -// hash from its contents -hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { - hash: [DIGEST_SIZE_256]byte - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_256 - _sha3.init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - _sha3.update(&ctx, buf[:read]) - } - } - _sha3.final(&ctx, hash[:]) - return hash, true -} - -// hash_file_256 will read the file provided by the given handle -// and compute a hash -hash_file_256 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_256]byte, bool) { - if !load_at_once { - return hash_stream_256(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_256(buf[:]), ok - } - } - return [DIGEST_SIZE_256]byte{}, false -} - -hash_256 :: proc { - hash_stream_256, - hash_file_256, - hash_bytes_256, - hash_string_256, - hash_bytes_to_buffer_256, - hash_string_to_buffer_256, -} - -// hash_string_384 will hash the given input and return the -// computed hash -hash_string_384 :: proc(data: string) -> [DIGEST_SIZE_384]byte { - return hash_bytes_384(transmute([]byte)(data)) -} - -// hash_bytes_384 will hash the given input and return the -// computed hash -hash_bytes_384 :: proc(data: []byte) -> [DIGEST_SIZE_384]byte { - hash: [DIGEST_SIZE_384]byte - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_384 - _sha3.init(&ctx) - _sha3.update(&ctx, data) - _sha3.final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_384 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_384 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_384(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_384 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_384 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_384, "Size of destination buffer is smaller than the digest size") - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_384 - _sha3.init(&ctx) - _sha3.update(&ctx, data) - _sha3.final(&ctx, hash) -} - -// hash_stream_384 will read the stream in chunks and compute a -// hash from its contents -hash_stream_384 :: proc(s: io.Stream) -> ([DIGEST_SIZE_384]byte, bool) { - hash: [DIGEST_SIZE_384]byte - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_384 - _sha3.init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - _sha3.update(&ctx, buf[:read]) - } - } - _sha3.final(&ctx, hash[:]) - return hash, true -} - -// hash_file_384 will read the file provided by the given handle -// and compute a hash -hash_file_384 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_384]byte, bool) { - if !load_at_once { - return hash_stream_384(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_384(buf[:]), ok - } - } - return [DIGEST_SIZE_384]byte{}, false -} - -hash_384 :: proc { - hash_stream_384, - hash_file_384, - hash_bytes_384, - hash_string_384, - hash_bytes_to_buffer_384, - hash_string_to_buffer_384, -} - -// hash_string_512 will hash the given input and return the -// computed hash -hash_string_512 :: proc(data: string) -> [DIGEST_SIZE_512]byte { - return hash_bytes_512(transmute([]byte)(data)) -} - -// hash_bytes_512 will hash the given input and return the -// computed hash -hash_bytes_512 :: proc(data: []byte) -> [DIGEST_SIZE_512]byte { - hash: [DIGEST_SIZE_512]byte - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_512 - _sha3.init(&ctx) - _sha3.update(&ctx, data) - _sha3.final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_512 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_512 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_512(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_512 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_512 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_512, "Size of destination buffer is smaller than the digest size") - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_512 - _sha3.init(&ctx) - _sha3.update(&ctx, data) - _sha3.final(&ctx, hash) -} - -// hash_stream_512 will read the stream in chunks and compute a -// hash from its contents -hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { - hash: [DIGEST_SIZE_512]byte - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_512 - _sha3.init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - _sha3.update(&ctx, buf[:read]) - } - } - _sha3.final(&ctx, hash[:]) - return hash, true -} - -// hash_file_512 will read the file provided by the given handle -// and compute a hash -hash_file_512 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_512]byte, bool) { - if !load_at_once { - return hash_stream_512(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_512(buf[:]), ok - } - } - return [DIGEST_SIZE_512]byte{}, false -} - -hash_512 :: proc { - hash_stream_512, - hash_file_512, - hash_bytes_512, - hash_string_512, - hash_bytes_to_buffer_512, - hash_string_to_buffer_512, -} - -/* - Low level API -*/ - -Sha3_Context :: _sha3.Sha3_Context - -init :: proc(ctx: ^_sha3.Sha3_Context) { - _sha3.init(ctx) -} - -update :: proc "contextless" (ctx: ^_sha3.Sha3_Context, data: []byte) { - _sha3.update(ctx, data) -} - -final :: proc "contextless" (ctx: ^_sha3.Sha3_Context, hash: []byte) { - _sha3.final(ctx, hash) +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. +reset :: proc(ctx: ^Context) { + _sha3.reset((^_sha3.Context)(ctx)) } diff --git a/core/crypto/shake/shake.odin b/core/crypto/shake/shake.odin index 020ba68f3..e20795b43 100644 --- a/core/crypto/shake/shake.odin +++ b/core/crypto/shake/shake.odin @@ -1,3 +1,12 @@ +/* +package shake implements the SHAKE and cSHAKE XOF algorithm families. + +The SHA3 hash algorithm can be found in the crypto/sha3. + +See: +- [[ https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.202.pdf ]] +- [[ https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-185.pdf ]] +*/ package shake /* @@ -6,202 +15,58 @@ package shake List of contributors: zhibog, dotbmp: Initial implementation. - - Interface for the SHAKE hashing algorithm. - The SHA3 functionality can be found in package sha3. */ -import "core:os" -import "core:io" - import "../_sha3" -/* - High level API -*/ +// Context is a SHAKE128, SHAKE256, cSHAKE128, or cSHAKE256 instance. +Context :: distinct _sha3.Context -DIGEST_SIZE_128 :: 16 -DIGEST_SIZE_256 :: 32 - -// hash_string_128 will hash the given input and return the -// computed hash -hash_string_128 :: proc(data: string) -> [DIGEST_SIZE_128]byte { - return hash_bytes_128(transmute([]byte)(data)) +// init_128 initializes a Context for SHAKE128. +init_128 :: proc(ctx: ^Context) { + _sha3.init_cshake((^_sha3.Context)(ctx), nil, nil, 128) } -// hash_bytes_128 will hash the given input and return the -// computed hash -hash_bytes_128 :: proc(data: []byte) -> [DIGEST_SIZE_128]byte { - hash: [DIGEST_SIZE_128]byte - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_128 - _sha3.init(&ctx) - _sha3.update(&ctx, data) - _sha3.shake_xof(&ctx) - _sha3.shake_out(&ctx, hash[:]) - return hash +// init_256 initializes a Context for SHAKE256. +init_256 :: proc(ctx: ^Context) { + _sha3.init_cshake((^_sha3.Context)(ctx), nil, nil, 256) } -// hash_string_to_buffer_128 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_128 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_128(transmute([]byte)(data), hash) +// init_cshake_128 initializes a Context for cSHAKE128. +init_cshake_128 :: proc(ctx: ^Context, domain_sep: []byte) { + _sha3.init_cshake((^_sha3.Context)(ctx), nil, domain_sep, 128) } -// hash_bytes_to_buffer_128 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_128 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_128, "Size of destination buffer is smaller than the digest size") - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_128 - _sha3.init(&ctx) - _sha3.update(&ctx, data) - _sha3.shake_xof(&ctx) - _sha3.shake_out(&ctx, hash) +// init_cshake_256 initializes a Context for cSHAKE256. +init_cshake_256 :: proc(ctx: ^Context, domain_sep: []byte) { + _sha3.init_cshake((^_sha3.Context)(ctx), nil, domain_sep, 256) } -// hash_stream_128 will read the stream in chunks and compute a -// hash from its contents -hash_stream_128 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { - hash: [DIGEST_SIZE_128]byte - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_128 - _sha3.init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - _sha3.update(&ctx, buf[:read]) - } - } - _sha3.shake_xof(&ctx) - _sha3.shake_out(&ctx, hash[:]) - return hash, true +// 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((^_sha3.Context)(ctx), data) } -// hash_file_128 will read the file provided by the given handle -// and compute a hash -hash_file_128 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_128]byte, bool) { - if !load_at_once { - return hash_stream_128(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_128(buf[:]), ok - } - } - return [DIGEST_SIZE_128]byte{}, false +// 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_ := (^_sha3.Context)(ctx) + if !ctx.is_finalized { + _sha3.shake_xof(ctx_) + } + + _sha3.shake_out(ctx_, dst) } -hash_128 :: proc { - hash_stream_128, - hash_file_128, - hash_bytes_128, - hash_string_128, - hash_bytes_to_buffer_128, - hash_string_to_buffer_128, +// clone clones the Context other into ctx. +clone :: proc(ctx, other: ^Context) { + _sha3.clone((^_sha3.Context)(ctx), (^_sha3.Context)(other)) } -// hash_string_256 will hash the given input and return the -// computed hash -hash_string_256 :: proc(data: string) -> [DIGEST_SIZE_256]byte { - return hash_bytes_256(transmute([]byte)(data)) -} - -// hash_bytes_256 will hash the given input and return the -// computed hash -hash_bytes_256 :: proc(data: []byte) -> [DIGEST_SIZE_256]byte { - hash: [DIGEST_SIZE_256]byte - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_256 - _sha3.init(&ctx) - _sha3.update(&ctx, data) - _sha3.shake_xof(&ctx) - _sha3.shake_out(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_256 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_256 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_256(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_256 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_256 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_256, "Size of destination buffer is smaller than the digest size") - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_256 - _sha3.init(&ctx) - _sha3.update(&ctx, data) - _sha3.shake_xof(&ctx) - _sha3.shake_out(&ctx, hash) -} - -// hash_stream_256 will read the stream in chunks and compute a -// hash from its contents -hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { - hash: [DIGEST_SIZE_256]byte - ctx: _sha3.Sha3_Context - ctx.mdlen = DIGEST_SIZE_256 - _sha3.init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - _sha3.update(&ctx, buf[:read]) - } - } - _sha3.shake_xof(&ctx) - _sha3.shake_out(&ctx, hash[:]) - return hash, true -} - -// hash_file_256 will read the file provided by the given handle -// and compute a hash -hash_file_256 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_256]byte, bool) { - if !load_at_once { - return hash_stream_256(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_256(buf[:]), ok - } - } - return [DIGEST_SIZE_256]byte{}, false -} - -hash_256 :: proc { - hash_stream_256, - hash_file_256, - hash_bytes_256, - hash_string_256, - hash_bytes_to_buffer_256, - hash_string_to_buffer_256, -} - -/* - Low level API -*/ - -Shake_Context :: _sha3.Sha3_Context - -init :: proc(ctx: ^_sha3.Sha3_Context) { - _sha3.init(ctx) -} - -update :: proc "contextless" (ctx: ^_sha3.Sha3_Context, data: []byte) { - _sha3.update(ctx, data) -} - -final :: proc "contextless" (ctx: ^_sha3.Sha3_Context, hash: []byte) { - _sha3.shake_xof(ctx) - _sha3.shake_out(ctx, hash[:]) +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. +reset :: proc(ctx: ^Context) { + _sha3.reset((^_sha3.Context)(ctx)) } diff --git a/core/crypto/siphash/siphash.odin b/core/crypto/siphash/siphash.odin index 2d03829c2..c145ab3f0 100644 --- a/core/crypto/siphash/siphash.odin +++ b/core/crypto/siphash/siphash.odin @@ -1,3 +1,12 @@ +/* +package siphash Implements the SipHash hashing algorithm. + +Use the specific procedures for a certain setup. The generic procedures will default to Siphash 2-4. + +See: +- [[ https://github.com/veorq/SipHash ]] +- [[ https://www.aumasson.jp/siphash/siphash.pdf ]] +*/ package siphash /* @@ -6,209 +15,203 @@ package siphash List of contributors: zhibog: Initial implementation. - - Implementation of the SipHash hashing algorithm, as defined at and - - Use the specific procedures for a certain setup. The generic procdedures will default to Siphash 2-4 */ import "core:crypto" -import "core:crypto/util" +import "core:encoding/endian" +import "core:math/bits" /* High level API */ -KEY_SIZE :: 16 +KEY_SIZE :: 16 DIGEST_SIZE :: 8 // sum_string_1_3 will hash the given message with the key and return // the computed hash as a u64 sum_string_1_3 :: proc(msg, key: string) -> u64 { - return sum_bytes_1_3(transmute([]byte)(msg), transmute([]byte)(key)) + return sum_bytes_1_3(transmute([]byte)(msg), transmute([]byte)(key)) } // sum_bytes_1_3 will hash the given message with the key and return // the computed hash as a u64 -sum_bytes_1_3 :: proc (msg, key: []byte) -> u64 { - ctx: Context - hash: u64 - init(&ctx, key, 1, 3) - update(&ctx, msg) - final(&ctx, &hash) - return hash +sum_bytes_1_3 :: proc(msg, key: []byte) -> u64 { + ctx: Context + hash: u64 + init(&ctx, key, 1, 3) + update(&ctx, msg) + final(&ctx, &hash) + return hash } // sum_string_to_buffer_1_3 will hash the given message with the key and write // the computed hash into the provided destination buffer sum_string_to_buffer_1_3 :: proc(msg, key: string, dst: []byte) { - sum_bytes_to_buffer_1_3(transmute([]byte)(msg), transmute([]byte)(key), dst) + sum_bytes_to_buffer_1_3(transmute([]byte)(msg), transmute([]byte)(key), dst) } // sum_bytes_to_buffer_1_3 will hash the given message with the key and write // the computed hash into the provided destination buffer sum_bytes_to_buffer_1_3 :: proc(msg, key, dst: []byte) { - assert(len(dst) >= DIGEST_SIZE, "crypto/siphash: Destination buffer needs to be at least of size 8") - hash := sum_bytes_1_3(msg, key) - _collect_output(dst[:], hash) + hash := sum_bytes_1_3(msg, key) + _collect_output(dst[:], hash) } sum_1_3 :: proc { - sum_string_1_3, - sum_bytes_1_3, - sum_string_to_buffer_1_3, - sum_bytes_to_buffer_1_3, + sum_string_1_3, + sum_bytes_1_3, + sum_string_to_buffer_1_3, + sum_bytes_to_buffer_1_3, } -// verify_u64_1_3 will check if the supplied tag matches with the output you +// verify_u64_1_3 will check if the supplied tag matches with the output you // will get from the provided message and key -verify_u64_1_3 :: proc (tag: u64 msg, key: []byte) -> bool { - return sum_bytes_1_3(msg, key) == tag +verify_u64_1_3 :: proc(tag: u64, msg, key: []byte) -> bool { + return sum_bytes_1_3(msg, key) == tag } -// verify_bytes will check if the supplied tag matches with the output you +// verify_bytes will check if the supplied tag matches with the output you // will get from the provided message and key -verify_bytes_1_3 :: proc (tag, msg, key: []byte) -> bool { - derived_tag: [8]byte - sum_bytes_to_buffer_1_3(msg, key, derived_tag[:]) - return crypto.compare_constant_time(derived_tag[:], tag) == 1 +verify_bytes_1_3 :: proc(tag, msg, key: []byte) -> bool { + derived_tag: [8]byte + sum_bytes_to_buffer_1_3(msg, key, derived_tag[:]) + return crypto.compare_constant_time(derived_tag[:], tag) == 1 } verify_1_3 :: proc { - verify_bytes_1_3, - verify_u64_1_3, + verify_bytes_1_3, + verify_u64_1_3, } // sum_string_2_4 will hash the given message with the key and return // the computed hash as a u64 sum_string_2_4 :: proc(msg, key: string) -> u64 { - return sum_bytes_2_4(transmute([]byte)(msg), transmute([]byte)(key)) + return sum_bytes_2_4(transmute([]byte)(msg), transmute([]byte)(key)) } // sum_bytes_2_4 will hash the given message with the key and return // the computed hash as a u64 -sum_bytes_2_4 :: proc (msg, key: []byte) -> u64 { - ctx: Context - hash: u64 - init(&ctx, key, 2, 4) - update(&ctx, msg) - final(&ctx, &hash) - return hash +sum_bytes_2_4 :: proc(msg, key: []byte) -> u64 { + ctx: Context + hash: u64 + init(&ctx, key, 2, 4) + update(&ctx, msg) + final(&ctx, &hash) + return hash } // sum_string_to_buffer_2_4 will hash the given message with the key and write // the computed hash into the provided destination buffer sum_string_to_buffer_2_4 :: proc(msg, key: string, dst: []byte) { - sum_bytes_to_buffer_2_4(transmute([]byte)(msg), transmute([]byte)(key), dst) + sum_bytes_to_buffer_2_4(transmute([]byte)(msg), transmute([]byte)(key), dst) } // sum_bytes_to_buffer_2_4 will hash the given message with the key and write // the computed hash into the provided destination buffer sum_bytes_to_buffer_2_4 :: proc(msg, key, dst: []byte) { - assert(len(dst) >= DIGEST_SIZE, "crypto/siphash: Destination buffer needs to be at least of size 8") - hash := sum_bytes_2_4(msg, key) - _collect_output(dst[:], hash) + hash := sum_bytes_2_4(msg, key) + _collect_output(dst[:], hash) } sum_2_4 :: proc { - sum_string_2_4, - sum_bytes_2_4, - sum_string_to_buffer_2_4, - sum_bytes_to_buffer_2_4, + sum_string_2_4, + sum_bytes_2_4, + sum_string_to_buffer_2_4, + sum_bytes_to_buffer_2_4, } -sum_string :: sum_string_2_4 -sum_bytes :: sum_bytes_2_4 +sum_string :: sum_string_2_4 +sum_bytes :: sum_bytes_2_4 sum_string_to_buffer :: sum_string_to_buffer_2_4 -sum_bytes_to_buffer :: sum_bytes_to_buffer_2_4 +sum_bytes_to_buffer :: sum_bytes_to_buffer_2_4 sum :: proc { - sum_string, - sum_bytes, - sum_string_to_buffer, - sum_bytes_to_buffer, + sum_string, + sum_bytes, + sum_string_to_buffer, + sum_bytes_to_buffer, } -// verify_u64_2_4 will check if the supplied tag matches with the output you +// verify_u64_2_4 will check if the supplied tag matches with the output you // will get from the provided message and key -verify_u64_2_4 :: proc (tag: u64 msg, key: []byte) -> bool { - return sum_bytes_2_4(msg, key) == tag +verify_u64_2_4 :: proc(tag: u64, msg, key: []byte) -> bool { + return sum_bytes_2_4(msg, key) == tag } -// verify_bytes will check if the supplied tag matches with the output you +// verify_bytes will check if the supplied tag matches with the output you // will get from the provided message and key -verify_bytes_2_4 :: proc (tag, msg, key: []byte) -> bool { - derived_tag: [8]byte - sum_bytes_to_buffer_2_4(msg, key, derived_tag[:]) - return crypto.compare_constant_time(derived_tag[:], tag) == 1 +verify_bytes_2_4 :: proc(tag, msg, key: []byte) -> bool { + derived_tag: [8]byte + sum_bytes_to_buffer_2_4(msg, key, derived_tag[:]) + return crypto.compare_constant_time(derived_tag[:], tag) == 1 } verify_2_4 :: proc { - verify_bytes_2_4, - verify_u64_2_4, + verify_bytes_2_4, + verify_u64_2_4, } verify_bytes :: verify_bytes_2_4 -verify_u64 :: verify_u64_2_4 +verify_u64 :: verify_u64_2_4 verify :: proc { - verify_bytes, - verify_u64, + verify_bytes, + verify_u64, } // sum_string_4_8 will hash the given message with the key and return // the computed hash as a u64 sum_string_4_8 :: proc(msg, key: string) -> u64 { - return sum_bytes_4_8(transmute([]byte)(msg), transmute([]byte)(key)) + return sum_bytes_4_8(transmute([]byte)(msg), transmute([]byte)(key)) } // sum_bytes_4_8 will hash the given message with the key and return // the computed hash as a u64 -sum_bytes_4_8 :: proc (msg, key: []byte) -> u64 { - ctx: Context - hash: u64 - init(&ctx, key, 4, 8) - update(&ctx, msg) - final(&ctx, &hash) - return hash +sum_bytes_4_8 :: proc(msg, key: []byte) -> u64 { + ctx: Context + hash: u64 + init(&ctx, key, 4, 8) + update(&ctx, msg) + final(&ctx, &hash) + return hash } // sum_string_to_buffer_4_8 will hash the given message with the key and write // the computed hash into the provided destination buffer sum_string_to_buffer_4_8 :: proc(msg, key: string, dst: []byte) { - sum_bytes_to_buffer_4_8(transmute([]byte)(msg), transmute([]byte)(key), dst) + sum_bytes_to_buffer_4_8(transmute([]byte)(msg), transmute([]byte)(key), dst) } // sum_bytes_to_buffer_4_8 will hash the given message with the key and write // the computed hash into the provided destination buffer sum_bytes_to_buffer_4_8 :: proc(msg, key, dst: []byte) { - assert(len(dst) >= DIGEST_SIZE, "crypto/siphash: Destination buffer needs to be at least of size 8") - hash := sum_bytes_4_8(msg, key) - _collect_output(dst[:], hash) + hash := sum_bytes_4_8(msg, key) + _collect_output(dst[:], hash) } sum_4_8 :: proc { - sum_string_4_8, - sum_bytes_4_8, - sum_string_to_buffer_4_8, - sum_bytes_to_buffer_4_8, + sum_string_4_8, + sum_bytes_4_8, + sum_string_to_buffer_4_8, + sum_bytes_to_buffer_4_8, } -// verify_u64_4_8 will check if the supplied tag matches with the output you +// verify_u64_4_8 will check if the supplied tag matches with the output you // will get from the provided message and key -verify_u64_4_8 :: proc (tag: u64 msg, key: []byte) -> bool { - return sum_bytes_4_8(msg, key) == tag +verify_u64_4_8 :: proc(tag: u64, msg, key: []byte) -> bool { + return sum_bytes_4_8(msg, key) == tag } -// verify_bytes will check if the supplied tag matches with the output you +// verify_bytes will check if the supplied tag matches with the output you // will get from the provided message and key -verify_bytes_4_8 :: proc (tag, msg, key: []byte) -> bool { - derived_tag: [8]byte - sum_bytes_to_buffer_4_8(msg, key, derived_tag[:]) - return crypto.compare_constant_time(derived_tag[:], tag) == 1 +verify_bytes_4_8 :: proc(tag, msg, key: []byte) -> bool { + derived_tag: [8]byte + sum_bytes_to_buffer_4_8(msg, key, derived_tag[:]) + return crypto.compare_constant_time(derived_tag[:], tag) == 1 } verify_4_8 :: proc { - verify_bytes_4_8, - verify_u64_4_8, + verify_bytes_4_8, + verify_u64_4_8, } /* @@ -216,120 +219,150 @@ verify_4_8 :: proc { */ init :: proc(ctx: ^Context, key: []byte, c_rounds, d_rounds: int) { - assert(len(key) == KEY_SIZE, "crypto/siphash: Invalid key size, want 16") - ctx.c_rounds = c_rounds - ctx.d_rounds = d_rounds - is_valid_setting := (ctx.c_rounds == 1 && ctx.d_rounds == 3) || - (ctx.c_rounds == 2 && ctx.d_rounds == 4) || - (ctx.c_rounds == 4 && ctx.d_rounds == 8) - assert(is_valid_setting, "crypto/siphash: Incorrect rounds set up. Valid pairs are (1,3), (2,4) and (4,8)") - ctx.k0 = util.U64_LE(key[:8]) - ctx.k1 = util.U64_LE(key[8:]) - ctx.v0 = 0x736f6d6570736575 ~ ctx.k0 - ctx.v1 = 0x646f72616e646f6d ~ ctx.k1 - ctx.v2 = 0x6c7967656e657261 ~ ctx.k0 - ctx.v3 = 0x7465646279746573 ~ ctx.k1 - ctx.is_initialized = true + if len(key) != KEY_SIZE { + panic("crypto/siphash; invalid key size") + } + ctx.c_rounds = c_rounds + ctx.d_rounds = d_rounds + is_valid_setting := + (ctx.c_rounds == 1 && ctx.d_rounds == 3) || + (ctx.c_rounds == 2 && ctx.d_rounds == 4) || + (ctx.c_rounds == 4 && ctx.d_rounds == 8) + if !is_valid_setting { + panic("crypto/siphash: incorrect rounds set up") + } + ctx.k0 = endian.unchecked_get_u64le(key[:8]) + ctx.k1 = endian.unchecked_get_u64le(key[8:]) + ctx.v0 = 0x736f6d6570736575 ~ ctx.k0 + ctx.v1 = 0x646f72616e646f6d ~ ctx.k1 + ctx.v2 = 0x6c7967656e657261 ~ ctx.k0 + ctx.v3 = 0x7465646279746573 ~ ctx.k1 + + ctx.last_block = 0 + ctx.total_length = 0 + + ctx.is_initialized = true } update :: proc(ctx: ^Context, data: []byte) { - assert(ctx.is_initialized, "crypto/siphash: Context is not initialized") - ctx.last_block = len(data) / 8 * 8 - ctx.buf = data - i := 0 - m: u64 - for i < ctx.last_block { - m = u64(ctx.buf[i] & 0xff) - i += 1 + assert(ctx.is_initialized, "crypto/siphash: context is not initialized") - for r in u64(1)..<8 { - m |= u64(ctx.buf[i] & 0xff) << (r * 8) - i += 1 - } - - ctx.v3 ~= m - for _ in 0.. 0 { + n := copy(ctx.buf[ctx.last_block:], data) + ctx.last_block += n + if ctx.last_block == BLOCK_SIZE { + block(ctx, ctx.buf[:]) + ctx.last_block = 0 + } + data = data[n:] + } + if len(data) >= BLOCK_SIZE { + n := len(data) &~ (BLOCK_SIZE - 1) + block(ctx, data[:n]) + data = data[n:] + } + if len(data) > 0 { + ctx.last_block = copy(ctx.buf[:], data) + } } final :: proc(ctx: ^Context, dst: ^u64) { - m: u64 - for i := len(ctx.buf) - 1; i >= ctx.last_block; i -= 1 { - m <<= 8 - m |= u64(ctx.buf[i] & 0xff) - } - m |= u64(len(ctx.buf) << 56) + assert(ctx.is_initialized, "crypto/siphash: context is not initialized") - ctx.v3 ~= m + tmp: [BLOCK_SIZE]byte + copy(tmp[:], ctx.buf[:ctx.last_block]) + tmp[7] = byte(ctx.total_length & 0xff) + block(ctx, tmp[:]) - for _ in 0..= BLOCK_SIZE { + m := endian.unchecked_get_u64le(buf) + + ctx.v3 ~= m + for _ in 0 ..< ctx.c_rounds { + _compress(ctx) + } + + ctx.v0 ~= m + + buf = buf[BLOCK_SIZE:] + } +} + +@(private) _get_byte :: #force_inline proc "contextless" (byte_num: byte, into: u64) -> byte { - return byte(into >> (((~byte_num) & (size_of(u64) - 1)) << 3)) + return byte(into >> (((~byte_num) & (size_of(u64) - 1)) << 3)) } -_collect_output :: #force_inline proc "contextless" (dst: []byte, hash: u64) { - dst[0] = _get_byte(7, hash) - dst[1] = _get_byte(6, hash) - dst[2] = _get_byte(5, hash) - dst[3] = _get_byte(4, hash) - dst[4] = _get_byte(3, hash) - dst[5] = _get_byte(2, hash) - dst[6] = _get_byte(1, hash) - dst[7] = _get_byte(0, hash) +@(private) +_collect_output :: #force_inline proc(dst: []byte, hash: u64) { + if len(dst) < DIGEST_SIZE { + panic("crypto/siphash: invalid tag size") + } + dst[0] = _get_byte(7, hash) + dst[1] = _get_byte(6, hash) + dst[2] = _get_byte(5, hash) + dst[3] = _get_byte(4, hash) + dst[4] = _get_byte(3, hash) + dst[5] = _get_byte(2, hash) + dst[6] = _get_byte(1, hash) + dst[7] = _get_byte(0, hash) } +@(private) _compress :: #force_inline proc "contextless" (ctx: ^Context) { - ctx.v0 += ctx.v1 - ctx.v1 = util.ROTL64(ctx.v1, 13) - ctx.v1 ~= ctx.v0 - ctx.v0 = util.ROTL64(ctx.v0, 32) - ctx.v2 += ctx.v3 - ctx.v3 = util.ROTL64(ctx.v3, 16) - ctx.v3 ~= ctx.v2 - ctx.v0 += ctx.v3 - ctx.v3 = util.ROTL64(ctx.v3, 21) - ctx.v3 ~= ctx.v0 - ctx.v2 += ctx.v1 - ctx.v1 = util.ROTL64(ctx.v1, 17) - ctx.v1 ~= ctx.v2 - ctx.v2 = util.ROTL64(ctx.v2, 32) + ctx.v0 += ctx.v1 + ctx.v1 = bits.rotate_left64(ctx.v1, 13) + ctx.v1 ~= ctx.v0 + ctx.v0 = bits.rotate_left64(ctx.v0, 32) + ctx.v2 += ctx.v3 + ctx.v3 = bits.rotate_left64(ctx.v3, 16) + ctx.v3 ~= ctx.v2 + ctx.v0 += ctx.v3 + ctx.v3 = bits.rotate_left64(ctx.v3, 21) + ctx.v3 ~= ctx.v0 + ctx.v2 += ctx.v1 + ctx.v1 = bits.rotate_left64(ctx.v1, 17) + ctx.v1 ~= ctx.v2 + ctx.v2 = bits.rotate_left64(ctx.v2, 32) } diff --git a/core/crypto/sm3/sm3.odin b/core/crypto/sm3/sm3.odin index 9e684ff08..f910d735b 100644 --- a/core/crypto/sm3/sm3.odin +++ b/core/crypto/sm3/sm3.odin @@ -1,3 +1,9 @@ +/* +package sm3 implements the SM3 hash algorithm. + +See: +- [[ https://datatracker.ietf.org/doc/html/draft-sca-cfrg-sm3-02 ]] +*/ package sm3 /* @@ -6,245 +12,217 @@ package sm3 List of contributors: zhibog, dotbmp: Initial implementation. - - Implementation of the SM3 hashing algorithm, as defined in */ -import "core:os" -import "core:io" - -import "../util" - -/* - High level API -*/ +import "core:encoding/endian" +import "core:math/bits" +import "core:mem" +// DIGEST_SIZE is the SM3 digest size in bytes. DIGEST_SIZE :: 32 -// hash_string will hash the given input and return the -// computed hash -hash_string :: proc(data: string) -> [DIGEST_SIZE]byte { - return hash_bytes(transmute([]byte)(data)) +// BLOCK_SIZE is the SM3 block size in bytes. +BLOCK_SIZE :: 64 + +// Context is a SM3 instance. +Context :: struct { + state: [8]u32, + x: [BLOCK_SIZE]byte, + bitlength: u64, + length: u64, + + is_initialized: bool, } -// hash_bytes will hash the given input and return the -// computed hash -hash_bytes :: proc(data: []byte) -> [DIGEST_SIZE]byte { - hash: [DIGEST_SIZE]byte - ctx: Sm3_Context - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash +// init initializes a Context. +init :: proc(ctx: ^Context) { + ctx.state[0] = IV[0] + ctx.state[1] = IV[1] + ctx.state[2] = IV[2] + ctx.state[3] = IV[3] + ctx.state[4] = IV[4] + ctx.state[5] = IV[5] + ctx.state[6] = IV[6] + ctx.state[7] = IV[7] + + ctx.length = 0 + ctx.bitlength = 0 + + ctx.is_initialized = true } -// hash_string_to_buffer will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer(transmute([]byte)(data), hash) +// update adds more data to the Context. +update :: proc(ctx: ^Context, data: []byte) { + assert(ctx.is_initialized) + + data := data + ctx.length += u64(len(data)) + + if ctx.bitlength > 0 { + n := copy(ctx.x[ctx.bitlength:], data[:]) + ctx.bitlength += u64(n) + if ctx.bitlength == BLOCK_SIZE { + block(ctx, ctx.x[:]) + ctx.bitlength = 0 + } + data = data[n:] + } + if len(data) >= BLOCK_SIZE { + n := len(data) &~ (BLOCK_SIZE - 1) + block(ctx, data[:n]) + data = data[n:] + } + if len(data) > 0 { + ctx.bitlength = u64(copy(ctx.x[:], data[:])) + } } -// hash_bytes_to_buffer will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE, "Size of destination buffer is smaller than the digest size") - ctx: Sm3_Context - init(&ctx) - update(&ctx, data) - final(&ctx, hash) +// final finalizes the Context, writes the digest to hash, and calls +// reset on the Context. +// +// 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) { + assert(ctx.is_initialized) + + if len(hash) < DIGEST_SIZE { + panic("crypto/sm3: invalid destination digest size") + } + + ctx := ctx + if finalize_clone { + tmp_ctx: Context + clone(&tmp_ctx, ctx) + ctx = &tmp_ctx + } + defer(reset(ctx)) + + length := ctx.length + + pad: [BLOCK_SIZE]byte + pad[0] = 0x80 + if length % BLOCK_SIZE < 56 { + update(ctx, pad[0:56 - length % BLOCK_SIZE]) + } else { + update(ctx, pad[0:BLOCK_SIZE + 56 - length % BLOCK_SIZE]) + } + + length <<= 3 + endian.unchecked_put_u64be(pad[:], length) + update(ctx, pad[0:8]) + assert(ctx.bitlength == 0) + + for i := 0; i < DIGEST_SIZE / 4; i += 1 { + endian.unchecked_put_u32be(hash[i * 4:], ctx.state[i]) + } } -// hash_stream will read the stream in chunks and compute a -// hash from its contents -hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { - hash: [DIGEST_SIZE]byte - ctx: Sm3_Context - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true +// clone clones the Context other into ctx. +clone :: proc(ctx, other: ^Context) { + ctx^ = other^ } -// hash_file will read the file provided by the given handle -// and compute a hash -hash_file :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE]byte, bool) { - if !load_at_once { - return hash_stream(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes(buf[:]), ok - } - } - return [DIGEST_SIZE]byte{}, false -} +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. +reset :: proc(ctx: ^Context) { + if !ctx.is_initialized { + return + } -hash :: proc { - hash_stream, - hash_file, - hash_bytes, - hash_string, - hash_bytes_to_buffer, - hash_string_to_buffer, -} - -/* - Low level API -*/ - -init :: proc(ctx: ^Sm3_Context) { - ctx.state[0] = IV[0] - ctx.state[1] = IV[1] - ctx.state[2] = IV[2] - ctx.state[3] = IV[3] - ctx.state[4] = IV[4] - ctx.state[5] = IV[5] - ctx.state[6] = IV[6] - ctx.state[7] = IV[7] -} - -update :: proc(ctx: ^Sm3_Context, data: []byte) { - data := data - ctx.length += u64(len(data)) - - if ctx.bitlength > 0 { - n := copy(ctx.x[ctx.bitlength:], data[:]) - ctx.bitlength += u64(n) - if ctx.bitlength == 64 { - block(ctx, ctx.x[:]) - ctx.bitlength = 0 - } - data = data[n:] - } - if len(data) >= 64 { - n := len(data) &~ (64 - 1) - block(ctx, data[:n]) - data = data[n:] - } - if len(data) > 0 { - ctx.bitlength = u64(copy(ctx.x[:], data[:])) - } -} - -final :: proc(ctx: ^Sm3_Context, hash: []byte) { - length := ctx.length - - pad: [64]byte - pad[0] = 0x80 - if length % 64 < 56 { - update(ctx, pad[0: 56 - length % 64]) - } else { - update(ctx, pad[0: 64 + 56 - length % 64]) - } - - length <<= 3 - util.PUT_U64_BE(pad[:], length) - update(ctx, pad[0: 8]) - assert(ctx.bitlength == 0) - - util.PUT_U32_BE(hash[0:], ctx.state[0]) - util.PUT_U32_BE(hash[4:], ctx.state[1]) - util.PUT_U32_BE(hash[8:], ctx.state[2]) - util.PUT_U32_BE(hash[12:], ctx.state[3]) - util.PUT_U32_BE(hash[16:], ctx.state[4]) - util.PUT_U32_BE(hash[20:], ctx.state[5]) - util.PUT_U32_BE(hash[24:], ctx.state[6]) - util.PUT_U32_BE(hash[28:], ctx.state[7]) + mem.zero_explicit(ctx, size_of(ctx^)) } /* SM3 implementation */ -Sm3_Context :: struct { - state: [8]u32, - x: [64]byte, - bitlength: u64, - length: u64, -} - +@(private) IV := [8]u32 { - 0x7380166f, 0x4914b2b9, 0x172442d7, 0xda8a0600, - 0xa96f30bc, 0x163138aa, 0xe38dee4d, 0xb0fb0e4e, + 0x7380166f, 0x4914b2b9, 0x172442d7, 0xda8a0600, + 0xa96f30bc, 0x163138aa, 0xe38dee4d, 0xb0fb0e4e, } -block :: proc "contextless" (ctx: ^Sm3_Context, buf: []byte) { - buf := buf +@(private) +block :: proc "contextless" (ctx: ^Context, buf: []byte) { + buf := buf - w: [68]u32 - wp: [64]u32 + w: [68]u32 + wp: [64]u32 - state0, state1, state2, state3 := ctx.state[0], ctx.state[1], ctx.state[2], ctx.state[3] - state4, state5, state6, state7 := ctx.state[4], ctx.state[5], ctx.state[6], ctx.state[7] + state0, state1, state2, state3 := ctx.state[0], ctx.state[1], ctx.state[2], ctx.state[3] + state4, state5, state6, state7 := ctx.state[4], ctx.state[5], ctx.state[6], ctx.state[7] - for len(buf) >= 64 { - for i := 0; i < 16; i += 1 { - j := i * 4 - w[i] = u32(buf[j]) << 24 | u32(buf[j + 1]) << 16 | u32(buf[j + 2]) << 8 | u32(buf[j + 3]) - } - for i := 16; i < 68; i += 1 { - p1v := w[i - 16] ~ w[i - 9] ~ util.ROTL32(w[i - 3], 15) - // @note(zh): inlined P1 - w[i] = p1v ~ util.ROTL32(p1v, 15) ~ util.ROTL32(p1v, 23) ~ util.ROTL32(w[i - 13], 7) ~ w[i - 6] - } - for i := 0; i < 64; i += 1 { - wp[i] = w[i] ~ w[i + 4] - } + for len(buf) >= BLOCK_SIZE { + for i := 0; i < 16; i += 1 { + w[i] = endian.unchecked_get_u32be(buf[i * 4:]) + } + for i := 16; i < 68; i += 1 { + p1v := w[i - 16] ~ w[i - 9] ~ bits.rotate_left32(w[i - 3], 15) + // @note(zh): inlined P1 + w[i] = + p1v ~ + bits.rotate_left32(p1v, 15) ~ + bits.rotate_left32(p1v, 23) ~ + bits.rotate_left32(w[i - 13], 7) ~ + w[i - 6] + } + for i := 0; i < 64; i += 1 { + wp[i] = w[i] ~ w[i + 4] + } - a, b, c, d := state0, state1, state2, state3 - e, f, g, h := state4, state5, state6, state7 + a, b, c, d := state0, state1, state2, state3 + e, f, g, h := state4, state5, state6, state7 - for i := 0; i < 16; i += 1 { - v1 := util.ROTL32(u32(a), 12) - ss1 := util.ROTL32(v1 + u32(e) + util.ROTL32(0x79cc4519, i), 7) - ss2 := ss1 ~ v1 + for i := 0; i < 16; i += 1 { + v1 := bits.rotate_left32(u32(a), 12) + ss1 := bits.rotate_left32(v1 + u32(e) + bits.rotate_left32(0x79cc4519, i), 7) + ss2 := ss1 ~ v1 - // @note(zh): inlined FF1 - tt1 := u32(a ~ b ~ c) + u32(d) + ss2 + wp[i] - // @note(zh): inlined GG1 - tt2 := u32(e ~ f ~ g) + u32(h) + ss1 + w[i] + // @note(zh): inlined FF1 + tt1 := u32(a ~ b ~ c) + u32(d) + ss2 + wp[i] + // @note(zh): inlined GG1 + tt2 := u32(e ~ f ~ g) + u32(h) + ss1 + w[i] - a, b, c, d = tt1, a, util.ROTL32(u32(b), 9), c - // @note(zh): inlined P0 - e, f, g, h = (tt2 ~ util.ROTL32(tt2, 9) ~ util.ROTL32(tt2, 17)), e, util.ROTL32(u32(f), 19), g - } + a, b, c, d = tt1, a, bits.rotate_left32(u32(b), 9), c + // @note(zh): inlined P0 + e, f, g, h = + (tt2 ~ bits.rotate_left32(tt2, 9) ~ bits.rotate_left32(tt2, 17)), + e, + bits.rotate_left32(u32(f), 19), + g + } - for i := 16; i < 64; i += 1 { - v := util.ROTL32(u32(a), 12) - ss1 := util.ROTL32(v + u32(e) + util.ROTL32(0x7a879d8a, i % 32), 7) - ss2 := ss1 ~ v + for i := 16; i < 64; i += 1 { + v := bits.rotate_left32(u32(a), 12) + ss1 := bits.rotate_left32(v + u32(e) + bits.rotate_left32(0x7a879d8a, i % 32), 7) + ss2 := ss1 ~ v - // @note(zh): inlined FF2 - tt1 := u32(((a & b) | (a & c) | (b & c)) + d) + ss2 + wp[i] - // @note(zh): inlined GG2 - tt2 := u32(((e & f) | ((~e) & g)) + h) + ss1 + w[i] + // @note(zh): inlined FF2 + tt1 := u32(((a & b) | (a & c) | (b & c)) + d) + ss2 + wp[i] + // @note(zh): inlined GG2 + tt2 := u32(((e & f) | ((~e) & g)) + h) + ss1 + w[i] - a, b, c, d = tt1, a, util.ROTL32(u32(b), 9), c - // @note(zh): inlined P0 - e, f, g, h = (tt2 ~ util.ROTL32(tt2, 9) ~ util.ROTL32(tt2, 17)), e, util.ROTL32(u32(f), 19), g - } + a, b, c, d = tt1, a, bits.rotate_left32(u32(b), 9), c + // @note(zh): inlined P0 + e, f, g, h = + (tt2 ~ bits.rotate_left32(tt2, 9) ~ bits.rotate_left32(tt2, 17)), + e, + bits.rotate_left32(u32(f), 19), + g + } - state0 ~= a - state1 ~= b - state2 ~= c - state3 ~= d - state4 ~= e - state5 ~= f - state6 ~= g - state7 ~= h + state0 ~= a + state1 ~= b + state2 ~= c + state3 ~= d + state4 ~= e + state5 ~= f + state6 ~= g + state7 ~= h - buf = buf[64:] - } + buf = buf[BLOCK_SIZE:] + } - ctx.state[0], ctx.state[1], ctx.state[2], ctx.state[3] = state0, state1, state2, state3 - ctx.state[4], ctx.state[5], ctx.state[6], ctx.state[7] = state4, state5, state6, state7 + ctx.state[0], ctx.state[1], ctx.state[2], ctx.state[3] = state0, state1, state2, state3 + ctx.state[4], ctx.state[5], ctx.state[6], ctx.state[7] = state4, state5, state6, state7 } diff --git a/core/crypto/streebog/streebog.odin b/core/crypto/streebog/streebog.odin deleted file mode 100644 index 42da1e695..000000000 --- a/core/crypto/streebog/streebog.odin +++ /dev/null @@ -1,517 +0,0 @@ -package streebog - -/* - Copyright 2021 zhibog - Made available under the BSD-3 license. - - List of contributors: - zhibog, dotbmp: Initial implementation. - - Implementation of the Streebog hashing algorithm, standardized as GOST R 34.11-2012 in RFC 6986 -*/ - -import "core:os" -import "core:io" - -import "../util" - -/* - High level API -*/ - -DIGEST_SIZE_256 :: 32 -DIGEST_SIZE_512 :: 64 - -// hash_string_256 will hash the given input and return the -// computed hash -hash_string_256 :: proc(data: string) -> [DIGEST_SIZE_256]byte { - return hash_bytes_256(transmute([]byte)(data)) -} - -// hash_bytes_256 will hash the given input and return the -// computed hash -hash_bytes_256 :: proc(data: []byte) -> [DIGEST_SIZE_256]byte { - hash: [DIGEST_SIZE_256]byte - ctx: Streebog_Context - ctx.is256 = true - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_256 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_256 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_256(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_256 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_256 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_256, "Size of destination buffer is smaller than the digest size") - ctx: Streebog_Context - ctx.is256 = true - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) -} - -// hash_stream_256 will read the stream in chunks and compute a -// hash from its contents -hash_stream_256 :: proc(s: io.Stream) -> ([DIGEST_SIZE_256]byte, bool) { - hash: [DIGEST_SIZE_256]byte - ctx: Streebog_Context - ctx.is256 = true - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_256 will read the file provided by the given handle -// and compute a hash -hash_file_256 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_256]byte, bool) { - if !load_at_once { - return hash_stream_256(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_256(buf[:]), ok - } - } - return [DIGEST_SIZE_256]byte{}, false -} - -hash_256 :: proc { - hash_stream_256, - hash_file_256, - hash_bytes_256, - hash_string_256, - hash_bytes_to_buffer_256, - hash_string_to_buffer_256, -} - -// hash_string_512 will hash the given input and return the -// computed hash -hash_string_512 :: proc(data: string) -> [DIGEST_SIZE_512]byte { - return hash_bytes_512(transmute([]byte)(data)) -} - -// hash_bytes_512 will hash the given input and return the -// computed hash -hash_bytes_512 :: proc(data: []byte) -> [DIGEST_SIZE_512]byte { - hash: [DIGEST_SIZE_512]byte - ctx: Streebog_Context - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_512 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_512 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_512(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_512 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_512 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_512, "Size of destination buffer is smaller than the digest size") - ctx: Streebog_Context - init(&ctx) - update(&ctx, data) - final(&ctx, hash[:]) -} - -// hash_stream_512 will read the stream in chunks and compute a -// hash from its contents -hash_stream_512 :: proc(s: io.Stream) -> ([DIGEST_SIZE_512]byte, bool) { - hash: [DIGEST_SIZE_512]byte - ctx: Streebog_Context - init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file_512 will read the file provided by the given handle -// and compute a hash -hash_file_512 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_512]byte, bool) { - if !load_at_once { - return hash_stream_512(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_512(buf[:]), ok - } - } - return [DIGEST_SIZE_512]byte{}, false -} - -hash_512 :: proc { - hash_stream_512, - hash_file_512, - hash_bytes_512, - hash_string_512, - hash_bytes_to_buffer_512, - hash_string_to_buffer_512, -} - -/* - Low level API -*/ - -init :: proc(ctx: ^Streebog_Context) { - if ctx.is256 { - ctx.hash_size = 256 - for _, i in ctx.h { - ctx.h[i] = 0x01 - } - } else { - ctx.hash_size = 512 - } - ctx.v_512[1] = 0x02 -} - -update :: proc(ctx: ^Streebog_Context, data: []byte) { - length := u64(len(data)) - chk_size: u64 - data := data - for (length > 63) && (ctx.buf_size == 0) { - stage2(ctx, data) - data = data[64:] - length -= 64 - } - - for length != 0 { - chk_size = 64 - ctx.buf_size - if chk_size > length { - chk_size = length - } - copy(ctx.buffer[ctx.buf_size:], data[:chk_size]) - ctx.buf_size += chk_size - length -= chk_size - data = data[chk_size:] - if ctx.buf_size == 64 { - stage2(ctx, ctx.buffer[:]) - ctx.buf_size = 0 - } - } -} - -final :: proc(ctx: ^Streebog_Context, hash: []byte) { - t: [64]byte - t[1] = byte((ctx.buf_size * 8) >> 8) & 0xff - t[0] = byte((ctx.buf_size) * 8) & 0xff - - padding(ctx) - - G(ctx.h[:], ctx.n[:], ctx.buffer[:]) - - add_mod_512(ctx.n[:], t[:], ctx.n[:]) - add_mod_512(ctx.sigma[:], ctx.buffer[:], ctx.sigma[:]) - - G(ctx.h[:], ctx.v_0[:], ctx.n[:]) - G(ctx.h[:], ctx.v_0[:], ctx.sigma[:]) - - if ctx.is256 { - copy(hash[:], ctx.h[32:]) - } else { - copy(hash[:], ctx.h[:]) - } -} - -/* - Streebog implementation -*/ - -PI := [256]byte { - 252, 238, 221, 17, 207, 110, 49, 22, 251, 196, 250, 218, 35, 197, 4, 77, - 233, 119, 240, 219, 147, 46, 153, 186, 23, 54, 241, 187, 20, 205, 95, 193, - 249, 24, 101, 90, 226, 92, 239, 33, 129, 28, 60, 66, 139, 1, 142, 79, - 5, 132, 2, 174, 227, 106, 143, 160, 6, 11, 237, 152, 127, 212, 211, 31, - 235, 52, 44, 81, 234, 200, 72, 171, 242, 42, 104, 162, 253, 58, 206, 204, - 181, 112, 14, 86, 8, 12, 118, 18, 191, 114, 19, 71, 156, 183, 93, 135, - 21, 161, 150, 41, 16, 123, 154, 199, 243, 145, 120, 111, 157, 158, 178, 177, - 50, 117, 25, 61, 255, 53, 138, 126, 109, 84, 198, 128, 195, 189, 13, 87, - 223, 245, 36, 169, 62, 168, 67, 201, 215, 121, 214, 246, 124, 34, 185, 3, - 224, 15, 236, 222, 122, 148, 176, 188, 220, 232, 40, 80, 78, 51, 10, 74, - 167, 151, 96, 115, 30, 0, 98, 68, 26, 184, 56, 130, 100, 159, 38, 65, - 173, 69, 70, 146, 39, 94, 85, 47, 140, 163, 165, 125, 105, 213, 149, 59, - 7, 88, 179, 64, 134, 172, 29, 247, 48, 55, 107, 228, 136, 217, 231, 137, - 225, 27, 131, 73, 76, 63, 248, 254, 141, 83, 170, 144, 202, 216, 133, 97, - 32, 113, 103, 164, 45, 43, 9, 91, 203, 155, 37, 208, 190, 229, 108, 82, - 89, 166, 116, 210, 230, 244, 180, 192, 209, 102, 175, 194, 57, 75, 99, 182, -} - -TAU := [64]byte { - 0, 8, 16, 24, 32, 40, 48, 56, - 1, 9, 17, 25, 33, 41, 49, 57, - 2, 10, 18, 26, 34, 42, 50, 58, - 3, 11, 19, 27, 35, 43, 51, 59, - 4, 12, 20, 28, 36, 44, 52, 60, - 5, 13, 21, 29, 37, 45, 53, 61, - 6, 14, 22, 30, 38, 46, 54, 62, - 7, 15, 23, 31, 39, 47, 55, 63, -} - -STREEBOG_A := [64]u64 { - 0x8e20faa72ba0b470, 0x47107ddd9b505a38, 0xad08b0e0c3282d1c, 0xd8045870ef14980e, - 0x6c022c38f90a4c07, 0x3601161cf205268d, 0x1b8e0b0e798c13c8, 0x83478b07b2468764, - 0xa011d380818e8f40, 0x5086e740ce47c920, 0x2843fd2067adea10, 0x14aff010bdd87508, - 0x0ad97808d06cb404, 0x05e23c0468365a02, 0x8c711e02341b2d01, 0x46b60f011a83988e, - 0x90dab52a387ae76f, 0x486dd4151c3dfdb9, 0x24b86a840e90f0d2, 0x125c354207487869, - 0x092e94218d243cba, 0x8a174a9ec8121e5d, 0x4585254f64090fa0, 0xaccc9ca9328a8950, - 0x9d4df05d5f661451, 0xc0a878a0a1330aa6, 0x60543c50de970553, 0x302a1e286fc58ca7, - 0x18150f14b9ec46dd, 0x0c84890ad27623e0, 0x0642ca05693b9f70, 0x0321658cba93c138, - 0x86275df09ce8aaa8, 0x439da0784e745554, 0xafc0503c273aa42a, 0xd960281e9d1d5215, - 0xe230140fc0802984, 0x71180a8960409a42, 0xb60c05ca30204d21, 0x5b068c651810a89e, - 0x456c34887a3805b9, 0xac361a443d1c8cd2, 0x561b0d22900e4669, 0x2b838811480723ba, - 0x9bcf4486248d9f5d, 0xc3e9224312c8c1a0, 0xeffa11af0964ee50, 0xf97d86d98a327728, - 0xe4fa2054a80b329c, 0x727d102a548b194e, 0x39b008152acb8227, 0x9258048415eb419d, - 0x492c024284fbaec0, 0xaa16012142f35760, 0x550b8e9e21f7a530, 0xa48b474f9ef5dc18, - 0x70a6a56e2440598e, 0x3853dc371220a247, 0x1ca76e95091051ad, 0x0edd37c48a08a6d8, - 0x07e095624504536c, 0x8d70c431ac02a736, 0xc83862965601dd1b, 0x641c314b2b8ee083, -} - -STREEBOG_C := [12][64]byte { - { - 0x07, 0x45, 0xa6, 0xf2, 0x59, 0x65, 0x80, 0xdd, - 0x23, 0x4d, 0x74, 0xcc, 0x36, 0x74, 0x76, 0x05, - 0x15, 0xd3, 0x60, 0xa4, 0x08, 0x2a, 0x42, 0xa2, - 0x01, 0x69, 0x67, 0x92, 0x91, 0xe0, 0x7c, 0x4b, - 0xfc, 0xc4, 0x85, 0x75, 0x8d, 0xb8, 0x4e, 0x71, - 0x16, 0xd0, 0x45, 0x2e, 0x43, 0x76, 0x6a, 0x2f, - 0x1f, 0x7c, 0x65, 0xc0, 0x81, 0x2f, 0xcb, 0xeb, - 0xe9, 0xda, 0xca, 0x1e, 0xda, 0x5b, 0x08, 0xb1, - }, - { - 0xb7, 0x9b, 0xb1, 0x21, 0x70, 0x04, 0x79, 0xe6, - 0x56, 0xcd, 0xcb, 0xd7, 0x1b, 0xa2, 0xdd, 0x55, - 0xca, 0xa7, 0x0a, 0xdb, 0xc2, 0x61, 0xb5, 0x5c, - 0x58, 0x99, 0xd6, 0x12, 0x6b, 0x17, 0xb5, 0x9a, - 0x31, 0x01, 0xb5, 0x16, 0x0f, 0x5e, 0xd5, 0x61, - 0x98, 0x2b, 0x23, 0x0a, 0x72, 0xea, 0xfe, 0xf3, - 0xd7, 0xb5, 0x70, 0x0f, 0x46, 0x9d, 0xe3, 0x4f, - 0x1a, 0x2f, 0x9d, 0xa9, 0x8a, 0xb5, 0xa3, 0x6f, - }, - { - 0xb2, 0x0a, 0xba, 0x0a, 0xf5, 0x96, 0x1e, 0x99, - 0x31, 0xdb, 0x7a, 0x86, 0x43, 0xf4, 0xb6, 0xc2, - 0x09, 0xdb, 0x62, 0x60, 0x37, 0x3a, 0xc9, 0xc1, - 0xb1, 0x9e, 0x35, 0x90, 0xe4, 0x0f, 0xe2, 0xd3, - 0x7b, 0x7b, 0x29, 0xb1, 0x14, 0x75, 0xea, 0xf2, - 0x8b, 0x1f, 0x9c, 0x52, 0x5f, 0x5e, 0xf1, 0x06, - 0x35, 0x84, 0x3d, 0x6a, 0x28, 0xfc, 0x39, 0x0a, - 0xc7, 0x2f, 0xce, 0x2b, 0xac, 0xdc, 0x74, 0xf5, - }, - { - 0x2e, 0xd1, 0xe3, 0x84, 0xbc, 0xbe, 0x0c, 0x22, - 0xf1, 0x37, 0xe8, 0x93, 0xa1, 0xea, 0x53, 0x34, - 0xbe, 0x03, 0x52, 0x93, 0x33, 0x13, 0xb7, 0xd8, - 0x75, 0xd6, 0x03, 0xed, 0x82, 0x2c, 0xd7, 0xa9, - 0x3f, 0x35, 0x5e, 0x68, 0xad, 0x1c, 0x72, 0x9d, - 0x7d, 0x3c, 0x5c, 0x33, 0x7e, 0x85, 0x8e, 0x48, - 0xdd, 0xe4, 0x71, 0x5d, 0xa0, 0xe1, 0x48, 0xf9, - 0xd2, 0x66, 0x15, 0xe8, 0xb3, 0xdf, 0x1f, 0xef, - }, - { - 0x57, 0xfe, 0x6c, 0x7c, 0xfd, 0x58, 0x17, 0x60, - 0xf5, 0x63, 0xea, 0xa9, 0x7e, 0xa2, 0x56, 0x7a, - 0x16, 0x1a, 0x27, 0x23, 0xb7, 0x00, 0xff, 0xdf, - 0xa3, 0xf5, 0x3a, 0x25, 0x47, 0x17, 0xcd, 0xbf, - 0xbd, 0xff, 0x0f, 0x80, 0xd7, 0x35, 0x9e, 0x35, - 0x4a, 0x10, 0x86, 0x16, 0x1f, 0x1c, 0x15, 0x7f, - 0x63, 0x23, 0xa9, 0x6c, 0x0c, 0x41, 0x3f, 0x9a, - 0x99, 0x47, 0x47, 0xad, 0xac, 0x6b, 0xea, 0x4b, - }, - { - 0x6e, 0x7d, 0x64, 0x46, 0x7a, 0x40, 0x68, 0xfa, - 0x35, 0x4f, 0x90, 0x36, 0x72, 0xc5, 0x71, 0xbf, - 0xb6, 0xc6, 0xbe, 0xc2, 0x66, 0x1f, 0xf2, 0x0a, - 0xb4, 0xb7, 0x9a, 0x1c, 0xb7, 0xa6, 0xfa, 0xcf, - 0xc6, 0x8e, 0xf0, 0x9a, 0xb4, 0x9a, 0x7f, 0x18, - 0x6c, 0xa4, 0x42, 0x51, 0xf9, 0xc4, 0x66, 0x2d, - 0xc0, 0x39, 0x30, 0x7a, 0x3b, 0xc3, 0xa4, 0x6f, - 0xd9, 0xd3, 0x3a, 0x1d, 0xae, 0xae, 0x4f, 0xae, - }, - { - 0x93, 0xd4, 0x14, 0x3a, 0x4d, 0x56, 0x86, 0x88, - 0xf3, 0x4a, 0x3c, 0xa2, 0x4c, 0x45, 0x17, 0x35, - 0x04, 0x05, 0x4a, 0x28, 0x83, 0x69, 0x47, 0x06, - 0x37, 0x2c, 0x82, 0x2d, 0xc5, 0xab, 0x92, 0x09, - 0xc9, 0x93, 0x7a, 0x19, 0x33, 0x3e, 0x47, 0xd3, - 0xc9, 0x87, 0xbf, 0xe6, 0xc7, 0xc6, 0x9e, 0x39, - 0x54, 0x09, 0x24, 0xbf, 0xfe, 0x86, 0xac, 0x51, - 0xec, 0xc5, 0xaa, 0xee, 0x16, 0x0e, 0xc7, 0xf4, - }, - { - 0x1e, 0xe7, 0x02, 0xbf, 0xd4, 0x0d, 0x7f, 0xa4, - 0xd9, 0xa8, 0x51, 0x59, 0x35, 0xc2, 0xac, 0x36, - 0x2f, 0xc4, 0xa5, 0xd1, 0x2b, 0x8d, 0xd1, 0x69, - 0x90, 0x06, 0x9b, 0x92, 0xcb, 0x2b, 0x89, 0xf4, - 0x9a, 0xc4, 0xdb, 0x4d, 0x3b, 0x44, 0xb4, 0x89, - 0x1e, 0xde, 0x36, 0x9c, 0x71, 0xf8, 0xb7, 0x4e, - 0x41, 0x41, 0x6e, 0x0c, 0x02, 0xaa, 0xe7, 0x03, - 0xa7, 0xc9, 0x93, 0x4d, 0x42, 0x5b, 0x1f, 0x9b, - }, - { - 0xdb, 0x5a, 0x23, 0x83, 0x51, 0x44, 0x61, 0x72, - 0x60, 0x2a, 0x1f, 0xcb, 0x92, 0xdc, 0x38, 0x0e, - 0x54, 0x9c, 0x07, 0xa6, 0x9a, 0x8a, 0x2b, 0x7b, - 0xb1, 0xce, 0xb2, 0xdb, 0x0b, 0x44, 0x0a, 0x80, - 0x84, 0x09, 0x0d, 0xe0, 0xb7, 0x55, 0xd9, 0x3c, - 0x24, 0x42, 0x89, 0x25, 0x1b, 0x3a, 0x7d, 0x3a, - 0xde, 0x5f, 0x16, 0xec, 0xd8, 0x9a, 0x4c, 0x94, - 0x9b, 0x22, 0x31, 0x16, 0x54, 0x5a, 0x8f, 0x37, - }, - { - 0xed, 0x9c, 0x45, 0x98, 0xfb, 0xc7, 0xb4, 0x74, - 0xc3, 0xb6, 0x3b, 0x15, 0xd1, 0xfa, 0x98, 0x36, - 0xf4, 0x52, 0x76, 0x3b, 0x30, 0x6c, 0x1e, 0x7a, - 0x4b, 0x33, 0x69, 0xaf, 0x02, 0x67, 0xe7, 0x9f, - 0x03, 0x61, 0x33, 0x1b, 0x8a, 0xe1, 0xff, 0x1f, - 0xdb, 0x78, 0x8a, 0xff, 0x1c, 0xe7, 0x41, 0x89, - 0xf3, 0xf3, 0xe4, 0xb2, 0x48, 0xe5, 0x2a, 0x38, - 0x52, 0x6f, 0x05, 0x80, 0xa6, 0xde, 0xbe, 0xab, - }, - { - 0x1b, 0x2d, 0xf3, 0x81, 0xcd, 0xa4, 0xca, 0x6b, - 0x5d, 0xd8, 0x6f, 0xc0, 0x4a, 0x59, 0xa2, 0xde, - 0x98, 0x6e, 0x47, 0x7d, 0x1d, 0xcd, 0xba, 0xef, - 0xca, 0xb9, 0x48, 0xea, 0xef, 0x71, 0x1d, 0x8a, - 0x79, 0x66, 0x84, 0x14, 0x21, 0x80, 0x01, 0x20, - 0x61, 0x07, 0xab, 0xeb, 0xbb, 0x6b, 0xfa, 0xd8, - 0x94, 0xfe, 0x5a, 0x63, 0xcd, 0xc6, 0x02, 0x30, - 0xfb, 0x89, 0xc8, 0xef, 0xd0, 0x9e, 0xcd, 0x7b, - }, - { - 0x20, 0xd7, 0x1b, 0xf1, 0x4a, 0x92, 0xbc, 0x48, - 0x99, 0x1b, 0xb2, 0xd9, 0xd5, 0x17, 0xf4, 0xfa, - 0x52, 0x28, 0xe1, 0x88, 0xaa, 0xa4, 0x1d, 0xe7, - 0x86, 0xcc, 0x91, 0x18, 0x9d, 0xef, 0x80, 0x5d, - 0x9b, 0x9f, 0x21, 0x30, 0xd4, 0x12, 0x20, 0xf8, - 0x77, 0x1d, 0xdf, 0xbc, 0x32, 0x3c, 0xa4, 0xcd, - 0x7a, 0xb1, 0x49, 0x04, 0xb0, 0x80, 0x13, 0xd2, - 0xba, 0x31, 0x16, 0xf1, 0x67, 0xe7, 0x8e, 0x37, - }, -} - -Streebog_Context :: struct { - buffer: [64]byte, - h: [64]byte, - n: [64]byte, - sigma: [64]byte, - v_0: [64]byte, - v_512: [64]byte, - buf_size: u64, - hash_size: int, - is256: bool, -} - -add_mod_512 :: proc(first_vector, second_vector, result_vector: []byte) { - t: i32 = 0 - for i: i32 = 0; i < 64; i += 1 { - t = i32(first_vector[i]) + i32(second_vector[i]) + (t >> 8) - result_vector[i] = byte(t & 0xff) - } -} - -X :: #force_inline proc(a, k, out: []byte) { - for i := 0; i < 64; i += 1 { - out[i] = a[i] ~ k[i] - } -} - -S :: #force_inline proc(state: []byte) { - t: [64]byte - for i: i32 = 63; i >= 0; i -= 1 { - t[i] = PI[state[i]] - } - copy(state, t[:]) -} - -P :: #force_inline proc(state: []byte) { - t: [64]byte - for i: i32 = 63; i >= 0; i -= 1 { - t[i] = state[TAU[i]] - } - copy(state, t[:]) -} - -L :: #force_inline proc(state: []byte) { - ins := util.cast_slice([]u64, state) - out: [8]u64 - for i: i32 = 7; i >= 0; i -= 1 { - for j: i32 = 63; j >= 0; j -= 1 { - if (ins[i] >> u32(j)) & 1 != 0 { - out[i] ~= STREEBOG_A[63 - j] - } - } - } - copy(state, util.cast_slice([]byte, out[:])) -} - -E :: #force_inline proc(K, m, state: []byte) { - X(m, K, state) - for i: i32 = 0; i < 12; i += 1 { - S(state) - P(state) - L(state) - get_key(K, i) - X(state, K, state) - } -} - -get_key :: #force_inline proc(K: []byte, i: i32) { - X(K, STREEBOG_C[i][:], K) - S(K) - P(K) - L(K) -} - -G :: #force_inline proc(h, N, m: []byte) { - t, K: [64]byte - X(N, h, K[:]) - S(K[:]) - P(K[:]) - L(K[:]) - E(K[:], m, t[:]) - X(t[:], h, t[:]) - X(t[:], m, h) -} - -stage2 :: proc(ctx: ^Streebog_Context, m: []byte) { - G(ctx.h[:], ctx.n[:], m) - add_mod_512(ctx.n[:], ctx.v_512[:], ctx.n[:]) - add_mod_512(ctx.sigma[:], m, ctx.sigma[:]) -} - -padding :: proc(ctx: ^Streebog_Context) { - if ctx.buf_size < 64 { - t: [64]byte - copy(t[:], ctx.buffer[:int(ctx.buf_size)]) - t[ctx.buf_size] = 0x01 - copy(ctx.buffer[:], t[:]) - } -} diff --git a/core/crypto/tiger/tiger.odin b/core/crypto/tiger/tiger.odin deleted file mode 100644 index 614926129..000000000 --- a/core/crypto/tiger/tiger.odin +++ /dev/null @@ -1,280 +0,0 @@ -package tiger - -/* - Copyright 2021 zhibog - Made available under the BSD-3 license. - - List of contributors: - zhibog, dotbmp: Initial implementation. - - Interface for the Tiger1 variant of the Tiger hashing algorithm as defined in -*/ - -import "core:os" -import "core:io" - -import "../_tiger" - -/* - High level API -*/ - -DIGEST_SIZE_128 :: 16 -DIGEST_SIZE_160 :: 20 -DIGEST_SIZE_192 :: 24 - -// hash_string_128 will hash the given input and return the -// computed hash -hash_string_128 :: proc(data: string) -> [DIGEST_SIZE_128]byte { - return hash_bytes_128(transmute([]byte)(data)) -} - -// hash_bytes_128 will hash the given input and return the -// computed hash -hash_bytes_128 :: proc(data: []byte) -> [DIGEST_SIZE_128]byte { - hash: [DIGEST_SIZE_128]byte - ctx: _tiger.Tiger_Context - ctx.ver = 1 - _tiger.init(&ctx) - _tiger.update(&ctx, data) - _tiger.final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_128 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_128 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_128(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_128 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_128 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_128, "Size of destination buffer is smaller than the digest size") - ctx: _tiger.Tiger_Context - ctx.ver = 1 - _tiger.init(&ctx) - _tiger.update(&ctx, data) - _tiger.final(&ctx, hash) -} - -// hash_stream_128 will read the stream in chunks and compute a -// hash from its contents -hash_stream_128 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { - hash: [DIGEST_SIZE_128]byte - ctx: _tiger.Tiger_Context - ctx.ver = 1 - _tiger.init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - _tiger.update(&ctx, buf[:read]) - } - } - _tiger.final(&ctx, hash[:]) - return hash, true -} - -// hash_file_128 will read the file provided by the given handle -// and compute a hash -hash_file_128 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_128]byte, bool) { - if !load_at_once { - return hash_stream_128(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_128(buf[:]), ok - } - } - return [DIGEST_SIZE_128]byte{}, false -} - -hash_128 :: proc { - hash_stream_128, - hash_file_128, - hash_bytes_128, - hash_string_128, - hash_bytes_to_buffer_128, - hash_string_to_buffer_128, -} - -// hash_string_160 will hash the given input and return the -// computed hash -hash_string_160 :: proc(data: string) -> [DIGEST_SIZE_160]byte { - return hash_bytes_160(transmute([]byte)(data)) -} - -// hash_bytes_160 will hash the given input and return the -// computed hash -hash_bytes_160 :: proc(data: []byte) -> [DIGEST_SIZE_160]byte { - hash: [DIGEST_SIZE_160]byte - ctx: _tiger.Tiger_Context - ctx.ver = 1 - _tiger.init(&ctx) - _tiger.update(&ctx, data) - _tiger.final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_160 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_160 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_160(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_160 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_160 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_160, "Size of destination buffer is smaller than the digest size") - ctx: _tiger.Tiger_Context - ctx.ver = 1 - _tiger.init(&ctx) - _tiger.update(&ctx, data) - _tiger.final(&ctx, hash) -} - -// hash_stream_160 will read the stream in chunks and compute a -// hash from its contents -hash_stream_160 :: proc(s: io.Stream) -> ([DIGEST_SIZE_160]byte, bool) { - hash: [DIGEST_SIZE_160]byte - ctx: _tiger.Tiger_Context - ctx.ver = 1 - _tiger.init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - _tiger.update(&ctx, buf[:read]) - } - } - _tiger.final(&ctx, hash[:]) - return hash, true -} - -// hash_file_160 will read the file provided by the given handle -// and compute a hash -hash_file_160 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_160]byte, bool) { - if !load_at_once { - return hash_stream_160(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_160(buf[:]), ok - } - } - return [DIGEST_SIZE_160]byte{}, false -} - -hash_160 :: proc { - hash_stream_160, - hash_file_160, - hash_bytes_160, - hash_string_160, - hash_bytes_to_buffer_160, - hash_string_to_buffer_160, -} - -// hash_string_192 will hash the given input and return the -// computed hash -hash_string_192 :: proc(data: string) -> [DIGEST_SIZE_192]byte { - return hash_bytes_192(transmute([]byte)(data)) -} - -// hash_bytes_192 will hash the given input and return the -// computed hash -hash_bytes_192 :: proc(data: []byte) -> [DIGEST_SIZE_192]byte { - hash: [DIGEST_SIZE_192]byte - ctx: _tiger.Tiger_Context - ctx.ver = 1 - _tiger.init(&ctx) - _tiger.update(&ctx, data) - _tiger.final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_192 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_192 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_192(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_192 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_192 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_192, "Size of destination buffer is smaller than the digest size") - ctx: _tiger.Tiger_Context - ctx.ver = 1 - _tiger.init(&ctx) - _tiger.update(&ctx, data) - _tiger.final(&ctx, hash) -} - -// hash_stream_192 will read the stream in chunks and compute a -// hash from its contents -hash_stream_192 :: proc(s: io.Stream) -> ([DIGEST_SIZE_192]byte, bool) { - hash: [DIGEST_SIZE_192]byte - ctx: _tiger.Tiger_Context - ctx.ver = 1 - _tiger.init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - _tiger.update(&ctx, buf[:read]) - } - } - _tiger.final(&ctx, hash[:]) - return hash, true -} - -// hash_file_192 will read the file provided by the given handle -// and compute a hash -hash_file_192 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_192]byte, bool) { - if !load_at_once { - return hash_stream_192(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_192(buf[:]), ok - } - } - return [DIGEST_SIZE_192]byte{}, false -} - -hash_192 :: proc { - hash_stream_192, - hash_file_192, - hash_bytes_192, - hash_string_192, - hash_bytes_to_buffer_192, - hash_string_to_buffer_192, -} - -/* - Low level API -*/ - -Tiger_Context :: _tiger.Tiger_Context - -init :: proc(ctx: ^_tiger.Tiger_Context) { - ctx.ver = 1 - _tiger.init(ctx) -} - -update :: proc(ctx: ^_tiger.Tiger_Context, data: []byte) { - _tiger.update(ctx, data) -} - -final :: proc(ctx: ^_tiger.Tiger_Context, hash: []byte) { - _tiger.final(ctx, hash) -} \ No newline at end of file diff --git a/core/crypto/tiger2/tiger2.odin b/core/crypto/tiger2/tiger2.odin deleted file mode 100644 index ead874d56..000000000 --- a/core/crypto/tiger2/tiger2.odin +++ /dev/null @@ -1,280 +0,0 @@ -package tiger2 - -/* - Copyright 2021 zhibog - Made available under the BSD-3 license. - - List of contributors: - zhibog, dotbmp: Initial implementation. - - Interface for the Tiger2 variant of the Tiger hashing algorithm as defined in -*/ - -import "core:os" -import "core:io" - -import "../_tiger" - -/* - High level API -*/ - -DIGEST_SIZE_128 :: 16 -DIGEST_SIZE_160 :: 20 -DIGEST_SIZE_192 :: 24 - -// hash_string_128 will hash the given input and return the -// computed hash -hash_string_128 :: proc(data: string) -> [DIGEST_SIZE_128]byte { - return hash_bytes_128(transmute([]byte)(data)) -} - -// hash_bytes_128 will hash the given input and return the -// computed hash -hash_bytes_128 :: proc(data: []byte) -> [DIGEST_SIZE_128]byte { - hash: [DIGEST_SIZE_128]byte - ctx: _tiger.Tiger_Context - ctx.ver = 2 - _tiger.init(&ctx) - _tiger.update(&ctx, data) - _tiger.final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_128 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_128 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_128(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_128 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_128 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_128, "Size of destination buffer is smaller than the digest size") - ctx: _tiger.Tiger_Context - ctx.ver = 2 - _tiger.init(&ctx) - _tiger.update(&ctx, data) - _tiger.final(&ctx, hash) -} - -// hash_stream_128 will read the stream in chunks and compute a -// hash from its contents -hash_stream_128 :: proc(s: io.Stream) -> ([DIGEST_SIZE_128]byte, bool) { - hash: [DIGEST_SIZE_128]byte - ctx: _tiger.Tiger_Context - ctx.ver = 2 - _tiger.init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - _tiger.update(&ctx, buf[:read]) - } - } - _tiger.final(&ctx, hash[:]) - return hash, true -} - -// hash_file_128 will read the file provided by the given handle -// and compute a hash -hash_file_128 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_128]byte, bool) { - if !load_at_once { - return hash_stream_128(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_128(buf[:]), ok - } - } - return [DIGEST_SIZE_128]byte{}, false -} - -hash_128 :: proc { - hash_stream_128, - hash_file_128, - hash_bytes_128, - hash_string_128, - hash_bytes_to_buffer_128, - hash_string_to_buffer_128, -} - -// hash_string_160 will hash the given input and return the -// computed hash -hash_string_160 :: proc(data: string) -> [DIGEST_SIZE_160]byte { - return hash_bytes_160(transmute([]byte)(data)) -} - -// hash_bytes_160 will hash the given input and return the -// computed hash -hash_bytes_160 :: proc(data: []byte) -> [DIGEST_SIZE_160]byte { - hash: [DIGEST_SIZE_160]byte - ctx: _tiger.Tiger_Context - ctx.ver = 2 - _tiger.init(&ctx) - _tiger.update(&ctx, data) - _tiger.final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_160 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_160 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_160(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_160 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_160 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_160, "Size of destination buffer is smaller than the digest size") - ctx: _tiger.Tiger_Context - ctx.ver = 2 - _tiger.init(&ctx) - _tiger.update(&ctx, data) - _tiger.final(&ctx, hash) -} - -// hash_stream_160 will read the stream in chunks and compute a -// hash from its contents -hash_stream_160 :: proc(s: io.Stream) -> ([DIGEST_SIZE_160]byte, bool) { - hash: [DIGEST_SIZE_160]byte - ctx: _tiger.Tiger_Context - ctx.ver = 2 - _tiger.init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - _tiger.update(&ctx, buf[:read]) - } - } - _tiger.final(&ctx, hash[:]) - return hash, true -} - -// hash_file_160 will read the file provided by the given handle -// and compute a hash -hash_file_160 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_160]byte, bool) { - if !load_at_once { - return hash_stream_160(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_160(buf[:]), ok - } - } - return [DIGEST_SIZE_160]byte{}, false -} - -hash_160 :: proc { - hash_stream_160, - hash_file_160, - hash_bytes_160, - hash_string_160, - hash_bytes_to_buffer_160, - hash_string_to_buffer_160, -} - -// hash_string_192 will hash the given input and return the -// computed hash -hash_string_192 :: proc(data: string) -> [DIGEST_SIZE_192]byte { - return hash_bytes_192(transmute([]byte)(data)) -} - -// hash_bytes_192 will hash the given input and return the -// computed hash -hash_bytes_192 :: proc(data: []byte) -> [DIGEST_SIZE_192]byte { - hash: [DIGEST_SIZE_192]byte - ctx: _tiger.Tiger_Context - ctx.ver = 2 - _tiger.init(&ctx) - _tiger.update(&ctx, data) - _tiger.final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer_192 will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer_192 :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer_192(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer_192 will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer_192 :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE_192, "Size of destination buffer is smaller than the digest size") - ctx: _tiger.Tiger_Context - ctx.ver = 2 - _tiger.init(&ctx) - _tiger.update(&ctx, data) - _tiger.final(&ctx, hash) -} - -// hash_stream_192 will read the stream in chunks and compute a -// hash from its contents -hash_stream_192 :: proc(s: io.Stream) -> ([DIGEST_SIZE_192]byte, bool) { - hash: [DIGEST_SIZE_192]byte - ctx: _tiger.Tiger_Context - ctx.ver = 2 - _tiger.init(&ctx) - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - _tiger.update(&ctx, buf[:read]) - } - } - _tiger.final(&ctx, hash[:]) - return hash, true -} - -// hash_file_192 will read the file provided by the given handle -// and compute a hash -hash_file_192 :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE_192]byte, bool) { - if !load_at_once { - return hash_stream_192(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes_192(buf[:]), ok - } - } - return [DIGEST_SIZE_192]byte{}, false -} - -hash_192 :: proc { - hash_stream_192, - hash_file_192, - hash_bytes_192, - hash_string_192, - hash_bytes_to_buffer_192, - hash_string_to_buffer_192, -} - -/* - Low level API -*/ - -Tiger_Context :: _tiger.Tiger_Context - -init :: proc(ctx: ^_tiger.Tiger_Context) { - ctx.ver = 2 - _tiger.init(ctx) -} - -update :: proc(ctx: ^_tiger.Tiger_Context, data: []byte) { - _tiger.update(ctx, data) -} - -final :: proc(ctx: ^_tiger.Tiger_Context, hash: []byte) { - _tiger.final(ctx, hash) -} \ No newline at end of file diff --git a/core/crypto/tuplehash/tuplehash.odin b/core/crypto/tuplehash/tuplehash.odin new file mode 100644 index 000000000..e5caaa9c9 --- /dev/null +++ b/core/crypto/tuplehash/tuplehash.odin @@ -0,0 +1,66 @@ +/* +package tuplehash implements the TupleHash and TupleHashXOF algorithms. + +See: +- [[ https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-185.pdf ]] +*/ +package tuplehash + +import "../_sha3" + +// Context is a TupleHash or TupleHashXOF instance. +Context :: distinct _sha3.Context + +// init_128 initializes a Context for TupleHash128 or TupleHashXOF128. +init_128 :: proc(ctx: ^Context, domain_sep: []byte) { + _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((^_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((^_sha3.Context)(ctx), data) +} + +// final finalizes the Context, writes the digest to hash, and calls +// reset on the Context. +// +// 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((^_sha3.Context)(ctx), hash, finalize_clone) +} + +// read reads output from the TupleHashXOF instance. There is no practical +// upper limit to the amount of data that can be read from TupleHashXOF. +// After read has been called one or more times, further calls to +// write_element will panic. +read :: proc(ctx: ^Context, dst: []byte) { + ctx_ := (^_sha3.Context)(ctx) + if !ctx.is_finalized { + _sha3.encode_byte_len(ctx_, 0, false) // right_encode + _sha3.shake_xof(ctx_) + } + + _sha3.shake_out(ctx_, dst) +} + +// clone clones the Context other into ctx. +clone :: proc(ctx, other: ^Context) { + _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((^_sha3.Context)(ctx)) +} + +@(private) +N_TUPLEHASH := []byte{'T', 'u', 'p', 'l', 'e', 'H', 'a', 's', 'h'} diff --git a/core/crypto/util/util.odin b/core/crypto/util/util.odin deleted file mode 100644 index b9b80124a..000000000 --- a/core/crypto/util/util.odin +++ /dev/null @@ -1,146 +0,0 @@ -package util - -/* - Copyright 2021 zhibog - Made available under the BSD-3 license. - - List of contributors: - zhibog, dotbmp: Initial implementation. - - Various utility procedures -*/ - -import "core:mem" -// Keep vet happy -_ :: mem - -// @note(bp): this can replace the other two -cast_slice :: #force_inline proc "contextless" ($D: typeid/[]$DE, src: $S/[]$SE) -> D { - src := src - dst := (^mem.Raw_Slice)(&src) - - when size_of(DE) < size_of(SE) { - when size_of(DE) % size_of(SE) == 0 { - dst.len /= size_of(SE) / size_of(DE) - } else { - dst.len *= size_of(SE) - dst.len /= size_of(DE) - } - } else when size_of(DE) > size_of(SE) { - when size_of(DE) % size_of(SE) == 0 { - dst.len *= size_of(DE) / size_of(SE) - } else { - dst.len *= size_of(SE) - dst.len /= size_of(DE) - } - } else when size_of(DE) != size_of(SE) { - #assert(size_of(DE) % size_of(SE) == 0, "Different size detected") - dst.len *= size_of(SE) - dst.len /= size_of(DE) - } - - return (^D)(dst)^ -} - -bytes_to_slice :: #force_inline proc "contextless" ($T: typeid/[]$E, bytes: []byte) -> T { - s := transmute(mem.Raw_Slice)bytes - s.len /= size_of(E) - return transmute(T)s -} - -slice_to_bytes :: #force_inline proc "contextless" (slice: $E/[]$T) -> []byte { - s := transmute(mem.Raw_Slice)slice - s.len *= size_of(T) - return transmute([]byte)s -} - -ROTL16 :: #force_inline proc "contextless" (a, b: u16) -> u16 { - return ((a << b) | (a >> (16 - b))) -} - -ROTR16 :: #force_inline proc "contextless" (a, b: u16) -> u16 { - return ((a >> b) | (a << (16 - b))) -} - -ROTL32 :: #force_inline proc "contextless"(a: u32, b: int) -> u32 { - s := uint(b) & 31 - return (a << s) | (a >> (32 - s)) -} - -ROTR32 :: #force_inline proc "contextless" (a: u32, b: int) -> u32 { - s := uint(b) & 31 - return (a >> s) | (a << (32 - s)) -} - -ROTL64 :: #force_inline proc "contextless" (a, b: u64) -> u64 { - return ((a << b) | (a >> (64 - b))) -} - -ROTR64 :: #force_inline proc "contextless" (a, b: u64) -> u64 { - return ((a >> b) | (a << (64 - b))) -} - -ROTL128 :: #force_inline proc "contextless" (a, b, c, d: ^u32, n: uint) { - a, b, c, d := a, b, c, d - t := a^ >> (32 - n) - a^ = ((a^ << n) | (b^ >> (32 - n))) - b^ = ((b^ << n) | (c^ >> (32 - n))) - c^ = ((c^ << n) | (d^ >> (32 - n))) - d^ = ((d^ << n) | t) -} - -U32_LE :: #force_inline proc "contextless" (b: []byte) -> u32 { - return u32(b[0]) | u32(b[1]) << 8 | u32(b[2]) << 16 | u32(b[3]) << 24 -} - -U64_LE :: #force_inline proc "contextless" (b: []byte) -> u64 { - return u64(b[0]) | u64(b[1]) << 8 | u64(b[2]) << 16 | u64(b[3]) << 24 | - u64(b[4]) << 32 | u64(b[5]) << 40 | u64(b[6]) << 48 | u64(b[7]) << 56 -} - -U64_BE :: #force_inline proc "contextless" (b: []byte) -> u64 { - return u64(b[7]) | u64(b[6]) << 8 | u64(b[5]) << 16 | u64(b[4]) << 24 | - u64(b[3]) << 32 | u64(b[2]) << 40 | u64(b[1]) << 48 | u64(b[0]) << 56 -} - -PUT_U64_LE :: #force_inline proc "contextless" (b: []byte, v: u64) { - b[0] = byte(v) - b[1] = byte(v >> 8) - b[2] = byte(v >> 16) - b[3] = byte(v >> 24) - b[4] = byte(v >> 32) - b[5] = byte(v >> 40) - b[6] = byte(v >> 48) - b[7] = byte(v >> 56) -} - -PUT_U32_LE :: #force_inline proc "contextless" (b: []byte, v: u32) { - b[0] = byte(v) - b[1] = byte(v >> 8) - b[2] = byte(v >> 16) - b[3] = byte(v >> 24) -} - -PUT_U32_BE :: #force_inline proc "contextless" (b: []byte, v: u32) { - b[0] = byte(v >> 24) - b[1] = byte(v >> 16) - b[2] = byte(v >> 8) - b[3] = byte(v) -} - -PUT_U64_BE :: #force_inline proc "contextless" (b: []byte, v: u64) { - b[0] = byte(v >> 56) - b[1] = byte(v >> 48) - b[2] = byte(v >> 40) - b[3] = byte(v >> 32) - b[4] = byte(v >> 24) - b[5] = byte(v >> 16) - b[6] = byte(v >> 8) - b[7] = byte(v) -} - -XOR_BUF :: #force_inline proc "contextless" (input, output: []byte) { - for i := 0; i < len(input); i += 1 { - output[i] ~= input[i] - } -} diff --git a/core/crypto/whirlpool/whirlpool.odin b/core/crypto/whirlpool/whirlpool.odin deleted file mode 100644 index cf0bf6490..000000000 --- a/core/crypto/whirlpool/whirlpool.odin +++ /dev/null @@ -1,806 +0,0 @@ -package whirlpool - -/* - Copyright 2021 zhibog - Made available under the BSD-3 license. - - List of contributors: - zhibog, dotbmp: Initial implementation. - - Implementation of the Whirlpool hashing algorithm, as defined in -*/ - -import "core:os" -import "core:io" - -import "../util" - -/* - High level API -*/ - -DIGEST_SIZE :: 64 - -// hash_string will hash the given input and return the -// computed hash -hash_string :: proc(data: string) -> [DIGEST_SIZE]byte { - return hash_bytes(transmute([]byte)(data)) -} - -// hash_bytes will hash the given input and return the -// computed hash -hash_bytes :: proc(data: []byte) -> [DIGEST_SIZE]byte { - hash: [DIGEST_SIZE]byte - ctx: Whirlpool_Context - // init(&ctx) No-op - update(&ctx, data) - final(&ctx, hash[:]) - return hash -} - -// hash_string_to_buffer will hash the given input and assign the -// computed hash to the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_string_to_buffer :: proc(data: string, hash: []byte) { - hash_bytes_to_buffer(transmute([]byte)(data), hash) -} - -// hash_bytes_to_buffer will hash the given input and write the -// computed hash into the second parameter. -// It requires that the destination buffer is at least as big as the digest size -hash_bytes_to_buffer :: proc(data, hash: []byte) { - assert(len(hash) >= DIGEST_SIZE, "Size of destination buffer is smaller than the digest size") - ctx: Whirlpool_Context - // init(&ctx) No-op - update(&ctx, data) - final(&ctx, hash) -} - -// hash_stream will read the stream in chunks and compute a -// hash from its contents -hash_stream :: proc(s: io.Stream) -> ([DIGEST_SIZE]byte, bool) { - hash: [DIGEST_SIZE]byte - ctx: Whirlpool_Context - // init(&ctx) No-op - buf := make([]byte, 512) - defer delete(buf) - read := 1 - for read > 0 { - read, _ = io.read(s, buf) - if read > 0 { - update(&ctx, buf[:read]) - } - } - final(&ctx, hash[:]) - return hash, true -} - -// hash_file will read the file provided by the given handle -// and compute a hash -hash_file :: proc(hd: os.Handle, load_at_once := false) -> ([DIGEST_SIZE]byte, bool) { - if !load_at_once { - return hash_stream(os.stream_from_handle(hd)) - } else { - if buf, ok := os.read_entire_file(hd); ok { - return hash_bytes(buf[:]), ok - } - } - return [DIGEST_SIZE]byte{}, false -} - -hash :: proc { - hash_stream, - hash_file, - hash_bytes, - hash_string, - hash_bytes_to_buffer, - hash_string_to_buffer, -} - -/* - Low level API -*/ - -@(warning="Init is a no-op for Whirlpool") -init :: proc(ctx: ^Whirlpool_Context) { - // No action needed here -} - -update :: proc(ctx: ^Whirlpool_Context, source: []byte) { - source_pos: int - nn := len(source) - source_bits := u64(nn * 8) - source_gap := u32((8 - (int(source_bits & 7))) & 7) - buffer_rem := uint(ctx.buffer_bits & 7) - b: u32 - - for i, carry, value := 31, u32(0), u32(source_bits); i >= 0 && (carry != 0 || value != 0); i -= 1 { - carry += u32(ctx.bitlength[i]) + (u32(value & 0xff)) - ctx.bitlength[i] = byte(carry) - carry >>= 8 - value >>= 8 - } - - for source_bits > 8 { - b = u32(u32((source[source_pos] << source_gap) & 0xff) | u32((source[source_pos+1] & 0xff) >> (8 - source_gap))) - - ctx.buffer[ctx.buffer_pos] |= u8(b >> buffer_rem) - ctx.buffer_pos += 1 - ctx.buffer_bits += int(8 - buffer_rem) - - if ctx.buffer_bits == 512 { - transform(ctx) - ctx.buffer_bits = 0 - ctx.buffer_pos = 0 - } - ctx.buffer[ctx.buffer_pos] = byte(b << (8 - buffer_rem)) - ctx.buffer_bits += int(buffer_rem) - source_bits -= 8 - source_pos += 1 - } - - if source_bits > 0 { - b = u32((source[source_pos] << source_gap) & 0xff) - ctx.buffer[ctx.buffer_pos] |= byte(b) >> buffer_rem - } else {b = 0} - - if u64(buffer_rem) + source_bits < 8 { - ctx.buffer_bits += int(source_bits) - } else { - ctx.buffer_pos += 1 - ctx.buffer_bits += 8 - int(buffer_rem) - source_bits -= u64(8 - buffer_rem) - - if ctx.buffer_bits == 512 { - transform(ctx) - ctx.buffer_bits = 0 - ctx.buffer_pos = 0 - } - ctx.buffer[ctx.buffer_pos] = byte(b << (8 - buffer_rem)) - ctx.buffer_bits += int(source_bits) - } -} - -final :: proc(ctx: ^Whirlpool_Context, hash: []byte) { - n := ctx - n.buffer[n.buffer_pos] |= 0x80 >> (uint(n.buffer_bits) & 7) - n.buffer_pos += 1 - - if n.buffer_pos > 64 - 32 { - if n.buffer_pos < 64 { - for i := 0; i < 64 - n.buffer_pos; i += 1 { - n.buffer[n.buffer_pos + i] = 0 - } - } - transform(ctx) - n.buffer_pos = 0 - } - - if n.buffer_pos < 64 - 32 { - for i := 0; i < (64 - 32) - n.buffer_pos; i += 1 { - n.buffer[n.buffer_pos + i] = 0 - } - } - n.buffer_pos = 64 - 32 - - for i := 0; i < 32; i += 1 { - n.buffer[n.buffer_pos + i] = n.bitlength[i] - } - transform(ctx) - - for i := 0; i < 8; i += 1 { - hash[i * 8] = byte(n.hash[i] >> 56) - hash[i * 8 + 1] = byte(n.hash[i] >> 48) - hash[i * 8 + 2] = byte(n.hash[i] >> 40) - hash[i * 8 + 3] = byte(n.hash[i] >> 32) - hash[i * 8 + 4] = byte(n.hash[i] >> 24) - hash[i * 8 + 5] = byte(n.hash[i] >> 16) - hash[i * 8 + 6] = byte(n.hash[i] >> 8) - hash[i * 8 + 7] = byte(n.hash[i]) - } -} - -/* - Whirlpool implementation -*/ - -ROUNDS :: 10 - -Whirlpool_Context :: struct { - bitlength: [32]byte, - buffer: [64]byte, - buffer_bits: int, - buffer_pos: int, - hash: [8]u64, -} - -C0 := [256]u64 { - 0x18186018c07830d8, 0x23238c2305af4626, 0xc6c63fc67ef991b8, 0xe8e887e8136fcdfb, - 0x878726874ca113cb, 0xb8b8dab8a9626d11, 0x0101040108050209, 0x4f4f214f426e9e0d, - 0x3636d836adee6c9b, 0xa6a6a2a6590451ff, 0xd2d26fd2debdb90c, 0xf5f5f3f5fb06f70e, - 0x7979f979ef80f296, 0x6f6fa16f5fcede30, 0x91917e91fcef3f6d, 0x52525552aa07a4f8, - 0x60609d6027fdc047, 0xbcbccabc89766535, 0x9b9b569baccd2b37, 0x8e8e028e048c018a, - 0xa3a3b6a371155bd2, 0x0c0c300c603c186c, 0x7b7bf17bff8af684, 0x3535d435b5e16a80, - 0x1d1d741de8693af5, 0xe0e0a7e05347ddb3, 0xd7d77bd7f6acb321, 0xc2c22fc25eed999c, - 0x2e2eb82e6d965c43, 0x4b4b314b627a9629, 0xfefedffea321e15d, 0x575741578216aed5, - 0x15155415a8412abd, 0x7777c1779fb6eee8, 0x3737dc37a5eb6e92, 0xe5e5b3e57b56d79e, - 0x9f9f469f8cd92313, 0xf0f0e7f0d317fd23, 0x4a4a354a6a7f9420, 0xdada4fda9e95a944, - 0x58587d58fa25b0a2, 0xc9c903c906ca8fcf, 0x2929a429558d527c, 0x0a0a280a5022145a, - 0xb1b1feb1e14f7f50, 0xa0a0baa0691a5dc9, 0x6b6bb16b7fdad614, 0x85852e855cab17d9, - 0xbdbdcebd8173673c, 0x5d5d695dd234ba8f, 0x1010401080502090, 0xf4f4f7f4f303f507, - 0xcbcb0bcb16c08bdd, 0x3e3ef83eedc67cd3, 0x0505140528110a2d, 0x676781671fe6ce78, - 0xe4e4b7e47353d597, 0x27279c2725bb4e02, 0x4141194132588273, 0x8b8b168b2c9d0ba7, - 0xa7a7a6a7510153f6, 0x7d7de97dcf94fab2, 0x95956e95dcfb3749, 0xd8d847d88e9fad56, - 0xfbfbcbfb8b30eb70, 0xeeee9fee2371c1cd, 0x7c7ced7cc791f8bb, 0x6666856617e3cc71, - 0xdddd53dda68ea77b, 0x17175c17b84b2eaf, 0x4747014702468e45, 0x9e9e429e84dc211a, - 0xcaca0fca1ec589d4, 0x2d2db42d75995a58, 0xbfbfc6bf9179632e, 0x07071c07381b0e3f, - 0xadad8ead012347ac, 0x5a5a755aea2fb4b0, 0x838336836cb51bef, 0x3333cc3385ff66b6, - 0x636391633ff2c65c, 0x02020802100a0412, 0xaaaa92aa39384993, 0x7171d971afa8e2de, - 0xc8c807c80ecf8dc6, 0x19196419c87d32d1, 0x494939497270923b, 0xd9d943d9869aaf5f, - 0xf2f2eff2c31df931, 0xe3e3abe34b48dba8, 0x5b5b715be22ab6b9, 0x88881a8834920dbc, - 0x9a9a529aa4c8293e, 0x262698262dbe4c0b, 0x3232c8328dfa64bf, 0xb0b0fab0e94a7d59, - 0xe9e983e91b6acff2, 0x0f0f3c0f78331e77, 0xd5d573d5e6a6b733, 0x80803a8074ba1df4, - 0xbebec2be997c6127, 0xcdcd13cd26de87eb, 0x3434d034bde46889, 0x48483d487a759032, - 0xffffdbffab24e354, 0x7a7af57af78ff48d, 0x90907a90f4ea3d64, 0x5f5f615fc23ebe9d, - 0x202080201da0403d, 0x6868bd6867d5d00f, 0x1a1a681ad07234ca, 0xaeae82ae192c41b7, - 0xb4b4eab4c95e757d, 0x54544d549a19a8ce, 0x93937693ece53b7f, 0x222288220daa442f, - 0x64648d6407e9c863, 0xf1f1e3f1db12ff2a, 0x7373d173bfa2e6cc, 0x12124812905a2482, - 0x40401d403a5d807a, 0x0808200840281048, 0xc3c32bc356e89b95, 0xecec97ec337bc5df, - 0xdbdb4bdb9690ab4d, 0xa1a1bea1611f5fc0, 0x8d8d0e8d1c830791, 0x3d3df43df5c97ac8, - 0x97976697ccf1335b, 0x0000000000000000, 0xcfcf1bcf36d483f9, 0x2b2bac2b4587566e, - 0x7676c57697b3ece1, 0x8282328264b019e6, 0xd6d67fd6fea9b128, 0x1b1b6c1bd87736c3, - 0xb5b5eeb5c15b7774, 0xafaf86af112943be, 0x6a6ab56a77dfd41d, 0x50505d50ba0da0ea, - 0x45450945124c8a57, 0xf3f3ebf3cb18fb38, 0x3030c0309df060ad, 0xefef9bef2b74c3c4, - 0x3f3ffc3fe5c37eda, 0x55554955921caac7, 0xa2a2b2a2791059db, 0xeaea8fea0365c9e9, - 0x656589650fecca6a, 0xbabad2bab9686903, 0x2f2fbc2f65935e4a, 0xc0c027c04ee79d8e, - 0xdede5fdebe81a160, 0x1c1c701ce06c38fc, 0xfdfdd3fdbb2ee746, 0x4d4d294d52649a1f, - 0x92927292e4e03976, 0x7575c9758fbceafa, 0x06061806301e0c36, 0x8a8a128a249809ae, - 0xb2b2f2b2f940794b, 0xe6e6bfe66359d185, 0x0e0e380e70361c7e, 0x1f1f7c1ff8633ee7, - 0x6262956237f7c455, 0xd4d477d4eea3b53a, 0xa8a89aa829324d81, 0x96966296c4f43152, - 0xf9f9c3f99b3aef62, 0xc5c533c566f697a3, 0x2525942535b14a10, 0x59597959f220b2ab, - 0x84842a8454ae15d0, 0x7272d572b7a7e4c5, 0x3939e439d5dd72ec, 0x4c4c2d4c5a619816, - 0x5e5e655eca3bbc94, 0x7878fd78e785f09f, 0x3838e038ddd870e5, 0x8c8c0a8c14860598, - 0xd1d163d1c6b2bf17, 0xa5a5aea5410b57e4, 0xe2e2afe2434dd9a1, 0x616199612ff8c24e, - 0xb3b3f6b3f1457b42, 0x2121842115a54234, 0x9c9c4a9c94d62508, 0x1e1e781ef0663cee, - 0x4343114322528661, 0xc7c73bc776fc93b1, 0xfcfcd7fcb32be54f, 0x0404100420140824, - 0x51515951b208a2e3, 0x99995e99bcc72f25, 0x6d6da96d4fc4da22, 0x0d0d340d68391a65, - 0xfafacffa8335e979, 0xdfdf5bdfb684a369, 0x7e7ee57ed79bfca9, 0x242490243db44819, - 0x3b3bec3bc5d776fe, 0xabab96ab313d4b9a, 0xcece1fce3ed181f0, 0x1111441188552299, - 0x8f8f068f0c890383, 0x4e4e254e4a6b9c04, 0xb7b7e6b7d1517366, 0xebeb8beb0b60cbe0, - 0x3c3cf03cfdcc78c1, 0x81813e817cbf1ffd, 0x94946a94d4fe3540, 0xf7f7fbf7eb0cf31c, - 0xb9b9deb9a1676f18, 0x13134c13985f268b, 0x2c2cb02c7d9c5851, 0xd3d36bd3d6b8bb05, - 0xe7e7bbe76b5cd38c, 0x6e6ea56e57cbdc39, 0xc4c437c46ef395aa, 0x03030c03180f061b, - 0x565645568a13acdc, 0x44440d441a49885e, 0x7f7fe17fdf9efea0, 0xa9a99ea921374f88, - 0x2a2aa82a4d825467, 0xbbbbd6bbb16d6b0a, 0xc1c123c146e29f87, 0x53535153a202a6f1, - 0xdcdc57dcae8ba572, 0x0b0b2c0b58271653, 0x9d9d4e9d9cd32701, 0x6c6cad6c47c1d82b, - 0x3131c43195f562a4, 0x7474cd7487b9e8f3, 0xf6f6fff6e309f115, 0x464605460a438c4c, - 0xacac8aac092645a5, 0x89891e893c970fb5, 0x14145014a04428b4, 0xe1e1a3e15b42dfba, - 0x16165816b04e2ca6, 0x3a3ae83acdd274f7, 0x6969b9696fd0d206, 0x09092409482d1241, - 0x7070dd70a7ade0d7, 0xb6b6e2b6d954716f, 0xd0d067d0ceb7bd1e, 0xeded93ed3b7ec7d6, - 0xcccc17cc2edb85e2, 0x424215422a578468, 0x98985a98b4c22d2c, 0xa4a4aaa4490e55ed, - 0x2828a0285d885075, 0x5c5c6d5cda31b886, 0xf8f8c7f8933fed6b, 0x8686228644a411c2, -} - -C1 := [256]u64 { - 0xd818186018c07830, 0x2623238c2305af46, 0xb8c6c63fc67ef991, 0xfbe8e887e8136fcd, - 0xcb878726874ca113, 0x11b8b8dab8a9626d, 0x0901010401080502, 0x0d4f4f214f426e9e, - 0x9b3636d836adee6c, 0xffa6a6a2a6590451, 0x0cd2d26fd2debdb9, 0x0ef5f5f3f5fb06f7, - 0x967979f979ef80f2, 0x306f6fa16f5fcede, 0x6d91917e91fcef3f, 0xf852525552aa07a4, - 0x4760609d6027fdc0, 0x35bcbccabc897665, 0x379b9b569baccd2b, 0x8a8e8e028e048c01, - 0xd2a3a3b6a371155b, 0x6c0c0c300c603c18, 0x847b7bf17bff8af6, 0x803535d435b5e16a, - 0xf51d1d741de8693a, 0xb3e0e0a7e05347dd, 0x21d7d77bd7f6acb3, 0x9cc2c22fc25eed99, - 0x432e2eb82e6d965c, 0x294b4b314b627a96, 0x5dfefedffea321e1, 0xd5575741578216ae, - 0xbd15155415a8412a, 0xe87777c1779fb6ee, 0x923737dc37a5eb6e, 0x9ee5e5b3e57b56d7, - 0x139f9f469f8cd923, 0x23f0f0e7f0d317fd, 0x204a4a354a6a7f94, 0x44dada4fda9e95a9, - 0xa258587d58fa25b0, 0xcfc9c903c906ca8f, 0x7c2929a429558d52, 0x5a0a0a280a502214, - 0x50b1b1feb1e14f7f, 0xc9a0a0baa0691a5d, 0x146b6bb16b7fdad6, 0xd985852e855cab17, - 0x3cbdbdcebd817367, 0x8f5d5d695dd234ba, 0x9010104010805020, 0x07f4f4f7f4f303f5, - 0xddcbcb0bcb16c08b, 0xd33e3ef83eedc67c, 0x2d0505140528110a, 0x78676781671fe6ce, - 0x97e4e4b7e47353d5, 0x0227279c2725bb4e, 0x7341411941325882, 0xa78b8b168b2c9d0b, - 0xf6a7a7a6a7510153, 0xb27d7de97dcf94fa, 0x4995956e95dcfb37, 0x56d8d847d88e9fad, - 0x70fbfbcbfb8b30eb, 0xcdeeee9fee2371c1, 0xbb7c7ced7cc791f8, 0x716666856617e3cc, - 0x7bdddd53dda68ea7, 0xaf17175c17b84b2e, 0x454747014702468e, 0x1a9e9e429e84dc21, - 0xd4caca0fca1ec589, 0x582d2db42d75995a, 0x2ebfbfc6bf917963, 0x3f07071c07381b0e, - 0xacadad8ead012347, 0xb05a5a755aea2fb4, 0xef838336836cb51b, 0xb63333cc3385ff66, - 0x5c636391633ff2c6, 0x1202020802100a04, 0x93aaaa92aa393849, 0xde7171d971afa8e2, - 0xc6c8c807c80ecf8d, 0xd119196419c87d32, 0x3b49493949727092, 0x5fd9d943d9869aaf, - 0x31f2f2eff2c31df9, 0xa8e3e3abe34b48db, 0xb95b5b715be22ab6, 0xbc88881a8834920d, - 0x3e9a9a529aa4c829, 0x0b262698262dbe4c, 0xbf3232c8328dfa64, 0x59b0b0fab0e94a7d, - 0xf2e9e983e91b6acf, 0x770f0f3c0f78331e, 0x33d5d573d5e6a6b7, 0xf480803a8074ba1d, - 0x27bebec2be997c61, 0xebcdcd13cd26de87, 0x893434d034bde468, 0x3248483d487a7590, - 0x54ffffdbffab24e3, 0x8d7a7af57af78ff4, 0x6490907a90f4ea3d, 0x9d5f5f615fc23ebe, - 0x3d202080201da040, 0x0f6868bd6867d5d0, 0xca1a1a681ad07234, 0xb7aeae82ae192c41, - 0x7db4b4eab4c95e75, 0xce54544d549a19a8, 0x7f93937693ece53b, 0x2f222288220daa44, - 0x6364648d6407e9c8, 0x2af1f1e3f1db12ff, 0xcc7373d173bfa2e6, 0x8212124812905a24, - 0x7a40401d403a5d80, 0x4808082008402810, 0x95c3c32bc356e89b, 0xdfecec97ec337bc5, - 0x4ddbdb4bdb9690ab, 0xc0a1a1bea1611f5f, 0x918d8d0e8d1c8307, 0xc83d3df43df5c97a, - 0x5b97976697ccf133, 0x0000000000000000, 0xf9cfcf1bcf36d483, 0x6e2b2bac2b458756, - 0xe17676c57697b3ec, 0xe68282328264b019, 0x28d6d67fd6fea9b1, 0xc31b1b6c1bd87736, - 0x74b5b5eeb5c15b77, 0xbeafaf86af112943, 0x1d6a6ab56a77dfd4, 0xea50505d50ba0da0, - 0x5745450945124c8a, 0x38f3f3ebf3cb18fb, 0xad3030c0309df060, 0xc4efef9bef2b74c3, - 0xda3f3ffc3fe5c37e, 0xc755554955921caa, 0xdba2a2b2a2791059, 0xe9eaea8fea0365c9, - 0x6a656589650fecca, 0x03babad2bab96869, 0x4a2f2fbc2f65935e, 0x8ec0c027c04ee79d, - 0x60dede5fdebe81a1, 0xfc1c1c701ce06c38, 0x46fdfdd3fdbb2ee7, 0x1f4d4d294d52649a, - 0x7692927292e4e039, 0xfa7575c9758fbcea, 0x3606061806301e0c, 0xae8a8a128a249809, - 0x4bb2b2f2b2f94079, 0x85e6e6bfe66359d1, 0x7e0e0e380e70361c, 0xe71f1f7c1ff8633e, - 0x556262956237f7c4, 0x3ad4d477d4eea3b5, 0x81a8a89aa829324d, 0x5296966296c4f431, - 0x62f9f9c3f99b3aef, 0xa3c5c533c566f697, 0x102525942535b14a, 0xab59597959f220b2, - 0xd084842a8454ae15, 0xc57272d572b7a7e4, 0xec3939e439d5dd72, 0x164c4c2d4c5a6198, - 0x945e5e655eca3bbc, 0x9f7878fd78e785f0, 0xe53838e038ddd870, 0x988c8c0a8c148605, - 0x17d1d163d1c6b2bf, 0xe4a5a5aea5410b57, 0xa1e2e2afe2434dd9, 0x4e616199612ff8c2, - 0x42b3b3f6b3f1457b, 0x342121842115a542, 0x089c9c4a9c94d625, 0xee1e1e781ef0663c, - 0x6143431143225286, 0xb1c7c73bc776fc93, 0x4ffcfcd7fcb32be5, 0x2404041004201408, - 0xe351515951b208a2, 0x2599995e99bcc72f, 0x226d6da96d4fc4da, 0x650d0d340d68391a, - 0x79fafacffa8335e9, 0x69dfdf5bdfb684a3, 0xa97e7ee57ed79bfc, 0x19242490243db448, - 0xfe3b3bec3bc5d776, 0x9aabab96ab313d4b, 0xf0cece1fce3ed181, 0x9911114411885522, - 0x838f8f068f0c8903, 0x044e4e254e4a6b9c, 0x66b7b7e6b7d15173, 0xe0ebeb8beb0b60cb, - 0xc13c3cf03cfdcc78, 0xfd81813e817cbf1f, 0x4094946a94d4fe35, 0x1cf7f7fbf7eb0cf3, - 0x18b9b9deb9a1676f, 0x8b13134c13985f26, 0x512c2cb02c7d9c58, 0x05d3d36bd3d6b8bb, - 0x8ce7e7bbe76b5cd3, 0x396e6ea56e57cbdc, 0xaac4c437c46ef395, 0x1b03030c03180f06, - 0xdc565645568a13ac, 0x5e44440d441a4988, 0xa07f7fe17fdf9efe, 0x88a9a99ea921374f, - 0x672a2aa82a4d8254, 0x0abbbbd6bbb16d6b, 0x87c1c123c146e29f, 0xf153535153a202a6, - 0x72dcdc57dcae8ba5, 0x530b0b2c0b582716, 0x019d9d4e9d9cd327, 0x2b6c6cad6c47c1d8, - 0xa43131c43195f562, 0xf37474cd7487b9e8, 0x15f6f6fff6e309f1, 0x4c464605460a438c, - 0xa5acac8aac092645, 0xb589891e893c970f, 0xb414145014a04428, 0xbae1e1a3e15b42df, - 0xa616165816b04e2c, 0xf73a3ae83acdd274, 0x066969b9696fd0d2, 0x4109092409482d12, - 0xd77070dd70a7ade0, 0x6fb6b6e2b6d95471, 0x1ed0d067d0ceb7bd, 0xd6eded93ed3b7ec7, - 0xe2cccc17cc2edb85, 0x68424215422a5784, 0x2c98985a98b4c22d, 0xeda4a4aaa4490e55, - 0x752828a0285d8850, 0x865c5c6d5cda31b8, 0x6bf8f8c7f8933fed, 0xc28686228644a411, -} - -C2 := [256]u64 { - 0x30d818186018c078, 0x462623238c2305af, 0x91b8c6c63fc67ef9, 0xcdfbe8e887e8136f, - 0x13cb878726874ca1, 0x6d11b8b8dab8a962, 0x0209010104010805, 0x9e0d4f4f214f426e, - 0x6c9b3636d836adee, 0x51ffa6a6a2a65904, 0xb90cd2d26fd2debd, 0xf70ef5f5f3f5fb06, - 0xf2967979f979ef80, 0xde306f6fa16f5fce, 0x3f6d91917e91fcef, 0xa4f852525552aa07, - 0xc04760609d6027fd, 0x6535bcbccabc8976, 0x2b379b9b569baccd, 0x018a8e8e028e048c, - 0x5bd2a3a3b6a37115, 0x186c0c0c300c603c, 0xf6847b7bf17bff8a, 0x6a803535d435b5e1, - 0x3af51d1d741de869, 0xddb3e0e0a7e05347, 0xb321d7d77bd7f6ac, 0x999cc2c22fc25eed, - 0x5c432e2eb82e6d96, 0x96294b4b314b627a, 0xe15dfefedffea321, 0xaed5575741578216, - 0x2abd15155415a841, 0xeee87777c1779fb6, 0x6e923737dc37a5eb, 0xd79ee5e5b3e57b56, - 0x23139f9f469f8cd9, 0xfd23f0f0e7f0d317, 0x94204a4a354a6a7f, 0xa944dada4fda9e95, - 0xb0a258587d58fa25, 0x8fcfc9c903c906ca, 0x527c2929a429558d, 0x145a0a0a280a5022, - 0x7f50b1b1feb1e14f, 0x5dc9a0a0baa0691a, 0xd6146b6bb16b7fda, 0x17d985852e855cab, - 0x673cbdbdcebd8173, 0xba8f5d5d695dd234, 0x2090101040108050, 0xf507f4f4f7f4f303, - 0x8bddcbcb0bcb16c0, 0x7cd33e3ef83eedc6, 0x0a2d050514052811, 0xce78676781671fe6, - 0xd597e4e4b7e47353, 0x4e0227279c2725bb, 0x8273414119413258, 0x0ba78b8b168b2c9d, - 0x53f6a7a7a6a75101, 0xfab27d7de97dcf94, 0x374995956e95dcfb, 0xad56d8d847d88e9f, - 0xeb70fbfbcbfb8b30, 0xc1cdeeee9fee2371, 0xf8bb7c7ced7cc791, 0xcc716666856617e3, - 0xa77bdddd53dda68e, 0x2eaf17175c17b84b, 0x8e45474701470246, 0x211a9e9e429e84dc, - 0x89d4caca0fca1ec5, 0x5a582d2db42d7599, 0x632ebfbfc6bf9179, 0x0e3f07071c07381b, - 0x47acadad8ead0123, 0xb4b05a5a755aea2f, 0x1bef838336836cb5, 0x66b63333cc3385ff, - 0xc65c636391633ff2, 0x041202020802100a, 0x4993aaaa92aa3938, 0xe2de7171d971afa8, - 0x8dc6c8c807c80ecf, 0x32d119196419c87d, 0x923b494939497270, 0xaf5fd9d943d9869a, - 0xf931f2f2eff2c31d, 0xdba8e3e3abe34b48, 0xb6b95b5b715be22a, 0x0dbc88881a883492, - 0x293e9a9a529aa4c8, 0x4c0b262698262dbe, 0x64bf3232c8328dfa, 0x7d59b0b0fab0e94a, - 0xcff2e9e983e91b6a, 0x1e770f0f3c0f7833, 0xb733d5d573d5e6a6, 0x1df480803a8074ba, - 0x6127bebec2be997c, 0x87ebcdcd13cd26de, 0x68893434d034bde4, 0x903248483d487a75, - 0xe354ffffdbffab24, 0xf48d7a7af57af78f, 0x3d6490907a90f4ea, 0xbe9d5f5f615fc23e, - 0x403d202080201da0, 0xd00f6868bd6867d5, 0x34ca1a1a681ad072, 0x41b7aeae82ae192c, - 0x757db4b4eab4c95e, 0xa8ce54544d549a19, 0x3b7f93937693ece5, 0x442f222288220daa, - 0xc86364648d6407e9, 0xff2af1f1e3f1db12, 0xe6cc7373d173bfa2, 0x248212124812905a, - 0x807a40401d403a5d, 0x1048080820084028, 0x9b95c3c32bc356e8, 0xc5dfecec97ec337b, - 0xab4ddbdb4bdb9690, 0x5fc0a1a1bea1611f, 0x07918d8d0e8d1c83, 0x7ac83d3df43df5c9, - 0x335b97976697ccf1, 0x0000000000000000, 0x83f9cfcf1bcf36d4, 0x566e2b2bac2b4587, - 0xece17676c57697b3, 0x19e68282328264b0, 0xb128d6d67fd6fea9, 0x36c31b1b6c1bd877, - 0x7774b5b5eeb5c15b, 0x43beafaf86af1129, 0xd41d6a6ab56a77df, 0xa0ea50505d50ba0d, - 0x8a5745450945124c, 0xfb38f3f3ebf3cb18, 0x60ad3030c0309df0, 0xc3c4efef9bef2b74, - 0x7eda3f3ffc3fe5c3, 0xaac755554955921c, 0x59dba2a2b2a27910, 0xc9e9eaea8fea0365, - 0xca6a656589650fec, 0x6903babad2bab968, 0x5e4a2f2fbc2f6593, 0x9d8ec0c027c04ee7, - 0xa160dede5fdebe81, 0x38fc1c1c701ce06c, 0xe746fdfdd3fdbb2e, 0x9a1f4d4d294d5264, - 0x397692927292e4e0, 0xeafa7575c9758fbc, 0x0c3606061806301e, 0x09ae8a8a128a2498, - 0x794bb2b2f2b2f940, 0xd185e6e6bfe66359, 0x1c7e0e0e380e7036, 0x3ee71f1f7c1ff863, - 0xc4556262956237f7, 0xb53ad4d477d4eea3, 0x4d81a8a89aa82932, 0x315296966296c4f4, - 0xef62f9f9c3f99b3a, 0x97a3c5c533c566f6, 0x4a102525942535b1, 0xb2ab59597959f220, - 0x15d084842a8454ae, 0xe4c57272d572b7a7, 0x72ec3939e439d5dd, 0x98164c4c2d4c5a61, - 0xbc945e5e655eca3b, 0xf09f7878fd78e785, 0x70e53838e038ddd8, 0x05988c8c0a8c1486, - 0xbf17d1d163d1c6b2, 0x57e4a5a5aea5410b, 0xd9a1e2e2afe2434d, 0xc24e616199612ff8, - 0x7b42b3b3f6b3f145, 0x42342121842115a5, 0x25089c9c4a9c94d6, 0x3cee1e1e781ef066, - 0x8661434311432252, 0x93b1c7c73bc776fc, 0xe54ffcfcd7fcb32b, 0x0824040410042014, - 0xa2e351515951b208, 0x2f2599995e99bcc7, 0xda226d6da96d4fc4, 0x1a650d0d340d6839, - 0xe979fafacffa8335, 0xa369dfdf5bdfb684, 0xfca97e7ee57ed79b, 0x4819242490243db4, - 0x76fe3b3bec3bc5d7, 0x4b9aabab96ab313d, 0x81f0cece1fce3ed1, 0x2299111144118855, - 0x03838f8f068f0c89, 0x9c044e4e254e4a6b, 0x7366b7b7e6b7d151, 0xcbe0ebeb8beb0b60, - 0x78c13c3cf03cfdcc, 0x1ffd81813e817cbf, 0x354094946a94d4fe, 0xf31cf7f7fbf7eb0c, - 0x6f18b9b9deb9a167, 0x268b13134c13985f, 0x58512c2cb02c7d9c, 0xbb05d3d36bd3d6b8, - 0xd38ce7e7bbe76b5c, 0xdc396e6ea56e57cb, 0x95aac4c437c46ef3, 0x061b03030c03180f, - 0xacdc565645568a13, 0x885e44440d441a49, 0xfea07f7fe17fdf9e, 0x4f88a9a99ea92137, - 0x54672a2aa82a4d82, 0x6b0abbbbd6bbb16d, 0x9f87c1c123c146e2, 0xa6f153535153a202, - 0xa572dcdc57dcae8b, 0x16530b0b2c0b5827, 0x27019d9d4e9d9cd3, 0xd82b6c6cad6c47c1, - 0x62a43131c43195f5, 0xe8f37474cd7487b9, 0xf115f6f6fff6e309, 0x8c4c464605460a43, - 0x45a5acac8aac0926, 0x0fb589891e893c97, 0x28b414145014a044, 0xdfbae1e1a3e15b42, - 0x2ca616165816b04e, 0x74f73a3ae83acdd2, 0xd2066969b9696fd0, 0x124109092409482d, - 0xe0d77070dd70a7ad, 0x716fb6b6e2b6d954, 0xbd1ed0d067d0ceb7, 0xc7d6eded93ed3b7e, - 0x85e2cccc17cc2edb, 0x8468424215422a57, 0x2d2c98985a98b4c2, 0x55eda4a4aaa4490e, - 0x50752828a0285d88, 0xb8865c5c6d5cda31, 0xed6bf8f8c7f8933f, 0x11c28686228644a4, -} - -C3 := [256]u64 { - 0x7830d818186018c0, 0xaf462623238c2305, 0xf991b8c6c63fc67e, 0x6fcdfbe8e887e813, - 0xa113cb878726874c, 0x626d11b8b8dab8a9, 0x0502090101040108, 0x6e9e0d4f4f214f42, - 0xee6c9b3636d836ad, 0x0451ffa6a6a2a659, 0xbdb90cd2d26fd2de, 0x06f70ef5f5f3f5fb, - 0x80f2967979f979ef, 0xcede306f6fa16f5f, 0xef3f6d91917e91fc, 0x07a4f852525552aa, - 0xfdc04760609d6027, 0x766535bcbccabc89, 0xcd2b379b9b569bac, 0x8c018a8e8e028e04, - 0x155bd2a3a3b6a371, 0x3c186c0c0c300c60, 0x8af6847b7bf17bff, 0xe16a803535d435b5, - 0x693af51d1d741de8, 0x47ddb3e0e0a7e053, 0xacb321d7d77bd7f6, 0xed999cc2c22fc25e, - 0x965c432e2eb82e6d, 0x7a96294b4b314b62, 0x21e15dfefedffea3, 0x16aed55757415782, - 0x412abd15155415a8, 0xb6eee87777c1779f, 0xeb6e923737dc37a5, 0x56d79ee5e5b3e57b, - 0xd923139f9f469f8c, 0x17fd23f0f0e7f0d3, 0x7f94204a4a354a6a, 0x95a944dada4fda9e, - 0x25b0a258587d58fa, 0xca8fcfc9c903c906, 0x8d527c2929a42955, 0x22145a0a0a280a50, - 0x4f7f50b1b1feb1e1, 0x1a5dc9a0a0baa069, 0xdad6146b6bb16b7f, 0xab17d985852e855c, - 0x73673cbdbdcebd81, 0x34ba8f5d5d695dd2, 0x5020901010401080, 0x03f507f4f4f7f4f3, - 0xc08bddcbcb0bcb16, 0xc67cd33e3ef83eed, 0x110a2d0505140528, 0xe6ce78676781671f, - 0x53d597e4e4b7e473, 0xbb4e0227279c2725, 0x5882734141194132, 0x9d0ba78b8b168b2c, - 0x0153f6a7a7a6a751, 0x94fab27d7de97dcf, 0xfb374995956e95dc, 0x9fad56d8d847d88e, - 0x30eb70fbfbcbfb8b, 0x71c1cdeeee9fee23, 0x91f8bb7c7ced7cc7, 0xe3cc716666856617, - 0x8ea77bdddd53dda6, 0x4b2eaf17175c17b8, 0x468e454747014702, 0xdc211a9e9e429e84, - 0xc589d4caca0fca1e, 0x995a582d2db42d75, 0x79632ebfbfc6bf91, 0x1b0e3f07071c0738, - 0x2347acadad8ead01, 0x2fb4b05a5a755aea, 0xb51bef838336836c, 0xff66b63333cc3385, - 0xf2c65c636391633f, 0x0a04120202080210, 0x384993aaaa92aa39, 0xa8e2de7171d971af, - 0xcf8dc6c8c807c80e, 0x7d32d119196419c8, 0x70923b4949394972, 0x9aaf5fd9d943d986, - 0x1df931f2f2eff2c3, 0x48dba8e3e3abe34b, 0x2ab6b95b5b715be2, 0x920dbc88881a8834, - 0xc8293e9a9a529aa4, 0xbe4c0b262698262d, 0xfa64bf3232c8328d, 0x4a7d59b0b0fab0e9, - 0x6acff2e9e983e91b, 0x331e770f0f3c0f78, 0xa6b733d5d573d5e6, 0xba1df480803a8074, - 0x7c6127bebec2be99, 0xde87ebcdcd13cd26, 0xe468893434d034bd, 0x75903248483d487a, - 0x24e354ffffdbffab, 0x8ff48d7a7af57af7, 0xea3d6490907a90f4, 0x3ebe9d5f5f615fc2, - 0xa0403d202080201d, 0xd5d00f6868bd6867, 0x7234ca1a1a681ad0, 0x2c41b7aeae82ae19, - 0x5e757db4b4eab4c9, 0x19a8ce54544d549a, 0xe53b7f93937693ec, 0xaa442f222288220d, - 0xe9c86364648d6407, 0x12ff2af1f1e3f1db, 0xa2e6cc7373d173bf, 0x5a24821212481290, - 0x5d807a40401d403a, 0x2810480808200840, 0xe89b95c3c32bc356, 0x7bc5dfecec97ec33, - 0x90ab4ddbdb4bdb96, 0x1f5fc0a1a1bea161, 0x8307918d8d0e8d1c, 0xc97ac83d3df43df5, - 0xf1335b97976697cc, 0x0000000000000000, 0xd483f9cfcf1bcf36, 0x87566e2b2bac2b45, - 0xb3ece17676c57697, 0xb019e68282328264, 0xa9b128d6d67fd6fe, 0x7736c31b1b6c1bd8, - 0x5b7774b5b5eeb5c1, 0x2943beafaf86af11, 0xdfd41d6a6ab56a77, 0x0da0ea50505d50ba, - 0x4c8a574545094512, 0x18fb38f3f3ebf3cb, 0xf060ad3030c0309d, 0x74c3c4efef9bef2b, - 0xc37eda3f3ffc3fe5, 0x1caac75555495592, 0x1059dba2a2b2a279, 0x65c9e9eaea8fea03, - 0xecca6a656589650f, 0x686903babad2bab9, 0x935e4a2f2fbc2f65, 0xe79d8ec0c027c04e, - 0x81a160dede5fdebe, 0x6c38fc1c1c701ce0, 0x2ee746fdfdd3fdbb, 0x649a1f4d4d294d52, - 0xe0397692927292e4, 0xbceafa7575c9758f, 0x1e0c360606180630, 0x9809ae8a8a128a24, - 0x40794bb2b2f2b2f9, 0x59d185e6e6bfe663, 0x361c7e0e0e380e70, 0x633ee71f1f7c1ff8, - 0xf7c4556262956237, 0xa3b53ad4d477d4ee, 0x324d81a8a89aa829, 0xf4315296966296c4, - 0x3aef62f9f9c3f99b, 0xf697a3c5c533c566, 0xb14a102525942535, 0x20b2ab59597959f2, - 0xae15d084842a8454, 0xa7e4c57272d572b7, 0xdd72ec3939e439d5, 0x6198164c4c2d4c5a, - 0x3bbc945e5e655eca, 0x85f09f7878fd78e7, 0xd870e53838e038dd, 0x8605988c8c0a8c14, - 0xb2bf17d1d163d1c6, 0x0b57e4a5a5aea541, 0x4dd9a1e2e2afe243, 0xf8c24e616199612f, - 0x457b42b3b3f6b3f1, 0xa542342121842115, 0xd625089c9c4a9c94, 0x663cee1e1e781ef0, - 0x5286614343114322, 0xfc93b1c7c73bc776, 0x2be54ffcfcd7fcb3, 0x1408240404100420, - 0x08a2e351515951b2, 0xc72f2599995e99bc, 0xc4da226d6da96d4f, 0x391a650d0d340d68, - 0x35e979fafacffa83, 0x84a369dfdf5bdfb6, 0x9bfca97e7ee57ed7, 0xb44819242490243d, - 0xd776fe3b3bec3bc5, 0x3d4b9aabab96ab31, 0xd181f0cece1fce3e, 0x5522991111441188, - 0x8903838f8f068f0c, 0x6b9c044e4e254e4a, 0x517366b7b7e6b7d1, 0x60cbe0ebeb8beb0b, - 0xcc78c13c3cf03cfd, 0xbf1ffd81813e817c, 0xfe354094946a94d4, 0x0cf31cf7f7fbf7eb, - 0x676f18b9b9deb9a1, 0x5f268b13134c1398, 0x9c58512c2cb02c7d, 0xb8bb05d3d36bd3d6, - 0x5cd38ce7e7bbe76b, 0xcbdc396e6ea56e57, 0xf395aac4c437c46e, 0x0f061b03030c0318, - 0x13acdc565645568a, 0x49885e44440d441a, 0x9efea07f7fe17fdf, 0x374f88a9a99ea921, - 0x8254672a2aa82a4d, 0x6d6b0abbbbd6bbb1, 0xe29f87c1c123c146, 0x02a6f153535153a2, - 0x8ba572dcdc57dcae, 0x2716530b0b2c0b58, 0xd327019d9d4e9d9c, 0xc1d82b6c6cad6c47, - 0xf562a43131c43195, 0xb9e8f37474cd7487, 0x09f115f6f6fff6e3, 0x438c4c464605460a, - 0x2645a5acac8aac09, 0x970fb589891e893c, 0x4428b414145014a0, 0x42dfbae1e1a3e15b, - 0x4e2ca616165816b0, 0xd274f73a3ae83acd, 0xd0d2066969b9696f, 0x2d12410909240948, - 0xade0d77070dd70a7, 0x54716fb6b6e2b6d9, 0xb7bd1ed0d067d0ce, 0x7ec7d6eded93ed3b, - 0xdb85e2cccc17cc2e, 0x578468424215422a, 0xc22d2c98985a98b4, 0x0e55eda4a4aaa449, - 0x8850752828a0285d, 0x31b8865c5c6d5cda, 0x3fed6bf8f8c7f893, 0xa411c28686228644, -} - -C4 := [256]u64 { - 0xc07830d818186018, 0x05af462623238c23, 0x7ef991b8c6c63fc6, 0x136fcdfbe8e887e8, - 0x4ca113cb87872687, 0xa9626d11b8b8dab8, 0x0805020901010401, 0x426e9e0d4f4f214f, - 0xadee6c9b3636d836, 0x590451ffa6a6a2a6, 0xdebdb90cd2d26fd2, 0xfb06f70ef5f5f3f5, - 0xef80f2967979f979, 0x5fcede306f6fa16f, 0xfcef3f6d91917e91, 0xaa07a4f852525552, - 0x27fdc04760609d60, 0x89766535bcbccabc, 0xaccd2b379b9b569b, 0x048c018a8e8e028e, - 0x71155bd2a3a3b6a3, 0x603c186c0c0c300c, 0xff8af6847b7bf17b, 0xb5e16a803535d435, - 0xe8693af51d1d741d, 0x5347ddb3e0e0a7e0, 0xf6acb321d7d77bd7, 0x5eed999cc2c22fc2, - 0x6d965c432e2eb82e, 0x627a96294b4b314b, 0xa321e15dfefedffe, 0x8216aed557574157, - 0xa8412abd15155415, 0x9fb6eee87777c177, 0xa5eb6e923737dc37, 0x7b56d79ee5e5b3e5, - 0x8cd923139f9f469f, 0xd317fd23f0f0e7f0, 0x6a7f94204a4a354a, 0x9e95a944dada4fda, - 0xfa25b0a258587d58, 0x06ca8fcfc9c903c9, 0x558d527c2929a429, 0x5022145a0a0a280a, - 0xe14f7f50b1b1feb1, 0x691a5dc9a0a0baa0, 0x7fdad6146b6bb16b, 0x5cab17d985852e85, - 0x8173673cbdbdcebd, 0xd234ba8f5d5d695d, 0x8050209010104010, 0xf303f507f4f4f7f4, - 0x16c08bddcbcb0bcb, 0xedc67cd33e3ef83e, 0x28110a2d05051405, 0x1fe6ce7867678167, - 0x7353d597e4e4b7e4, 0x25bb4e0227279c27, 0x3258827341411941, 0x2c9d0ba78b8b168b, - 0x510153f6a7a7a6a7, 0xcf94fab27d7de97d, 0xdcfb374995956e95, 0x8e9fad56d8d847d8, - 0x8b30eb70fbfbcbfb, 0x2371c1cdeeee9fee, 0xc791f8bb7c7ced7c, 0x17e3cc7166668566, - 0xa68ea77bdddd53dd, 0xb84b2eaf17175c17, 0x02468e4547470147, 0x84dc211a9e9e429e, - 0x1ec589d4caca0fca, 0x75995a582d2db42d, 0x9179632ebfbfc6bf, 0x381b0e3f07071c07, - 0x012347acadad8ead, 0xea2fb4b05a5a755a, 0x6cb51bef83833683, 0x85ff66b63333cc33, - 0x3ff2c65c63639163, 0x100a041202020802, 0x39384993aaaa92aa, 0xafa8e2de7171d971, - 0x0ecf8dc6c8c807c8, 0xc87d32d119196419, 0x7270923b49493949, 0x869aaf5fd9d943d9, - 0xc31df931f2f2eff2, 0x4b48dba8e3e3abe3, 0xe22ab6b95b5b715b, 0x34920dbc88881a88, - 0xa4c8293e9a9a529a, 0x2dbe4c0b26269826, 0x8dfa64bf3232c832, 0xe94a7d59b0b0fab0, - 0x1b6acff2e9e983e9, 0x78331e770f0f3c0f, 0xe6a6b733d5d573d5, 0x74ba1df480803a80, - 0x997c6127bebec2be, 0x26de87ebcdcd13cd, 0xbde468893434d034, 0x7a75903248483d48, - 0xab24e354ffffdbff, 0xf78ff48d7a7af57a, 0xf4ea3d6490907a90, 0xc23ebe9d5f5f615f, - 0x1da0403d20208020, 0x67d5d00f6868bd68, 0xd07234ca1a1a681a, 0x192c41b7aeae82ae, - 0xc95e757db4b4eab4, 0x9a19a8ce54544d54, 0xece53b7f93937693, 0x0daa442f22228822, - 0x07e9c86364648d64, 0xdb12ff2af1f1e3f1, 0xbfa2e6cc7373d173, 0x905a248212124812, - 0x3a5d807a40401d40, 0x4028104808082008, 0x56e89b95c3c32bc3, 0x337bc5dfecec97ec, - 0x9690ab4ddbdb4bdb, 0x611f5fc0a1a1bea1, 0x1c8307918d8d0e8d, 0xf5c97ac83d3df43d, - 0xccf1335b97976697, 0x0000000000000000, 0x36d483f9cfcf1bcf, 0x4587566e2b2bac2b, - 0x97b3ece17676c576, 0x64b019e682823282, 0xfea9b128d6d67fd6, 0xd87736c31b1b6c1b, - 0xc15b7774b5b5eeb5, 0x112943beafaf86af, 0x77dfd41d6a6ab56a, 0xba0da0ea50505d50, - 0x124c8a5745450945, 0xcb18fb38f3f3ebf3, 0x9df060ad3030c030, 0x2b74c3c4efef9bef, - 0xe5c37eda3f3ffc3f, 0x921caac755554955, 0x791059dba2a2b2a2, 0x0365c9e9eaea8fea, - 0x0fecca6a65658965, 0xb9686903babad2ba, 0x65935e4a2f2fbc2f, 0x4ee79d8ec0c027c0, - 0xbe81a160dede5fde, 0xe06c38fc1c1c701c, 0xbb2ee746fdfdd3fd, 0x52649a1f4d4d294d, - 0xe4e0397692927292, 0x8fbceafa7575c975, 0x301e0c3606061806, 0x249809ae8a8a128a, - 0xf940794bb2b2f2b2, 0x6359d185e6e6bfe6, 0x70361c7e0e0e380e, 0xf8633ee71f1f7c1f, - 0x37f7c45562629562, 0xeea3b53ad4d477d4, 0x29324d81a8a89aa8, 0xc4f4315296966296, - 0x9b3aef62f9f9c3f9, 0x66f697a3c5c533c5, 0x35b14a1025259425, 0xf220b2ab59597959, - 0x54ae15d084842a84, 0xb7a7e4c57272d572, 0xd5dd72ec3939e439, 0x5a6198164c4c2d4c, - 0xca3bbc945e5e655e, 0xe785f09f7878fd78, 0xddd870e53838e038, 0x148605988c8c0a8c, - 0xc6b2bf17d1d163d1, 0x410b57e4a5a5aea5, 0x434dd9a1e2e2afe2, 0x2ff8c24e61619961, - 0xf1457b42b3b3f6b3, 0x15a5423421218421, 0x94d625089c9c4a9c, 0xf0663cee1e1e781e, - 0x2252866143431143, 0x76fc93b1c7c73bc7, 0xb32be54ffcfcd7fc, 0x2014082404041004, - 0xb208a2e351515951, 0xbcc72f2599995e99, 0x4fc4da226d6da96d, 0x68391a650d0d340d, - 0x8335e979fafacffa, 0xb684a369dfdf5bdf, 0xd79bfca97e7ee57e, 0x3db4481924249024, - 0xc5d776fe3b3bec3b, 0x313d4b9aabab96ab, 0x3ed181f0cece1fce, 0x8855229911114411, - 0x0c8903838f8f068f, 0x4a6b9c044e4e254e, 0xd1517366b7b7e6b7, 0x0b60cbe0ebeb8beb, - 0xfdcc78c13c3cf03c, 0x7cbf1ffd81813e81, 0xd4fe354094946a94, 0xeb0cf31cf7f7fbf7, - 0xa1676f18b9b9deb9, 0x985f268b13134c13, 0x7d9c58512c2cb02c, 0xd6b8bb05d3d36bd3, - 0x6b5cd38ce7e7bbe7, 0x57cbdc396e6ea56e, 0x6ef395aac4c437c4, 0x180f061b03030c03, - 0x8a13acdc56564556, 0x1a49885e44440d44, 0xdf9efea07f7fe17f, 0x21374f88a9a99ea9, - 0x4d8254672a2aa82a, 0xb16d6b0abbbbd6bb, 0x46e29f87c1c123c1, 0xa202a6f153535153, - 0xae8ba572dcdc57dc, 0x582716530b0b2c0b, 0x9cd327019d9d4e9d, 0x47c1d82b6c6cad6c, - 0x95f562a43131c431, 0x87b9e8f37474cd74, 0xe309f115f6f6fff6, 0x0a438c4c46460546, - 0x092645a5acac8aac, 0x3c970fb589891e89, 0xa04428b414145014, 0x5b42dfbae1e1a3e1, - 0xb04e2ca616165816, 0xcdd274f73a3ae83a, 0x6fd0d2066969b969, 0x482d124109092409, - 0xa7ade0d77070dd70, 0xd954716fb6b6e2b6, 0xceb7bd1ed0d067d0, 0x3b7ec7d6eded93ed, - 0x2edb85e2cccc17cc, 0x2a57846842421542, 0xb4c22d2c98985a98, 0x490e55eda4a4aaa4, - 0x5d8850752828a028, 0xda31b8865c5c6d5c, 0x933fed6bf8f8c7f8, 0x44a411c286862286, -} - -C5 := [256]u64 { - 0x18c07830d8181860, 0x2305af462623238c, 0xc67ef991b8c6c63f, 0xe8136fcdfbe8e887, - 0x874ca113cb878726, 0xb8a9626d11b8b8da, 0x0108050209010104, 0x4f426e9e0d4f4f21, - 0x36adee6c9b3636d8, 0xa6590451ffa6a6a2, 0xd2debdb90cd2d26f, 0xf5fb06f70ef5f5f3, - 0x79ef80f2967979f9, 0x6f5fcede306f6fa1, 0x91fcef3f6d91917e, 0x52aa07a4f8525255, - 0x6027fdc04760609d, 0xbc89766535bcbcca, 0x9baccd2b379b9b56, 0x8e048c018a8e8e02, - 0xa371155bd2a3a3b6, 0x0c603c186c0c0c30, 0x7bff8af6847b7bf1, 0x35b5e16a803535d4, - 0x1de8693af51d1d74, 0xe05347ddb3e0e0a7, 0xd7f6acb321d7d77b, 0xc25eed999cc2c22f, - 0x2e6d965c432e2eb8, 0x4b627a96294b4b31, 0xfea321e15dfefedf, 0x578216aed5575741, - 0x15a8412abd151554, 0x779fb6eee87777c1, 0x37a5eb6e923737dc, 0xe57b56d79ee5e5b3, - 0x9f8cd923139f9f46, 0xf0d317fd23f0f0e7, 0x4a6a7f94204a4a35, 0xda9e95a944dada4f, - 0x58fa25b0a258587d, 0xc906ca8fcfc9c903, 0x29558d527c2929a4, 0x0a5022145a0a0a28, - 0xb1e14f7f50b1b1fe, 0xa0691a5dc9a0a0ba, 0x6b7fdad6146b6bb1, 0x855cab17d985852e, - 0xbd8173673cbdbdce, 0x5dd234ba8f5d5d69, 0x1080502090101040, 0xf4f303f507f4f4f7, - 0xcb16c08bddcbcb0b, 0x3eedc67cd33e3ef8, 0x0528110a2d050514, 0x671fe6ce78676781, - 0xe47353d597e4e4b7, 0x2725bb4e0227279c, 0x4132588273414119, 0x8b2c9d0ba78b8b16, - 0xa7510153f6a7a7a6, 0x7dcf94fab27d7de9, 0x95dcfb374995956e, 0xd88e9fad56d8d847, - 0xfb8b30eb70fbfbcb, 0xee2371c1cdeeee9f, 0x7cc791f8bb7c7ced, 0x6617e3cc71666685, - 0xdda68ea77bdddd53, 0x17b84b2eaf17175c, 0x4702468e45474701, 0x9e84dc211a9e9e42, - 0xca1ec589d4caca0f, 0x2d75995a582d2db4, 0xbf9179632ebfbfc6, 0x07381b0e3f07071c, - 0xad012347acadad8e, 0x5aea2fb4b05a5a75, 0x836cb51bef838336, 0x3385ff66b63333cc, - 0x633ff2c65c636391, 0x02100a0412020208, 0xaa39384993aaaa92, 0x71afa8e2de7171d9, - 0xc80ecf8dc6c8c807, 0x19c87d32d1191964, 0x497270923b494939, 0xd9869aaf5fd9d943, - 0xf2c31df931f2f2ef, 0xe34b48dba8e3e3ab, 0x5be22ab6b95b5b71, 0x8834920dbc88881a, - 0x9aa4c8293e9a9a52, 0x262dbe4c0b262698, 0x328dfa64bf3232c8, 0xb0e94a7d59b0b0fa, - 0xe91b6acff2e9e983, 0x0f78331e770f0f3c, 0xd5e6a6b733d5d573, 0x8074ba1df480803a, - 0xbe997c6127bebec2, 0xcd26de87ebcdcd13, 0x34bde468893434d0, 0x487a75903248483d, - 0xffab24e354ffffdb, 0x7af78ff48d7a7af5, 0x90f4ea3d6490907a, 0x5fc23ebe9d5f5f61, - 0x201da0403d202080, 0x6867d5d00f6868bd, 0x1ad07234ca1a1a68, 0xae192c41b7aeae82, - 0xb4c95e757db4b4ea, 0x549a19a8ce54544d, 0x93ece53b7f939376, 0x220daa442f222288, - 0x6407e9c86364648d, 0xf1db12ff2af1f1e3, 0x73bfa2e6cc7373d1, 0x12905a2482121248, - 0x403a5d807a40401d, 0x0840281048080820, 0xc356e89b95c3c32b, 0xec337bc5dfecec97, - 0xdb9690ab4ddbdb4b, 0xa1611f5fc0a1a1be, 0x8d1c8307918d8d0e, 0x3df5c97ac83d3df4, - 0x97ccf1335b979766, 0x0000000000000000, 0xcf36d483f9cfcf1b, 0x2b4587566e2b2bac, - 0x7697b3ece17676c5, 0x8264b019e6828232, 0xd6fea9b128d6d67f, 0x1bd87736c31b1b6c, - 0xb5c15b7774b5b5ee, 0xaf112943beafaf86, 0x6a77dfd41d6a6ab5, 0x50ba0da0ea50505d, - 0x45124c8a57454509, 0xf3cb18fb38f3f3eb, 0x309df060ad3030c0, 0xef2b74c3c4efef9b, - 0x3fe5c37eda3f3ffc, 0x55921caac7555549, 0xa2791059dba2a2b2, 0xea0365c9e9eaea8f, - 0x650fecca6a656589, 0xbab9686903babad2, 0x2f65935e4a2f2fbc, 0xc04ee79d8ec0c027, - 0xdebe81a160dede5f, 0x1ce06c38fc1c1c70, 0xfdbb2ee746fdfdd3, 0x4d52649a1f4d4d29, - 0x92e4e03976929272, 0x758fbceafa7575c9, 0x06301e0c36060618, 0x8a249809ae8a8a12, - 0xb2f940794bb2b2f2, 0xe66359d185e6e6bf, 0x0e70361c7e0e0e38, 0x1ff8633ee71f1f7c, - 0x6237f7c455626295, 0xd4eea3b53ad4d477, 0xa829324d81a8a89a, 0x96c4f43152969662, - 0xf99b3aef62f9f9c3, 0xc566f697a3c5c533, 0x2535b14a10252594, 0x59f220b2ab595979, - 0x8454ae15d084842a, 0x72b7a7e4c57272d5, 0x39d5dd72ec3939e4, 0x4c5a6198164c4c2d, - 0x5eca3bbc945e5e65, 0x78e785f09f7878fd, 0x38ddd870e53838e0, 0x8c148605988c8c0a, - 0xd1c6b2bf17d1d163, 0xa5410b57e4a5a5ae, 0xe2434dd9a1e2e2af, 0x612ff8c24e616199, - 0xb3f1457b42b3b3f6, 0x2115a54234212184, 0x9c94d625089c9c4a, 0x1ef0663cee1e1e78, - 0x4322528661434311, 0xc776fc93b1c7c73b, 0xfcb32be54ffcfcd7, 0x0420140824040410, - 0x51b208a2e3515159, 0x99bcc72f2599995e, 0x6d4fc4da226d6da9, 0x0d68391a650d0d34, - 0xfa8335e979fafacf, 0xdfb684a369dfdf5b, 0x7ed79bfca97e7ee5, 0x243db44819242490, - 0x3bc5d776fe3b3bec, 0xab313d4b9aabab96, 0xce3ed181f0cece1f, 0x1188552299111144, - 0x8f0c8903838f8f06, 0x4e4a6b9c044e4e25, 0xb7d1517366b7b7e6, 0xeb0b60cbe0ebeb8b, - 0x3cfdcc78c13c3cf0, 0x817cbf1ffd81813e, 0x94d4fe354094946a, 0xf7eb0cf31cf7f7fb, - 0xb9a1676f18b9b9de, 0x13985f268b13134c, 0x2c7d9c58512c2cb0, 0xd3d6b8bb05d3d36b, - 0xe76b5cd38ce7e7bb, 0x6e57cbdc396e6ea5, 0xc46ef395aac4c437, 0x03180f061b03030c, - 0x568a13acdc565645, 0x441a49885e44440d, 0x7fdf9efea07f7fe1, 0xa921374f88a9a99e, - 0x2a4d8254672a2aa8, 0xbbb16d6b0abbbbd6, 0xc146e29f87c1c123, 0x53a202a6f1535351, - 0xdcae8ba572dcdc57, 0x0b582716530b0b2c, 0x9d9cd327019d9d4e, 0x6c47c1d82b6c6cad, - 0x3195f562a43131c4, 0x7487b9e8f37474cd, 0xf6e309f115f6f6ff, 0x460a438c4c464605, - 0xac092645a5acac8a, 0x893c970fb589891e, 0x14a04428b4141450, 0xe15b42dfbae1e1a3, - 0x16b04e2ca6161658, 0x3acdd274f73a3ae8, 0x696fd0d2066969b9, 0x09482d1241090924, - 0x70a7ade0d77070dd, 0xb6d954716fb6b6e2, 0xd0ceb7bd1ed0d067, 0xed3b7ec7d6eded93, - 0xcc2edb85e2cccc17, 0x422a578468424215, 0x98b4c22d2c98985a, 0xa4490e55eda4a4aa, - 0x285d8850752828a0, 0x5cda31b8865c5c6d, 0xf8933fed6bf8f8c7, 0x8644a411c2868622, -} - -C6 := [256]u64 { - 0x6018c07830d81818, 0x8c2305af46262323, 0x3fc67ef991b8c6c6, 0x87e8136fcdfbe8e8, - 0x26874ca113cb8787, 0xdab8a9626d11b8b8, 0x0401080502090101, 0x214f426e9e0d4f4f, - 0xd836adee6c9b3636, 0xa2a6590451ffa6a6, 0x6fd2debdb90cd2d2, 0xf3f5fb06f70ef5f5, - 0xf979ef80f2967979, 0xa16f5fcede306f6f, 0x7e91fcef3f6d9191, 0x5552aa07a4f85252, - 0x9d6027fdc0476060, 0xcabc89766535bcbc, 0x569baccd2b379b9b, 0x028e048c018a8e8e, - 0xb6a371155bd2a3a3, 0x300c603c186c0c0c, 0xf17bff8af6847b7b, 0xd435b5e16a803535, - 0x741de8693af51d1d, 0xa7e05347ddb3e0e0, 0x7bd7f6acb321d7d7, 0x2fc25eed999cc2c2, - 0xb82e6d965c432e2e, 0x314b627a96294b4b, 0xdffea321e15dfefe, 0x41578216aed55757, - 0x5415a8412abd1515, 0xc1779fb6eee87777, 0xdc37a5eb6e923737, 0xb3e57b56d79ee5e5, - 0x469f8cd923139f9f, 0xe7f0d317fd23f0f0, 0x354a6a7f94204a4a, 0x4fda9e95a944dada, - 0x7d58fa25b0a25858, 0x03c906ca8fcfc9c9, 0xa429558d527c2929, 0x280a5022145a0a0a, - 0xfeb1e14f7f50b1b1, 0xbaa0691a5dc9a0a0, 0xb16b7fdad6146b6b, 0x2e855cab17d98585, - 0xcebd8173673cbdbd, 0x695dd234ba8f5d5d, 0x4010805020901010, 0xf7f4f303f507f4f4, - 0x0bcb16c08bddcbcb, 0xf83eedc67cd33e3e, 0x140528110a2d0505, 0x81671fe6ce786767, - 0xb7e47353d597e4e4, 0x9c2725bb4e022727, 0x1941325882734141, 0x168b2c9d0ba78b8b, - 0xa6a7510153f6a7a7, 0xe97dcf94fab27d7d, 0x6e95dcfb37499595, 0x47d88e9fad56d8d8, - 0xcbfb8b30eb70fbfb, 0x9fee2371c1cdeeee, 0xed7cc791f8bb7c7c, 0x856617e3cc716666, - 0x53dda68ea77bdddd, 0x5c17b84b2eaf1717, 0x014702468e454747, 0x429e84dc211a9e9e, - 0x0fca1ec589d4caca, 0xb42d75995a582d2d, 0xc6bf9179632ebfbf, 0x1c07381b0e3f0707, - 0x8ead012347acadad, 0x755aea2fb4b05a5a, 0x36836cb51bef8383, 0xcc3385ff66b63333, - 0x91633ff2c65c6363, 0x0802100a04120202, 0x92aa39384993aaaa, 0xd971afa8e2de7171, - 0x07c80ecf8dc6c8c8, 0x6419c87d32d11919, 0x39497270923b4949, 0x43d9869aaf5fd9d9, - 0xeff2c31df931f2f2, 0xabe34b48dba8e3e3, 0x715be22ab6b95b5b, 0x1a8834920dbc8888, - 0x529aa4c8293e9a9a, 0x98262dbe4c0b2626, 0xc8328dfa64bf3232, 0xfab0e94a7d59b0b0, - 0x83e91b6acff2e9e9, 0x3c0f78331e770f0f, 0x73d5e6a6b733d5d5, 0x3a8074ba1df48080, - 0xc2be997c6127bebe, 0x13cd26de87ebcdcd, 0xd034bde468893434, 0x3d487a7590324848, - 0xdbffab24e354ffff, 0xf57af78ff48d7a7a, 0x7a90f4ea3d649090, 0x615fc23ebe9d5f5f, - 0x80201da0403d2020, 0xbd6867d5d00f6868, 0x681ad07234ca1a1a, 0x82ae192c41b7aeae, - 0xeab4c95e757db4b4, 0x4d549a19a8ce5454, 0x7693ece53b7f9393, 0x88220daa442f2222, - 0x8d6407e9c8636464, 0xe3f1db12ff2af1f1, 0xd173bfa2e6cc7373, 0x4812905a24821212, - 0x1d403a5d807a4040, 0x2008402810480808, 0x2bc356e89b95c3c3, 0x97ec337bc5dfecec, - 0x4bdb9690ab4ddbdb, 0xbea1611f5fc0a1a1, 0x0e8d1c8307918d8d, 0xf43df5c97ac83d3d, - 0x6697ccf1335b9797, 0x0000000000000000, 0x1bcf36d483f9cfcf, 0xac2b4587566e2b2b, - 0xc57697b3ece17676, 0x328264b019e68282, 0x7fd6fea9b128d6d6, 0x6c1bd87736c31b1b, - 0xeeb5c15b7774b5b5, 0x86af112943beafaf, 0xb56a77dfd41d6a6a, 0x5d50ba0da0ea5050, - 0x0945124c8a574545, 0xebf3cb18fb38f3f3, 0xc0309df060ad3030, 0x9bef2b74c3c4efef, - 0xfc3fe5c37eda3f3f, 0x4955921caac75555, 0xb2a2791059dba2a2, 0x8fea0365c9e9eaea, - 0x89650fecca6a6565, 0xd2bab9686903baba, 0xbc2f65935e4a2f2f, 0x27c04ee79d8ec0c0, - 0x5fdebe81a160dede, 0x701ce06c38fc1c1c, 0xd3fdbb2ee746fdfd, 0x294d52649a1f4d4d, - 0x7292e4e039769292, 0xc9758fbceafa7575, 0x1806301e0c360606, 0x128a249809ae8a8a, - 0xf2b2f940794bb2b2, 0xbfe66359d185e6e6, 0x380e70361c7e0e0e, 0x7c1ff8633ee71f1f, - 0x956237f7c4556262, 0x77d4eea3b53ad4d4, 0x9aa829324d81a8a8, 0x6296c4f431529696, - 0xc3f99b3aef62f9f9, 0x33c566f697a3c5c5, 0x942535b14a102525, 0x7959f220b2ab5959, - 0x2a8454ae15d08484, 0xd572b7a7e4c57272, 0xe439d5dd72ec3939, 0x2d4c5a6198164c4c, - 0x655eca3bbc945e5e, 0xfd78e785f09f7878, 0xe038ddd870e53838, 0x0a8c148605988c8c, - 0x63d1c6b2bf17d1d1, 0xaea5410b57e4a5a5, 0xafe2434dd9a1e2e2, 0x99612ff8c24e6161, - 0xf6b3f1457b42b3b3, 0x842115a542342121, 0x4a9c94d625089c9c, 0x781ef0663cee1e1e, - 0x1143225286614343, 0x3bc776fc93b1c7c7, 0xd7fcb32be54ffcfc, 0x1004201408240404, - 0x5951b208a2e35151, 0x5e99bcc72f259999, 0xa96d4fc4da226d6d, 0x340d68391a650d0d, - 0xcffa8335e979fafa, 0x5bdfb684a369dfdf, 0xe57ed79bfca97e7e, 0x90243db448192424, - 0xec3bc5d776fe3b3b, 0x96ab313d4b9aabab, 0x1fce3ed181f0cece, 0x4411885522991111, - 0x068f0c8903838f8f, 0x254e4a6b9c044e4e, 0xe6b7d1517366b7b7, 0x8beb0b60cbe0ebeb, - 0xf03cfdcc78c13c3c, 0x3e817cbf1ffd8181, 0x6a94d4fe35409494, 0xfbf7eb0cf31cf7f7, - 0xdeb9a1676f18b9b9, 0x4c13985f268b1313, 0xb02c7d9c58512c2c, 0x6bd3d6b8bb05d3d3, - 0xbbe76b5cd38ce7e7, 0xa56e57cbdc396e6e, 0x37c46ef395aac4c4, 0x0c03180f061b0303, - 0x45568a13acdc5656, 0x0d441a49885e4444, 0xe17fdf9efea07f7f, 0x9ea921374f88a9a9, - 0xa82a4d8254672a2a, 0xd6bbb16d6b0abbbb, 0x23c146e29f87c1c1, 0x5153a202a6f15353, - 0x57dcae8ba572dcdc, 0x2c0b582716530b0b, 0x4e9d9cd327019d9d, 0xad6c47c1d82b6c6c, - 0xc43195f562a43131, 0xcd7487b9e8f37474, 0xfff6e309f115f6f6, 0x05460a438c4c4646, - 0x8aac092645a5acac, 0x1e893c970fb58989, 0x5014a04428b41414, 0xa3e15b42dfbae1e1, - 0x5816b04e2ca61616, 0xe83acdd274f73a3a, 0xb9696fd0d2066969, 0x2409482d12410909, - 0xdd70a7ade0d77070, 0xe2b6d954716fb6b6, 0x67d0ceb7bd1ed0d0, 0x93ed3b7ec7d6eded, - 0x17cc2edb85e2cccc, 0x15422a5784684242, 0x5a98b4c22d2c9898, 0xaaa4490e55eda4a4, - 0xa0285d8850752828, 0x6d5cda31b8865c5c, 0xc7f8933fed6bf8f8, 0x228644a411c28686, -} - -C7 := [256]u64 { - 0x186018c07830d818, 0x238c2305af462623, 0xc63fc67ef991b8c6, 0xe887e8136fcdfbe8, - 0x8726874ca113cb87, 0xb8dab8a9626d11b8, 0x0104010805020901, 0x4f214f426e9e0d4f, - 0x36d836adee6c9b36, 0xa6a2a6590451ffa6, 0xd26fd2debdb90cd2, 0xf5f3f5fb06f70ef5, - 0x79f979ef80f29679, 0x6fa16f5fcede306f, 0x917e91fcef3f6d91, 0x525552aa07a4f852, - 0x609d6027fdc04760, 0xbccabc89766535bc, 0x9b569baccd2b379b, 0x8e028e048c018a8e, - 0xa3b6a371155bd2a3, 0x0c300c603c186c0c, 0x7bf17bff8af6847b, 0x35d435b5e16a8035, - 0x1d741de8693af51d, 0xe0a7e05347ddb3e0, 0xd77bd7f6acb321d7, 0xc22fc25eed999cc2, - 0x2eb82e6d965c432e, 0x4b314b627a96294b, 0xfedffea321e15dfe, 0x5741578216aed557, - 0x155415a8412abd15, 0x77c1779fb6eee877, 0x37dc37a5eb6e9237, 0xe5b3e57b56d79ee5, - 0x9f469f8cd923139f, 0xf0e7f0d317fd23f0, 0x4a354a6a7f94204a, 0xda4fda9e95a944da, - 0x587d58fa25b0a258, 0xc903c906ca8fcfc9, 0x29a429558d527c29, 0x0a280a5022145a0a, - 0xb1feb1e14f7f50b1, 0xa0baa0691a5dc9a0, 0x6bb16b7fdad6146b, 0x852e855cab17d985, - 0xbdcebd8173673cbd, 0x5d695dd234ba8f5d, 0x1040108050209010, 0xf4f7f4f303f507f4, - 0xcb0bcb16c08bddcb, 0x3ef83eedc67cd33e, 0x05140528110a2d05, 0x6781671fe6ce7867, - 0xe4b7e47353d597e4, 0x279c2725bb4e0227, 0x4119413258827341, 0x8b168b2c9d0ba78b, - 0xa7a6a7510153f6a7, 0x7de97dcf94fab27d, 0x956e95dcfb374995, 0xd847d88e9fad56d8, - 0xfbcbfb8b30eb70fb, 0xee9fee2371c1cdee, 0x7ced7cc791f8bb7c, 0x66856617e3cc7166, - 0xdd53dda68ea77bdd, 0x175c17b84b2eaf17, 0x47014702468e4547, 0x9e429e84dc211a9e, - 0xca0fca1ec589d4ca, 0x2db42d75995a582d, 0xbfc6bf9179632ebf, 0x071c07381b0e3f07, - 0xad8ead012347acad, 0x5a755aea2fb4b05a, 0x8336836cb51bef83, 0x33cc3385ff66b633, - 0x6391633ff2c65c63, 0x020802100a041202, 0xaa92aa39384993aa, 0x71d971afa8e2de71, - 0xc807c80ecf8dc6c8, 0x196419c87d32d119, 0x4939497270923b49, 0xd943d9869aaf5fd9, - 0xf2eff2c31df931f2, 0xe3abe34b48dba8e3, 0x5b715be22ab6b95b, 0x881a8834920dbc88, - 0x9a529aa4c8293e9a, 0x2698262dbe4c0b26, 0x32c8328dfa64bf32, 0xb0fab0e94a7d59b0, - 0xe983e91b6acff2e9, 0x0f3c0f78331e770f, 0xd573d5e6a6b733d5, 0x803a8074ba1df480, - 0xbec2be997c6127be, 0xcd13cd26de87ebcd, 0x34d034bde4688934, 0x483d487a75903248, - 0xffdbffab24e354ff, 0x7af57af78ff48d7a, 0x907a90f4ea3d6490, 0x5f615fc23ebe9d5f, - 0x2080201da0403d20, 0x68bd6867d5d00f68, 0x1a681ad07234ca1a, 0xae82ae192c41b7ae, - 0xb4eab4c95e757db4, 0x544d549a19a8ce54, 0x937693ece53b7f93, 0x2288220daa442f22, - 0x648d6407e9c86364, 0xf1e3f1db12ff2af1, 0x73d173bfa2e6cc73, 0x124812905a248212, - 0x401d403a5d807a40, 0x0820084028104808, 0xc32bc356e89b95c3, 0xec97ec337bc5dfec, - 0xdb4bdb9690ab4ddb, 0xa1bea1611f5fc0a1, 0x8d0e8d1c8307918d, 0x3df43df5c97ac83d, - 0x976697ccf1335b97, 0x0000000000000000, 0xcf1bcf36d483f9cf, 0x2bac2b4587566e2b, - 0x76c57697b3ece176, 0x82328264b019e682, 0xd67fd6fea9b128d6, 0x1b6c1bd87736c31b, - 0xb5eeb5c15b7774b5, 0xaf86af112943beaf, 0x6ab56a77dfd41d6a, 0x505d50ba0da0ea50, - 0x450945124c8a5745, 0xf3ebf3cb18fb38f3, 0x30c0309df060ad30, 0xef9bef2b74c3c4ef, - 0x3ffc3fe5c37eda3f, 0x554955921caac755, 0xa2b2a2791059dba2, 0xea8fea0365c9e9ea, - 0x6589650fecca6a65, 0xbad2bab9686903ba, 0x2fbc2f65935e4a2f, 0xc027c04ee79d8ec0, - 0xde5fdebe81a160de, 0x1c701ce06c38fc1c, 0xfdd3fdbb2ee746fd, 0x4d294d52649a1f4d, - 0x927292e4e0397692, 0x75c9758fbceafa75, 0x061806301e0c3606, 0x8a128a249809ae8a, - 0xb2f2b2f940794bb2, 0xe6bfe66359d185e6, 0x0e380e70361c7e0e, 0x1f7c1ff8633ee71f, - 0x62956237f7c45562, 0xd477d4eea3b53ad4, 0xa89aa829324d81a8, 0x966296c4f4315296, - 0xf9c3f99b3aef62f9, 0xc533c566f697a3c5, 0x25942535b14a1025, 0x597959f220b2ab59, - 0x842a8454ae15d084, 0x72d572b7a7e4c572, 0x39e439d5dd72ec39, 0x4c2d4c5a6198164c, - 0x5e655eca3bbc945e, 0x78fd78e785f09f78, 0x38e038ddd870e538, 0x8c0a8c148605988c, - 0xd163d1c6b2bf17d1, 0xa5aea5410b57e4a5, 0xe2afe2434dd9a1e2, 0x6199612ff8c24e61, - 0xb3f6b3f1457b42b3, 0x21842115a5423421, 0x9c4a9c94d625089c, 0x1e781ef0663cee1e, - 0x4311432252866143, 0xc73bc776fc93b1c7, 0xfcd7fcb32be54ffc, 0x0410042014082404, - 0x515951b208a2e351, 0x995e99bcc72f2599, 0x6da96d4fc4da226d, 0x0d340d68391a650d, - 0xfacffa8335e979fa, 0xdf5bdfb684a369df, 0x7ee57ed79bfca97e, 0x2490243db4481924, - 0x3bec3bc5d776fe3b, 0xab96ab313d4b9aab, 0xce1fce3ed181f0ce, 0x1144118855229911, - 0x8f068f0c8903838f, 0x4e254e4a6b9c044e, 0xb7e6b7d1517366b7, 0xeb8beb0b60cbe0eb, - 0x3cf03cfdcc78c13c, 0x813e817cbf1ffd81, 0x946a94d4fe354094, 0xf7fbf7eb0cf31cf7, - 0xb9deb9a1676f18b9, 0x134c13985f268b13, 0x2cb02c7d9c58512c, 0xd36bd3d6b8bb05d3, - 0xe7bbe76b5cd38ce7, 0x6ea56e57cbdc396e, 0xc437c46ef395aac4, 0x030c03180f061b03, - 0x5645568a13acdc56, 0x440d441a49885e44, 0x7fe17fdf9efea07f, 0xa99ea921374f88a9, - 0x2aa82a4d8254672a, 0xbbd6bbb16d6b0abb, 0xc123c146e29f87c1, 0x535153a202a6f153, - 0xdc57dcae8ba572dc, 0x0b2c0b582716530b, 0x9d4e9d9cd327019d, 0x6cad6c47c1d82b6c, - 0x31c43195f562a431, 0x74cd7487b9e8f374, 0xf6fff6e309f115f6, 0x4605460a438c4c46, - 0xac8aac092645a5ac, 0x891e893c970fb589, 0x145014a04428b414, 0xe1a3e15b42dfbae1, - 0x165816b04e2ca616, 0x3ae83acdd274f73a, 0x69b9696fd0d20669, 0x092409482d124109, - 0x70dd70a7ade0d770, 0xb6e2b6d954716fb6, 0xd067d0ceb7bd1ed0, 0xed93ed3b7ec7d6ed, - 0xcc17cc2edb85e2cc, 0x4215422a57846842, 0x985a98b4c22d2c98, 0xa4aaa4490e55eda4, - 0x28a0285d88507528, 0x5c6d5cda31b8865c, 0xf8c7f8933fed6bf8, 0x86228644a411c286, -} - -RC := [ROUNDS + 1]u64 { - 0x0000000000000000, - 0x1823c6e887b8014f, - 0x36a6d2f5796f9152, - 0x60bc9b8ea30c7b35, - 0x1de0d7c22e4bfe57, - 0x157737e59ff04ada, - 0x58c9290ab1a06b85, - 0xbd5d10f4cb3e0567, - 0xe427418ba77d95d8, - 0xfbee7c66dd17479e, - 0xca2dbf07ad5a8333, -} - -transform :: proc (ctx: ^Whirlpool_Context) { - K, block, state, L: [8]u64 - - for i := 0; i < 8; i += 1 {block[i] = util.U64_BE(ctx.buffer[8 * i:])} - - for i := 0; i < 8; i += 1 { - K[i] = ctx.hash[i] - state[i] = block[i] ~ K[i] - } - - for r := 1; r <= ROUNDS; r += 1 { - for i := 0; i < 8; i += 1 { - L[i] = C0[byte(K[i % 8] >> 56)] ~ - C1[byte(K[(i + 7) % 8] >> 48)] ~ - C2[byte(K[(i + 6) % 8] >> 40)] ~ - C3[byte(K[(i + 5) % 8] >> 32)] ~ - C4[byte(K[(i + 4) % 8] >> 24)] ~ - C5[byte(K[(i + 3) % 8] >> 16)] ~ - C6[byte(K[(i + 2) % 8] >> 8)] ~ - C7[byte(K[(i + 1) % 8])] - } - L[0] ~= RC[r] - - for i := 0; i < 8; i += 1 {K[i] = L[i]} - - for i := 0; i < 8; i += 1 { - L[i] = C0[byte(state[i % 8] >> 56)] ~ - C1[byte(state[(i + 7) % 8] >> 48)] ~ - C2[byte(state[(i + 6) % 8] >> 40)] ~ - C3[byte(state[(i + 5) % 8] >> 32)] ~ - C4[byte(state[(i + 4) % 8] >> 24)] ~ - C5[byte(state[(i + 3) % 8] >> 16)] ~ - C6[byte(state[(i + 2) % 8] >> 8)] ~ - C7[byte(state[(i + 1) % 8])] ~ - K[i % 8] - } - for i := 0; i < 8; i += 1 {state[i] = L[i]} - } - for i := 0; i < 8; i += 1 {ctx.hash[i] ~= state[i] ~ block[i]} -} diff --git a/core/crypto/x25519/x25519.odin b/core/crypto/x25519/x25519.odin index fc446d25c..412a767b8 100644 --- a/core/crypto/x25519/x25519.odin +++ b/core/crypto/x25519/x25519.odin @@ -1,9 +1,18 @@ +/* +package x25519 implements the X25519 (aka curve25519) Elliptic-Curve +Diffie-Hellman key exchange protocol. + +See: +- [[ https://www.rfc-editor.org/rfc/rfc7748 ]] +*/ package x25519 import field "core:crypto/_fiat/field_curve25519" import "core:mem" +// SCALAR_SIZE is the size of a X25519 scalar (private key) in bytes. SCALAR_SIZE :: 32 +// POINT_SIZE is the size of a X25519 point (public key/shared secret) in bytes. POINT_SIZE :: 32 @(private) @@ -14,11 +23,11 @@ _scalar_bit :: #force_inline proc "contextless" (s: ^[32]byte, i: int) -> u8 { if i < 0 { return 0 } - return (s[i>>3] >> uint(i&7)) & 1 + return (s[i >> 3] >> uint(i & 7)) & 1 } @(private) -_scalarmult :: proc (out, scalar, point: ^[32]byte) { +_scalarmult :: proc "contextless" (out, scalar, point: ^[32]byte) { // Montgomery pseduo-multiplication taken from Monocypher. // computes the scalar product @@ -26,7 +35,7 @@ _scalarmult :: proc (out, scalar, point: ^[32]byte) { field.fe_from_bytes(&x1, point) // computes the actual scalar product (the result is in x2 and z2) - x2, x3, z2, z3: field.Tight_Field_Element = ---, ---, ---, --- + x2, x3, z2, z3: field.Tight_Field_Element = ---, ---, ---, --- t0, t1: field.Loose_Field_Element = ---, --- // Montgomery ladder @@ -38,7 +47,7 @@ _scalarmult :: proc (out, scalar, point: ^[32]byte) { field.fe_one(&z3) swap: int - for pos := 255-1; pos >= 0; pos = pos - 1 { + for pos := 255 - 1; pos >= 0; pos = pos - 1 { // constant time conditional swap before ladder step b := int(_scalar_bit(scalar, pos)) swap ~= b // xor trick avoids swapping at the end of the loop @@ -85,16 +94,13 @@ _scalarmult :: proc (out, scalar, point: ^[32]byte) { field.fe_carry_mul(&x2, field.fe_relax_cast(&x2), field.fe_relax_cast(&z2)) field.fe_to_bytes(out, &x2) - mem.zero_explicit(&x1, size_of(x1)) - mem.zero_explicit(&x2, size_of(x2)) - mem.zero_explicit(&x3, size_of(x3)) - mem.zero_explicit(&z2, size_of(z2)) - mem.zero_explicit(&z3, size_of(z3)) - mem.zero_explicit(&t0, size_of(t0)) - mem.zero_explicit(&t1, size_of(t1)) + field.fe_clear_vec([]^field.Tight_Field_Element{&x1, &x2, &x3, &z2, &z3}) + field.fe_clear_vec([]^field.Loose_Field_Element{&t0, &t1}) } -scalarmult :: proc (dst, scalar, point: []byte) { +// scalarmult "multiplies" the provided scalar and point, and writes the +// resulting point to dst. +scalarmult :: proc(dst, scalar, point: []byte) { if len(scalar) != SCALAR_SIZE { panic("crypto/x25519: invalid scalar size") } @@ -123,7 +129,8 @@ scalarmult :: proc (dst, scalar, point: []byte) { mem.zero_explicit(&d, size_of(d)) } -scalarmult_basepoint :: proc (dst, scalar: []byte) { - // TODO/perf: Switch to using a precomputed table. +// scalarmult_basepoint "multiplies" the provided scalar with the X25519 +// base point and writes the resulting point to dst. +scalarmult_basepoint :: proc(dst, scalar: []byte) { scalarmult(dst, scalar, _BASE_POINT[:]) } 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..8ef377cef --- /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..8ee96720e --- /dev/null +++ b/core/debug/trace/trace_nil.odin @@ -0,0 +1,22 @@ +#+build !windows +#+build !linux +#+build !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..c9868e338 --- /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/doc.odin b/core/dynlib/doc.odin index 812fb02d5..487fcb715 100644 --- a/core/dynlib/doc.odin +++ b/core/dynlib/doc.odin @@ -1,7 +1,9 @@ /* -Package core:dynlib implements loading of shared libraries/DLLs and their symbols. +Package `core:dynlib` implements loading of shared libraries/DLLs and their symbols. The behaviour of dynamically loaded libraries is specific to the target platform of the program. For in depth detail on the underlying behaviour please refer to your target platform's documentation. + +For a full example, see: [[ core/dynlib/example; https://github.com/odin-lang/Odin/tree/master/core/dynlib/example ]] */ package dynlib diff --git a/core/dynlib/example/example.odin b/core/dynlib/example/example.odin new file mode 100644 index 000000000..78fb5a98c --- /dev/null +++ b/core/dynlib/example/example.odin @@ -0,0 +1,47 @@ +package example + +import "core:dynlib" +import "core:fmt" + +Symbols :: struct { + // `foo_` is prefixed, so we look for the symbol `foo_add`. + add: proc "c" (int, int) -> int, + // We use the tag here to override the symbol to look for, namely `bar_sub`. + sub: proc "c" (int, int) -> int `dynlib:"bar_sub"`, + + // Exported global (if exporting an i32, the type must be ^i32 because the symbol is a pointer to the export.) + // If it's not a pointer or procedure type, we'll skip the struct field. + hellope: ^i32, + + // Handle to free library. + // We can have more than one of these so we can match symbols for more than one DLL with one struct. + _my_lib_handle: dynlib.Library, +} + +main :: proc() { + sym: Symbols + + LIB_PATH :: "lib." + dynlib.LIBRARY_FILE_EXTENSION + + // Load symbols from `lib.dll` into Symbols struct. + // Each struct field is prefixed with `foo_` before lookup in the DLL's symbol table. + // The library's Handle (to unload) will be stored in `sym._my_lib_handle`. This way you can load multiple DLLs in one struct. + count, ok := dynlib.initialize_symbols(&sym, LIB_PATH, "foo_", "_my_lib_handle") + defer dynlib.unload_library(sym._my_lib_handle) + fmt.printf("(Initial DLL Load) ok: %v. %v symbols loaded from " + LIB_PATH + " (%p).\n", ok, count, sym._my_lib_handle) + + if count > 0 { + fmt.println("42 + 42 =", sym.add(42, 42)) + fmt.println("84 - 13 =", sym.sub(84, 13)) + fmt.println("hellope =", sym.hellope^) + } + + count, ok = dynlib.initialize_symbols(&sym, LIB_PATH, "foo_", "_my_lib_handle") + fmt.printf("(DLL Reload) ok: %v. %v symbols loaded from " + LIB_PATH + " (%p).\n", ok, count, sym._my_lib_handle) + + if count > 0 { + fmt.println("42 + 42 =", sym.add(42, 42)) + fmt.println("84 - 13 =", sym.sub(84, 13)) + fmt.println("hellope =", sym.hellope^) + } +} diff --git a/core/dynlib/example/lib.odin b/core/dynlib/example/lib.odin new file mode 100644 index 000000000..25687a653 --- /dev/null +++ b/core/dynlib/example/lib.odin @@ -0,0 +1,14 @@ +package library + +@(export) +foo_add :: proc "c" (a, b: int) -> (res: int) { + return a + b +} + +@(export) +bar_sub :: proc "c" (a, b: int) -> (res: int) { + return a - b +} + +@(export) +foo_hellope: i32 = 42 \ No newline at end of file diff --git a/core/dynlib/lib.odin b/core/dynlib/lib.odin index b5cb16e3c..cd9eed5f8 100644 --- a/core/dynlib/lib.odin +++ b/core/dynlib/lib.odin @@ -1,23 +1,32 @@ package dynlib +import "base:intrinsics" +import "core:reflect" +import "base:runtime" +_ :: intrinsics +_ :: reflect +_ :: runtime + /* A handle to a dynamically loaded library. */ Library :: distinct rawptr +/* +The file extension for dynamic libraries on the target OS. +*/ +LIBRARY_FILE_EXTENSION :: _LIBRARY_FILE_EXTENSION + /* 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` +The underlying behaviour is platform specific. +On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlopen`. +On `windows` refer to `LoadLibraryW`. Also temporarily needs an allocator to convert a string. Example: import "core:dynlib" @@ -27,20 +36,21 @@ Example: LIBRARY_PATH :: "my_library.dll" library, ok := dynlib.load_library(LIBRARY_PATH) if ! ok { + fmt.eprintln(dynlib.last_error()) return } fmt.println("The library %q was successfully loaded", LIBRARY_PATH) } */ -load_library :: proc(path: string, global_symbols := false) -> (library: Library, did_load: bool) { - return _load_library(path, global_symbols) +load_library :: proc(path: string, global_symbols := false, allocator := context.temp_allocator) -> (library: Library, did_load: bool) { + return _load_library(path, global_symbols, allocator) } /* Unloads a dynamic library. -The underlying behaviour is platform specific. -On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlclose`. +The underlying behaviour is platform specific. +On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlclose`. On `windows` refer to `FreeLibrary`. Example: @@ -51,10 +61,12 @@ Example: LIBRARY_PATH :: "my_library.dll" library, ok := dynlib.load_library(LIBRARY_PATH) if ! ok { + fmt.eprintln(dynlib.last_error()) return } did_unload := dynlib.unload_library(library) if ! did_unload { + fmt.eprintln(dynlib.last_error()) return } fmt.println("The library %q was successfully unloaded", LIBRARY_PATH) @@ -67,12 +79,9 @@ unload_library :: proc(library: Library) -> (did_unload: bool) { /* 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` +The underlying behaviour is platform specific. +On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlsym`. +On `windows` refer to `GetProcAddress`. Also temporarily needs an allocator to convert a string. Example: import "core:dynlib" @@ -82,13 +91,97 @@ Example: LIBRARY_PATH :: "my_library.dll" library, ok := dynlib.load_library(LIBRARY_PATH) if ! ok { + fmt.eprintln(dynlib.last_error()) return } a, found_a := dynlib.symbol_address(library, "a") - if found_a do fmt.printf("The symbol %q was found at the address %v", "a", a) + if found_a { + fmt.printf("The symbol %q was found at the address %v", "a", a) + } else { + fmt.eprintln(dynlib.last_error()) + } } */ -symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) #optional_ok { - return _symbol_address(library, symbol) +symbol_address :: proc(library: Library, symbol: string, allocator := context.temp_allocator) -> (ptr: rawptr, found: bool) #optional_ok { + return _symbol_address(library, symbol, allocator) +} + +/* +Scans a dynamic library for symbols matching a struct's members, assigning found procedure pointers to the corresponding entry. +Optionally takes a symbol prefix added to the struct's member name to construct the symbol looked up in the library. +Optionally also takes the struct member to assign the library handle to, `__handle` by default. + +This allows using one struct to hold library handles and symbol pointers for more than 1 dynamic library. + +Loading the same library twice unloads the previous incarnation, allowing for straightforward hot reload support. + +Returns: +* `-1, false` if the library could not be loaded. +* The number of symbols assigned on success. `ok` = true if `count` > 0 + +See doc.odin for an example. +*/ +initialize_symbols :: proc( + symbol_table: ^$T, library_path: string, + symbol_prefix := "", handle_field_name := "__handle", +) -> (count: int = -1, ok: bool = false) where intrinsics.type_is_struct(T) { + assert(symbol_table != nil) + + // First, (re)load the library. + handle: Library + for field in reflect.struct_fields_zipped(T) { + if field.name == handle_field_name { + field_ptr := rawptr(uintptr(symbol_table) + field.offset) + + // We appear to be hot reloading. Unload previous incarnation of the library. + if old_handle := (^Library)(field_ptr)^; old_handle != nil { + unload_library(old_handle) or_return + } + + handle = load_library(library_path) or_return + (^Library)(field_ptr)^ = handle + break + } + } + + // Buffer to concatenate the prefix + symbol name. + prefixed_symbol_buf: [2048]u8 = --- + + count = 0 + for field in reflect.struct_fields_zipped(T) { + // If we're not the library handle, the field needs to be a pointer type, be it a procedure pointer or an exported global. + if field.name == handle_field_name || !(reflect.is_procedure(field.type) || reflect.is_pointer(field.type)) { + continue + } + + // Calculate address of struct member + field_ptr := rawptr(uintptr(symbol_table) + field.offset) + + // Let's look up or construct the symbol name to find in the library + prefixed_name: string + + // Do we have a symbol override tag? + if override, tag_ok := reflect.struct_tag_lookup(field.tag, "dynlib"); tag_ok { + prefixed_name = override + } + + // No valid symbol override tag found, fall back to `name`. + if len(prefixed_name) == 0 { + offset := copy(prefixed_symbol_buf[:], symbol_prefix) + copy(prefixed_symbol_buf[offset:], field.name) + prefixed_name = string(prefixed_symbol_buf[:len(symbol_prefix) + len(field.name)]) + } + + // Assign procedure (or global) pointer if found. + sym_ptr := symbol_address(handle, prefixed_name) or_continue + (^rawptr)(field_ptr)^ = sym_ptr + count += 1 + } + return count, count > 0 +} + +// Returns an error message for the last failed procedure call. +last_error :: proc() -> string { + return _last_error() } diff --git a/core/dynlib/lib_js.odin b/core/dynlib/lib_js.odin index ace1b0939..99f486dd0 100644 --- a/core/dynlib/lib_js.odin +++ b/core/dynlib/lib_js.odin @@ -1,8 +1,12 @@ -//+build js -//+private +#+build js +#+private package dynlib -_load_library :: proc(path: string, global_symbols := false) -> (Library, bool) { +import "base:runtime" + +_LIBRARY_FILE_EXTENSION :: "" + +_load_library :: proc(path: string, global_symbols: bool, allocator: runtime.Allocator) -> (Library, bool) { return nil, false } @@ -10,6 +14,10 @@ _unload_library :: proc(library: Library) -> bool { return false } -_symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) { +_symbol_address :: proc(library: Library, symbol: string, allocator: runtime.Allocator) -> (ptr: rawptr, found: bool) { return nil, false } + +_last_error :: proc() -> string { + return "" +} diff --git a/core/dynlib/lib_unix.odin b/core/dynlib/lib_unix.odin index b0cc37e99..50ab1acc8 100644 --- a/core/dynlib/lib_unix.odin +++ b/core/dynlib/lib_unix.odin @@ -1,24 +1,41 @@ -//+build linux, darwin, freebsd, openbsd -//+private +#+build linux, darwin, freebsd, openbsd, netbsd +#+private package dynlib -import "core:os" +import "base:runtime" -_load_library :: proc(path: string, global_symbols := false) -> (Library, bool) { - flags := os.RTLD_NOW +import "core:strings" +import "core:sys/posix" + +_LIBRARY_FILE_EXTENSION :: "dylib" when ODIN_OS == .Darwin else "so" + +_load_library :: proc(path: string, global_symbols: bool, allocator: runtime.Allocator) -> (Library, bool) { + flags := posix.RTLD_Flags{.NOW} if global_symbols { - flags |= os.RTLD_GLOBAL + flags += {.GLOBAL} } - lib := os.dlopen(path, flags) + + cpath := strings.clone_to_cstring(path, allocator) + defer delete(cpath, allocator) + + lib := posix.dlopen(cpath, flags) return Library(lib), lib != nil } _unload_library :: proc(library: Library) -> bool { - return os.dlclose(rawptr(library)) + return posix.dlclose(posix.Symbol_Table(library)) == 0 } -_symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) { - ptr = os.dlsym(rawptr(library), symbol) +_symbol_address :: proc(library: Library, symbol: string, allocator: runtime.Allocator) -> (ptr: rawptr, found: bool) { + csymbol := strings.clone_to_cstring(symbol, allocator) + defer delete(csymbol, allocator) + + ptr = posix.dlsym(posix.Symbol_Table(library), csymbol) found = ptr != nil return } + +_last_error :: proc() -> string { + err := string(posix.dlerror()) + return "unknown" if err == "" else err +} diff --git a/core/dynlib/lib_windows.odin b/core/dynlib/lib_windows.odin index 67880df4b..05cd2cb3c 100644 --- a/core/dynlib/lib_windows.odin +++ b/core/dynlib/lib_windows.odin @@ -1,16 +1,19 @@ -//+build windows -//+private +#+build windows +#+private package dynlib +import "base:runtime" + import win32 "core:sys/windows" import "core:strings" -import "core:runtime" +import "core:reflect" -_load_library :: proc(path: string, global_symbols := false) -> (Library, bool) { +_LIBRARY_FILE_EXTENSION :: "dll" + +_load_library :: proc(path: string, global_symbols: bool, allocator: runtime.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 } @@ -20,10 +23,16 @@ _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: runtime.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 } + +_last_error :: proc() -> string { + err := win32.System_Error(win32.GetLastError()) + err_msg := reflect.enum_string(err) + return "unknown" if err_msg == "" else err_msg +} 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..966e6be00 --- /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: +- [[ https://en.wikipedia.org/wiki/ANSI_escape_code ]] +- [[ https://www.vt100.net/docs/vt102-ug/chapter5.html ]] +- [[ https://invisible-island.net/xterm/ctlseqs/ctlseqs.html ]] +*/ +package ansi diff --git a/core/encoding/base32/base32.odin b/core/encoding/base32/base32.odin index 7ab35afd0..8629491b1 100644 --- a/core/encoding/base32/base32.odin +++ b/core/encoding/base32/base32.odin @@ -1,148 +1,230 @@ -package base32 - -// @note(zh): Encoding utility for Base32 -// A secondary param can be used to supply a custom alphabet to -// @link(encode) and a matching decoding table to @link(decode). -// If none is supplied it just uses the standard Base32 alphabet. -// Incase your specific version does not use padding, you may -// 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', -} - -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, -} - -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) -} - -@private -_encode :: proc(out, data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) { - 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] - } - - 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 - } - - outi := 0 - data := data - - 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 - } - - 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 -} +// Base32 encoding/decoding implementation as specified in RFC 4648. +// [[ More; https://www.rfc-editor.org/rfc/rfc4648.html ]] +package encoding_base32 + +// @note(zh): Encoding utility for Base32 +// A secondary param can be used to supply a custom alphabet to +// @link(encode) and a matching decoding table to @link(decode). +// If none is supplied it just uses the standard Base32 alphabet. +// In case your specific version does not use padding, you may +// truncate it from the encoded output. + +// Error represents errors that can occur during base32 decoding operations. +// As per RFC 4648: +// - Section 3.3: Invalid character handling +// - Section 3.2: Padding requirements +// - Section 6: Base32 encoding specifics (including block size requirements) +Error :: enum { + None, + Invalid_Character, // Input contains characters outside the specified alphabet + Invalid_Length, // Input length is not valid for base32 (must be a multiple of 8 with proper padding) + Malformed_Input, // Input has improper structure (wrong padding position or incomplete groups) +} + +Validate_Proc :: #type proc(c: byte) -> bool + +@private +_validate_default :: proc(c: byte) -> bool { + return (c >= 'A' && c <= 'Z') || (c >= '2' && c <= '7') +} + +@(rodata) +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', +} + +PADDING :: '=' + +@(rodata) +DEC_TABLE := [256]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, +} + +encode :: proc(data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) -> string { + out_length := (len(data) + 4) / 5 * 8 + out := make([]byte, out_length, allocator) + _encode(out, data, ENC_TBL) + return string(out[:]) +} + +@private +_encode :: proc(out, data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) { + out := out + data := data + + for len(data) > 0 { + carry: byte + switch len(data) { + case: + out[7] = ENC_TBL[data[4] & 0x1f] + carry = data[4] >> 5 + fallthrough + case 4: + out[6] = ENC_TBL[carry | (data[3] << 3) & 0x1f] + out[5] = ENC_TBL[(data[3] >> 2) & 0x1f] + carry = data[3] >> 7 + fallthrough + case 3: + out[4] = ENC_TBL[carry | (data[2] << 1) & 0x1f] + carry = (data[2] >> 4) & 0x1f + fallthrough + case 2: + out[3] = ENC_TBL[carry | (data[1] << 4) & 0x1f] + out[2] = ENC_TBL[(data[1] >> 1) & 0x1f] + carry = (data[1] >> 6) & 0x1f + fallthrough + case 1: + out[1] = ENC_TBL[carry | (data[0] << 2) & 0x1f] + out[0] = ENC_TBL[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:] + } +} + +@(optimization_mode="favor_size") +decode :: proc( + data: string, + DEC_TBL := DEC_TABLE, + validate: Validate_Proc = _validate_default, + allocator := context.allocator) -> (out: []byte, err: Error) { + if len(data) == 0 { + return nil, .None + } + + // Check minimum length requirement first + if len(data) < 2 { + return nil, .Invalid_Length + } + + // Validate characters using provided validation function + for i := 0; i < len(data); i += 1 { + c := data[i] + if c == byte(PADDING) { + break + } + if !validate(c) { + return nil, .Invalid_Character + } + } + + // Validate padding and length + data_len := len(data) + padding_count := 0 + for i := data_len - 1; i >= 0; i -= 1 { + if data[i] != byte(PADDING) { + break + } + padding_count += 1 + } + + // Check for proper padding and length combinations + if padding_count > 0 { + // Verify no padding in the middle + for i := 0; i < data_len - padding_count; i += 1 { + if data[i] == byte(PADDING) { + return nil, .Malformed_Input + } + } + + content_len := data_len - padding_count + mod8 := content_len % 8 + required_padding: int + switch mod8 { + case 2: required_padding = 6 // 2 chars need 6 padding chars + case 4: required_padding = 4 // 4 chars need 4 padding chars + case 5: required_padding = 3 // 5 chars need 3 padding chars + case 7: required_padding = 1 // 7 chars need 1 padding char + case: required_padding = 0 + } + + if required_padding > 0 { + if padding_count != required_padding { + return nil, .Malformed_Input + } + } else if mod8 != 0 { + return nil, .Malformed_Input + } + } else { + // No padding - must be multiple of 8 + if data_len % 8 != 0 { + return nil, .Malformed_Input + } + } + + // Calculate decoded length: 5 bytes for every 8 input chars + input_chars := data_len - padding_count + out_len := input_chars * 5 / 8 + out = make([]byte, out_len, allocator) + defer if err != .None { + delete(out) + } + + // Process input in 8-byte blocks + outi := 0 + for i := 0; i < input_chars; i += 8 { + buf: [8]byte + block_size := min(8, input_chars - i) + + // Decode block + for j := 0; j < block_size; j += 1 { + buf[j] = DEC_TBL[data[i + j]] + } + + // Convert to output bytes based on block size + bytes_to_write := block_size * 5 / 8 + switch block_size { + case 8: + out[outi + 4] = (buf[6] << 5) | buf[7] + fallthrough + case 7: + out[outi + 3] = (buf[4] << 7) | (buf[5] << 2) | (buf[6] >> 3) + fallthrough + case 5: + out[outi + 2] = (buf[3] << 4) | (buf[4] >> 1) + fallthrough + case 4: + out[outi + 1] = (buf[1] << 6) | (buf[2] << 1) | (buf[3] >> 4) + fallthrough + case 2: + out[outi] = (buf[0] << 3) | (buf[1] >> 2) + } + outi += bytes_to_write + } + + return +} diff --git a/core/encoding/base32/base32_test.odin b/core/encoding/base32/base32_test.odin new file mode 100644 index 000000000..ea41ae36f --- /dev/null +++ b/core/encoding/base32/base32_test.odin @@ -0,0 +1,227 @@ +package encoding_base32 + +import "core:testing" +import "core:bytes" + +@(test) +test_base32_decode_valid :: proc(t: ^testing.T) { + // RFC 4648 Section 10 - Test vectors + cases := [?]struct { + input, expected: string, + }{ + {"", ""}, + {"MY======", "f"}, + {"MZXQ====", "fo"}, + {"MZXW6===", "foo"}, + {"MZXW6YQ=", "foob"}, + {"MZXW6YTB", "fooba"}, + {"MZXW6YTBOI======", "foobar"}, + } + + for c in cases { + output, err := decode(c.input) + if output != nil { + defer delete(output) + } + testing.expect_value(t, err, Error.None) + expected := transmute([]u8)c.expected + if output != nil { + testing.expect(t, bytes.equal(output, expected)) + } else { + testing.expect(t, len(c.expected) == 0) + } + } +} + +@(test) +test_base32_encode :: proc(t: ^testing.T) { + // RFC 4648 Section 10 - Test vectors + cases := [?]struct { + input, expected: string, + }{ + {"", ""}, + {"f", "MY======"}, + {"fo", "MZXQ===="}, + {"foo", "MZXW6==="}, + {"foob", "MZXW6YQ="}, + {"fooba", "MZXW6YTB"}, + {"foobar", "MZXW6YTBOI======"}, + } + + for c in cases { + output := encode(transmute([]byte)c.input) + defer delete(output) + testing.expect(t, output == c.expected) + } +} + +@(test) +test_base32_decode_invalid :: proc(t: ^testing.T) { + // Section 3.3 - Non-alphabet characters + { + // Characters outside alphabet + input := "MZ1W6YTB" // '1' not in alphabet (A-Z, 2-7) + output, err := decode(input) + if output != nil { + defer delete(output) + } + testing.expect_value(t, err, Error.Invalid_Character) + } + { + // Lowercase not allowed + input := "mzxq====" + output, err := decode(input) + if output != nil { + defer delete(output) + } + testing.expect_value(t, err, Error.Invalid_Character) + } + + // Section 3.2 - Padding requirements + { + // Padding must only be at end + input := "MZ=Q====" + output, err := decode(input) + if output != nil { + defer delete(output) + } + testing.expect_value(t, err, Error.Malformed_Input) + } + { + // Missing padding + input := "MZXQ" // Should be MZXQ==== + output, err := decode(input) + if output != nil { + defer delete(output) + } + testing.expect_value(t, err, Error.Malformed_Input) + } + { + // Incorrect padding length + input := "MZXQ=" // Needs 4 padding chars + output, err := decode(input) + if output != nil { + defer delete(output) + } + testing.expect_value(t, err, Error.Malformed_Input) + } + { + // Too much padding + input := "MY=========" // Extra padding chars + output, err := decode(input) + if output != nil { + defer delete(output) + } + testing.expect_value(t, err, Error.Malformed_Input) + } + + // Section 6 - Base32 block size requirements + { + // Single character (invalid block) + input := "M" + output, err := decode(input) + if output != nil { + defer delete(output) + } + testing.expect_value(t, err, Error.Invalid_Length) + } +} + +@(test) +test_base32_roundtrip :: proc(t: ^testing.T) { + cases := [?]string{ + "", + "f", + "fo", + "foo", + "foob", + "fooba", + "foobar", + } + + for input in cases { + encoded := encode(transmute([]byte)input) + defer delete(encoded) + decoded, err := decode(encoded) + if decoded != nil { + defer delete(decoded) + } + testing.expect_value(t, err, Error.None) + testing.expect(t, bytes.equal(decoded, transmute([]byte)input)) + } +} + +@(test) +test_base32_custom_alphabet :: proc(t: ^testing.T) { + custom_enc_table := [32]byte{ + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + } + + custom_dec_table: [256]u8 + for i := 0; i < len(custom_enc_table); i += 1 { + custom_dec_table[custom_enc_table[i]] = u8(i) + } + + /* + custom_dec_table := [256]u8{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00-0x0f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10-0x1f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x20-0x2f + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, // 0x30-0x3f ('0'-'9') + 0, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 0x40-0x4f ('A'-'O') + 25, 26, 27, 28, 29, 30, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x50-0x5f ('P'-'V') + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x60-0x6f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x70-0x7f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x80-0x8f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90-0x9f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xa0-0xaf + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xb0-0xbf + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xc0-0xcf + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xd0-0xdf + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xe0-0xef + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xf0-0xff + } + */ + + custom_validate :: proc(c: byte) -> bool { + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'V') || c == byte(PADDING) + } + + cases := [?]struct { + input: string, + enc_expected: string, + }{ + {"f", "CO======"}, + {"fo", "CPNG===="}, + {"foo", "CPNMU==="}, + } + + for c in cases { + // Test encoding + encoded := encode(transmute([]byte)c.input, custom_enc_table) + defer delete(encoded) + testing.expect(t, encoded == c.enc_expected) + + // Test decoding + decoded, err := decode(encoded, custom_dec_table, custom_validate) + defer if decoded != nil { + delete(decoded) + } + + testing.expect_value(t, err, Error.None) + testing.expect(t, bytes.equal(decoded, transmute([]byte)c.input)) + } + + // Test invalid character detection + { + input := "WXY=====" // Contains chars not in our alphabet + output, err := decode(input, custom_dec_table, custom_validate) + if output != nil { + delete(output) + } + testing.expect_value(t, err, Error.Invalid_Character) + } +} diff --git a/core/encoding/base64/base64.odin b/core/encoding/base64/base64.odin index cf2ea1c12..1013a7d0b 100644 --- a/core/encoding/base64/base64.odin +++ b/core/encoding/base64/base64.odin @@ -1,4 +1,8 @@ -package base64 +package encoding_base64 + +import "core:io" +import "core:mem" +import "core:strings" // @note(zh): Encoding utility for Base64 // A secondary param can be used to supply a custom alphabet to @@ -39,59 +43,132 @@ DEC_TABLE := [128]int { 49, 50, 51, -1, -1, -1, -1, -1, } -encode :: proc(data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) -> string #no_bounds_check { - length := len(data) - if length == 0 { - return "" - } +encode :: proc(data: []byte, ENC_TBL := ENC_TABLE, allocator := context.allocator) -> (encoded: string, err: mem.Allocator_Error) #optional_allocator_error { + out_length := encoded_len(data) + if out_length == 0 { + return + } - out_length := ((4 * length / 3) + 3) &~ 3 - out := make([]byte, out_length, allocator) + out := strings.builder_make(0, out_length, allocator) or_return + ioerr := encode_into(strings.to_stream(&out), data, ENC_TBL) - c0, c1, c2, block: int + assert(ioerr == nil, "string builder should not IO error") + assert(strings.builder_cap(out) == out_length, "buffer resized, `encoded_len` was wrong") - for i, d := 0, 0; i < length; i, d = i + 3, d + 4 { - c0, c1, c2 = int(data[i]), -1, -1 - - if i + 1 < length { c1 = int(data[i + 1]) } - if i + 2 < length { c2 = int(data[i + 2]) } - - block = (c0 << 16) | (max(c1, 0) << 8) | max(c2, 0) - - out[d] = ENC_TBL[block >> 18 & 63] - out[d + 1] = ENC_TBL[block >> 12 & 63] - out[d + 2] = c1 == -1 ? PADDING : ENC_TBL[block >> 6 & 63] - out[d + 3] = c2 == -1 ? PADDING : ENC_TBL[block & 63] - } - return string(out) + return strings.to_string(out), nil } -decode :: proc(data: string, DEC_TBL := DEC_TABLE, allocator := context.allocator) -> []byte #no_bounds_check { - length := len(data) - if length == 0 { - return nil - } +encode_into :: proc(w: io.Writer, data: []byte, ENC_TBL := ENC_TABLE) -> io.Error { + length := len(data) + if length == 0 { + return nil + } - pad_count := data[length - 1] == PADDING ? (data[length - 2] == PADDING ? 2 : 1) : 0 - out_length := ((length * 6) >> 3) - pad_count - out := make([]byte, out_length, allocator) + c0, c1, c2, block: int + out: [4]byte + for i := 0; i < length; i += 3 { + #no_bounds_check { + c0, c1, c2 = int(data[i]), -1, -1 - c0, c1, c2, c3: int - b0, b1, b2: int + if i + 1 < length { c1 = int(data[i + 1]) } + if i + 2 < length { c2 = int(data[i + 2]) } - for i, j := 0, 0; i < length; i, j = i + 4, j + 3 { - c0 = DEC_TBL[data[i]] - c1 = DEC_TBL[data[i + 1]] - c2 = DEC_TBL[data[i + 2]] - c3 = DEC_TBL[data[i + 3]] - - b0 = (c0 << 2) | (c1 >> 4) - b1 = (c1 << 4) | (c2 >> 2) - b2 = (c2 << 6) | c3 - - out[j] = byte(b0) - out[j + 1] = byte(b1) - out[j + 2] = byte(b2) - } - return out + block = (c0 << 16) | (max(c1, 0) << 8) | max(c2, 0) + + out[0] = ENC_TBL[block >> 18 & 63] + out[1] = ENC_TBL[block >> 12 & 63] + out[2] = c1 == -1 ? PADDING : ENC_TBL[block >> 6 & 63] + out[3] = c2 == -1 ? PADDING : ENC_TBL[block & 63] + } + io.write_full(w, out[:]) or_return + } + return nil +} + +encoded_len :: proc(data: []byte) -> int { + length := len(data) + if length == 0 { + return 0 + } + + return ((4 * length / 3) + 3) &~ 3 +} + +decode :: proc(data: string, DEC_TBL := DEC_TABLE, allocator := context.allocator) -> (decoded: []byte, err: mem.Allocator_Error) #optional_allocator_error { + out_length := decoded_len(data) + + out := strings.builder_make(0, out_length, allocator) or_return + ioerr := decode_into(strings.to_stream(&out), data, DEC_TBL) + + assert(ioerr == nil, "string builder should not IO error") + assert(strings.builder_cap(out) == out_length, "buffer resized, `decoded_len` was wrong") + + return out.buf[:], nil +} + +decode_into :: proc(w: io.Writer, data: string, DEC_TBL := DEC_TABLE) -> io.Error { + length := decoded_len(data) + if length == 0 { + return nil + } + + c0, c1, c2, c3: int + b0, b1, b2: int + buf: [3]byte + i, j: int + for ; j + 3 <= length; i, j = i + 4, j + 3 { + #no_bounds_check { + c0 = DEC_TBL[data[i]] + c1 = DEC_TBL[data[i + 1]] + c2 = DEC_TBL[data[i + 2]] + c3 = DEC_TBL[data[i + 3]] + + b0 = (c0 << 2) | (c1 >> 4) + b1 = (c1 << 4) | (c2 >> 2) + b2 = (c2 << 6) | c3 + + buf[0] = byte(b0) + buf[1] = byte(b1) + buf[2] = byte(b2) + } + + io.write_full(w, buf[:]) or_return + } + + rest := length - j + if rest > 0 { + #no_bounds_check { + c0 = DEC_TBL[data[i]] + c1 = DEC_TBL[data[i + 1]] + c2 = DEC_TBL[data[i + 2]] + + b0 = (c0 << 2) | (c1 >> 4) + b1 = (c1 << 4) | (c2 >> 2) + } + + switch rest { + case 1: io.write_byte(w, byte(b0)) or_return + case 2: io.write_full(w, {byte(b0), byte(b1)}) or_return + } + } + + return nil +} + +decoded_len :: proc(data: string) -> int { + length := len(data) + if length == 0 { + return 0 + } + + padding: int + if data[length - 1] == PADDING { + if length > 1 && data[length - 2] == PADDING { + padding = 2 + } else { + padding = 1 + } + } + + return ((length * 6) >> 3) - padding } diff --git a/core/encoding/cbor/cbor.odin b/core/encoding/cbor/cbor.odin new file mode 100644 index 000000000..8eb829ed3 --- /dev/null +++ b/core/encoding/cbor/cbor.odin @@ -0,0 +1,674 @@ +package encoding_cbor + +import "base:intrinsics" + +import "core:encoding/json" +import "core:encoding/hex" +import "core:io" +import "core:mem" +import "core:strconv" +import "core:strings" + +// If we are decoding a stream of either a map or list, the initial capacity will be this value. +INITIAL_STREAMED_CONTAINER_CAPACITY :: 8 + +// If we are decoding a stream of either text or bytes, the initial capacity will be this value. +INITIAL_STREAMED_BYTES_CAPACITY :: 16 + +// The default maximum amount of bytes to allocate on a buffer/container at once to prevent +// malicious input from causing massive allocations. +DEFAULT_MAX_PRE_ALLOC :: mem.Kilobyte + +// Known/common headers are defined, undefined headers can still be valid. +// Higher 3 bits is for the major type and lower 5 bits for the additional information. +Header :: enum u8 { + U8 = (u8(Major.Unsigned) << 5) | u8(Add.One_Byte), + U16 = (u8(Major.Unsigned) << 5) | u8(Add.Two_Bytes), + U32 = (u8(Major.Unsigned) << 5) | u8(Add.Four_Bytes), + U64 = (u8(Major.Unsigned) << 5) | u8(Add.Eight_Bytes), + + Neg_U8 = (u8(Major.Negative) << 5) | u8(Add.One_Byte), + Neg_U16 = (u8(Major.Negative) << 5) | u8(Add.Two_Bytes), + Neg_U32 = (u8(Major.Negative) << 5) | u8(Add.Four_Bytes), + Neg_U64 = (u8(Major.Negative) << 5) | u8(Add.Eight_Bytes), + + False = (u8(Major.Other) << 5) | u8(Add.False), + True = (u8(Major.Other) << 5) | u8(Add.True), + + Nil = (u8(Major.Other) << 5) | u8(Add.Nil), + Undefined = (u8(Major.Other) << 5) | u8(Add.Undefined), + + Simple = (u8(Major.Other) << 5) | u8(Add.One_Byte), + + F16 = (u8(Major.Other) << 5) | u8(Add.Two_Bytes), + F32 = (u8(Major.Other) << 5) | u8(Add.Four_Bytes), + F64 = (u8(Major.Other) << 5) | u8(Add.Eight_Bytes), + + Break = (u8(Major.Other) << 5) | u8(Add.Break), +} + +// The higher 3 bits of the header which denotes what type of value it is. +Major :: enum u8 { + Unsigned, + Negative, + Bytes, + Text, + Array, + Map, + Tag, + Other, +} + +// The lower 3 bits of the header which denotes additional information for the type of value. +Add :: enum u8 { + False = 20, + True = 21, + Nil = 22, + Undefined = 23, + + One_Byte = 24, + Two_Bytes = 25, + Four_Bytes = 26, + Eight_Bytes = 27, + + Length_Unknown = 31, + Break = Length_Unknown, +} + +Value :: union { + u8, + u16, + u32, + u64, + + Negative_U8, + Negative_U16, + Negative_U32, + Negative_U64, + + // Pointers so the size of the Value union stays small. + ^Bytes, + ^Text, + ^Array, + ^Map, + ^Tag, + + Simple, + f16, + f32, + f64, + bool, + Undefined, + Nil, +} + +Bytes :: []byte +Text :: string + +Array :: []Value + +Map :: []Map_Entry +Map_Entry :: struct { + key: Value, // Can be any unsigned, negative, float, Simple, bool, Text. + value: Value, +} + +Tag :: struct { + number: Tag_Number, + value: Value, // Value based on the number. +} + +Tag_Number :: u64 + +Nil :: distinct rawptr +Undefined :: distinct rawptr + +// A distinct atom-like number, range from `0..=19` and `32..=max(u8)`. +Simple :: distinct u8 +Atom :: Simple + +Unmarshal_Error :: union #shared_nil { + io.Error, + mem.Allocator_Error, + Decode_Data_Error, + Unmarshal_Data_Error, + Maybe(Unsupported_Type_Error), +} + +Marshal_Error :: union #shared_nil { + io.Error, + mem.Allocator_Error, + Encode_Data_Error, + Marshal_Data_Error, + Maybe(Unsupported_Type_Error), +} + +Decode_Error :: union #shared_nil { + io.Error, + mem.Allocator_Error, + Decode_Data_Error, +} + +Encode_Error :: union #shared_nil { + io.Error, + mem.Allocator_Error, + Encode_Data_Error, +} + +Decode_Data_Error :: enum { + None, + Bad_Major, // An invalid major type was encountered. + Bad_Argument, // A general unexpected value (most likely invalid additional info in header). + Bad_Tag_Value, // When the type of value for the given tag is not valid. + Nested_Indefinite_Length, // When an streamed/indefinite length container nests another, this is not allowed. + Nested_Tag, // When a tag's value is another tag, this is not allowed. + Length_Too_Big, // When the length of a container (map, array, bytes, string) is more than `max(int)`. + Disallowed_Streaming, // When the `.Disallow_Streaming` flag is set and a streaming header is encountered. + Break, // When the `break` header was found without any stream to break off. +} + +Encode_Data_Error :: enum { + None, + Invalid_Simple, // When a simple is being encoded that is out of the range `0..=19` and `32..=max(u8)`. + Int_Too_Big, // When an int is being encoded that is larger than `max(u64)` or smaller than `min(u64)`. + Bad_Tag_Value, // When the type of value is not supported by the tag implementation. +} + +Unmarshal_Data_Error :: enum { + None, + Invalid_Parameter, // When the given `any` can not be unmarshalled into. + Non_Pointer_Parameter, // When the given `any` is not a pointer. +} + +Marshal_Data_Error :: enum { + None, + Invalid_CBOR_Tag, // When the struct tag `cbor_tag:""` is not a registered name or number. +} + +// Error that is returned when a type couldn't be marshalled into or out of, as much information +// as possible/available is added. +Unsupported_Type_Error :: struct { + id: typeid, + hdr: Header, + add: Add, +} + +_unsupported :: proc(v: any, hdr: Header, add: Add = nil) -> Maybe(Unsupported_Type_Error) { + return Unsupported_Type_Error{ + id = v.id, + hdr = hdr, + add = add, + } +} + +// Actual value is `-1 - x` (be careful of overflows). + +Negative_U8 :: distinct u8 +Negative_U16 :: distinct u16 +Negative_U32 :: distinct u32 +Negative_U64 :: distinct u64 + +// Turns the CBOR negative unsigned int type into a signed integer type. +negative_to_int :: proc { + negative_u8_to_int, + negative_u16_to_int, + negative_u32_to_int, + negative_u64_to_int, +} + +negative_u8_to_int :: #force_inline proc(u: Negative_U8) -> i16 { + return -1 - i16(u) +} + +negative_u16_to_int :: #force_inline proc(u: Negative_U16) -> i32 { + return -1 - i32(u) +} + +negative_u32_to_int :: #force_inline proc(u: Negative_U32) -> i64 { + return -1 - i64(u) +} + +negative_u64_to_int :: #force_inline proc(u: Negative_U64) -> i128 { + return -1 - i128(u) +} + +// Utility for converting between the different errors when they are subsets of the other. +err_conv :: proc { + encode_to_marshal_err, + encode_to_marshal_err_p2, + decode_to_unmarshal_err, + decode_to_unmarshal_err_p, + decode_to_unmarshal_err_p2, +} + +encode_to_marshal_err :: #force_inline proc(err: Encode_Error) -> Marshal_Error { + switch e in err { + case nil: return nil + case io.Error: return e + case mem.Allocator_Error: return e + case Encode_Data_Error: return e + case: return nil + } +} + +encode_to_marshal_err_p2 :: #force_inline proc(v: $T, v2: $T2, err: Encode_Error) -> (T, T2, Marshal_Error) { + return v, v2, err_conv(err) +} + +decode_to_unmarshal_err :: #force_inline proc(err: Decode_Error) -> Unmarshal_Error { + switch e in err { + case nil: return nil + case io.Error: return e + case mem.Allocator_Error: return e + case Decode_Data_Error: return e + case: return nil + } +} + +decode_to_unmarshal_err_p :: #force_inline proc(v: $T, err: Decode_Error) -> (T, Unmarshal_Error) { + return v, err_conv(err) +} + +decode_to_unmarshal_err_p2 :: #force_inline proc(v: $T, v2: $T2, err: Decode_Error) -> (T, T2, Unmarshal_Error) { + return v, v2, err_conv(err) +} + +// Recursively frees all memory allocated when decoding the passed value. +destroy :: proc(val: Value, allocator := context.allocator) { + context.allocator = allocator + #partial switch v in val { + case ^Map: + if v == nil { return } + for entry in v { + destroy(entry.key) + destroy(entry.value) + } + delete(v^) + free(v) + case ^Array: + if v == nil { return } + for entry in v { + destroy(entry) + } + delete(v^) + free(v) + case ^Text: + if v == nil { return } + delete(v^) + free(v) + case ^Bytes: + if v == nil { return } + delete(v^) + free(v) + case ^Tag: + if v == nil { return } + destroy(v.value) + free(v) + } +} + +/* +to_diagnostic_format either writes or returns a human-readable representation of the value, +optionally formatted, defined as the diagnostic format in [[RFC 8949 Section 8;https://www.rfc-editor.org/rfc/rfc8949.html#name-diagnostic-notation]]. + +Incidentally, if the CBOR does not contain any of the additional types defined on top of JSON +this will also be valid JSON. +*/ +to_diagnostic_format :: proc { + to_diagnostic_format_string, + to_diagnostic_format_writer, +} + +// 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, 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 { + // The string builder stream only returns .EOF, and only if it can't write (out of memory). + return "", .Out_Of_Memory + } + assert(err == nil) + + return strings.to_string(b), nil +} + +// Writes the given CBOR value into the writer as human-readable text. +// See docs on the proc group `diagnose` for more info. +to_diagnostic_format_writer :: proc(w: io.Writer, val: Value, padding := 0) -> io.Error { + @(require_results) + indent :: proc(padding: int) -> int { + padding := padding + if padding != -1 { + padding += 1 + } + return padding + } + + @(require_results) + dedent :: proc(padding: int) -> int { + padding := padding + if padding != -1 { + padding -= 1 + } + return padding + } + + comma :: proc(w: io.Writer, padding: int) -> io.Error { + _ = io.write_string(w, ", " if padding == -1 else ",") or_return + return nil + } + + newline :: proc(w: io.Writer, padding: int) -> io.Error { + if padding != -1 { + io.write_string(w, "\n") or_return + for _ in 0.. (Value, mem.Allocator_Error) #optional_allocator_error { + internal :: proc(val: json.Value) -> (ret: Value, err: mem.Allocator_Error) { + switch v in val { + case json.Null: return Nil{}, nil + case json.Integer: + i, major := _int_to_uint(v) + #partial switch major { + case .Unsigned: return i, nil + case .Negative: return Negative_U64(i), nil + case: unreachable() + } + case json.Float: return v, nil + case json.Boolean: return v, nil + case json.String: + container := new(Text) or_return + + // We need the string to have a nil byte at the end so we clone to cstring. + container^ = string(strings.clone_to_cstring(v) or_return) + return container, nil + case json.Array: + arr := new(Array) or_return + arr^ = make([]Value, len(v)) or_return + for _, i in arr { + arr[i] = internal(v[i]) or_return + } + return arr, nil + case json.Object: + m := new(Map) or_return + dm := make([dynamic]Map_Entry, 0, len(v)) or_return + for mkey, mval in v { + append(&dm, Map_Entry{from_json(mkey) or_return, from_json(mval) or_return}) + } + m^ = dm[:] + return m, nil + } + return nil, nil + } + + context.allocator = allocator + return internal(val) +} + +/* +Converts from CBOR to JSON. + +NOTE: overflow on integers or floats is not handled. + +Everything is copied to the given allocator, the passed in CBOR value can be `destroy`'ed after. + +If a CBOR map with non-string keys is encountered it is turned into an array of tuples. +*/ +to_json :: proc(val: Value, allocator := context.allocator) -> (json.Value, mem.Allocator_Error) #optional_allocator_error { + internal :: proc(val: Value) -> (ret: json.Value, err: mem.Allocator_Error) { + switch v in val { + case Simple: return json.Integer(v), nil + + case u8: return json.Integer(v), nil + case u16: return json.Integer(v), nil + case u32: return json.Integer(v), nil + case u64: return json.Integer(v), nil + + case Negative_U8: return json.Integer(negative_to_int(v)), nil + case Negative_U16: return json.Integer(negative_to_int(v)), nil + case Negative_U32: return json.Integer(negative_to_int(v)), nil + case Negative_U64: return json.Integer(negative_to_int(v)), nil + + case f16: return json.Float(v), nil + case f32: return json.Float(v), nil + case f64: return json.Float(v), nil + + case bool: return json.Boolean(v), nil + + case Undefined: return json.Null{}, nil + case Nil: return json.Null{}, nil + + case ^Bytes: return json.String(strings.clone(string(v^)) or_return), nil + case ^Text: return json.String(strings.clone(v^) or_return), nil + + case ^Map: + keys_all_strings :: proc(m: ^Map) -> bool { + for entry in m { + #partial switch kv in entry.key { + case ^Bytes: + case ^Text: + case: return false + } + } + return true + } + + if keys_all_strings(v) { + obj := make(json.Object, len(v)) or_return + for entry in v { + k: string + #partial switch kv in entry.key { + case ^Bytes: k = string(kv^) + case ^Text: k = kv^ + case: unreachable() + } + + v := internal(entry.value) or_return + obj[k] = v + } + return obj, nil + } else { + // Resort to an array of tuples if keys aren't all strings. + arr := make(json.Array, 0, len(v)) or_return + for entry in v { + entry_arr := make(json.Array, 0, 2) or_return + append(&entry_arr, internal(entry.key) or_return) or_return + append(&entry_arr, internal(entry.value) or_return) or_return + append(&arr, entry_arr) or_return + } + return arr, nil + } + + case ^Array: + arr := make(json.Array, 0, len(v)) or_return + for entry in v { + append(&arr, internal(entry) or_return) or_return + } + return arr, nil + + case ^Tag: + obj := make(json.Object, 2) or_return + obj[strings.clone("number") or_return] = internal(v.number) or_return + obj[strings.clone("value") or_return] = internal(v.value) or_return + return obj, nil + + case: return json.Null{}, nil + } + } + + context.allocator = allocator + return internal(val) +} + +_int_to_uint :: proc { + _i8_to_uint, + _i16_to_uint, + _i32_to_uint, + _i64_to_uint, + _i128_to_uint, +} + +_u128_to_u64 :: #force_inline proc(v: u128) -> (u64, Encode_Data_Error) { + if v > u128(max(u64)) { + return 0, .Int_Too_Big + } + + return u64(v), nil +} + +_i8_to_uint :: #force_inline proc(v: i8) -> (u: u8, m: Major) { + if v < 0 { + return u8(abs(v)-1), .Negative + } + + return u8(v), .Unsigned +} + +_i16_to_uint :: #force_inline proc(v: i16) -> (u: u16, m: Major) { + if v < 0 { + return u16(abs(v)-1), .Negative + } + + return u16(v), .Unsigned +} + +_i32_to_uint :: #force_inline proc(v: i32) -> (u: u32, m: Major) { + if v < 0 { + return u32(abs(v)-1), .Negative + } + + return u32(v), .Unsigned +} + +_i64_to_uint :: #force_inline proc(v: i64) -> (u: u64, m: Major) { + if v < 0 { + return u64(abs(v)-1), .Negative + } + + return u64(v), .Unsigned +} + +_i128_to_uint :: proc(v: i128) -> (u: u64, m: Major, err: Encode_Data_Error) { + if v < 0 { + m = .Negative + u, err = _u128_to_u64(u128(abs(v) - 1)) + return + } + + m = .Unsigned + u, err = _u128_to_u64(u128(v)) + return +} diff --git a/core/encoding/cbor/coding.odin b/core/encoding/cbor/coding.odin new file mode 100644 index 000000000..bfeb147c4 --- /dev/null +++ b/core/encoding/cbor/coding.odin @@ -0,0 +1,887 @@ +package encoding_cbor + +import "base:intrinsics" +import "base:runtime" + +import "core:bytes" +import "core:encoding/endian" +import "core:io" +import "core:slice" +import "core:strings" + +Encoder_Flag :: enum { + // CBOR defines a tag header that also acts as a file/binary header, + // this way decoders can check the first header of the binary and see if it is CBOR. + Self_Described_CBOR, + + // Integers are stored in the smallest integer type it fits. + // This involves checking each int against the max of all its smaller types. + Deterministic_Int_Size, + + // Floats are stored in the smallest size float type without losing precision. + // This involves casting each float down to its smaller types and checking if it changed. + Deterministic_Float_Size, + + // Sort maps by their keys in bytewise lexicographic order of their deterministic encoding. + // NOTE: In order to do this, all keys of a map have to be pre-computed, sorted, and + // then written, this involves temporary allocations for the keys and a copy of the map itself. + Deterministic_Map_Sorting, +} + +Encoder_Flags :: bit_set[Encoder_Flag] + +// Flags for fully deterministic output (if you are not using streaming/indeterminate length). +ENCODE_FULLY_DETERMINISTIC :: Encoder_Flags{.Deterministic_Int_Size, .Deterministic_Float_Size, .Deterministic_Map_Sorting} + +// Flags for the smallest encoding output. +ENCODE_SMALL :: Encoder_Flags{.Deterministic_Int_Size, .Deterministic_Float_Size} + +Encoder :: struct { + flags: Encoder_Flags, + writer: io.Writer, + temp_allocator: runtime.Allocator, +} + +Decoder_Flag :: enum { + // Rejects (with an error `.Disallowed_Streaming`) when a streaming CBOR header is encountered. + Disallow_Streaming, + + // Pre-allocates buffers and containers with the size that was set in the CBOR header. + // This should only be enabled when you control both ends of the encoding, if you don't, + // attackers can craft input that causes massive (`max(u64)`) byte allocations for a few bytes of + // CBOR. + Trusted_Input, + + // Makes the decoder shrink of excess capacity from allocated buffers/containers before returning. + Shrink_Excess, +} + +Decoder_Flags :: bit_set[Decoder_Flag] + +Decoder :: struct { + // The max amount of bytes allowed to pre-allocate when `.Trusted_Input` is not set on the + // flags. + max_pre_alloc: int, + + flags: Decoder_Flags, + reader: io.Reader, +} + +/* +Decodes both deterministic and non-deterministic CBOR into a `Value` variant. + +`Text` and `Bytes` can safely be cast to cstrings because of an added 0 byte. + +Allocations are done using the given allocator, +*no* allocations are done on the `context.temp_allocator`. + +A value can be (fully and recursively) deallocated using the `destroy` proc in this package. + +Disable streaming/indeterminate lengths with the `.Disallow_Streaming` flag. + +Shrink excess bytes in buffers and containers with the `.Shrink_Excess` flag. + +Mark the input as trusted input with the `.Trusted_Input` flag, this turns off the safety feature +of not pre-allocating more than `max_pre_alloc` bytes before reading into the bytes. You should only +do this when you own both sides of the encoding and are sure there can't be malicious bytes used as +an input. +*/ +decode_from :: proc { + decode_from_string, + decode_from_reader, + decode_from_decoder, +} +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, 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, 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, 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, loc := #caller_location) -> (v: Value, err: Decode_Error) { + context.allocator = allocator + + d := d + + if d.max_pre_alloc <= 0 { + d.max_pre_alloc = DEFAULT_MAX_PRE_ALLOC + } + + 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), 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 } + switch hdr { + case .U8: return _decode_u8 (r) + case .U16: return _decode_u16(r) + case .U32: return _decode_u32(r) + case .U64: return _decode_u64(r) + + case .Neg_U8: return Negative_U8 (_decode_u8 (r) or_return), nil + case .Neg_U16: return Negative_U16(_decode_u16(r) or_return), nil + case .Neg_U32: return Negative_U32(_decode_u32(r) or_return), nil + case .Neg_U64: return Negative_U64(_decode_u64(r) or_return), nil + + case .Simple: return _decode_simple(r) + + case .F16: return _decode_f16(r) + case .F32: return _decode_f32(r) + case .F64: return _decode_f64(r) + + case .True: return true, nil + case .False: return false, nil + + case .Nil: return Nil{}, nil + case .Undefined: return Undefined{}, nil + + case .Break: return nil, .Break + } + + maj, add := _header_split(hdr) + 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, .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 + } +} + +/* +Encodes the CBOR value into a binary CBOR. + +Flags can be used to control the output (mainly determinism, which coincidently affects size). + +The default flags `ENCODE_SMALL` (`.Deterministic_Int_Size`, `.Deterministic_Float_Size`) will try +to put ints and floats into their smallest possible byte size without losing equality. + +Adding the `.Self_Described_CBOR` flag will wrap the value in a tag that lets generic decoders know +the contents are CBOR from just reading the first byte. + +Adding the `.Deterministic_Map_Sorting` flag will sort the encoded maps by the byte content of the +encoded key. This flag has a cost on performance and memory efficiency because all keys in a map +have to be precomputed, sorted and only then written to the output. + +Empty flags will do nothing extra to the value. + +The allocations for the `.Deterministic_Map_Sorting` flag are done using the given temp_allocator. +but are followed by the necessary `delete` and `free` calls if the allocator supports them. +This is helpful when the CBOR size is so big that you don't want to collect all the temporary +allocations until the end. +*/ +encode_into :: proc { + encode_into_bytes, + encode_into_builder, + encode_into_writer, + encode_into_encoder, +} +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, 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, 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, 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, loc := #caller_location) -> Encode_Error { + e := e + + if e.temp_allocator.procedure == nil { + e.temp_allocator = context.temp_allocator + } + + if .Self_Described_CBOR in e.flags { + _encode_u64(e, TAG_SELF_DESCRIBED_CBOR, .Tag) or_return + e.flags -= { .Self_Described_CBOR } + } + + switch v_spec in v { + case u8: return _encode_u8(e.writer, v_spec, .Unsigned) + case u16: return _encode_u16(e, v_spec, .Unsigned) + case u32: return _encode_u32(e, v_spec, .Unsigned) + case u64: return _encode_u64(e, v_spec, .Unsigned) + case Negative_U8: return _encode_u8(e.writer, u8(v_spec), .Negative) + case Negative_U16: return _encode_u16(e, u16(v_spec), .Negative) + case Negative_U32: return _encode_u32(e, u32(v_spec), .Negative) + case Negative_U64: return _encode_u64(e, u64(v_spec), .Negative) + case ^Bytes: return _encode_bytes(e, v_spec^) + case ^Text: return _encode_text(e, v_spec^) + case ^Array: return _encode_array(e, v_spec^) + case ^Map: return _encode_map(e, v_spec^) + case ^Tag: return _encode_tag(e, v_spec^) + case Simple: return _encode_simple(e.writer, v_spec) + case f16: return _encode_f16(e.writer, v_spec) + case f32: return _encode_f32(e, v_spec) + case f64: return _encode_f64(e, v_spec) + case bool: return _encode_bool(e.writer, v_spec) + case Nil: return _encode_nil(e.writer) + case Undefined: return _encode_undefined(e.writer) + case: return nil + } +} + +_decode_header :: proc(r: io.Reader) -> (hdr: Header, err: io.Error) { + hdr = Header(_decode_u8(r) or_return) + return +} + +_header_split :: proc(hdr: Header) -> (Major, Add) { + return Major(u8(hdr) >> 5), Add(u8(hdr) & 0x1f) +} + +_decode_u8 :: proc(r: io.Reader) -> (v: u8, err: io.Error) { + byte: [1]byte = --- + io.read_full(r, byte[:]) or_return + return byte[0], nil +} + +_encode_uint :: proc { + _encode_u8, + _encode_u16, + _encode_u32, + _encode_u64, +} + +_encode_u8 :: proc(w: io.Writer, v: u8, major: Major = .Unsigned) -> (err: io.Error) { + header := u8(major) << 5 + if v < u8(Add.One_Byte) { + header |= v + _, err = io.write_full(w, {header}) + return + } + + header |= u8(Add.One_Byte) + _, err = io.write_full(w, {header, v}) + return +} + +_decode_tiny_u8 :: proc(additional: Add) -> (u8, Decode_Data_Error) { + if additional < .One_Byte { + return u8(additional), nil + } + + return 0, .Bad_Argument +} + +_decode_u16 :: proc(r: io.Reader) -> (v: u16, err: io.Error) { + bytes: [2]byte = --- + io.read_full(r, bytes[:]) or_return + return endian.unchecked_get_u16be(bytes[:]), nil +} + +_encode_u16 :: proc(e: Encoder, v: u16, major: Major = .Unsigned) -> Encode_Error { + if .Deterministic_Int_Size in e.flags { + return _encode_deterministic_uint(e.writer, v, major) + } + return _encode_u16_exact(e.writer, v, major) +} + +_encode_u16_exact :: proc(w: io.Writer, v: u16, major: Major = .Unsigned) -> (err: io.Error) { + bytes: [3]byte = --- + bytes[0] = (u8(major) << 5) | u8(Add.Two_Bytes) + endian.unchecked_put_u16be(bytes[1:], v) + _, err = io.write_full(w, bytes[:]) + return +} + +_decode_u32 :: proc(r: io.Reader) -> (v: u32, err: io.Error) { + bytes: [4]byte = --- + io.read_full(r, bytes[:]) or_return + return endian.unchecked_get_u32be(bytes[:]), nil +} + +_encode_u32 :: proc(e: Encoder, v: u32, major: Major = .Unsigned) -> Encode_Error { + if .Deterministic_Int_Size in e.flags { + return _encode_deterministic_uint(e.writer, v, major) + } + return _encode_u32_exact(e.writer, v, major) +} + +_encode_u32_exact :: proc(w: io.Writer, v: u32, major: Major = .Unsigned) -> (err: io.Error) { + bytes: [5]byte = --- + bytes[0] = (u8(major) << 5) | u8(Add.Four_Bytes) + endian.unchecked_put_u32be(bytes[1:], v) + _, err = io.write_full(w, bytes[:]) + return +} + +_decode_u64 :: proc(r: io.Reader) -> (v: u64, err: io.Error) { + bytes: [8]byte = --- + io.read_full(r, bytes[:]) or_return + return endian.unchecked_get_u64be(bytes[:]), nil +} + +_encode_u64 :: proc(e: Encoder, v: u64, major: Major = .Unsigned) -> Encode_Error { + if .Deterministic_Int_Size in e.flags { + return _encode_deterministic_uint(e.writer, v, major) + } + return _encode_u64_exact(e.writer, v, major) +} + +_encode_u64_exact :: proc(w: io.Writer, v: u64, major: Major = .Unsigned) -> (err: io.Error) { + bytes: [9]byte = --- + bytes[0] = (u8(major) << 5) | u8(Add.Eight_Bytes) + endian.unchecked_put_u64be(bytes[1:], v) + _, err = io.write_full(w, bytes[:]) + return +} + +_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, allocator, loc) or_return + return +} + +_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, allocator, loc) or_return + defer if err != nil { strings.builder_destroy(&buf) } + buf_stream := strings.to_stream(&buf) + + if n == -1 { + indefinite_loop: for { + header := _decode_header(d.reader) or_return + maj: Major + maj, add = _header_split(header) + #partial switch maj { + case type: + iter_n, iter_cap := _decode_len_str(d, add) or_return + if iter_n == -1 { + return nil, .Nested_Indefinite_Length + } + reserve(&buf.buf, len(buf.buf) + iter_cap) or_return + io.copy_n(buf_stream, d.reader, i64(iter_n)) or_return + + case .Other: + if add != .Break { return nil, .Bad_Argument } + break indefinite_loop + + case: + return nil, .Bad_Major + } + } + } else { + io.copy_n(buf_stream, d.reader, i64(n)) or_return + } + + v = buf.buf[:] + + // Write zero byte so this can be converted to cstring. + strings.write_byte(&buf, 0) + + if .Shrink_Excess in d.flags { shrink(&buf.buf) } + return +} + +_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[:]) + 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, allocator, loc) or_return + return +} + +_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) +} + +_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, allocator, loc) or_return + return +} + +_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, allocator, loc) or_return + defer if err != nil { + 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, {}, allocator, loc) + if n == -1 && verr == .Break { + break + } else if verr != nil { + err = verr + return + } + + append(&array, val) or_return + } + + if .Shrink_Excess in d.flags { shrink(&array) } + + v = array[:] + return +} + +_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 +} + +_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, allocator, loc) or_return + return +} + +_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, allocator, loc) or_return + defer if err != nil { + for entry in items { + destroy(entry.key) + destroy(entry.value) + } + delete(items, loc) + } + + for i := 0; n == -1 || i < n; i += 1 { + 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, {}, allocator, loc) or_return + + append(&items, Map_Entry{ + key = key, + value = value, + }, loc) or_return + } + + if .Shrink_Excess in d.flags { shrink(&items) } + + v = items[:] + return +} + +_encode_map :: proc(e: Encoder, m: Map) -> (err: Encode_Error) { + assert(len(m) >= 0) + _encode_u64(e, u64(len(m)), .Map) or_return + + if .Deterministic_Map_Sorting not_in e.flags { + for entry in m { + encode(e, entry.key) or_return + encode(e, entry.value) or_return + } + return + } + + // Deterministic_Map_Sorting needs us to sort the entries by the byte contents of the + // encoded key. + // + // This means we have to store and sort them before writing incurring extra (temporary) allocations. + + Map_Entry_With_Key :: struct { + encoded_key: []byte, + entry: Map_Entry, + } + + entries := make([]Map_Entry_With_Key, len(m), e.temp_allocator) or_return + defer delete(entries, e.temp_allocator) + + for &entry, i in entries { + entry.entry = m[i] + + buf := strings.builder_make(e.temp_allocator) or_return + + ke := e + ke.writer = strings.to_stream(&buf) + + encode(ke, entry.entry.key) or_return + entry.encoded_key = buf.buf[:] + } + + // Sort lexicographic on the bytes of the key. + slice.sort_by_cmp(entries, proc(a, b: Map_Entry_With_Key) -> slice.Ordering { + return slice.Ordering(bytes.compare(a.encoded_key, b.encoded_key)) + }) + + for entry in entries { + io.write_full(e.writer, entry.encoded_key) or_return + delete(entry.encoded_key, e.temp_allocator) + + encode(e, entry.entry.value) or_return + } + + return nil +} + +_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, 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, {}, allocator, loc) +} + +_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. + // We can ignore it here. + if num == TAG_SELF_DESCRIBED_CBOR { + return + } + + t := Tag{ + number = num, + value = _decode_from_decoder(d, {}, allocator, loc) or_return, + } + + if nested, ok := t.value.(^Tag); ok { + destroy(nested) + return nil, .Nested_Tag + } + + return t, nil +} + +_decode_uint_as_u64 :: proc(r: io.Reader, add: Add) -> (nr: u64, err: Decode_Error) { + #partial switch add { + case .One_Byte: return u64(_decode_u8(r) or_return), nil + case .Two_Bytes: return u64(_decode_u16(r) or_return), nil + case .Four_Bytes: return u64(_decode_u32(r) or_return), nil + case .Eight_Bytes: return u64(_decode_u64(r) or_return), nil + case: return u64(_decode_tiny_u8(add) or_return), nil + } +} + +_encode_tag :: proc(e: Encoder, val: Tag) -> Encode_Error { + _encode_u64(e, val.number, .Tag) or_return + return encode(e, val.value) +} + +_decode_simple :: proc(r: io.Reader) -> (v: Simple, err: io.Error) { + buf: [1]byte = --- + io.read_full(r, buf[:]) or_return + return Simple(buf[0]), nil +} + +_encode_simple :: proc(w: io.Writer, v: Simple) -> (err: Encode_Error) { + header := u8(Major.Other) << 5 + + if v < Simple(Add.False) { + header |= u8(v) + _, err = io.write_full(w, {header}) + return + } else if v <= Simple(Add.Break) { + return .Invalid_Simple + } + + header |= u8(Add.One_Byte) + _, err = io.write_full(w, {header, u8(v)}) + return +} + +_decode_tiny_simple :: proc(add: Add) -> (Simple, Decode_Data_Error) { + if add < Add.False { + return Simple(add), nil + } + + return 0, .Bad_Argument +} + +_decode_f16 :: proc(r: io.Reader) -> (v: f16, err: io.Error) { + bytes: [2]byte = --- + io.read_full(r, bytes[:]) or_return + n := endian.unchecked_get_u16be(bytes[:]) + return transmute(f16)n, nil +} + +_encode_f16 :: proc(w: io.Writer, v: f16) -> (err: io.Error) { + bytes: [3]byte = --- + bytes[0] = u8(Header.F16) + endian.unchecked_put_u16be(bytes[1:], transmute(u16)v) + _, err = io.write_full(w, bytes[:]) + return +} + +_decode_f32 :: proc(r: io.Reader) -> (v: f32, err: io.Error) { + bytes: [4]byte = --- + io.read_full(r, bytes[:]) or_return + n := endian.unchecked_get_u32be(bytes[:]) + return transmute(f32)n, nil +} + +_encode_f32 :: proc(e: Encoder, v: f32) -> io.Error { + if .Deterministic_Float_Size in e.flags { + return _encode_deterministic_float(e.writer, v) + } + return _encode_f32_exact(e.writer, v) +} + +_encode_f32_exact :: proc(w: io.Writer, v: f32) -> (err: io.Error) { + bytes: [5]byte = --- + bytes[0] = u8(Header.F32) + endian.unchecked_put_u32be(bytes[1:], transmute(u32)v) + _, err = io.write_full(w, bytes[:]) + return +} + +_decode_f64 :: proc(r: io.Reader) -> (v: f64, err: io.Error) { + bytes: [8]byte = --- + io.read_full(r, bytes[:]) or_return + n := endian.unchecked_get_u64be(bytes[:]) + return transmute(f64)n, nil +} + +_encode_f64 :: proc(e: Encoder, v: f64) -> io.Error { + if .Deterministic_Float_Size in e.flags { + return _encode_deterministic_float(e.writer, v) + } + return _encode_f64_exact(e.writer, v) +} + +_encode_f64_exact :: proc(w: io.Writer, v: f64) -> (err: io.Error) { + bytes: [9]byte = --- + bytes[0] = u8(Header.F64) + endian.unchecked_put_u64be(bytes[1:], transmute(u64)v) + _, err = io.write_full(w, bytes[:]) + return +} + +_encode_bool :: proc(w: io.Writer, v: bool) -> (err: io.Error) { + switch v { + case true: _, err = io.write_full(w, {u8(Header.True )}); return + case false: _, err = io.write_full(w, {u8(Header.False)}); return + case: unreachable() + } +} + +_encode_undefined :: proc(w: io.Writer) -> io.Error { + _, err := io.write_full(w, {u8(Header.Undefined)}) + return err +} + +_encode_nil :: proc(w: io.Writer) -> io.Error { + _, err := io.write_full(w, {u8(Header.Nil)}) + return err +} + +// Streaming + +encode_stream_begin :: proc(w: io.Writer, major: Major) -> (err: io.Error) { + assert(major >= Major(.Bytes) && major <= Major(.Map), "illegal stream type") + + 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}) + return err +} + +encode_stream_bytes :: _encode_bytes +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) +} + +// For `Bytes` and `Text` strings: Decodes the number of items the header says follows. +// If the number is not specified -1 is returned and streaming should be initiated. +// A suitable starting capacity is also returned for a buffer that is allocated up the stack. +_decode_len_str :: proc(d: Decoder, add: Add) -> (n: int, scap: int, err: Decode_Error) { + if add == .Length_Unknown { + if .Disallow_Streaming in d.flags { + return -1, -1, .Disallowed_Streaming + } + return -1, INITIAL_STREAMED_BYTES_CAPACITY, nil + } + + _n := _decode_uint_as_u64(d.reader, add) or_return + if _n > u64(max(int)) { return -1, -1, .Length_Too_Big } + n = int(_n) + + scap = n + 1 // Space for zero byte. + if .Trusted_Input not_in d.flags { + scap = min(d.max_pre_alloc, scap) + } + + return +} + +// For `Array` and `Map` types: Decodes the number of items the header says follows. +// If the number is not specified -1 is returned and streaming should be initiated. +// A suitable starting capacity is also returned for a buffer that is allocated up the stack. +_decode_len_container :: proc(d: Decoder, add: Add) -> (n: int, scap: int, err: Decode_Error) { + if add == .Length_Unknown { + if .Disallow_Streaming in d.flags { + return -1, -1, .Disallowed_Streaming + } + return -1, INITIAL_STREAMED_CONTAINER_CAPACITY, nil + } + + _n := _decode_uint_as_u64(d.reader, add) or_return + if _n > u64(max(int)) { return -1, -1, .Length_Too_Big } + n = int(_n) + + scap = n + if .Trusted_Input not_in d.flags { + // NOTE: if this is a map it will be twice this. + scap = min(d.max_pre_alloc / size_of(Value), scap) + } + + return +} + +// Deterministic encoding is (among other things) encoding all values into their smallest +// possible representation. +// See section 4 of RFC 8949. + +_encode_deterministic_uint :: proc { + _encode_u8, + _encode_deterministic_u16, + _encode_deterministic_u32, + _encode_deterministic_u64, + _encode_deterministic_u128, +} + +_encode_deterministic_u16 :: proc(w: io.Writer, v: u16, major: Major = .Unsigned) -> Encode_Error { + switch { + case v <= u16(max(u8)): return _encode_u8(w, u8(v), major) + case: return _encode_u16_exact(w, v, major) + } +} + +_encode_deterministic_u32 :: proc(w: io.Writer, v: u32, major: Major = .Unsigned) -> Encode_Error { + switch { + case v <= u32(max(u8)): return _encode_u8(w, u8(v), major) + case v <= u32(max(u16)): return _encode_u16_exact(w, u16(v), major) + case: return _encode_u32_exact(w, u32(v), major) + } +} + +_encode_deterministic_u64 :: proc(w: io.Writer, v: u64, major: Major = .Unsigned) -> Encode_Error { + switch { + case v <= u64(max(u8)): return _encode_u8(w, u8(v), major) + case v <= u64(max(u16)): return _encode_u16_exact(w, u16(v), major) + case v <= u64(max(u32)): return _encode_u32_exact(w, u32(v), major) + case: return _encode_u64_exact(w, u64(v), major) + } +} + +_encode_deterministic_u128 :: proc(w: io.Writer, v: u128, major: Major = .Unsigned) -> Encode_Error { + switch { + case v <= u128(max(u8)): return _encode_u8(w, u8(v), major) + case v <= u128(max(u16)): return _encode_u16_exact(w, u16(v), major) + case v <= u128(max(u32)): return _encode_u32_exact(w, u32(v), major) + case v <= u128(max(u64)): return _encode_u64_exact(w, u64(v), major) + case: return .Int_Too_Big + } +} + +_encode_deterministic_negative :: #force_inline proc(w: io.Writer, v: $T) -> Encode_Error + where T == Negative_U8 || T == Negative_U16 || T == Negative_U32 || T == Negative_U64 { + return _encode_deterministic_uint(w, v, .Negative) +} + +// A Deterministic float is a float in the smallest type that stays the same after down casting. +_encode_deterministic_float :: proc { + _encode_f16, + _encode_deterministic_f32, + _encode_deterministic_f64, +} + +_encode_deterministic_f32 :: proc(w: io.Writer, v: f32) -> io.Error { + if (f32(f16(v)) == v) { + return _encode_f16(w, f16(v)) + } + + return _encode_f32_exact(w, v) +} + +_encode_deterministic_f64 :: proc(w: io.Writer, v: f64) -> io.Error { + if (f64(f16(v)) == v) { + return _encode_f16(w, f16(v)) + } + + if (f64(f32(v)) == v) { + return _encode_f32_exact(w, f32(v)) + } + + 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 new file mode 100644 index 000000000..b3fa36130 --- /dev/null +++ b/core/encoding/cbor/doc.odin @@ -0,0 +1,169 @@ +/* +Package cbor encodes, decodes, marshals and unmarshals types from/into RCF 8949 compatible CBOR binary. +Also provided are conversion to and from JSON and the CBOR diagnostic format. + +**Allocations:** + +In general, when in the following table it says allocations are done on the `temp_allocator`, these allocations +are still attempted to be deallocated. +This allows you to use an allocator with freeing implemented as the `temp_allocator` which is handy with big CBOR. + +- *Encoding*: If the `.Deterministic_Map_Sorting` flag is set on the encoder, this allocates on the given `temp_allocator` + some space for the keys of maps in order to sort them and then write them. + Other than that there are no allocations (only for the final bytes if you use `cbor.encode_into_bytes`. + +- *Decoding*: Allocates everything on the given allocator and input given can be deleted after decoding. + *No* temporary allocations are done. + +- *Marshal*: Same allocation strategy as encoding. + +- *Unmarshal*: Allocates everything on the given allocator and input given can be deleted after unmarshalling. + Some temporary allocations are done on the given `temp_allocator`. + +**Determinism:** + +CBOR defines a deterministic en/decoder, which among other things uses the smallest type possible for integers and floats, +and sorts map keys by their (encoded) lexical bytewise order. + +You can enable this behaviour using a combination of flags, also available as the `cbor.ENCODE_FULLY_DETERMINISTIC` constant. +If you just want the small size that comes with this, but not the map sorting (which has a performance cost) you can use the +`cbor.ENCODE_SMALL` constant for the flags. + +A deterministic float is a float in the smallest type (f16, f32, f64) that hasn't changed after conversion. +A deterministic integer is an integer in the smallest representation (u8, u16, u32, u64) it fits in. + +**Untrusted Input:** + +By default input is treated as untrusted, this means the sizes that are encoded in the CBOR are not blindly trusted. +If you were to trust these sizes, and allocate space for them an attacker would be able to cause massive allocations with small payloads. + +The decoder has a `max_pre_alloc` field that specifies the maximum amount of bytes (roughly) to pre allocate, a KiB by default. + +This does mean reallocations are more common though, you can, if you know the input is trusted, add the `.Trusted_Input` flag to the decoder. + +**Tags:** + +CBOR describes tags that you can wrap values with to assign a number to describe what type of data will follow. + +More information and a list of default tags can be found here: [[RFC 8949 Section 3.4;https://www.rfc-editor.org/rfc/rfc8949.html#name-tagging-of-items]]. + +A list of registered extension types can be found here: [[IANA CBOR assignments;https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml]]. + +Tags can either be assigned to a distinct Odin type (used by default), +or be used with struct tags (`cbor_tag:"base64"`, or `cbor_tag:"1"` for example). + +By default, the following tags are supported/provided by this implementation: + +- *1/epoch*: Assign this tag to `time.Time` or integer fields to use the defined seconds since epoch format. + +- *24/cbor*: Assign this tag to string or byte fields to store encoded CBOR (not decoding it). + +- *34/base64*: Assign this tag to string or byte fields to store and decode the contents in base64. + +- *2 & 3*: Used automatically by the implementation to encode and decode big numbers into/from `core:math/big`. + +- *55799*: Self described CBOR, used when `.Self_Described_CBOR` flag is used to wrap the entire binary. + This shows other implementations that we are dealing with CBOR by just looking at the first byte of input. + +- *1010*: An extension tag that defines a string type followed by its value, this is used by this implementation to support Odin's unions. + +Users can provide their own tag implementations using the `cbor.tag_register_type(...)` to register a tag for a distinct Odin type +used automatically when it is encountered during marshal and unmarshal. +Or with `cbor.tag_register_number(...)` to register a tag number along with an identifier for convenience that can be used with struct tags, +e.g. `cbor_tag:"69"` or `cbor_tag:"my_tag"`. + +You can look at the default tags provided for pointers on how these implementations work. + +Example: + package main + + import "base:intrinsics" + + import "core:encoding/cbor" + import "core:fmt" + import "core:reflect" + import "core:time" + + Possibilities :: union { + string, + int, + } + + Data :: struct { + str: string, + neg: cbor.Negative_U16, // Store a CBOR value directly. + now: time.Time `cbor_tag:"epoch"`, // Wrapped in the epoch tag. + 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{ + str = "Hello, World!", + neg = 300, + now = now, + 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) + 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)) + 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) + fmt.assertf(eerr == nil, "to diagnostic error: %v", eerr) + defer delete(diagnosis) + + fmt.println(diagnosis) + } + +Output: + { + "my_raw": 200(h'00001000200030000000000000000000'), + "my_union": 1010([ + "int", + 3 + ]), + "neg": -301, + "now": 1(1701117968), + "renamed :)": 123123.12500000, + "str": "Hello, World!" + } +*/ +package encoding_cbor + diff --git a/core/encoding/cbor/marshal.odin b/core/encoding/cbor/marshal.odin new file mode 100644 index 000000000..aca71deb2 --- /dev/null +++ b/core/encoding/cbor/marshal.odin @@ -0,0 +1,618 @@ +package encoding_cbor + +import "base:intrinsics" +import "base:runtime" + +import "core:bytes" +import "core:io" +import "core:mem" +import "core:reflect" +import "core:slice" +import "core:strconv" +import "core:strings" +import "core:unicode/utf8" + +/* +Marshal a value into binary CBOR. + +Flags can be used to control the output (mainly determinism, which coincidently affects size). + +The default flags `ENCODE_SMALL` (`.Deterministic_Int_Size`, `.Deterministic_Float_Size`) will try +to put ints and floats into their smallest possible byte size without losing equality. + +Adding the `.Self_Described_CBOR` flag will wrap the value in a tag that lets generic decoders know +the contents are CBOR from just reading the first byte. + +Adding the `.Deterministic_Map_Sorting` flag will sort the encoded maps by the byte content of the +encoded key. This flag has a cost on performance and memory efficiency because all keys in a map +have to be precomputed, sorted and only then written to the output. + +Empty flags will do nothing extra to the value. + +The allocations for the `.Deterministic_Map_Sorting` flag are done using the given `temp_allocator`. +but are followed by the necessary `delete` and `free` calls if the allocator supports them. +This is helpful when the CBOR size is so big that you don't want to collect all the temporary +allocations until the end. +*/ +marshal_into :: proc { + marshal_into_bytes, + marshal_into_builder, + marshal_into_writer, + marshal_into_encoder, +} + +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, 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 + } + + defer if err != nil { strings.builder_destroy(&b) } + + if err = marshal_into_builder(&b, v, flags, temp_allocator); err != nil { + return + } + + return b.buf[:], nil +} + +// Marshals the given value into a CBOR byte stream written to the given builder. +// See docs on the `marshal_into` proc group for more info. +marshal_into_builder :: proc(b: ^strings.Builder, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Marshal_Error { + return marshal_into_writer(strings.to_writer(b), v, flags, temp_allocator) +} + +// Marshals the given value into a CBOR byte stream written to the given writer. +// See docs on the `marshal_into` proc group for more info. +marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Marshal_Error { + encoder := Encoder{flags, w, temp_allocator} + return marshal_into_encoder(encoder, v) +} + +// Marshals the given value into a CBOR byte stream written to the given encoder. +// See docs on the `marshal_into` proc group for more info. +marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { + e := e + + if e.temp_allocator.procedure == nil { + e.temp_allocator = context.temp_allocator + } + + if .Self_Described_CBOR in e.flags { + err_conv(_encode_u64(e, TAG_SELF_DESCRIBED_CBOR, .Tag)) or_return + e.flags -= { .Self_Described_CBOR } + } + + if v == nil { + return _encode_nil(e.writer) + } + + // Check if type has a tag implementation to use. + if impl, ok := _tag_implementations_type[v.id]; ok { + return impl->marshal(e, v) + } + + 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, runtime.Type_Info_Enum, runtime.Type_Info_Bit_Field: + unreachable() + + case runtime.Type_Info_Pointer: + switch vv in v { + case Undefined: return _encode_undefined(e.writer) + case Nil: return _encode_nil(e.writer) + } + + case runtime.Type_Info_Integer: + switch vv in v { + case Simple: return err_conv(_encode_simple(e.writer, vv)) + case Negative_U8: return _encode_u8(e.writer, u8(vv), .Negative) + case Negative_U16: return err_conv(_encode_u16(e, u16(vv), .Negative)) + case Negative_U32: return err_conv(_encode_u32(e, u32(vv), .Negative)) + case Negative_U64: return err_conv(_encode_u64(e, u64(vv), .Negative)) + } + + switch i in a { + case i8: return _encode_uint(e.writer, _int_to_uint(i)) + case i16: return err_conv(_encode_uint(e, _int_to_uint(i))) + case i32: return err_conv(_encode_uint(e, _int_to_uint(i))) + case i64: return err_conv(_encode_uint(e, _int_to_uint(i))) + case i128: return err_conv(_encode_uint(e, _int_to_uint(i128(i)) or_return)) + case int: return err_conv(_encode_uint(e, _int_to_uint(i64(i)))) + + case u8: return _encode_uint(e.writer, i) + case u16: return err_conv(_encode_uint(e, i)) + case u32: return err_conv(_encode_uint(e, i)) + case u64: return err_conv(_encode_uint(e, i)) + case u128: return err_conv(_encode_uint(e, _u128_to_u64(u128(i)) or_return)) + case uint: return err_conv(_encode_uint(e, u64(i))) + case uintptr: return err_conv(_encode_uint(e, u64(i))) + + case i16le: return err_conv(_encode_uint(e, _int_to_uint(i16(i)))) + case i32le: return err_conv(_encode_uint(e, _int_to_uint(i32(i)))) + case i64le: return err_conv(_encode_uint(e, _int_to_uint(i64(i)))) + case i128le: return err_conv(_encode_uint(e, _int_to_uint(i128(i)) or_return)) + + case u16le: return err_conv(_encode_uint(e, u16(i))) + case u32le: return err_conv(_encode_uint(e, u32(i))) + case u64le: return err_conv(_encode_uint(e, u64(i))) + case u128le: return err_conv(_encode_uint(e, _u128_to_u64(u128(i)) or_return)) + + case i16be: return err_conv(_encode_uint(e, _int_to_uint(i16(i)))) + case i32be: return err_conv(_encode_uint(e, _int_to_uint(i32(i)))) + case i64be: return err_conv(_encode_uint(e, _int_to_uint(i64(i)))) + case i128be: return err_conv(_encode_uint(e, _int_to_uint(i128(i)) or_return)) + + case u16be: return err_conv(_encode_uint(e, u16(i))) + case u32be: return err_conv(_encode_uint(e, u32(i))) + case u64be: return err_conv(_encode_uint(e, u64(i))) + case u128be: return err_conv(_encode_uint(e, _u128_to_u64(u128(i)) or_return)) + } + + case runtime.Type_Info_Rune: + buf, w := utf8.encode_rune(a.(rune)) + return err_conv(_encode_text(e, string(buf[:w]))) + + case runtime.Type_Info_Float: + switch f in a { + case f16: return _encode_f16(e.writer, f) + case f32: return _encode_f32(e, f) + case f64: return _encode_f64(e, f) + + case f16le: return _encode_f16(e.writer, f16(f)) + case f32le: return _encode_f32(e, f32(f)) + case f64le: return _encode_f64(e, f64(f)) + + case f16be: return _encode_f16(e.writer, f16(f)) + case f32be: return _encode_f32(e, f32(f)) + case f64be: return _encode_f64(e, f64(f)) + } + + case runtime.Type_Info_Complex: + switch z in a { + case complex32: + arr: [2]Value = {real(z), imag(z)} + return err_conv(_encode_array(e, arr[:])) + case complex64: + arr: [2]Value = {real(z), imag(z)} + return err_conv(_encode_array(e, arr[:])) + case complex128: + arr: [2]Value = {real(z), imag(z)} + return err_conv(_encode_array(e, arr[:])) + } + + case runtime.Type_Info_Quaternion: + switch q in a { + case quaternion64: + arr: [4]Value = {imag(q), jmag(q), kmag(q), real(q)} + return err_conv(_encode_array(e, arr[:])) + case quaternion128: + arr: [4]Value = {imag(q), jmag(q), kmag(q), real(q)} + return err_conv(_encode_array(e, arr[:])) + case quaternion256: + arr: [4]Value = {imag(q), jmag(q), kmag(q), real(q)} + return err_conv(_encode_array(e, arr[:])) + } + + case runtime.Type_Info_String: + switch s in a { + case string: return err_conv(_encode_text(e, s)) + case cstring: return err_conv(_encode_text(e, string(s))) + } + + case runtime.Type_Info_Boolean: + switch b in a { + case bool: return _encode_bool(e.writer, b) + case b8: return _encode_bool(e.writer, bool(b)) + case b16: return _encode_bool(e.writer, bool(b)) + case b32: return _encode_bool(e.writer, bool(b)) + case b64: return _encode_bool(e.writer, bool(b)) + } + + case runtime.Type_Info_Array: + if info.elem.id == byte { + raw := ([^]byte)(v.data) + return err_conv(_encode_bytes(e, raw[:info.count])) + } + + 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..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.. (res: [10]byte) { + e := e + builder := strings.builder_from_slice(res[:]) + e.writer = strings.to_stream(&builder) + + err := _encode_u64(e, u64(len(str)), .Text) + assert(err == nil) + res[9] = u8(len(builder.buf)) + assert(res[9] < 10) + return + } + + Encoded_Entry_Fast :: struct($T: typeid) { + pre_key: [10]byte, + key: T, + val_idx: uintptr, + } + + Encoded_Entry :: struct { + key: ^[dynamic]byte, + val_idx: uintptr, + } + + switch info.key.id { + case string: + entries := make([dynamic]Encoded_Entry_Fast(^[]byte), 0, map_cap, e.temp_allocator) or_return + defer delete(entries) + + for bucket_index in 0.. slice.Ordering { + a, b := a, b + pre_cmp := slice.Ordering(bytes.compare(a.pre_key[:a.pre_key[9]], b.pre_key[:b.pre_key[9]])) + if pre_cmp != .Equal { + return pre_cmp + } + + return slice.Ordering(bytes.compare(a.key^, b.key^)) + }) + + for &entry in entries { + io.write_full(e.writer, entry.pre_key[:entry.pre_key[9]]) or_return + io.write_full(e.writer, entry.key^) or_return + + value := rawptr(runtime.map_cell_index_dynamic(vs, info.map_info.vs, entry.val_idx)) + marshal_into(e, any{ value, info.value.id }) or_return + } + return + + case cstring: + entries := make([dynamic]Encoded_Entry_Fast(^cstring), 0, map_cap, e.temp_allocator) or_return + defer delete(entries) + + for bucket_index in 0.. slice.Ordering { + a, b := a, b + pre_cmp := slice.Ordering(bytes.compare(a.pre_key[:a.pre_key[9]], b.pre_key[:b.pre_key[9]])) + if pre_cmp != .Equal { + return pre_cmp + } + + ab := transmute([]byte)string(a.key^) + bb := transmute([]byte)string(b.key^) + return slice.Ordering(bytes.compare(ab, bb)) + }) + + for &entry in entries { + io.write_full(e.writer, entry.pre_key[:entry.pre_key[9]]) or_return + io.write_full(e.writer, transmute([]byte)string(entry.key^)) or_return + + value := rawptr(runtime.map_cell_index_dynamic(vs, info.map_info.vs, entry.val_idx)) + marshal_into(e, any{ value, info.value.id }) or_return + } + return + + case: + entries := make([dynamic]Encoded_Entry, 0, map_cap, e.temp_allocator) or_return + defer delete(entries) + + for bucket_index in 0.. slice.Ordering { + return slice.Ordering(bytes.compare(a.key[:], b.key[:])) + }) + + for entry in entries { + io.write_full(e.writer, entry.key[:]) or_return + delete(entry.key^) + + value := rawptr(runtime.map_cell_index_dynamic(vs, info.map_info.vs, entry.val_idx)) + marshal_into(e, any{ value, info.value.id }) or_return + } + return + } + } + + case runtime.Type_Info_Struct: + switch vv in v { + case Tag: return err_conv(_encode_tag(e, vv)) + } + + field_name :: #force_inline proc(info: runtime.Type_Info_Struct, i: int) -> string { + if cbor_name := string(reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "cbor")); cbor_name != "" { + return cbor_name + } else { + return info.names[i] + } + } + + marshal_entry :: #force_inline proc(e: Encoder, info: runtime.Type_Info_Struct, v: any, i: int) -> Marshal_Error { + id := info.types[i].id + data := rawptr(uintptr(v.data) + info.offsets[i]) + field_any := any{data, id} + + if tag := string(reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "cbor_tag")); tag != "" { + if impl, ok := _tag_implementations_id[tag]; ok { + return impl->marshal(e, field_any) + } + + nr, ok := strconv.parse_u64_of_base(tag, 10) + if !ok { return .Invalid_CBOR_Tag } + + if impl, nok := _tag_implementations_nr[nr]; nok { + return impl->marshal(e, field_any) + } + + err_conv(_encode_u64(e, nr, .Tag)) or_return + } + + return marshal_into(e, field_any) + } + + n: u64; { + for _, i in info.names[:info.field_count] { + if field_name(info, i) != "-" { + n += 1 + } + } + err_conv(_encode_u64(e, n, .Map)) or_return + } + + if .Deterministic_Map_Sorting in e.flags { + Name :: struct { + name: []byte, + field: int, + } + entries := make([dynamic]Name, 0, n, e.temp_allocator) or_return + defer delete(entries) + + for _, i in info.names[:info.field_count] { + fname := field_name(info, i) + if fname == "-" { + continue + } + + key_builder := strings.builder_make(e.temp_allocator) or_return + err_conv(_encode_text(Encoder{e.flags, strings.to_stream(&key_builder), e.temp_allocator}, fname)) or_return + append(&entries, Name{key_builder.buf[:], i}) or_return + } + + // Sort lexicographic on the bytes of the key. + slice.sort_by_cmp(entries[:], proc(a, b: Name) -> slice.Ordering { + return slice.Ordering(bytes.compare(a.name, b.name)) + }) + + for entry in entries { + io.write_full(e.writer, entry.name) or_return + marshal_entry(e, info, v, entry.field) or_return + } + } else { + for _, i in info.names[:info.field_count] { + fname := field_name(info, i) + if fname == "-" { + continue + } + + err_conv(_encode_text(e, fname)) or_return + marshal_entry(e, info, v, i) or_return + } + } + return + + case runtime.Type_Info_Union: + switch vv in v { + case Value: return err_conv(encode(e, vv)) + } + + id := reflect.union_variant_typeid(v) + if v.data == nil || id == nil { + return _encode_nil(e.writer) + } + + if len(info.variants) == 1 { + return marshal_into(e, any{v.data, id}) + } + + // Encode a non-nil multi-variant union as the `TAG_OBJECT_TYPE`. + // Which is a tag of an array, where the first element is the textual id/type of the object + // that follows it. + + err_conv(_encode_u16(e, TAG_OBJECT_TYPE, .Tag)) or_return + _encode_u8(e.writer, 2, .Array) or_return + + vti := reflect.union_variant_type_info(v) + #partial switch vt in vti.variant { + case reflect.Type_Info_Named: + err_conv(_encode_text(e, vt.name)) or_return + case: + builder := strings.builder_make(e.temp_allocator) or_return + defer strings.builder_destroy(&builder) + reflect.write_type(&builder, vti) + err_conv(_encode_text(e, strings.to_string(builder))) or_return + } + + return marshal_into(e, any{v.data, vti.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) + switch ti.size * 8 { + case 0: + return _encode_u8(e.writer, 0) + case 8: + x := (^u8)(v.data)^ + return _encode_u8(e.writer, x) + case 16: + x := (^u16)(v.data)^ + if do_byte_swap { x = intrinsics.byte_swap(x) } + return err_conv(_encode_u16(e, x)) + case 32: + x := (^u32)(v.data)^ + if do_byte_swap { x = intrinsics.byte_swap(x) } + return err_conv(_encode_u32(e, x)) + case 64: + x := (^u64)(v.data)^ + if do_byte_swap { x = intrinsics.byte_swap(x) } + return err_conv(_encode_u64(e, x)) + case: + panic("unknown bit_size size") + } + } + + return _unsupported(v.id, nil) +} diff --git a/core/encoding/cbor/tags.odin b/core/encoding/cbor/tags.odin new file mode 100644 index 000000000..17420af46 --- /dev/null +++ b/core/encoding/cbor/tags.odin @@ -0,0 +1,380 @@ +package encoding_cbor + +import "base:runtime" + +import "core:encoding/base64" +import "core:io" +import "core:math" +import "core:math/big" +import "core:mem" +import "core:reflect" +import "core:strings" +import "core:time" + +// Tags defined in RFC 7049 that we provide implementations for. + +// UTC time in seconds, unmarshalled into a `core:time` `time.Time` or integer. +// Use the struct tag `cbor_tag:"1"` or `cbor_tag:"epoch"` to have your `time.Time` field en/decoded as epoch time. +TAG_EPOCH_TIME_NR :: 1 +TAG_EPOCH_TIME_ID :: "epoch" + +// Using `core:math/big`, big integers are properly encoded and decoded during marshal and unmarshal. +// These fields use this tag by default, no struct tag required. +TAG_UNSIGNED_BIG_NR :: 2 +// Using `core:math/big`, big integers are properly encoded and decoded during marshal and unmarshal. +// These fields use this tag by default, no struct tag required. +TAG_NEGATIVE_BIG_NR :: 3 + +// TAG_DECIMAL_FRACTION :: 4 // NOTE: We could probably implement this with `math/fixed`. + +// Sometimes it is beneficial to carry an embedded CBOR data item that is not meant to be decoded +// immediately at the time the enclosing data item is being decoded. Tag number 24 (CBOR data item) +// can be used to tag the embedded byte string as a single data item encoded in CBOR format. +// Use the struct tag `cbor_tag:"24"` or `cbor_tag:"cbor"` to keep a non-decoded field (string or bytes) of raw CBOR. +TAG_CBOR_NR :: 24 +TAG_CBOR_ID :: "cbor" + +// The contents of this tag are base64 encoded during marshal and decoded during unmarshal. +// Use the struct tag `cbor_tag:"34"` or `cbor_tag:"base64"` to have your field string or bytes field en/decoded as base64. +TAG_BASE64_NR :: 34 +TAG_BASE64_ID :: "base64" + +// A tag that is used to detect the contents of a binary buffer (like a file) are CBOR. +// This tag would wrap everything else, decoders can then check for this header and see if the +// given content is definitely CBOR. +// Added by the encoder if it has the flag `.Self_Described_CBOR`, decoded by default. +TAG_SELF_DESCRIBED_CBOR :: 55799 + +// A tag that is used to assign a textual type to the object following it. +// The tag's value must be an array of 2 items, where the first is text (describing the following type) +// and the second is any valid CBOR value. +// +// See the registration: https://datatracker.ietf.org/doc/draft-rundgren-cotx/05/ +// +// We use this in Odin to marshal and unmarshal unions. +TAG_OBJECT_TYPE :: 1010 + +// A tag implementation that handles marshals and unmarshals for the tag it is registered on. +Tag_Implementation :: struct { + data: rawptr, + unmarshal: Tag_Unmarshal_Proc, + marshal: Tag_Marshal_Proc, +} + +// Procedure responsible for umarshalling the tag out of the reader into the given `any`. +Tag_Unmarshal_Proc :: #type proc(self: ^Tag_Implementation, d: Decoder, tag_nr: Tag_Number, v: any) -> Unmarshal_Error + +// Procedure responsible for marshalling the tag in the given `any` into the given encoder. +Tag_Marshal_Proc :: #type proc(self: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_Error + +// When encountering a tag in the CBOR being unmarshalled, the implementation is used to unmarshal it. +// When encountering a struct tag like `cbor_tag:"Tag_Number"`, the implementation is used to marshal it. +_tag_implementations_nr: map[Tag_Number]Tag_Implementation + +// Same as the number implementations but friendlier to use as a struct tag. +// Instead of `cbor_tag:"34"` you can use `cbor_tag:"base64"`. +_tag_implementations_id: map[string]Tag_Implementation + +// Tag implementations that are always used by a type, if that type is encountered in marshal it +// will rely on the implementation to marshal it. +// +// This is good for types that don't make sense or can't marshal in its default form. +_tag_implementations_type: map[typeid]Tag_Implementation + +// Register a custom tag implementation to be used when marshalling that type and unmarshalling that tag number. +tag_register_type :: proc(impl: Tag_Implementation, nr: Tag_Number, type: typeid) { + _tag_implementations_nr[nr] = impl + _tag_implementations_type[type] = impl +} + +// Register a custom tag implementation to be used when marshalling that tag number or marshalling +// a field with the struct tag `cbor_tag:"nr"`. +tag_register_number :: proc(impl: Tag_Implementation, nr: Tag_Number, id: string) { + _tag_implementations_nr[nr] = impl + _tag_implementations_id[id] = impl +} + +// Controls initialization of default tag implementations. +INITIALIZE_DEFAULT_TAGS :: #config(CBOR_INITIALIZE_DEFAULT_TAGS, !ODIN_DEFAULT_TO_PANIC_ALLOCATOR && !ODIN_DEFAULT_TO_NIL_ALLOCATOR) + +@(private, init, disabled=!INITIALIZE_DEFAULT_TAGS) +tags_initialize_defaults :: proc() { + tags_register_defaults() +} + +// Registers tags that have implementations provided by this package. +// This is done by default and can be controlled with the `CBOR_INITIALIZE_DEFAULT_TAGS` define. +tags_register_defaults :: proc() { + tag_register_number({nil, tag_time_unmarshal, tag_time_marshal}, TAG_EPOCH_TIME_NR, TAG_EPOCH_TIME_ID) + tag_register_number({nil, tag_base64_unmarshal, tag_base64_marshal}, TAG_BASE64_NR, TAG_BASE64_ID) + tag_register_number({nil, tag_cbor_unmarshal, tag_cbor_marshal}, TAG_CBOR_NR, TAG_CBOR_ID) + + // These following tags are registered at the type level and don't require an opt-in struct tag. + // Encoding these types on its own make no sense or no data is lost to encode it. + + // En/Decoding of `big.Int` fields by default. + tag_register_type({nil, tag_big_unmarshal, tag_big_marshal}, TAG_UNSIGNED_BIG_NR, big.Int) + tag_register_type({nil, tag_big_unmarshal, tag_big_marshal}, TAG_NEGATIVE_BIG_NR, big.Int) +} + +// Tag number 1 contains a numerical value counting the number of seconds from 1970-01-01T00:00Z +// in UTC time to the represented point in civil time. +// +// See RFC 8949 section 3.4.2. +@(private) +tag_time_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, _: Tag_Number, v: any) -> (err: Unmarshal_Error) { + hdr := _decode_header(d.reader) or_return + #partial switch hdr { + case .U8, .U16, .U32, .U64, .Neg_U8, .Neg_U16, .Neg_U32, .Neg_U64: + switch &dst in v { + case time.Time: + i: i64 + _unmarshal_any_ptr(d, &i, hdr) or_return + dst = time.unix(i64(i), 0) + return + case: + return _unmarshal_value(d, v, hdr) + } + + case .F16, .F32, .F64: + switch &dst in v { + case time.Time: + f: f64 + _unmarshal_any_ptr(d, &f, hdr) or_return + whole, fract := math.modf(f) + dst = time.unix(i64(whole), i64(fract * 1e9)) + return + case: + return _unmarshal_value(d, v, hdr) + } + + case: + maj, add := _header_split(hdr) + if maj == .Other { + i := _decode_tiny_u8(add) or_return + + switch &dst in v { + case time.Time: + dst = time.unix(i64(i), 0) + case: + if _assign_int(v, i) { return } + } + } + + // Only numbers and floats are allowed in this tag. + return .Bad_Tag_Value + } + + return _unsupported(v, hdr) +} + +@(private) +tag_time_marshal :: proc(_: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_Error { + switch vv in v { + case time.Time: + // NOTE: we lose precision here, which is one of the reasons for this tag being opt-in. + i := time.time_to_unix(vv) + + _encode_u8(e.writer, TAG_EPOCH_TIME_NR, .Tag) or_return + return err_conv(_encode_uint(e, _int_to_uint(i))) + case: + unreachable() + } +} + +@(private) +tag_big_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, tnr: Tag_Number, v: any) -> (err: Unmarshal_Error) { + hdr := _decode_header(d.reader) or_return + maj, add := _header_split(hdr) + if maj != .Bytes { + // Only bytes are supported in this tag. + return .Bad_Tag_Value + } + + switch &dst in v { + case big.Int: + bytes := err_conv(_decode_bytes(d, add)) or_return + defer delete(bytes) + + if err := big.int_from_bytes_big(&dst, bytes); err != nil { + return .Bad_Tag_Value + } + + if tnr == TAG_NEGATIVE_BIG_NR { + dst.sign = .Negative + } + + return + } + + return _unsupported(v, hdr) +} + +@(private) +tag_big_marshal :: proc(_: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_Error { + switch &vv in v { + case big.Int: + if !big.int_is_initialized(&vv) { + _encode_u8(e.writer, TAG_UNSIGNED_BIG_NR, .Tag) or_return + return _encode_u8(e.writer, 0, .Bytes) + } + + // NOTE: using the panic_allocator because all procedures should only allocate if the Int + // is uninitialized (which we checked). + + is_neg, err := big.is_negative(&vv, mem.panic_allocator()) + assert(err == nil, "should only error if not initialized, which has been checked") + + tnr: u8 = TAG_NEGATIVE_BIG_NR if is_neg else TAG_UNSIGNED_BIG_NR + _encode_u8(e.writer, tnr, .Tag) or_return + + size_in_bytes, berr := big.int_to_bytes_size(&vv, false, mem.panic_allocator()) + assert(berr == nil, "should only error if not initialized, which has been checked") + assert(size_in_bytes >= 0) + + err_conv(_encode_u64(e, u64(size_in_bytes), .Bytes)) or_return + + for offset := (size_in_bytes*8)-8; offset >= 0; offset -= 8 { + bits, derr := big.int_bitfield_extract(&vv, offset, 8, mem.panic_allocator()) + assert(derr == nil, "should only error if not initialized or invalid argument (offset and count), which won't happen") + + io.write_full(e.writer, {u8(bits & 255)}) or_return + } + return nil + + case: unreachable() + } +} + +@(private) +tag_cbor_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, _: Tag_Number, v: any) -> Unmarshal_Error { + hdr := _decode_header(d.reader) or_return + major, add := _header_split(hdr) + #partial switch major { + case .Bytes: + ti := reflect.type_info_base(type_info_of(v.id)) + return _unmarshal_bytes(d, v, ti, hdr, add) + + case: return .Bad_Tag_Value + } +} + +@(private) +tag_cbor_marshal :: proc(_: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_Error { + _encode_u8(e.writer, TAG_CBOR_NR, .Tag) or_return + ti := runtime.type_info_base(type_info_of(v.id)) + #partial switch t in ti.variant { + case runtime.Type_Info_String: + return marshal_into(e, v) + case runtime.Type_Info_Array: + elem_base := reflect.type_info_base(t.elem) + if elem_base.id != byte { return .Bad_Tag_Value } + return marshal_into(e, v) + case runtime.Type_Info_Slice: + elem_base := reflect.type_info_base(t.elem) + if elem_base.id != byte { return .Bad_Tag_Value } + return marshal_into(e, v) + case runtime.Type_Info_Dynamic_Array: + elem_base := reflect.type_info_base(t.elem) + if elem_base.id != byte { return .Bad_Tag_Value } + return marshal_into(e, v) + case: + return .Bad_Tag_Value + } +} + +@(private) +tag_base64_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, _: Tag_Number, v: any) -> (err: Unmarshal_Error) { + hdr := _decode_header(d.reader) or_return + major, add := _header_split(hdr) + ti := reflect.type_info_base(type_info_of(v.id)) + + if major != .Text && major != .Bytes { + return .Bad_Tag_Value + } + + bytes := string(err_conv(_decode_bytes(d, add, allocator=context.temp_allocator)) or_return) + defer delete(bytes, context.temp_allocator) + + #partial switch t in ti.variant { + case reflect.Type_Info_String: + + if t.is_cstring { + length := base64.decoded_len(bytes) + builder := strings.builder_make(0, length+1) + base64.decode_into(strings.to_stream(&builder), bytes) or_return + + raw := (^cstring)(v.data) + raw^ = cstring(raw_data(builder.buf)) + } else { + raw := (^string)(v.data) + raw^ = string(base64.decode(bytes) or_return) + } + + return + + case reflect.Type_Info_Slice: + elem_base := reflect.type_info_base(t.elem) + + if elem_base.id != byte { return _unsupported(v, hdr) } + + raw := (^[]byte)(v.data) + raw^ = base64.decode(bytes) or_return + return + + case reflect.Type_Info_Dynamic_Array: + elem_base := reflect.type_info_base(t.elem) + + if elem_base.id != byte { return _unsupported(v, hdr) } + + decoded := base64.decode(bytes) or_return + + raw := (^mem.Raw_Dynamic_Array)(v.data) + raw.data = raw_data(decoded) + raw.len = len(decoded) + raw.cap = len(decoded) + raw.allocator = context.allocator + return + + case reflect.Type_Info_Array: + elem_base := reflect.type_info_base(t.elem) + + if elem_base.id != byte { return _unsupported(v, hdr) } + + if base64.decoded_len(bytes) > t.count { return _unsupported(v, hdr) } + + slice := ([^]byte)(v.data)[:len(bytes)] + copy(slice, base64.decode(bytes) or_return) + return + } + + return _unsupported(v, hdr) +} + +@(private) +tag_base64_marshal :: proc(_: ^Tag_Implementation, e: Encoder, v: any) -> Marshal_Error { + _encode_u8(e.writer, TAG_BASE64_NR, .Tag) or_return + + ti := runtime.type_info_base(type_info_of(v.id)) + a := any{v.data, ti.id} + + bytes: []byte + switch val in a { + case string: bytes = transmute([]byte)val + case cstring: bytes = transmute([]byte)string(val) + case []byte: bytes = val + case [dynamic]byte: bytes = val[:] + case: + #partial switch t in ti.variant { + case runtime.Type_Info_Array: + if t.elem.id != byte { return .Bad_Tag_Value } + bytes = ([^]byte)(v.data)[:t.count] + case: + return .Bad_Tag_Value + } + } + + out_len := base64.encoded_len(bytes) + err_conv(_encode_u64(e, u64(out_len), .Text)) or_return + return base64.encode_into(e.writer, bytes) +} diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin new file mode 100644 index 000000000..c39255d9d --- /dev/null +++ b/core/encoding/cbor/unmarshal.odin @@ -0,0 +1,926 @@ +package encoding_cbor + +import "base:intrinsics" +import "base:runtime" + +import "core:io" +import "core:mem" +import "core:reflect" +import "core:strings" +import "core:unicode/utf8" + +/* +Unmarshals the given CBOR into the given pointer using reflection. +Types that require allocation are allocated using the given allocator. + +Some temporary allocations are done on the given `temp_allocator`, but, if you want to, +this can be set to a "normal" allocator, because the necessary `delete` and `free` calls are still made. +This is helpful when the CBOR size is so big that you don't want to collect all the temporary allocations until the end. + +Disable streaming/indeterminate lengths with the `.Disallow_Streaming` flag. + +Shrink excess bytes in buffers and containers with the `.Shrink_Excess` flag. + +Mark the input as trusted input with the `.Trusted_Input` flag, this turns off the safety feature +of not pre-allocating more than `max_pre_alloc` bytes before reading into the bytes. You should only +do this when you own both sides of the encoding and are sure there can't be malicious bytes used as +an input. +*/ +unmarshal :: proc { + unmarshal_from_reader, + unmarshal_from_string, +} + +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 } + return +} + +// 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, 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, 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, loc := #caller_location) -> (err: Unmarshal_Error) { + d := d + + 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 } + return + +} + +_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 + + if v == nil || v.id == nil { + return .Invalid_Parameter + } + + v = reflect.any_base(v) + ti := type_info_of(v.id) + if !reflect.is_pointer(ti) || ti.id == rawptr { + return .Non_Pointer_Parameter + } + + 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), allocator, temp_allocator, loc) +} + +_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 + + // If it's a union with only one variant, then treat it as that variant + if u, ok := ti.variant.(reflect.Type_Info_Union); ok && len(u.variants) == 1 { + #partial switch hdr { + case .Nil, .Undefined, nil: // no-op. + case: + variant := u.variants[0] + v.id = variant.id + 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} + assigned := _assign_int(tag, 1) + assert(assigned) + } + } + } + + // Allow generic unmarshal by doing it into a `Value`. + switch &dst in v { + case Value: + dst = err_conv(_decode_from_decoder(d, hdr, allocator, loc)) or_return + return + } + + switch hdr { + case .U8: + decoded := _decode_u8(r) or_return + if !_assign_int(v, decoded) { return _unsupported(v, hdr) } + return + + case .U16: + decoded := _decode_u16(r) or_return + if !_assign_int(v, decoded) { return _unsupported(v, hdr) } + return + + case .U32: + decoded := _decode_u32(r) or_return + if !_assign_int(v, decoded) { return _unsupported(v, hdr) } + return + + case .U64: + decoded := _decode_u64(r) or_return + if !_assign_int(v, decoded) { return _unsupported(v, hdr) } + return + + case .Neg_U8: + decoded := Negative_U8(_decode_u8(r) or_return) + + switch &dst in v { + case Negative_U8: + dst = decoded + return + case Negative_U16: + dst = Negative_U16(decoded) + return + case Negative_U32: + dst = Negative_U32(decoded) + return + case Negative_U64: + dst = Negative_U64(decoded) + return + } + + if reflect.is_unsigned(ti) { return _unsupported(v, hdr) } + + if !_assign_int(v, negative_to_int(decoded)) { return _unsupported(v, hdr) } + return + + case .Neg_U16: + decoded := Negative_U16(_decode_u16(r) or_return) + + switch &dst in v { + case Negative_U16: + dst = decoded + return + case Negative_U32: + dst = Negative_U32(decoded) + return + case Negative_U64: + dst = Negative_U64(decoded) + return + } + + if reflect.is_unsigned(ti) { return _unsupported(v, hdr) } + + if !_assign_int(v, negative_to_int(decoded)) { return _unsupported(v, hdr) } + return + + case .Neg_U32: + decoded := Negative_U32(_decode_u32(r) or_return) + + switch &dst in v { + case Negative_U32: + dst = decoded + return + case Negative_U64: + dst = Negative_U64(decoded) + return + } + + if reflect.is_unsigned(ti) { return _unsupported(v, hdr) } + + if !_assign_int(v, negative_to_int(decoded)) { return _unsupported(v, hdr) } + return + + case .Neg_U64: + decoded := Negative_U64(_decode_u64(r) or_return) + + switch &dst in v { + case Negative_U64: + dst = decoded + return + } + + if reflect.is_unsigned(ti) { return _unsupported(v, hdr) } + + if !_assign_int(v, negative_to_int(decoded)) { return _unsupported(v, hdr) } + return + + case .Simple: + decoded := _decode_simple(r) or_return + + // 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) + } + + case .F16: + decoded := _decode_f16(r) or_return + if !_assign_float(v, decoded) { return _unsupported(v, hdr) } + return + + case .F32: + decoded := _decode_f32(r) or_return + if !_assign_float(v, decoded) { return _unsupported(v, hdr) } + return + + case .F64: + decoded := _decode_f64(r) or_return + if !_assign_float(v, decoded) { return _unsupported(v, hdr) } + return + + case .True: + if !_assign_bool(v, true) { return _unsupported(v, hdr) } + return + + case .False: + if !_assign_bool(v, false) { return _unsupported(v, hdr) } + return + + case .Nil, .Undefined: + mem.zero(v.data, ti.size) + return + + case .Break: + return .Break + } + + maj, add := _header_split(hdr) + switch maj { + case .Unsigned: + decoded := _decode_tiny_u8(add) or_return + if !_assign_int(v, decoded) { return _unsupported(v, hdr, add) } + return + + case .Negative: + decoded := Negative_U8(_decode_tiny_u8(add) or_return) + + switch &dst in v { + case Negative_U8: + dst = decoded + return + } + + if reflect.is_unsigned(ti) { return _unsupported(v, hdr, add) } + + if !_assign_int(v, negative_to_int(decoded)) { return _unsupported(v, hdr, add) } + return + + case .Other: + decoded := _decode_tiny_simple(add) or_return + + // 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) + } + + case .Tag: + switch &dst in v { + case ^Tag: + tval := err_conv(_decode_tag_ptr(d, add)) or_return + if t, is_tag := tval.(^Tag); is_tag { + dst = t + return + } + + destroy(tval) + return .Bad_Tag_Value + case Tag: + t := err_conv(_decode_tag(d, add)) or_return + if t, is_tag := t.?; is_tag { + dst = t + return + } + + return .Bad_Tag_Value + } + + nr := err_conv(_decode_uint_as_u64(r, add)) or_return + + // Custom tag implementations. + 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, loc=loc) + } else { + // Discard the tag info and unmarshal as its value. + return _unmarshal_value(d, v, _decode_header(r) or_return) + } + + return _unsupported(v, 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, 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, allocator=allocator, loc=loc)) or_return + + if t.is_cstring { + raw := (^cstring)(v.data) + assert_safe_for_cstring(string(bytes)) + raw^ = cstring(raw_data(bytes)) + } else { + // String has same memory layout as a slice, so we can directly use it as a slice. + raw := (^mem.Raw_String)(v.data) + raw^ = transmute(mem.Raw_String)bytes + } + + return + + case reflect.Type_Info_Slice: + elem_base := reflect.type_info_base(t.elem) + + if elem_base.id != byte { return _unsupported(v, hdr) } + + 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 + + case reflect.Type_Info_Dynamic_Array: + elem_base := reflect.type_info_base(t.elem) + + if elem_base.id != byte { return _unsupported(v, hdr) } + + 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 = allocator + return + + case reflect.Type_Info_Array: + elem_base := reflect.type_info_base(t.elem) + + if elem_base.id != byte { return _unsupported(v, hdr) } + + bytes := err_conv(_decode_bytes(d, add, allocator=context.temp_allocator)) or_return + defer delete(bytes, context.temp_allocator) + + if len(bytes) > t.count { return _unsupported(v, hdr) } + + // Copy into array type, delete original. + slice := ([^]byte)(v.data)[:len(bytes)] + n := copy(slice, bytes) + assert(n == len(bytes)) + return + } + + return _unsupported(v, hdr) +} + +_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, allocator, loc)) or_return + + if t.is_cstring { + raw := (^cstring)(v.data) + + assert_safe_for_cstring(text) + raw^ = cstring(raw_data(text)) + } else { + raw := (^string)(v.data) + raw^ = text + } + return + + // Enum by its variant name. + case reflect.Type_Info_Enum: + 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 { + if !_assign_int(any{v.data, ti.id}, t.values[i]) { return _unsupported(v, hdr) } + return + } + } + + case reflect.Type_Info_Rune: + 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) + if dr == utf8.RUNE_ERROR || n < len(text) { + return _unsupported(v, hdr) + } + + r^ = dr + return + } + + return _unsupported(v, hdr) +} + +_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 { + hdr := _decode_header(d.reader) or_return + + // Double size if out of capacity. + if da.cap <= da.len { + // Not growable, error out. + if !growable { return true, .Out_Of_Memory } + + cap := 2 * da.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 } + } + + // Set ptr after potential resizes to avoid invalidation. + elem_ptr := rawptr(uintptr(da.data) + idx*uintptr(elemt.size)) + elem := any{elem_ptr, elemt.id} + + err = _unmarshal_value(d, elem, hdr, allocator=allocator, loc=loc) + if length == -1 && err == .Break { break } + if err != nil { return } + + da.len += 1 + } + + return false, nil + } + + // Allow generically storing the values array. + switch &dst in v { + case ^Array: + dst = err_conv(_decode_array_ptr(d, add, allocator=allocator, loc=loc)) or_return + return + case Array: + dst = err_conv(_decode_array(d, add, allocator=allocator, loc=loc)) or_return + return + } + + #partial switch t in ti.variant { + 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, 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 } + + assign_array(d, &da, t.elem, length) or_return + + 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, loc=loc) + } + + raw := (^mem.Raw_Slice)(v.data) + raw.data = da.data + raw.len = da.len + return + + 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, 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) + raw.len = 0 + raw.cap = scap + raw.allocator = context.allocator + + _ = assign_array(d, raw, t.elem, length) or_return + + 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, loc=loc) + } + return + + case reflect.Type_Info_Array: + 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, 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: + 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, 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: + 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, allocator } + + info: ^runtime.Type_Info + switch ti.id { + case complex32: info = type_info_of(f16) + case complex64: info = type_info_of(f32) + case complex128: info = type_info_of(f64) + case: unreachable() + } + + out_of_space := assign_array(d, &da, info, 2, growable=false) or_return + if out_of_space { return _unsupported(v, hdr) } + return + + case reflect.Type_Info_Quaternion: + 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, allocator } + + info: ^runtime.Type_Info + switch ti.id { + case quaternion64: info = type_info_of(f16) + case quaternion128: info = type_info_of(f32) + case quaternion256: info = type_info_of(f64) + case: unreachable() + } + + out_of_space := assign_array(d, &da, info, 4, growable=false) or_return + if out_of_space { return _unsupported(v, hdr) } + return + + case: return _unsupported(v, hdr) + } +} + +_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, 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=allocator, loc=loc)) or_return + return + case .Bytes: + bytes := err_conv(_decode_bytes(d, entry_add, allocator=allocator, loc=loc)) or_return + k = string(bytes) + return + case: + err = _unsupported(v, entry_hdr) + return + } + } + + // Allow generically storing the map array. + switch &dst in v { + case ^Map: + dst = err_conv(_decode_map_ptr(d, add, allocator=allocator, loc=loc)) or_return + return + case Map: + 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 .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) + + idx := 0 + for ; idx < len(fields) && (unknown || idx < length); idx += 1 { + // Decode key, keys can only be strings. + key: string + if keyv, kerr := decode_key(d, v, context.temp_allocator); unknown && kerr == .Break { + break + } else if kerr != nil { + err = kerr + return + } else { + key = keyv + } + defer delete(key, context.temp_allocator) + + // Find matching field. + use_field_idx := -1 + { + for field, field_idx in fields { + tag_value := string(reflect.struct_tag_get(field.tag, "cbor")) + if tag_value == "-" { + continue + } + + if key == tag_value { + use_field_idx = field_idx + break + } + + if key == field.name { + // No break because we want to still check remaining struct tags. + use_field_idx = field_idx + } + } + + // Skips unused map entries. + if use_field_idx < 0 { + val := err_conv(_decode_from_decoder(d, allocator=context.temp_allocator)) or_return + destroy(val, context.temp_allocator) + continue + } + } + + field := fields[use_field_idx] + // name := field.name + ptr := rawptr(uintptr(v.data) + field.offset) + fany := any{ptr, field.type.id} + _unmarshal_value(d, fany, _decode_header(r) or_return) or_return + } + + // If there are fields left in the map that did not get decoded into the struct, decode and discard them. + if !unknown { + for _ in idx.. scap { + // Reserve space for new element so we can return allocator errors. + new_len := uintptr(runtime.map_len(raw_map^)+1) + runtime.map_reserve_dynamic(raw_map, t.map_info, new_len) or_return + } + + mem.zero_slice(key_backing) + _unmarshal_value(d, key_backing_value, _decode_header(r) or_return) or_return + + mem.zero_slice(elem_backing) + _unmarshal_value(d, map_backing_value, _decode_header(r) or_return) or_return + + set_ptr := runtime.__dynamic_map_set_without_hash(raw_map, t.map_info, key_backing_value.data, map_backing_value.data) + // We already reserved space for it, so this shouldn't fail. + assert(set_ptr != nil) + } + + if .Shrink_Excess in d.flags { + _, _ = runtime.map_shrink_dynamic(raw_map, t.map_info) + } + return + + case: + return _unsupported(v, hdr) + } +} + +// 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, loc := #caller_location) -> (err: Unmarshal_Error) { + r := d.reader + #partial switch t in ti.variant { + case reflect.Type_Info_Union: + idhdr: Header + target_name: string + { + vhdr := _decode_header(r) or_return + vmaj, vadd := _header_split(vhdr) + if vmaj != .Array { + return .Bad_Tag_Value + } + + n_items, _ := err_conv(_decode_len_container(d, vadd)) or_return + if n_items != 2 { + return .Bad_Tag_Value + } + + idhdr = _decode_header(r) or_return + idmaj, idadd := _header_split(idhdr) + if idmaj != .Text { + return .Bad_Tag_Value + } + + target_name = err_conv(_decode_text(d, idadd, context.temp_allocator)) or_return + } + defer delete(target_name, context.temp_allocator) + + for variant, i in t.variants { + tag := i64(i) + if !t.no_nil { + tag += 1 + } + + #partial switch vti in variant.variant { + 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, loc=loc) + } + + case: + builder := strings.builder_make(context.temp_allocator) + defer strings.builder_destroy(&builder) + + reflect.write_type(&builder, variant) + variant_name := strings.to_string(builder) + + 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, loc=loc) + } + } + } + + // No variant matched. + return _unsupported(v, idhdr) + + case: + // Not a union. + return _unsupported(v, hdr) + } +} + +_assign_int :: proc(val: any, i: $T) -> bool { + v := reflect.any_core(val) + + // NOTE: should under/over flow be checked here? `encoding/json` doesn't, but maybe that is a + // less strict encoding?. + + switch &dst in v { + case i8: dst = i8 (i) + case i16: dst = i16 (i) + case i16le: dst = i16le (i) + case i16be: dst = i16be (i) + case i32: dst = i32 (i) + case i32le: dst = i32le (i) + case i32be: dst = i32be (i) + case i64: dst = i64 (i) + case i64le: dst = i64le (i) + case i64be: dst = i64be (i) + case i128: dst = i128 (i) + case i128le: dst = i128le (i) + case i128be: dst = i128be (i) + case u8: dst = u8 (i) + case u16: dst = u16 (i) + case u16le: dst = u16le (i) + case u16be: dst = u16be (i) + case u32: dst = u32 (i) + case u32le: dst = u32le (i) + case u32be: dst = u32be (i) + case u64: dst = u64 (i) + case u64le: dst = u64le (i) + case u64be: dst = u64be (i) + case u128: dst = u128 (i) + case u128le: dst = u128le (i) + case u128be: dst = u128be (i) + case int: dst = int (i) + case uint: dst = uint (i) + case uintptr: dst = uintptr(i) + case: + ti := type_info_of(v.id) + if _, ok := ti.variant.(runtime.Type_Info_Bit_Set); ok { + do_byte_swap := !reflect.bit_set_is_big_endian(v) + switch ti.size * 8 { + case 0: // no-op. + case 8: + x := (^u8)(v.data) + x^ = u8(i) + case 16: + x := (^u16)(v.data) + x^ = do_byte_swap ? intrinsics.byte_swap(u16(i)) : u16(i) + case 32: + x := (^u32)(v.data) + x^ = do_byte_swap ? intrinsics.byte_swap(u32(i)) : u32(i) + case 64: + x := (^u64)(v.data) + x^ = do_byte_swap ? intrinsics.byte_swap(u64(i)) : u64(i) + case: + panic("unknown bit_size size") + } + return true + } + return false + } + return true +} + +_assign_float :: proc(val: any, f: $T) -> bool { + v := reflect.any_core(val) + + // NOTE: should under/over flow be checked here? `encoding/json` doesn't, but maybe that is a + // less strict encoding?. + + switch &dst in v { + case f16: dst = f16 (f) + case f16le: dst = f16le(f) + case f16be: dst = f16be(f) + case f32: dst = f32 (f) + case f32le: dst = f32le(f) + case f32be: dst = f32be(f) + case f64: dst = f64 (f) + case f64le: dst = f64le(f) + case f64be: dst = f64be(f) + + case complex32: dst = complex(f16(f), 0) + case complex64: dst = complex(f32(f), 0) + case complex128: dst = complex(f64(f), 0) + + case quaternion64: dst = quaternion(w=f16(f), x=0, y=0, z=0) + case quaternion128: dst = quaternion(w=f32(f), x=0, y=0, z=0) + case quaternion256: dst = quaternion(w=f64(f), x=0, y=0, z=0) + + case: return false + } + return true +} + +_assign_bool :: proc(val: any, b: bool) -> bool { + v := reflect.any_core(val) + switch &dst in v { + case bool: dst = bool(b) + case b8: dst = b8 (b) + case b16: dst = b16 (b) + case b32: dst = b32 (b) + case b64: dst = b64 (b) + case: return false + } + return true +} + +// Sanity check that the decoder added a nil byte to the end. +@(private, disabled=ODIN_DISABLE_ASSERT) +assert_safe_for_cstring :: proc(s: string, loc := #caller_location) { + assert(([^]byte)(raw_data(s))[len(s)] == 0, loc = loc) +} diff --git a/core/encoding/csv/doc.odin b/core/encoding/csv/doc.odin new file mode 100644 index 000000000..bfeadafd6 --- /dev/null +++ b/core/encoding/csv/doc.odin @@ -0,0 +1,96 @@ +/* +package csv reads and writes comma-separated values (CSV) files. +This package supports the format described in [[ RFC 4180; https://tools.ietf.org/html/rfc4180.html ]] + +Example: + package main + + import "core:fmt" + import "core:encoding/csv" + import "core:os" + + // Requires keeping the entire CSV file in memory at once + iterate_csv_from_string :: proc(filename: string) { + r: csv.Reader + r.trim_leading_space = true + r.reuse_record = true // Without it you have to delete(record) + r.reuse_record_buffer = true // Without it you have to each of the fields within it + defer csv.reader_destroy(&r) + + csv_data, ok := os.read_entire_file(filename) + if ok { + csv.reader_init_with_string(&r, string(csv_data)) + } else { + fmt.printfln("Unable to open file: %v", filename) + return + } + defer delete(csv_data) + + for r, i, err in csv.iterator_next(&r) { + if err != nil { /* Do something with error */ } + for f, j in r { + fmt.printfln("Record %v, field %v: %q", i, j, f) + } + } + } + + // Reads the CSV as it's processed (with a small buffer) + iterate_csv_from_stream :: proc(filename: string) { + fmt.printfln("Hellope from %v", filename) + r: csv.Reader + r.trim_leading_space = true + r.reuse_record = true // Without it you have to delete(record) + r.reuse_record_buffer = true // Without it you have to each of the fields within it + defer csv.reader_destroy(&r) + + handle, err := os.open(filename) + if err != nil { + fmt.eprintfln("Error opening file: %v", filename) + return + } + defer os.close(handle) + csv.reader_init(&r, os.stream_from_handle(handle)) + + for r, i in csv.iterator_next(&r) { + for f, j in r { + fmt.printfln("Record %v, field %v: %q", i, j, f) + } + } + fmt.printfln("Error: %v", csv.iterator_last_error(r)) + } + + // Read all records at once + read_csv_from_string :: proc(filename: string) { + r: csv.Reader + r.trim_leading_space = true + r.reuse_record = true // Without it you have to delete(record) + r.reuse_record_buffer = true // Without it you have to each of the fields within it + defer csv.reader_destroy(&r) + + csv_data, ok := os.read_entire_file(filename) + if ok { + csv.reader_init_with_string(&r, string(csv_data)) + } else { + fmt.printfln("Unable to open file: %v", filename) + return + } + defer delete(csv_data) + + records, err := csv.read_all(&r) + if err != nil { /* Do something with CSV parse error */ } + + defer { + for rec in records { + delete(rec) + } + delete(records) + } + + for r, i in records { + for f, j in r { + fmt.printfln("Record %v, field %v: %q", i, j, f) + } + } + } +*/ +package encoding_csv diff --git a/core/encoding/csv/reader.odin b/core/encoding/csv/reader.odin index 44a9fdcc4..5348624d5 100644 --- a/core/encoding/csv/reader.odin +++ b/core/encoding/csv/reader.odin @@ -1,6 +1,6 @@ // package csv reads and writes comma-separated values (CSV) files. -// This package supports the format described in RFC 4180 -package csv +// This package supports the format described in [[ RFC 4180; https://tools.ietf.org/html/rfc4180.html ]] +package encoding_csv import "core:bufio" import "core:bytes" @@ -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, } @@ -91,7 +94,10 @@ DEFAULT_RECORD_BUFFER_CAPACITY :: 256 // reader_init initializes a new Reader from r reader_init :: proc(reader: ^Reader, r: io.Reader, buffer_allocator := context.allocator) { - reader.comma = ',' + switch reader.comma { + case '\x00', '\n', '\r', 0xfffd: + reader.comma = ',' + } context.allocator = buffer_allocator reserve(&reader.record_buffer, DEFAULT_RECORD_BUFFER_CAPACITY) @@ -118,9 +124,31 @@ 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 +@(require_results) read :: proc(r: ^Reader, allocator := context.allocator) -> (record: []string, err: Error) { if r.reuse_record { record, err = _read_record(r, &r.last_record, allocator) @@ -133,6 +161,7 @@ read :: proc(r: ^Reader, allocator := context.allocator) -> (record: []string, e } // is_io_error checks where an Error is a specific io.Error kind +@(require_results) is_io_error :: proc(err: Error, io_err: io.Error) -> bool { if v, ok := err.(io.Error); ok { return v == io_err @@ -140,10 +169,10 @@ is_io_error :: proc(err: Error, io_err: io.Error) -> bool { return false } - // 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 records: [dynamic][]string @@ -153,13 +182,18 @@ read_all :: proc(r: ^Reader, allocator := context.allocator) -> ([][]string, Err return records[:], nil } if rerr != nil { - return nil, rerr + // allow for a partial read + if record != nil { + append(&records, record) + } + return records[:], rerr } append(&records, record) } } // read reads a single record (a slice of fields) from the provided input. +@(require_results) read_from_string :: proc(input: string, record_allocator := context.allocator, buffer_allocator := context.allocator) -> (record: []string, n: int, err: Error) { ir: strings.Reader strings.reader_init(&ir, input) @@ -175,6 +209,7 @@ read_from_string :: proc(input: string, record_allocator := context.allocator, b // read_all reads all the remaining records from the provided input. +@(require_results) read_all_from_string :: proc(input: string, records_allocator := context.allocator, buffer_allocator := context.allocator) -> ([][]string, Error) { ir: strings.Reader strings.reader_init(&ir, input) @@ -186,7 +221,7 @@ read_all_from_string :: proc(input: string, records_allocator := context.allocat return read_all(&r, records_allocator) } -@private +@(private, require_results) is_valid_delim :: proc(r: rune) -> bool { switch r { case 0, '"', '\r', '\n', utf8.RUNE_ERROR: @@ -195,8 +230,9 @@ is_valid_delim :: proc(r: rune) -> bool { return utf8.valid_rune(r) } -@private +@(private, require_results) _read_record :: proc(r: ^Reader, dst: ^[dynamic]string, allocator := context.allocator) -> ([]string, Error) { + @(require_results) read_line :: proc(r: ^Reader) -> ([]byte, io.Error) { if !r.multiline_fields { line, err := bufio.reader_read_slice(&r.r, '\n') @@ -266,6 +302,7 @@ _read_record :: proc(r: ^Reader, dst: ^[dynamic]string, allocator := context.all unreachable() } + @(require_results) length_newline :: proc(b: []byte) -> int { if len(b) > 0 && b[len(b)-1] == '\n' { return 1 @@ -273,6 +310,7 @@ _read_record :: proc(r: ^Reader, dst: ^[dynamic]string, allocator := context.all return 0 } + @(require_results) next_rune :: proc(b: []byte) -> rune { r, _ := utf8.decode_rune(b) return r @@ -446,5 +484,4 @@ _read_record :: proc(r: ^Reader, dst: ^[dynamic]string, allocator := context.all r.fields_per_record = len(dst) } return dst[:], err - } diff --git a/core/encoding/csv/writer.odin b/core/encoding/csv/writer.odin index d519104f2..132fa0a51 100644 --- a/core/encoding/csv/writer.odin +++ b/core/encoding/csv/writer.odin @@ -1,4 +1,4 @@ -package csv +package encoding_csv import "core:io" import "core:strings" @@ -17,7 +17,10 @@ Writer :: struct { // writer_init initializes a Writer that writes to w writer_init :: proc(writer: ^Writer, w: io.Writer) { - writer.comma = ',' + switch writer.comma { + case '\x00', '\n', '\r', 0xfffd: + writer.comma = ',' + } writer.w = w } diff --git a/core/encoding/endian/doc.odin b/core/encoding/endian/doc.odin index 754ffa583..0b43e3097 100644 --- a/core/encoding/endian/doc.odin +++ b/core/encoding/endian/doc.odin @@ -1,23 +1,24 @@ /* - Package endian implements sa simple translation between bytes and numbers with + Package endian implements a simple translation between bytes and numbers with specific endian encodings. - buf: [100]u8 - put_u16(buf[:], .Little, 16) or_return +Example: + buf: [100]u8 + put_u16(buf[:], .Little, 16) or_return - You may ask yourself, why isn't `byte_order` platform Endianness by default, so we can write: - put_u16(buf[:], 16) or_return + // You may ask yourself, why isn't `byte_order` platform Endianness by default, so we can write: + put_u16(buf[:], 16) or_return - The answer is that very few file formats are written in native/platform endianness. Most of them specify the endianness of - each of their fields, or use a header field which specifies it for the entire file. + // The answer is that very few file formats are written in native/platform endianness. Most of them specify the endianness of + // each of their fields, or use a header field which specifies it for the entire file. - e.g. a file which specifies it at the top for all fields could do this: - file_order := .Little if buf[0] == 0 else .Big - field := get_u16(buf[1:], file_order) or_return + // e.g. a file which specifies it at the top for all fields could do this: + file_order := .Little if buf[0] == 0 else .Big + field := get_u16(buf[1:], file_order) or_return - If on the other hand a field is *always* Big-Endian, you're wise to explicitly state it for the benefit of the reader, - be that your future self or someone else. + // If on the other hand a field is *always* Big-Endian, you're wise to explicitly state it for the benefit of the reader, + // be that your future self or someone else. - field := get_u16(buf[:], .Big) or_return + field := get_u16(buf[:], .Big) or_return */ package encoding_endian diff --git a/core/encoding/endian/endian.odin b/core/encoding/endian/endian.odin index 08bde3139..708b919fb 100644 --- a/core/encoding/endian/endian.odin +++ b/core/encoding/endian/endian.odin @@ -1,5 +1,8 @@ package encoding_endian +import "base:intrinsics" +import "core:math/bits" + Byte_Order :: enum u8 { Little, Big, @@ -7,147 +10,154 @@ Byte_Order :: enum u8 { PLATFORM_BYTE_ORDER :: Byte_Order.Little when ODIN_ENDIAN == .Little else Byte_Order.Big -get_u16 :: proc(b: []byte, order: Byte_Order) -> (v: u16, ok: bool) { +unchecked_get_u16le :: #force_inline proc "contextless" (b: []byte) -> u16 { + return bits.from_le_u16(intrinsics.unaligned_load((^u16)(raw_data(b)))) +} +unchecked_get_u32le :: #force_inline proc "contextless" (b: []byte) -> u32 { + return bits.from_le_u32(intrinsics.unaligned_load((^u32)(raw_data(b)))) +} +unchecked_get_u64le :: #force_inline proc "contextless" (b: []byte) -> u64 { + return bits.from_le_u64(intrinsics.unaligned_load((^u64)(raw_data(b)))) +} +unchecked_get_u16be :: #force_inline proc "contextless" (b: []byte) -> u16 { + return bits.from_be_u16(intrinsics.unaligned_load((^u16)(raw_data(b)))) +} +unchecked_get_u32be :: #force_inline proc "contextless" (b: []byte) -> u32 { + return bits.from_be_u32(intrinsics.unaligned_load((^u32)(raw_data(b)))) +} +unchecked_get_u64be :: #force_inline proc "contextless" (b: []byte) -> u64 { + return bits.from_be_u64(intrinsics.unaligned_load((^u64)(raw_data(b)))) +} + +get_u16 :: proc "contextless" (b: []byte, order: Byte_Order) -> (v: u16, ok: bool) { if len(b) < 2 { return 0, false } - #no_bounds_check if order == .Little { - v = u16(b[0]) | u16(b[1])<<8 + if order == .Little { + v = unchecked_get_u16le(b) } else { - v = u16(b[1]) | u16(b[0])<<8 + v = unchecked_get_u16be(b) } return v, true } -get_u32 :: proc(b: []byte, order: Byte_Order) -> (v: u32, ok: bool) { +get_u32 :: proc "contextless" (b: []byte, order: Byte_Order) -> (v: u32, ok: bool) { if len(b) < 4 { return 0, false } - #no_bounds_check if order == .Little { - v = u32(b[0]) | u32(b[1])<<8 | u32(b[2])<<16 | u32(b[3])<<24 + if order == .Little { + v = unchecked_get_u32le(b) } else { - v = u32(b[3]) | u32(b[2])<<8 | u32(b[1])<<16 | u32(b[0])<<24 + v = unchecked_get_u32be(b) } return v, true } - -get_u64 :: proc(b: []byte, order: Byte_Order) -> (v: u64, ok: bool) { +get_u64 :: proc "contextless" (b: []byte, order: Byte_Order) -> (v: u64, ok: bool) { if len(b) < 8 { return 0, false } - #no_bounds_check if order == .Little { - v = u64(b[0]) | u64(b[1])<<8 | u64(b[2])<<16 | u64(b[3])<<24 | - u64(b[4])<<32 | u64(b[5])<<40 | u64(b[6])<<48 | u64(b[7])<<56 + if order == .Little { + v = unchecked_get_u64le(b) } else { - v = u64(b[7]) | u64(b[6])<<8 | u64(b[5])<<16 | u64(b[4])<<24 | - u64(b[3])<<32 | u64(b[2])<<40 | u64(b[1])<<48 | u64(b[0])<<56 + v = unchecked_get_u64be(b) } return v, true } -get_i16 :: proc(b: []byte, order: Byte_Order) -> (i16, bool) { +get_i16 :: proc "contextless" (b: []byte, order: Byte_Order) -> (i16, bool) { v, ok := get_u16(b, order) return i16(v), ok } -get_i32 :: proc(b: []byte, order: Byte_Order) -> (i32, bool) { +get_i32 :: proc "contextless" (b: []byte, order: Byte_Order) -> (i32, bool) { v, ok := get_u32(b, order) return i32(v), ok } -get_i64 :: proc(b: []byte, order: Byte_Order) -> (i64, bool) { +get_i64 :: proc "contextless" (b: []byte, order: Byte_Order) -> (i64, bool) { v, ok := get_u64(b, order) return i64(v), ok } -get_f16 :: proc(b: []byte, order: Byte_Order) -> (f16, bool) { +get_f16 :: proc "contextless" (b: []byte, order: Byte_Order) -> (f16, bool) { v, ok := get_u16(b, order) return transmute(f16)v, ok } -get_f32 :: proc(b: []byte, order: Byte_Order) -> (f32, bool) { +get_f32 :: proc "contextless" (b: []byte, order: Byte_Order) -> (f32, bool) { v, ok := get_u32(b, order) return transmute(f32)v, ok } -get_f64 :: proc(b: []byte, order: Byte_Order) -> (f64, bool) { +get_f64 :: proc "contextless" (b: []byte, order: Byte_Order) -> (f64, bool) { v, ok := get_u64(b, order) return transmute(f64)v, ok } +unchecked_put_u16le :: #force_inline proc "contextless" (b: []byte, v: u16) { + intrinsics.unaligned_store((^u16)(raw_data(b)), bits.to_le_u16(v)) +} +unchecked_put_u32le :: #force_inline proc "contextless" (b: []byte, v: u32) { + intrinsics.unaligned_store((^u32)(raw_data(b)), bits.to_le_u32(v)) +} +unchecked_put_u64le :: #force_inline proc "contextless" (b: []byte, v: u64) { + intrinsics.unaligned_store((^u64)(raw_data(b)), bits.to_le_u64(v)) +} +unchecked_put_u16be :: #force_inline proc "contextless" (b: []byte, v: u16) { + intrinsics.unaligned_store((^u16)(raw_data(b)), bits.to_be_u16(v)) +} +unchecked_put_u32be :: #force_inline proc "contextless" (b: []byte, v: u32) { + intrinsics.unaligned_store((^u32)(raw_data(b)), bits.to_be_u32(v)) +} +unchecked_put_u64be :: #force_inline proc "contextless" (b: []byte, v: u64) { + intrinsics.unaligned_store((^u64)(raw_data(b)), bits.to_be_u64(v)) +} -put_u16 :: proc(b: []byte, order: Byte_Order, v: u16) -> bool { +put_u16 :: proc "contextless" (b: []byte, order: Byte_Order, v: u16) -> bool { if len(b) < 2 { return false } - #no_bounds_check if order == .Little { - b[0] = byte(v) - b[1] = byte(v >> 8) + if order == .Little { + unchecked_put_u16le(b, v) } else { - b[0] = byte(v >> 8) - b[1] = byte(v) + unchecked_put_u16be(b, v) } return true } -put_u32 :: proc(b: []byte, order: Byte_Order, v: u32) -> bool { +put_u32 :: proc "contextless" (b: []byte, order: Byte_Order, v: u32) -> bool { if len(b) < 4 { return false } - #no_bounds_check if order == .Little { - b[0] = byte(v) - b[1] = byte(v >> 8) - b[2] = byte(v >> 16) - b[3] = byte(v >> 24) + if order == .Little { + unchecked_put_u32le(b, v) } else { - b[0] = byte(v >> 24) - b[1] = byte(v >> 16) - b[2] = byte(v >> 8) - b[3] = byte(v) + unchecked_put_u32be(b, v) } return true } -put_u64 :: proc(b: []byte, order: Byte_Order, v: u64) -> bool { +put_u64 :: proc "contextless" (b: []byte, order: Byte_Order, v: u64) -> bool { if len(b) < 8 { return false } - #no_bounds_check if order == .Little { - b[0] = byte(v >> 0) - b[1] = byte(v >> 8) - b[2] = byte(v >> 16) - b[3] = byte(v >> 24) - b[4] = byte(v >> 32) - b[5] = byte(v >> 40) - b[6] = byte(v >> 48) - b[7] = byte(v >> 56) + if order == .Little { + unchecked_put_u64le(b, v) } else { - b[0] = byte(v >> 56) - b[1] = byte(v >> 48) - b[2] = byte(v >> 40) - b[3] = byte(v >> 32) - b[4] = byte(v >> 24) - b[5] = byte(v >> 16) - b[6] = byte(v >> 8) - b[7] = byte(v) + unchecked_put_u64be(b, v) } return true } -put_i16 :: proc(b: []byte, order: Byte_Order, v: i16) -> bool { +put_i16 :: proc "contextless" (b: []byte, order: Byte_Order, v: i16) -> bool { return put_u16(b, order, u16(v)) } - -put_i32 :: proc(b: []byte, order: Byte_Order, v: i32) -> bool { +put_i32 :: proc "contextless" (b: []byte, order: Byte_Order, v: i32) -> bool { return put_u32(b, order, u32(v)) } - -put_i64 :: proc(b: []byte, order: Byte_Order, v: i64) -> bool { +put_i64 :: proc "contextless" (b: []byte, order: Byte_Order, v: i64) -> bool { return put_u64(b, order, u64(v)) } - -put_f16 :: proc(b: []byte, order: Byte_Order, v: f16) -> bool { +put_f16 :: proc "contextless" (b: []byte, order: Byte_Order, v: f16) -> bool { return put_u16(b, order, transmute(u16)v) } - -put_f32 :: proc(b: []byte, order: Byte_Order, v: f32) -> bool { +put_f32 :: proc "contextless" (b: []byte, order: Byte_Order, v: f32) -> bool { return put_u32(b, order, transmute(u32)v) } - -put_f64 :: proc(b: []byte, order: Byte_Order, v: f64) -> bool { +put_f64 :: proc "contextless" (b: []byte, order: Byte_Order, v: f64) -> bool { return put_u64(b, order, transmute(u64)v) } diff --git a/core/encoding/entity/entity.odin b/core/encoding/entity/entity.odin index ec640c69f..d2f1d46b2 100644 --- a/core/encoding/entity/entity.odin +++ b/core/encoding/entity/entity.odin @@ -1,24 +1,26 @@ -package unicode_entity /* - A unicode entity encoder/decoder - Copyright 2021 Jeroen van Rijn . Made available under Odin's BSD-3 license. - This code has several procedures to map unicode runes to/from different textual encodings. - - SGML/XML/HTML entity - -- &#; - -- &#x; - -- &; (If the lookup tables are compiled in). - Reference: https://www.w3.org/2003/entities/2007xml/unicode.xml - - - URL encode / decode %hex entity - Reference: https://datatracker.ietf.org/doc/html/rfc3986/#section-2.1 - List of contributors: Jeroen van Rijn: Initial implementation. */ +/* + A unicode entity encoder/decoder. + + This code has several procedures to map unicode runes to/from different textual encodings. + - SGML/XML/HTML entity + - &#; + - &#x; + - &; (If the lookup tables are compiled in). + Reference: [[ https://www.w3.org/2003/entities/2007xml/unicode.xml ]] + + - URL encode / decode %hex entity + Reference: [[ https://datatracker.ietf.org/doc/html/rfc3986/#section-2.1 ]] +*/ +package encoding_unicode_entity + import "core:unicode/utf8" import "core:unicode" import "core:strings" @@ -56,38 +58,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 +91,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 +117,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 +132,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 +149,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 +258,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 +291,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 +301,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 +332,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 } @@ -368,4 +355,4 @@ _handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: X } return false, .None -} \ No newline at end of file +} diff --git a/core/encoding/entity/example/entity_example.odin b/core/encoding/entity/example/entity_example.odin index 6301eb263..1a6b8e9ba 100644 --- a/core/encoding/entity/example/entity_example.odin +++ b/core/encoding/entity/example/entity_example.odin @@ -47,8 +47,6 @@ _entities :: proc() { } _main :: proc() { - using fmt - options := xml.Options{ flags = { .Ignore_Unsupported, .Intern_Comments, .Unbox_CDATA, .Decode_SGML_Entities }} doc, _ := xml.parse(#load("test.html"), options) @@ -58,8 +56,6 @@ _main :: proc() { } main :: proc() { - using fmt - track: mem.Tracking_Allocator mem.tracking_allocator_init(&track, context.allocator) context.allocator = mem.tracking_allocator(&track) @@ -68,9 +64,9 @@ main :: proc() { _entities() if len(track.allocation_map) > 0 { - println() + fmt.println() for _, v in track.allocation_map { - printf("%v Leaked %v bytes.\n", v.location, v.size) + fmt.printf("%v Leaked %v bytes.\n", v.location, v.size) } } } \ No newline at end of file diff --git a/core/encoding/entity/generated.odin b/core/encoding/entity/generated.odin index 9afdcae6d..52027ae03 100644 --- a/core/encoding/entity/generated.odin +++ b/core/encoding/entity/generated.odin @@ -1,11 +1,11 @@ -package unicode_entity +package encoding_unicode_entity /* ------ GENERATED ------ DO NOT EDIT ------ GENERATED ------ DO NOT EDIT ------ GENERATED ------ */ /* - This file is generated from "https://www.w3.org/2003/entities/2007xml/unicode.xml". + This file is generated from "https://github.com/w3c/xml-entities/blob/gh-pages/unicode.xml". UPDATE: - Ensure the XML file was downloaded using "tests\core\download_assets.py". @@ -13,15 +13,21 @@ package unicode_entity Odin unicode generated tables: https://github.com/odin-lang/Odin/tree/master/core/encoding/entity - Copyright © 2021 World Wide Web Consortium, (Massachusetts Institute of Technology, - European Research Consortium for Informatics and Mathematics, Keio University, Beihang). + Copyright David Carlisle 1999-2023 - All Rights Reserved. + Use and distribution of this code are permitted under the terms of the + W3C Software Notice and License. + http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231.html - This work is distributed under the W3C® Software License [1] in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - [1] http://www.w3.org/Consortium/Legal/copyright-software + + This file is a collection of information about how to map + Unicode entities to LaTeX, and various SGML/XML entity + sets (ISO and MathML/HTML). A Unicode character may be mapped + to several entities. + + Originally designed by Sebastian Rahtz in conjunction with + Barbara Beeton for the STIX project See also: LICENSE_table.md */ @@ -36,7 +42,7 @@ XML_NAME_TO_RUNE_MAX_LENGTH :: 31 Input: entity_name - a string, like "copy" that describes a user-encoded Unicode entity as used in XML. - Output: + Returns: "decoded" - The decoded rune if found by name, or -1 otherwise. "ok" - true if found, false if not. @@ -55,7434 +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 "Agrave": - // LATIN CAPITAL LETTER A WITH GRAVE - return rune(0xc0), true - case "Agr": - // GREEK CAPITAL LETTER ALPHA - return rune(0x0391), 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 "Barwedl": - // LOGICAL AND WITH DOUBLE OVERBAR - return rune(0x2a5e), true - case "Barwed": - // PERSPECTIVE - return rune(0x2306), 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 "CapitalDifferentialD": - // DOUBLE-STRUCK ITALIC CAPITAL D - return rune(0x2145), true - case "Cap": - // DOUBLE INTERSECTION - return rune(0x22d2), 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 "CupCap": - // EQUIVALENT TO - return rune(0x224d), true - case "Cup": - // DOUBLE UNION - return rune(0x22d3), 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 "DownArrowUpArrow": - // DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW - return rune(0x21f5), true - case "DownArrow": - // DOWNWARDS ARROW - return rune(0x2193), true - case "DownArrowBar": - // DOWNWARDS ARROW TO BAR - return rune(0x2913), 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 "DownTeeArrow": - // DOWNWARDS ARROW FROM BAR - return rune(0x21a7), true - case "DownTee": - // DOWN TACK - return rune(0x22a4), 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 "Egrave": - // LATIN CAPITAL LETTER E WITH GRAVE - return rune(0xc8), true - case "Egr": - // GREEK CAPITAL LETTER EPSILON - return rune(0x0395), 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 "EqualTilde": - // MINUS TILDE - return rune(0x2242), true - case "Equal": - // TWO CONSECUTIVE EQUALS SIGNS - return rune(0x2a75), 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 "Ggr": - // GREEK CAPITAL LETTER GAMMA - return rune(0x0393), true - case "Gg": - // VERY MUCH GREATER-THAN - return rune(0x22d9), 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 "Igrave": - // LATIN CAPITAL LETTER I WITH GRAVE - return rune(0xcc), true - case "Igr": - // GREEK CAPITAL LETTER IOTA - return rune(0x0399), 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 "Im": - // BLACK-LETTER CAPITAL I - return rune(0x2111), true - case "Integral": - // INTEGRAL - return rune(0x222b), true - case "Int": - // DOUBLE INTEGRAL - return rune(0x222c), 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 "LeftArrowBar": - // LEFTWARDS ARROW TO BAR - return rune(0x21e4), true - case "LeftArrowRightArrow": - // LEFTWARDS ARROW OVER RIGHTWARDS ARROW - return rune(0x21c6), true - case "LeftArrow": - // LEFTWARDS ARROW - return rune(0x2190), 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 "LeftTeeArrow": - // LEFTWARDS ARROW FROM BAR - return rune(0x21a4), true - case "LeftTeeVector": - // LEFTWARDS HARPOON WITH BARB UP FROM BAR - return rune(0x295a), true - case "LeftTee": - // LEFT TACK - return rune(0x22a3), true - case "LeftTriangleBar": - // LEFT TRIANGLE BESIDE VERTICAL BAR - return rune(0x29cf), true - case "LeftTriangle": - // NORMAL SUBGROUP OF - return rune(0x22b2), 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 "Lleftarrow": - // LEFTWARDS TRIPLE ARROW - return rune(0x21da), true - case "Ll": - // VERY MUCH LESS-THAN - return rune(0x22d8), 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 "Ltbar": - // DOUBLE NESTED LESS-THAN WITH UNDERBAR - return rune(0x2aa3), true - case "Lt": - // MUCH LESS-THAN - return rune(0x226a), 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 "Mapfrom": - // LEFTWARDS DOUBLE ARROW FROM BAR - return rune(0x2906), true - case "Mapto": - // RIGHTWARDS DOUBLE ARROW FROM BAR - return rune(0x2907), true - case "Map": - // RIGHTWARDS TWO-HEADED ARROW FROM BAR - return rune(0x2905), 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 "NotDoubleVerticalBar": - // NOT PARALLEL TO - return rune(0x2226), true - case "NotElement": - // NOT AN ELEMENT OF - return rune(0x2209), true - case "NotEqualTilde": - // MINUS TILDE with slash - return rune(0x2242), true - case "NotEqual": - // NOT EQUAL TO - return rune(0x2260), true - case "NotExists": - // THERE DOES NOT EXIST - return rune(0x2204), true - case "NotHumpDownHump": - // GEOMETRICALLY EQUIVALENT TO with slash - return rune(0x224e), true - case "NotHumpEqual": - // DIFFERENCE BETWEEN with slash - return rune(0x224f), true - case "NotLessGreater": - // NEITHER LESS-THAN NOR GREATER-THAN - return rune(0x2278), true - case "NotReverseElement": - // DOES NOT CONTAIN AS MEMBER - return rune(0x220c), 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 "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 "NotGreaterFullEqual": - // GREATER-THAN OVER EQUAL TO with slash - return rune(0x2267), true - case "NotGreaterGreater": - // MUCH GREATER THAN with slash - return rune(0x226b), true - case "NotGreaterSlantEqual": - // GREATER-THAN OR SLANTED EQUAL TO with slash - return rune(0x2a7e), true - case "NotGreater": - // NOT GREATER-THAN - return rune(0x226f), true - case "NotGreaterEqual": - // NEITHER GREATER-THAN NOR EQUAL TO - return rune(0x2271), true - case "NotGreaterLess": - // NEITHER GREATER-THAN NOR LESS-THAN - return rune(0x2279), true - case "NotGreaterTilde": - // NEITHER GREATER-THAN NOR EQUIVALENT TO - return rune(0x2275), true - case "NotLeftTriangleBar": - // LEFT TRIANGLE BESIDE VERTICAL BAR with slash - return rune(0x29cf), true - case "NotLeftTriangle": - // NOT NORMAL SUBGROUP OF - return rune(0x22ea), true - case "NotLeftTriangleEqual": - // NOT NORMAL SUBGROUP OF OR EQUAL TO - return rune(0x22ec), 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 "NotLess": - // NOT LESS-THAN - return rune(0x226e), true - case "NotLessEqual": - // NEITHER LESS-THAN NOR EQUAL TO - return rune(0x2270), 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 "NotPrecedesEqual": - // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash - return rune(0x2aaf), true - case "NotPrecedes": - // DOES NOT PRECEDE - return rune(0x2280), true - case "NotPrecedesSlantEqual": - // DOES NOT PRECEDE OR EQUAL - return rune(0x22e0), true - case "NotRightTriangleBar": - // VERTICAL BAR BESIDE RIGHT TRIANGLE with slash - return rune(0x29d0), true - case "NotRightTriangle": - // DOES NOT CONTAIN AS NORMAL SUBGROUP - return rune(0x22eb), 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 "NotSucceedsEqual": - // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash - return rune(0x2ab0), true - case "NotSucceedsTilde": - // SUCCEEDS OR EQUIVALENT TO with slash - return rune(0x227f), true - case "NotSucceeds": - // DOES NOT SUCCEED - return rune(0x2281), true - case "NotSucceedsSlantEqual": - // DOES NOT SUCCEED OR EQUAL - return rune(0x22e1), 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 "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 "Ograve": - // LATIN CAPITAL LETTER O WITH GRAVE - return rune(0xd2), true - case "Ogr": - // GREEK CAPITAL LETTER OMICRON - return rune(0x039f), 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 "Product": - // N-ARY PRODUCT - return rune(0x220f), true - case "Proportional": - // PROPORTIONAL TO - return rune(0x221d), true - case "Proportion": - // PROPORTION - return rune(0x2237), true - case "Pr": - // DOUBLE PRECEDES - return rune(0x2abb), true - case "PrecedesEqual": - // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN - return rune(0x2aaf), true - case "Precedes": - // PRECEDES - return rune(0x227a), 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 "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 "ReverseElement": - // CONTAINS AS MEMBER - return rune(0x220b), true - case "ReverseEquilibrium": - // LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON - return rune(0x21cb), true - case "Re": - // BLACK-LETTER CAPITAL R - return rune(0x211c), 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 "RightArrowBar": - // RIGHTWARDS ARROW TO BAR - return rune(0x21e5), true - case "RightArrow": - // RIGHTWARDS ARROW - return rune(0x2192), 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 "RightTeeArrow": - // RIGHTWARDS ARROW FROM BAR - return rune(0x21a6), true - case "RightTeeVector": - // RIGHTWARDS HARPOON WITH BARB UP FROM BAR - return rune(0x295b), true - case "RightTee": - // RIGHT TACK - return rune(0x22a2), true - case "RightTriangleBar": - // VERTICAL BAR BESIDE RIGHT TRIANGLE - return rune(0x29d0), true - case "RightTriangle": - // CONTAINS AS NORMAL SUBGROUP - return rune(0x22b3), 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 "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 "Square": - // WHITE SQUARE - return rune(0x25a1), 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 "SupersetEqual": - // SUPERSET OF OR EQUAL TO - return rune(0x2287), true - case "Sup": - // DOUBLE SUPERSET - return rune(0x22d1), true - case "Superset": - // SUPERSET OF - return rune(0x2283), 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 "Uarrocir": - // UPWARDS TWO-HEADED ARROW FROM SMALL CIRCLE - return rune(0x2949), true - case "Uarr": - // UPWARDS TWO HEADED ARROW - return rune(0x219f), 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 "Ugrave": - // LATIN CAPITAL LETTER U WITH GRAVE - return rune(0xd9), true - case "Ugr": - // GREEK CAPITAL LETTER UPSILON - return rune(0x03a5), 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 "Upsilon": - // GREEK CAPITAL LETTER UPSILON - return rune(0x03a5), true - case "Upsi": - // GREEK UPSILON WITH HOOK SYMBOL - return rune(0x03d2), 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 "Vdashl": - // LONG DASH FROM LEFT MEMBER OF DOUBLE VERTICAL - return rune(0x2ae6), true - case "Vdash": - // FORCES - return rune(0x22a9), 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 "acE": - // INVERTED LAZY S with double underline - return rune(0x223e), true - case "acd": - // SINE WAVE - return rune(0x223f), true - case "acute": - // ACUTE ACCENT - return rune(0xb4), true - case "ac": - // INVERTED LAZY S - return rune(0x223e), true - case "acirc": - // LATIN SMALL LETTER A WITH CIRCUMFLEX - return rune(0xe2), true - case "actuary": - // COMBINING ANNUITY SYMBOL - return rune(0x20e7), 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 "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 "and": - // LOGICAL AND - return rune(0x2227), 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 "angles": - // ANGLE WITH S INSIDE - return rune(0x299e), true - case "angle": - // ANGLE - return rune(0x2220), 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 "angmsd": - // MEASURED ANGLE - return rune(0x2221), true - case "angrtvbd": - // MEASURED RIGHT ANGLE WITH DOT - return rune(0x299d), true - case "angrtvb": - // RIGHT ANGLE WITH ARC - return rune(0x22be), 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 "ang": - // ANGLE - return rune(0x2220), true - case "ang90": - // RIGHT ANGLE - return rune(0x221f), true - case "angrt": - // RIGHT ANGLE - return rune(0x221f), 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 "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 "approxeq": - // ALMOST EQUAL OR EQUAL TO - return rune(0x224a), true - case "approx": - // ALMOST EQUAL TO - return rune(0x2248), true - case "ap": - // ALMOST EQUAL TO - return rune(0x2248), true - case "apos": - // APOSTROPHE - return rune(0x27), 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 "astb": - // SQUARED ASTERISK - return rune(0x29c6), true - case "ast": - // ASTERISK - return rune(0x2a), true - case "asympeq": - // EQUIVALENT TO - return rune(0x224d), true - case "asymp": - // ALMOST EQUAL TO - return rune(0x2248), 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.gammad": - // MATHEMATICAL BOLD SMALL DIGAMMA - return rune(0x01d7cb), true - case "b.gamma": - // MATHEMATICAL BOLD SMALL GAMMA - return rune(0x01d6c4), 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.sigmav": - // MATHEMATICAL BOLD SMALL FINAL SIGMA - return rune(0x01d6d3), true - case "b.sigma": - // MATHEMATICAL BOLD SMALL SIGMA - return rune(0x01d6d4), 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 "backsimeq": - // REVERSED TILDE EQUALS - return rune(0x22cd), true - case "backsim": - // REVERSED TILDE - return rune(0x223d), 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 "because": - // BECAUSE - return rune(0x2235), true - case "becaus": - // 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 "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 "blacktriangle": - // BLACK UP-POINTING SMALL TRIANGLE - return rune(0x25b4), 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 "bnequiv": - // IDENTICAL TO with reverse slash - return rune(0x2261), true - case "bne": - // EQUALS SIGN with reverse slash - return rune(0x3d), 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 "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 "boxH": - // BOX DRAWINGS DOUBLE HORIZONTAL - return rune(0x2550), 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 "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 "boxV": - // BOX DRAWINGS DOUBLE VERTICAL - return rune(0x2551), 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 "boxhU": - // BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE - return rune(0x2568), 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 "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 "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 "boxvl": - // BOX DRAWINGS LIGHT VERTICAL AND LEFT - return rune(0x2524), true - case "boxvr": - // BOX DRAWINGS LIGHT VERTICAL AND RIGHT - return rune(0x251c), 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 "boxvh": - // BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL - return rune(0x253c), 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 "bsolb": - // SQUARED FALLING DIAGONAL SLASH - return rune(0x29c5), true - case "bsolhsub": - // REVERSE SOLIDUS PRECEDING SUBSET - return rune(0x27c8), true - case "bsol": - // REVERSE SOLIDUS - return rune(0x5c), 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 "bullet": - // BULLET - return rune(0x2022), true - case "bull": - // 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 "centerdot": - // MIDDLE DOT - return rune(0xb7), true - case "cent": - // CENT SIGN - return rune(0xa2), 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 "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 "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 "cire": - // RING EQUAL TO - return rune(0x2257), 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 "circledR": - // REGISTERED SIGN - return rune(0xae), true - case "cirdarr": - // WHITE CIRCLE WITH DOWN ARROW - return rune(0x29ec), 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 "colone": - // COLON EQUALS - return rune(0x2254), true - case "coloneq": - // COLON EQUALS - return rune(0x2254), true - case "colon": - // COLON - return rune(0x3a), true - case "commat": - // COMMERCIAL AT - return rune(0x40), true - case "comma": - // COMMA - return rune(0x2c), 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 "copysr": - // SOUND RECORDING COPYRIGHT - return rune(0x2117), true - case "copy": - // COPYRIGHT SIGN - return rune(0xa9), 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 "darr2": - // DOWNWARDS PAIRED ARROWS - return rune(0x21ca), true - case "darr": - // DOWNWARDS ARROW - return rune(0x2193), true - case "darrb": - // DOWNWARDS ARROW TO BAR - return rune(0x2913), true - case "darrln": - // DOWNWARDS ARROW WITH HORIZONTAL STROKE - return rune(0x2908), true - case "dashv": - // LEFT TACK - return rune(0x22a3), true - case "dash": - // HYPHEN - return rune(0x2010), true - case "dashV": - // DOUBLE VERTICAL BAR LEFT TURNSTILE - return rune(0x2ae3), 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 "ddarr": - // DOWNWARDS PAIRED ARROWS - return rune(0x21ca), true - case "dd": - // DOUBLE-STRUCK ITALIC SMALL D - return rune(0x2146), true - case "ddagger": - // DOUBLE DAGGER - return rune(0x2021), 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 "divideontimes": - // DIVISION TIMES - return rune(0x22c7), true - case "divonx": - // DIVISION TIMES - return rune(0x22c7), true - case "div": - // DIVISION SIGN - return rune(0xf7), true - case "divide": - // DIVISION SIGN - return rune(0xf7), 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 "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 "dot": - // DOT ABOVE - return rune(0x02d9), 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 "dtrif": - // BLACK DOWN-POINTING SMALL TRIANGLE - return rune(0x25be), true - case "dtri": - // WHITE DOWN-POINTING SMALL TRIANGLE - return rune(0x25bf), 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 "egr": - // GREEK SMALL LETTER EPSILON - return rune(0x03b5), 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 "eg": - // DOUBLE-LINE EQUAL TO OR GREATER-THAN - return rune(0x2a9a), true - case "egrave": - // LATIN SMALL LETTER E WITH GRAVE - return rune(0xe8), 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 "el": - // DOUBLE-LINE EQUAL TO OR LESS-THAN - return rune(0x2a99), 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 "emsp13": - // THREE-PER-EM SPACE - return rune(0x2004), true - case "emsp14": - // FOUR-PER-EM SPACE - return rune(0x2005), true - case "emsp": - // EM SPACE - return rune(0x2003), 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 "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 "epsi": - // GREEK SMALL LETTER EPSILON - return rune(0x03b5), 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 "gammad": - // GREEK SMALL LETTER DIGAMMA - return rune(0x03dd), true - case "gamma": - // GREEK SMALL LETTER GAMMA - return rune(0x03b3), 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 "gesl": - // GREATER-THAN slanted EQUAL TO OR LESS-THAN - return rune(0x22db), 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 "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 "gneqq": - // GREATER-THAN BUT NOT EQUAL TO - return rune(0x2269), 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 "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 "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 "gt": - // GREATER-THAN SIGN - return rune(0x3e), 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 "harrw": - // LEFT RIGHT WAVE ARROW - return rune(0x21ad), true - case "harr": - // LEFT RIGHT ARROW - return rune(0x2194), true - case "harrcir": - // LEFT RIGHT ARROW THROUGH SMALL CIRCLE - return rune(0x2948), 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 "iiint": - // TRIPLE INTEGRAL - return rune(0x222d), true - case "ii": - // DOUBLE-STRUCK ITALIC SMALL I - return rune(0x2148), true - case "iiiint": - // QUADRUPLE INTEGRAL OPERATOR - return rune(0x2a0c), 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 "isinsv": - // ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE - return rune(0x22f3), true - case "isins": - // SMALL ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE - return rune(0x22f4), 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 "kappav": - // GREEK KAPPA SYMBOL - return rune(0x03f0), true - case "kappa": - // GREEK SMALL LETTER KAPPA - return rune(0x03ba), 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 "larr2": - // LEFTWARDS PAIRED ARROWS - return rune(0x21c7), true - case "larrb": - // LEFTWARDS ARROW TO BAR - return rune(0x21e4), true - case "larrhk": - // LEFTWARDS ARROW WITH HOOK - return rune(0x21a9), true - case "larrlp": - // LEFTWARDS ARROW WITH LOOP - return rune(0x21ab), true - case "larrtl": - // LEFTWARDS ARROW WITH TAIL - return rune(0x21a2), true - case "larr": - // LEFTWARDS ARROW - return rune(0x2190), 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 "larrpl": - // LEFT-SIDE ARC ANTICLOCKWISE ARROW - return rune(0x2939), true - case "larrsim": - // LEFTWARDS ARROW ABOVE TILDE OPERATOR - return rune(0x2973), true - case "latail": - // LEFTWARDS ARROW-TAIL - return rune(0x2919), true - case "lat": - // LARGER THAN - return rune(0x2aab), 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 "ldquor": - // DOUBLE LOW-9 QUOTATION MARK - return rune(0x201e), true - case "ldquo": - // LEFT DOUBLE QUOTATION MARK - return rune(0x201c), 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 "leftarrowtail": - // LEFTWARDS ARROW WITH TAIL - return rune(0x21a2), true - case "leftarrow": - // LEFTWARDS ARROW - return rune(0x2190), 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 "leftrightarrows": - // LEFTWARDS ARROW OVER RIGHTWARDS ARROW - return rune(0x21c6), true - case "leftrightarrow": - // LEFT RIGHT ARROW - return rune(0x2194), true - case "leftrightharpoons": - // LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON - return rune(0x21cb), true - case "leftrightsquigarrow": - // LEFT RIGHT WAVE ARROW - return rune(0x21ad), true - case "le": - // LESS-THAN OR EQUAL TO - return rune(0x2264), 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 "lesg": - // LESS-THAN slanted EQUAL TO OR GREATER-THAN - return rune(0x22da), 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 "lessgtr": - // LESS-THAN OR GREATER-THAN - return rune(0x2276), true - case "lesssim": - // LESS-THAN OR EQUIVALENT TO - return rune(0x2272), 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 "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 "lesseqqgtr": - // LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN - return rune(0x2a8b), 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 "llarr": - // LEFTWARDS PAIRED ARROWS - return rune(0x21c7), true - case "ll": - // MUCH LESS-THAN - return rune(0x226a), 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 "lltrif": - // BLACK LOWER LEFT TRIANGLE - return rune(0x25e3), true - case "lltri": - // LOWER LEFT TRIANGLE - return rune(0x25fa), 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 "lneqq": - // LESS-THAN BUT NOT EQUAL TO - return rune(0x2268), 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 "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 "lpargt": - // SPHERICAL ANGLE OPENING LEFT - return rune(0x29a0), true - case "lparlt": - // LEFT ARC LESS-THAN BRACKET - return rune(0x2993), true - case "lpar": - // LEFT PARENTHESIS - return rune(0x28), true - case "lrarr2": - // LEFTWARDS ARROW OVER RIGHTWARDS ARROW - return rune(0x21c6), true - case "lrarr": - // 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 "lsquor": - // SINGLE LOW-9 QUOTATION MARK - return rune(0x201a), true - case "lsquo": - // LEFT SINGLE QUOTATION MARK - return rune(0x2018), true - case "lstrok": - // LATIN SMALL LETTER L WITH STROKE - return rune(0x0142), 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 "ltrie": - // NORMAL SUBGROUP OF OR EQUAL TO - return rune(0x22b4), true - case "ltrif": - // BLACK LEFT-POINTING SMALL TRIANGLE - return rune(0x25c2), true - case "ltri": - // WHITE LEFT-POINTING SMALL TRIANGLE - return rune(0x25c3), true - case "ltrivb": - // LEFT TRIANGLE BESIDE VERTICAL BAR - return rune(0x29cf), true - case "lt": - // LESS-THAN SIGN - return rune(0x3c), 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 "mapstodown": - // DOWNWARDS ARROW FROM BAR - return rune(0x21a7), true - case "mapsto": - // RIGHTWARDS ARROW FROM BAR - return rune(0x21a6), true - case "map": - // RIGHTWARDS ARROW FROM BAR - return rune(0x21a6), 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 "multimap": - // MULTIMAP - return rune(0x22b8), true - case "mumap": - // MULTIMAP - return rune(0x22b8), true - case "mu": - // GREEK SMALL LETTER MU - return rune(0x03bc), 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 "nGtv": - // MUCH GREATER THAN with slash - return rune(0x226b), true - case "nGt": - // MUCH GREATER THAN with vertical line - 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 "nLtv": - // MUCH LESS THAN with slash - return rune(0x226a), true - case "nLt": - // MUCH LESS THAN with vertical line - 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 "naturals": - // DOUBLE-STRUCK CAPITAL N - return rune(0x2115), true - case "natur": - // MUSIC NATURAL SIGN - return rune(0x266e), true - case "natural": - // MUSIC NATURAL SIGN - return rune(0x266e), 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 "neArr": - // NORTH EAST DOUBLE ARROW - return rune(0x21d7), true - case "nearrow": - // NORTH EAST ARROW - return rune(0x2197), true - case "nearr": - // NORTH EAST ARROW - return rune(0x2197), true - case "nedot": - // APPROACHES THE LIMIT with slash - return rune(0x2250), true - case "nesim": - // MINUS TILDE with slash - return rune(0x2242), true - case "nexist": - // THERE DOES NOT EXIST - return rune(0x2204), true - case "nexists": - // THERE DOES NOT EXIST - return rune(0x2204), true - case "ne": - // NOT EQUAL TO - return rune(0x2260), true - case "nearhk": - // NORTH EAST ARROW WITH HOOK - return rune(0x2924), 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 "neswsarr": - // NORTH EAST AND SOUTH WEST ARROW - return rune(0x2922), 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 "ngeqq": - // 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 "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 "niv": - // CONTAINS AS MEMBER - return rune(0x220b), true - case "ni": - // CONTAINS AS MEMBER - return rune(0x220b), true - case "nisd": - // CONTAINS WITH LONG HORIZONTAL STROKE - return rune(0x22fa), true - case "nis": - // SMALL CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE - return rune(0x22fc), 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 "nleftarrow": - // LEFTWARDS ARROW WITH STROKE - return rune(0x219a), true - case "nleftrightarrow": - // LEFT RIGHT ARROW WITH STROKE - return rune(0x21ae), true - case "nleqq": - // LESS-THAN OVER EQUAL TO with slash - return rune(0x2266), true - case "nless": - // NOT LESS-THAN - return rune(0x226e), true - case "nle": - // NEITHER LESS-THAN NOR EQUAL TO - return rune(0x2270), true - case "nleq": - // NEITHER LESS-THAN NOR EQUAL TO - return rune(0x2270), 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 "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 "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 "not": - // NOT SIGN - return rune(0xac), true - case "npart": - // PARTIAL DIFFERENTIAL with slash - return rune(0x2202), 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 "npolint": - // LINE INTEGRATION NOT INCLUDING THE POLE - return rune(0x2a14), true - case "nprsim": - // PRECEDES OR EQUIVALENT TO with slash - return rune(0x227e), true - case "npr": - // DOES NOT PRECEDE - return rune(0x2280), true - case "nprcue": - // DOES NOT PRECEDE OR EQUAL - return rune(0x22e0), true - case "nprec": - // DOES NOT PRECEDE - return rune(0x2280), true - case "npre": - // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash - return rune(0x2aaf), true - case "npreceq": - // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash - return rune(0x2aaf), true - case "nrArr": - // RIGHTWARDS DOUBLE ARROW WITH STROKE - return rune(0x21cf), true - case "nrarrw": - // RIGHTWARDS WAVE ARROW with slash - return rune(0x219d), 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 "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 "nscsim": - // SUCCEEDS OR EQUIVALENT TO with slash - return rune(0x227f), 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 "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 "nsubset": - // SUBSET OF with vertical line - return rune(0x2282), 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 "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 "nsupset": - // SUPERSET OF with vertical line - return rune(0x2283), 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 "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 "numero": - // NUMERO SIGN - return rune(0x2116), true - case "numsp": - // FIGURE SPACE - return rune(0x2007), true - case "nu": - // GREEK SMALL LETTER NU - return rune(0x03bd), true - case "num": - // NUMBER SIGN - return rune(0x23), 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 "nvltrie": - // NORMAL SUBGROUP OF OR EQUAL TO with vertical line - return rune(0x22b4), true - case "nvlt": - // LESS-THAN SIGN with vertical line - return rune(0x3c), 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 "nwarrow": - // NORTH WEST ARROW - return rune(0x2196), true - case "nwarr": - // 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 "orarr": - // CLOCKWISE OPEN CIRCLE ARROW - return rune(0x21bb), true - case "or": - // LOGICAL OR - return rune(0x2228), true - case "orderof": - // SCRIPT SMALL O - return rune(0x2134), true - case "order": - // SCRIPT SMALL O - return rune(0x2134), true - case "ord": - // LOGICAL OR WITH HORIZONTAL DASH - return rune(0x2a5d), 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 "part": - // PARTIAL DIFFERENTIAL - return rune(0x2202), true - case "par": - // PARALLEL TO - return rune(0x2225), true - case "parallel": - // PARALLEL TO - return rune(0x2225), true - case "para": - // PILCROW SIGN - return rune(0xb6), true - case "parsim": - // PARALLEL WITH TILDE OPERATOR - return rune(0x2af3), true - case "parsl": - // DOUBLE SOLIDUS OPERATOR - return rune(0x2afd), 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 "phis": - // GREEK PHI SYMBOL - return rune(0x03d5), true - case "phiv": - // GREEK PHI SYMBOL - return rune(0x03d5), true - case "phi": - // GREEK SMALL LETTER PHI - return rune(0x03c6), true - case "phmmat": - // SCRIPT CAPITAL M - return rune(0x2133), true - case "phone": - // BLACK TELEPHONE - return rune(0x260e), true - case "pitchfork": - // PITCHFORK - return rune(0x22d4), true - case "piv": - // GREEK PI SYMBOL - return rune(0x03d6), true - case "pi": - // GREEK SMALL LETTER PI - return rune(0x03c0), 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 "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 "plus": - // PLUS SIGN - return rune(0x2b), 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 "prod": - // N-ARY PRODUCT - return rune(0x220f), true - case "prop": - // PROPORTIONAL TO - return rune(0x221d), true - case "propto": - // PROPORTIONAL TO - return rune(0x221d), 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 "prec": - // PRECEDES - return rune(0x227a), true - case "preccurlyeq": - // PRECEDES OR EQUAL TO - return rune(0x227c), true - case "precnsim": - // PRECEDES BUT NOT EQUIVALENT TO - return rune(0x22e8), true - case "precsim": - // PRECEDES OR EQUIVALENT TO - return rune(0x227e), true - case "pre": - // PRECEDES ABOVE SINGLE-LINE EQUALS SIGN - return rune(0x2aaf), true - case "precapprox": - // PRECEDES ABOVE ALMOST EQUAL TO - return rune(0x2ab7), 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 "primes": - // DOUBLE-STRUCK CAPITAL P - return rune(0x2119), true - case "prime": - // PRIME - return rune(0x2032), 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 "profalar": - // ALL AROUND-PROFILE - return rune(0x232e), true - case "profline": - // ARC - return rune(0x2312), true - case "profsurf": - // SEGMENT - return rune(0x2313), 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 "questeq": - // QUESTIONED EQUAL TO - return rune(0x225f), true - case "quest": - // QUESTION MARK - return rune(0x3f), 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 "rarr2": - // RIGHTWARDS PAIRED ARROWS - return rune(0x21c9), true - case "rarr3": - // THREE RIGHTWARDS ARROWS - return rune(0x21f6), true - case "rarrb": - // RIGHTWARDS ARROW TO BAR - return rune(0x21e5), true - case "rarrhk": - // RIGHTWARDS ARROW WITH HOOK - return rune(0x21aa), true - case "rarrlp": - // RIGHTWARDS ARROW WITH LOOP - return rune(0x21ac), true - case "rarrtl": - // RIGHTWARDS ARROW WITH TAIL - return rune(0x21a3), true - case "rarrw": - // RIGHTWARDS WAVE ARROW - return rune(0x219d), true - case "rarr": - // RIGHTWARDS ARROW - return rune(0x2192), true - case "rarrap": - // RIGHTWARDS ARROW ABOVE ALMOST EQUAL TO - return rune(0x2975), 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 "rarrpl": - // RIGHTWARDS ARROW WITH PLUS BELOW - return rune(0x2945), true - case "rarrsim": - // RIGHTWARDS ARROW ABOVE TILDE OPERATOR - return rune(0x2974), 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 "rdquor": - // RIGHT DOUBLE QUOTATION MARK - return rune(0x201d), true - case "rdquo": - // RIGHT DOUBLE QUOTATION MARK - return rune(0x201d), true - case "rdsh": - // DOWNWARDS ARROW WITH TIP RIGHTWARDS - return rune(0x21b3), true - case "realpart": - // BLACK-LETTER CAPITAL R - return rune(0x211c), true - case "reals": - // DOUBLE-STRUCK CAPITAL R - return rune(0x211d), true - case "real": - // BLACK-LETTER CAPITAL R - return rune(0x211c), true - case "realine": - // SCRIPT CAPITAL R - return rune(0x211b), 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 "rhov": - // GREEK RHO SYMBOL - return rune(0x03f1), true - case "rho": - // GREEK SMALL LETTER RHO - return rune(0x03c1), true - case "rightarrowtail": - // RIGHTWARDS ARROW WITH TAIL - return rune(0x21a3), true - case "rightarrow": - // RIGHTWARDS ARROW - return rune(0x2192), 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 "rlarr2": - // RIGHTWARDS ARROW OVER LEFTWARDS ARROW - return rune(0x21c4), true - case "rlarr": - // 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 "rpargt": - // RIGHT ARC GREATER-THAN BRACKET - return rune(0x2994), true - case "rpar": - // RIGHT PARENTHESIS - return rune(0x29), 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 "rsquor": - // RIGHT SINGLE QUOTATION MARK - return rune(0x2019), true - case "rsquo": - // 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 "rtrie": - // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO - return rune(0x22b5), true - case "rtrif": - // BLACK RIGHT-POINTING SMALL TRIANGLE - return rune(0x25b8), true - case "rtri": - // WHITE RIGHT-POINTING SMALL TRIANGLE - return rune(0x25b9), 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 "scedil": - // LATIN SMALL LETTER S WITH CEDILLA - return rune(0x015f), true - case "sce": - // SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN - return rune(0x2ab0), 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 "sdotb": - // SQUARED DOT OPERATOR - return rune(0x22a1), true - case "sdot": - // DOT OPERATOR - return rune(0x22c5), 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 "searrow": - // SOUTH EAST ARROW - return rune(0x2198), true - case "searr": - // 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 "sfrown": - // FROWN - return rune(0x2322), true - case "sfr": - // MATHEMATICAL FRAKTUR SMALL S - return rune(0x01d530), 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 "solbar": - // APL FUNCTIONAL SYMBOL SLASH BAR - return rune(0x233f), true - case "solb": - // SQUARED RISING DIAGONAL SLASH - return rune(0x29c4), true - case "sol": - // SOLIDUS - return rune(0x2f), 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 "starf": - // BLACK STAR - return rune(0x2605), true - case "star": - // WHITE STAR - return rune(0x2606), 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 "swarrow": - // SOUTH WEST ARROW - return rune(0x2199), true - case "swarr": - // 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 "thetasym": - // GREEK THETA SYMBOL - return rune(0x03d1), true - case "thetas": - // GREEK SMALL LETTER THETA - return rune(0x03b8), true - case "thetav": - // GREEK THETA SYMBOL - return rune(0x03d1), true - case "theta": - // GREEK SMALL LETTER THETA - return rune(0x03b8), 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 "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 "times": - // MULTIPLICATION SIGN - return rune(0xd7), 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 "topfork": - // PITCHFORK WITH TEE TOP - return rune(0x2ada), true - case "topf": - // MATHEMATICAL DOUBLE-STRUCK SMALL T - return rune(0x01d565), 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 "trianglelefteq": - // NORMAL SUBGROUP OF OR EQUAL TO - return rune(0x22b4), true - case "triangleq": - // DELTA EQUAL TO - return rune(0x225c), true - case "trianglerighteq": - // CONTAINS AS NORMAL SUBGROUP OR EQUAL TO - return rune(0x22b5), 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 "triangleright": - // WHITE RIGHT-POINTING SMALL TRIANGLE - return rune(0x25b9), 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 "uarr2": - // UPWARDS PAIRED ARROWS - return rune(0x21c8), true - case "uarr": - // UPWARDS ARROW - return rune(0x2191), 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 "upsih": - // GREEK UPSILON WITH HOOK SYMBOL - return rune(0x03d2), true - case "upsilon": - // GREEK SMALL LETTER UPSILON - return rune(0x03c5), true - case "upsi": - // 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 "urtrif": - // BLACK UPPER RIGHT TRIANGLE - return rune(0x25e5), true - case "urtri": - // UPPER RIGHT TRIANGLE - return rune(0x25f9), 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 "utrif": - // BLACK UP-POINTING SMALL TRIANGLE - return rune(0x25b4), true - case "utri": - // WHITE UP-POINTING SMALL TRIANGLE - return rune(0x25b5), 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 "vert3": - // TRIPLE VERTICAL BAR BINARY RELATION - return rune(0x2af4), true - case "vert": - // VERTICAL LINE - return rune(0x7c), 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 "wreath": - // WREATH PRODUCT - return rune(0x2240), true - case "wr": - // 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 ef0bab1d0..c1753003e 100644 --- a/core/encoding/hex/hex.odin +++ b/core/encoding/hex/hex.odin @@ -1,9 +1,10 @@ -package hex +package encoding_hex +import "core:io" 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] @@ -14,13 +15,19 @@ encode :: proc(src: []byte, allocator := context.allocator) -> []byte #no_bounds return dst } +encode_into_writer :: proc(dst: io.Writer, src: []byte) -> io.Error { + for v in src { + io.write(dst, {HEXTABLE[v>>4], HEXTABLE[v&0x0f]}) or_return + } + return nil +} -decode :: proc(src: []byte, allocator := context.allocator) -> (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 +76,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/doc.odin b/core/encoding/hxa/doc.odin index 230d6ea66..b696bef7e 100644 --- a/core/encoding/hxa/doc.odin +++ b/core/encoding/hxa/doc.odin @@ -1,83 +1,89 @@ -// Implementation of the HxA 3D asset format -// HxA is a interchangeable graphics asset format. -// Designed by Eskil Steenberg. @quelsolaar / eskil 'at' obsession 'dot' se / www.quelsolaar.com -// -// Author of this Odin package: Ginger Bill -// -// Following comment is copied from the original C-implementation -// --------- -// -Does the world need another Graphics file format? -// Unfortunately, Yes. All existing formats are either too large and complicated to be implemented from -// scratch, or don't have some basic features needed in modern computer graphics. -// -Who is this format for? -// For people who want a capable open Graphics format that can be implemented from scratch in -// a few hours. It is ideal for graphics researchers, game developers or other people who -// wants to build custom graphics pipelines. Given how easy it is to parse and write, it -// should be easy to write utilities that process assets to preform tasks like: generating -// normals, light-maps, tangent spaces, Error detection, GPU optimization, LOD generation, -// and UV mapping. -// -Why store images in the format when there are so many good image formats already? -// Yes there are, but only for 2D RGB/RGBA images. A lot of computer graphics rendering rely -// on 1D, 3D, cube, multilayer, multi channel, floating point bitmap buffers. There almost no -// formats for this kind of data. Also 3D files that reference separate image files rely on -// file paths, and this often creates issues when the assets are moved. By including the -// texture data in the files directly the assets become self contained. -// -Why doesn't the format support ? -// Because the entire point is to make a format that can be implemented. Features like NURBSs, -// Construction history, or BSP trees would make the format too large to serve its purpose. -// The facilities of the formats to store meta data should make the format flexible enough -// for most uses. Adding HxA support should be something anyone can do in a days work. -// -// Structure: -// ---------- -// HxA is designed to be extremely simple to parse, and is therefore based around conventions. It has -// a few basic structures, and depending on how they are used they mean different things. This means -// that you can implement a tool that loads the entire file, modifies the parts it cares about and -// leaves the rest intact. It is also possible to write a tool that makes all data in the file -// editable without the need to understand its use. It is also possible for anyone to use the format -// to store data axillary data. Anyone who wants to store data not covered by a convention can submit -// a convention to extend the format. There should never be a convention for storing the same data in -// two differed ways. -// The data is story in a number of nodes that are stored in an array. Each node stores an array of -// meta data. Meta data can describe anything you want, and a lot of conventions will use meta data -// to store additional information, for things like transforms, lights, shaders and animation. -// Data for Vertices, Corners, Faces, and Pixels are stored in named layer stacks. Each stack consists -// of a number of named layers. All layers in the stack have the same number of elements. Each layer -// describes one property of the primitive. Each layer can have multiple channels and each layer can -// store data of a different type. -// -// HaX stores 3 kinds of nodes -// - Pixel data. -// - Polygon geometry data. -// - Meta data only. -// -// Pixel Nodes stores pixels in a layer stack. A layer may store things like Albedo, Roughness, -// Reflectance, Light maps, Masks, Normal maps, and Displacement. Layers use the channels of the -// layers to store things like color. The length of the layer stack is determined by the type and -// dimensions stored in the -// -// Geometry data is stored in 3 separate layer stacks for: vertex data, corner data and face data. The -// vertex data stores things like verities, blend shapes, weight maps, and vertex colors. The first -// layer in a vertex stack has to be a 3 channel layer named "position" describing the base position -// of the vertices. The corner stack describes data per corner or edge of the polygons. It can be used -// for things like UV, normals, and adjacency. The first layer in a corner stack has to be a 1 channel -// integer layer named "index" describing the vertices used to form polygons. The last value in each -// polygon has a negative - 1 index to indicate the end of the polygon. -// -// Example: -// A quad and a tri with the vertex index: -// [0, 1, 2, 3] [1, 4, 2] -// is stored: -// [0, 1, 2, -4, 1, 4, -3] -// The face stack stores values per face. the length of the face stack has to match the number of -// negative values in the index layer in the corner stack. The face stack can be used to store things -// like material index. -// -// Storage -// ------- -// All data is stored in little endian byte order with no padding. The layout mirrors the structs -// defined below with a few exceptions. All names are stored as a 8-bit unsigned integer indicating -// the length of the name followed by that many characters. Termination is not stored in the file. -// Text strings stored in meta data are stored the same way as names, but instead of a 8-bit unsigned -// integer a 32-bit unsigned integer is used. -package encoding_hxa \ No newline at end of file +/* +Implementation of the HxA 3D asset format +HxA is a interchangeable graphics asset format. +Designed by Eskil Steenberg. @quelsolaar / eskil 'at' obsession 'dot' se / www.quelsolaar.com + +Author of this Odin package: Ginger Bill + +Following comment is copied from the original C-implementation +--------- +- Does the world need another Graphics file format? +Unfortunately, Yes. All existing formats are either too large and complicated to be implemented from +scratch, or don't have some basic features needed in modern computer graphics. + +- Who is this format for? +For people who want a capable open Graphics format that can be implemented from scratch in +a few hours. It is ideal for graphics researchers, game developers or other people who +wants to build custom graphics pipelines. Given how easy it is to parse and write, it +should be easy to write utilities that process assets to preform tasks like: generating +normals, light-maps, tangent spaces, Error detection, GPU optimization, LOD generation, +and UV mapping. + +- Why store images in the format when there are so many good image formats already? +Yes there are, but only for 2D RGB/RGBA images. A lot of computer graphics rendering rely +on 1D, 3D, cube, multilayer, multi channel, floating point bitmap buffers. There almost no +formats for this kind of data. Also 3D files that reference separate image files rely on +file paths, and this often creates issues when the assets are moved. By including the +texture data in the files directly the assets become self contained. + +- Why doesn't the format support ? +Because the entire point is to make a format that can be implemented. Features like NURBSs, +Construction history, or BSP trees would make the format too large to serve its purpose. +The facilities of the formats to store meta data should make the format flexible enough +for most uses. Adding HxA support should be something anyone can do in a days work. + +Structure: +---------- +HxA is designed to be extremely simple to parse, and is therefore based around conventions. It has +a few basic structures, and depending on how they are used they mean different things. This means +that you can implement a tool that loads the entire file, modifies the parts it cares about and +leaves the rest intact. It is also possible to write a tool that makes all data in the file +editable without the need to understand its use. It is also possible for anyone to use the format +to store data axillary data. Anyone who wants to store data not covered by a convention can submit +a convention to extend the format. There should never be a convention for storing the same data in +two differed ways. + +The data is story in a number of nodes that are stored in an array. Each node stores an array of +meta data. Meta data can describe anything you want, and a lot of conventions will use meta data +to store additional information, for things like transforms, lights, shaders and animation. +Data for Vertices, Corners, Faces, and Pixels are stored in named layer stacks. Each stack consists +of a number of named layers. All layers in the stack have the same number of elements. Each layer +describes one property of the primitive. Each layer can have multiple channels and each layer can +store data of a different type. + +HaX stores 3 kinds of nodes +- Pixel data. +- Polygon geometry data. +- Meta data only. + +Pixel Nodes stores pixels in a layer stack. A layer may store things like Albedo, Roughness, +Reflectance, Light maps, Masks, Normal maps, and Displacement. Layers use the channels of the +layers to store things like color. +The length of the layer stack is determined by the type and dimensions stored in the Geometry data +is stored in 3 separate layer stacks for: vertex data, corner data and face data. The +vertex data stores things like verities, blend shapes, weight maps, and vertex colors. The first +layer in a vertex stack has to be a 3 channel layer named "position" describing the base position +of the vertices. The corner stack describes data per corner or edge of the polygons. It can be used +for things like UV, normals, and adjacency. The first layer in a corner stack has to be a 1 channel +integer layer named "index" describing the vertices used to form polygons. The last value in each +polygon has a negative - 1 index to indicate the end of the polygon. + +For Example: + A quad and a tri with the vertex index: + [0, 1, 2, 3] [1, 4, 2] + is stored: + [0, 1, 2, -4, 1, 4, -3] + +The face stack stores values per face. the length of the face stack has to match the number of +negative values in the index layer in the corner stack. The face stack can be used to store things +like material index. + +Storage: +------- +All data is stored in little endian byte order with no padding. The layout mirrors the structs +defined below with a few exceptions. All names are stored as a 8-bit unsigned integer indicating +the length of the name followed by that many characters. Termination is not stored in the file. +Text strings stored in meta data are stored the same way as names, but instead of a 8-bit unsigned +integer a 32-bit unsigned integer is used. +*/ +package encoding_hxa diff --git a/core/encoding/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 8a8636f19..a679946f8 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,16 +108,16 @@ 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 layer.components = read_value(r, u8) or_return type := read_value(r, Layer_Data_Type) or_return - if type > max(type) { + if type > max(Layer_Data_Type) { if r.print_error { fmt.eprintf("HxA Error: file '%s' has layer data type %d. Maximum value is %d\n", r.filename, u8(type), u8(max(Layer_Data_Type))) @@ -170,14 +167,15 @@ 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 } defer file.nodes = file.nodes[:node_count] - for node_idx in 0.. max(Node_Type) { @@ -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..c32b1deb5 --- /dev/null +++ b/core/encoding/ini/ini.odin @@ -0,0 +1,193 @@ +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(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] = 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 + io.write_byte (w, '\n', &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 85eca50b6..f0f0927a1 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -1,12 +1,13 @@ -package json +package encoding_json import "core:mem" import "core:math/bits" -import "core:runtime" +import "base:runtime" import "core:strconv" import "core:strings" import "core:reflect" import "core:io" +import "core:slice" Marshal_Data_Error :: enum { None, @@ -18,38 +19,57 @@ Marshal_Error :: union #shared_nil { io.Error, } -// careful with MJSON maps & non quotes usage as keys without whitespace will lead to bad results +// careful with MJSON maps & non quotes usage as keys with whitespace will lead to bad results Marshal_Options :: struct { // output based on spec spec: Specification, - // use line breaks & tab|spaces - pretty: bool, + // Use line breaks & tabs/spaces + pretty: bool, - // spacing + // Use spaces for indentation instead of tabs use_spaces: bool, + + // Given use_spaces true, use this many spaces per indent level. 0 means 4 spaces. spaces: int, - // state - indentation: int, + // Output uint as hex in JSON5 & MJSON + write_uint_as_hex: bool, - // option to output uint in JSON5 & MJSON - write_uint_as_hex: bool, - - // mjson output options + // If spec is MJSON and this is true, then keys will be quoted. + // + // WARNING: If your keys contain whitespace and this is false, then the + // output will be bad. mjson_keys_use_quotes: bool, + + // If spec is MJSON and this is true, then use '=' as delimiter between + // keys and values, otherwise ':' is used. mjson_keys_use_equal_sign: bool, - // mjson state + // When outputting a map, sort the output by key. + // + // NOTE: This will temp allocate and sort a list for each map. + sort_maps_by_key: bool, + + // Output enum value's name instead of its underlying value. + // + // NOTE: If a name isn't found it'll use the underlying value. + use_enum_names: bool, + + // Internal state + indentation: int, mjson_skipped_first_braces_start: bool, 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) } + + // temp guard in case we are sorting map keys, which will use temp allocations + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator) opt := opt marshal_to_builder(&b, v, &opt) or_return @@ -80,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: @@ -203,30 +192,26 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: case runtime.Type_Info_Simd_Vector: return .Unsupported_Type - - case runtime.Type_Info_Relative_Pointer: - return .Unsupported_Type - - case runtime.Type_Info_Relative_Multi_Pointer: - return .Unsupported_Type case runtime.Type_Info_Matrix: return .Unsupported_Type + case runtime.Type_Info_Bit_Field: + return .Unsupported_Type + case runtime.Type_Info_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: map_cap := uintptr(runtime.map_cap(m^)) ks, vs, hs, _, _ := runtime.map_kvh_data_dynamic(m^, info.map_info) - i := 0 - for bucket_index in 0.. bool { return i.key < j.key }) + + for s, i in sorted { + 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 + } } } opt_write_end(w, opt, '}') or_return case runtime.Type_Info_Struct: - opt_write_start(w, opt, '{') or_return - - for name, i in info.names { - opt_write_iteration(w, opt, i) or_return - if json_name := string(reflect.struct_tag_get(auto_cast info.tags[i], "json")); json_name != "" { - opt_write_key(w, opt, json_name) or_return - } else { - opt_write_key(w, opt, name) or_return + is_omitempty :: proc(v: any) -> bool { + v := v + if v == nil { + return true } - - id := info.types[i].id - data := rawptr(uintptr(v.data) + info.offsets[i]) - marshal_to_writer(w, any{data, id}, opt) or_return + ti := runtime.type_info_core(type_info_of(v.id)) + #partial switch info in ti.variant { + case runtime.Type_Info_String: + switch x in v { + case string: + return x == "" + case cstring: + return x == nil || x == "" + } + case runtime.Type_Info_Any: + return v.(any) == nil + case runtime.Type_Info_Type_Id: + return v.(typeid) == nil + case runtime.Type_Info_Pointer, + runtime.Type_Info_Multi_Pointer, + runtime.Type_Info_Procedure: + return (^rawptr)(v.data)^ == nil + case runtime.Type_Info_Dynamic_Array: + return (^runtime.Raw_Dynamic_Array)(v.data).len == 0 + case runtime.Type_Info_Slice: + 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 false } + 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) + 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": + omitempty = true + } + } + + id := info.types[i].id + data := rawptr(uintptr(v.data) + info.offsets[i]) + the_value := any{data, id} + + if omitempty && is_omitempty(the_value) { + continue + } + + 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 { + // Marshal the fields of 'using _: T' fields directly into the parent struct + if info.usings[i] && name == "_" { + marshal_struct_fields(w, the_value, opt) or_return + continue + } else { + opt_write_key(w, opt, name) or_return + } + } + + + marshal_to_writer(w, the_value, opt) or_return + } + return + } + + opt_write_start(w, opt, '{') or_return + marshal_struct_fields(w, v, opt) or_return opt_write_end(w, opt, '}') or_return case runtime.Type_Info_Union: + if len(info.variants) == 0 || v.data == nil { + io.write_string(w, "null") or_return + return nil + } + tag_ptr := uintptr(v.data) + info.tag_offset tag_any := any{rawptr(tag_ptr), info.tag_type.id} @@ -333,15 +444,27 @@ 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: - return marshal_to_writer(w, any{v.data, info.base.id}, opt) + if !opt.use_enum_names || len(info.names) == 0 { + return marshal_to_writer(w, any{v.data, info.base.id}, opt) + } else { + name, found := reflect.enum_name_from_value_any(v) + if found { + return marshal_to_writer(w, name, opt) + } else { + return marshal_to_writer(w, any{v.data, info.base.id}, opt) + } + } case runtime.Type_Info_Bit_Set: is_bit_set_different_endian_to_platform :: proc(ti: ^runtime.Type_Info) -> bool { @@ -391,8 +514,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 @@ -424,8 +545,9 @@ opt_write_key :: proc(w: io.Writer, opt: ^Marshal_Options, name: string) -> (err // insert start byte and increase indentation on pretty opt_write_start :: proc(w: io.Writer, opt: ^Marshal_Options, c: byte) -> (err: io.Error) { - // skip mjson starting braces - if opt.spec == .MJSON && !opt.mjson_skipped_first_braces_start { + // Skip MJSON starting braces. We make sure to only do this for c == '{', + // skipping a starting '[' is not allowed. + if opt.spec == .MJSON && !opt.mjson_skipped_first_braces_start && opt.indentation == 0 && c == '{' { opt.mjson_skipped_first_braces_start = true return } @@ -441,10 +563,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 { @@ -454,8 +576,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 @@ -473,11 +595,9 @@ opt_write_iteration :: proc(w: io.Writer, opt: ^Marshal_Options, iteration: int) // decrease indent, write spacing and insert end byte opt_write_end :: proc(w: io.Writer, opt: ^Marshal_Options, c: byte) -> (err: io.Error) { - if opt.spec == .MJSON && opt.mjson_skipped_first_braces_start && !opt.mjson_skipped_first_braces_end { - if opt.indentation == 0 { - opt.mjson_skipped_first_braces_end = true - return - } + if opt.spec == .MJSON && opt.mjson_skipped_first_braces_start && !opt.mjson_skipped_first_braces_end && opt.indentation == 0 && c == '}' { + opt.mjson_skipped_first_braces_end = true + return } opt.indentation -= 1 @@ -510,3 +630,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 6faaf3f32..38f71edf6 100644 --- a/core/encoding/json/parser.odin +++ b/core/encoding/json/parser.odin @@ -1,4 +1,4 @@ -package json +package encoding_json import "core:mem" import "core:unicode/utf8" @@ -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 @@ -204,8 +204,8 @@ parse_array :: proc(p: ^Parser) -> (value: Value, err: Error) { } @(private) -bytes_make :: proc(size, alignment: int, allocator: mem.Allocator) -> (bytes: []byte, err: Error) { - b, berr := mem.alloc_bytes(size, alignment, allocator) +bytes_make :: proc(size, alignment: int, allocator: mem.Allocator, loc := #caller_location) -> (bytes: []byte, err: Error) { + b, berr := mem.alloc_bytes(size, alignment, allocator, loc) if berr != nil { if berr == .Out_Of_Memory { err = .Out_Of_Memory @@ -217,9 +217,9 @@ bytes_make :: proc(size, alignment: int, allocator: mem.Allocator) -> (bytes: [] return } -clone_string :: proc(s: string, allocator: mem.Allocator) -> (str: string, err: Error) { +clone_string :: proc(s: string, allocator: mem.Allocator, loc := #caller_location) -> (str: string, err: Error) { n := len(s) - b := bytes_make(n+1, 1, allocator) or_return + b := bytes_make(n+1, 1, allocator, loc) or_return copy(b, s) if len(b) > n { b[n] = 0 @@ -228,38 +228,39 @@ clone_string :: proc(s: string, allocator: mem.Allocator) -> (str: string, err: 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,16 +282,16 @@ 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 } // IMPORTANT NOTE(bill): unquote_string assumes a mostly valid string -unquote_string :: proc(token: Token, spec: Specification, allocator := context.allocator) -> (value: string, err: Error) { +unquote_string :: proc(token: Token, spec: Specification, allocator := context.allocator, loc := #caller_location) -> (value: string, err: Error) { get_u2_rune :: proc(s: string) -> rune { if len(s) < 4 || s[0] != '\\' || s[1] != 'x' { return -1 @@ -359,7 +360,7 @@ unquote_string :: proc(token: Token, spec: Specification, allocator := context.a i += w } if i == len(s) { - return clone_string(s, allocator) + return clone_string(s, allocator, loc) } b := bytes_make(len(s) + 2*utf8.UTF_MAX, 1, allocator) or_return @@ -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/tokenizer.odin b/core/encoding/json/tokenizer.odin index a406a73a5..e46d879a7 100644 --- a/core/encoding/json/tokenizer.odin +++ b/core/encoding/json/tokenizer.odin @@ -1,4 +1,4 @@ -package json +package encoding_json import "core:unicode/utf8" @@ -259,6 +259,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) { skip_digits(t) } if t.r == 'e' || t.r == 'E' { + token.kind = .Float switch r := next_rune(t); r { case '+', '-': next_rune(t) @@ -485,7 +486,7 @@ is_valid_string_literal :: proc(str: string, spec: Specification) -> bool { case '"': // okay case '\'': - if spec != .JSON { + if spec == .JSON { return false } // okay diff --git a/core/encoding/json/types.odin b/core/encoding/json/types.odin index 089fd9c9b..41eb21377 100644 --- a/core/encoding/json/types.odin +++ b/core/encoding/json/types.odin @@ -1,4 +1,6 @@ -package json +package encoding_json + +import "core:strings" /* JSON @@ -87,21 +89,44 @@ 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) } +} + +clone_value :: proc(value: Value, allocator := context.allocator) -> Value { + context.allocator = allocator + + #partial switch &v in value { + case Object: + new_o := make(Object, len(v)) + for key, elem in v { + new_o[strings.clone(key)] = clone_value(elem) + } + return new_o + case Array: + new_a := make(Array, len(v)) + for elem, idx in v { + new_a[idx] = clone_value(elem) + } + return new_a + case String: + return strings.clone(v) + } + + return value } \ No newline at end of file diff --git a/core/encoding/json/unmarshal.odin b/core/encoding/json/unmarshal.odin index 678f2dcfa..57371e360 100644 --- a/core/encoding/json/unmarshal.odin +++ b/core/encoding/json/unmarshal.odin @@ -1,11 +1,12 @@ -package json +package encoding_json import "core:mem" import "core:math" import "core:reflect" import "core:strconv" import "core:strings" -import "core:runtime" +import "base:runtime" +import "base:intrinsics" Unmarshal_Data_Error :: enum { Invalid_Data, @@ -115,7 +116,30 @@ assign_int :: proc(val: any, i: $T) -> bool { case int: dst = int (i) case uint: dst = uint (i) case uintptr: dst = uintptr(i) - case: return false + case: + ti := type_info_of(v.id) + if _, ok := ti.variant.(runtime.Type_Info_Bit_Set); ok { + do_byte_swap := !reflect.bit_set_is_big_endian(v) + switch ti.size * 8 { + case 0: // no-op. + case 8: + x := (^u8)(v.data) + x^ = u8(i) + case 16: + x := (^u16)(v.data) + x^ = do_byte_swap ? intrinsics.byte_swap(u16(i)) : u16(i) + case 32: + x := (^u32)(v.data) + x^ = do_byte_swap ? intrinsics.byte_swap(u32(i)) : u32(i) + case 64: + x := (^u64)(v.data) + x^ = do_byte_swap ? intrinsics.byte_swap(u64(i)) : u64(i) + case: + panic("unknown bit_size size") + } + return true + } + return false } return true } @@ -137,9 +161,9 @@ assign_float :: proc(val: any, f: $T) -> bool { case complex64: dst = complex(f32(f), 0) case complex128: dst = complex(f64(f), 0) - case quaternion64: dst = quaternion(f16(f), 0, 0, 0) - case quaternion128: dst = quaternion(f32(f), 0, 0, 0) - case quaternion256: dst = quaternion(f64(f), 0, 0, 0) + case quaternion64: dst = quaternion(w=f16(f), x=0, y=0, z=0) + case quaternion128: dst = quaternion(w=f32(f), x=0, y=0, z=0) + case quaternion256: dst = quaternion(w=f64(f), x=0, y=0, z=0) case: return false } @@ -148,20 +172,33 @@ assign_float :: proc(val: any, f: $T) -> bool { @(private) -unmarshal_string_token :: proc(p: ^Parser, val: any, str: string, ti: ^reflect.Type_Info) -> bool { +unmarshal_string_token :: proc(p: ^Parser, val: any, str: string, ti: ^reflect.Type_Info) -> (ok: bool, err: Error) { val := val switch &dst in val { case string: dst = str - return true + return true, nil case cstring: if str == "" { - dst = strings.clone_to_cstring("", p.allocator) + a_err: runtime.Allocator_Error + dst, a_err = strings.clone_to_cstring("", p.allocator) + #partial switch a_err { + case nil: + // okay + case .Out_Of_Memory: + err = .Out_Of_Memory + case: + err = .Invalid_Allocator + } + if err != nil { + return + } } else { // NOTE: This is valid because 'clone_string' appends a NUL terminator dst = cstring(raw_data(str)) } - return true + ok = true + return } #partial switch variant in ti.variant { @@ -169,31 +206,37 @@ unmarshal_string_token :: proc(p: ^Parser, val: any, str: string, ti: ^reflect.T for name, i in variant.names { if name == str { assign_int(val, variant.values[i]) - return true + return true, nil } } // TODO(bill): should this be an error or not? - return true + return true, nil case reflect.Type_Info_Integer: - i := strconv.parse_i128(str) or_return + i, pok := strconv.parse_i128(str) + if !pok { + return false, nil + } if assign_int(val, i) { - return true + return true, nil } if assign_float(val, i) { - return true + return true, nil } case reflect.Type_Info_Float: - f := strconv.parse_f64(str) or_return + f, pok := strconv.parse_f64(str) + if !pok { + return false, nil + } if assign_int(val, f) { - return true + return true, nil } if assign_float(val, f) { - return true + return true, nil } } - return false + return false, nil } @@ -201,20 +244,37 @@ unmarshal_string_token :: proc(p: ^Parser, val: any, str: string, ti: ^reflect.T unmarshal_value :: proc(p: ^Parser, v: any) -> (err: Unmarshal_Error) { UNSUPPORTED_TYPE := Unsupported_Type_Error{v.id, p.curr_token} token := p.curr_token - + v := v ti := reflect.type_info_base(type_info_of(v.id)) - // NOTE: If it's a union with only one variant, then treat it as that variant - if u, ok := ti.variant.(reflect.Type_Info_Union); ok && len(u.variants) == 1 && token.kind != .Null { - variant := u.variants[0] - v.id = variant.id - 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} - assign_int(tag, 1) + if u, ok := ti.variant.(reflect.Type_Info_Union); ok && token.kind != .Null { + // NOTE: If it's a union with only one variant, then treat it as that variant + if len(u.variants) == 1 { + variant := u.variants[0] + v.id = variant.id + 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} + assign_int(tag, 1) + } + } else if v.id != Value { + for variant, i in u.variants { + variant_any := any{v.data, variant.id} + variant_p := p^ + if err = unmarshal_value(&variant_p, variant_any); err == nil { + p^ = variant_p + + raw_tag := i + if !u.no_nil { raw_tag += 1 } + tag := any{rawptr(uintptr(v.data) + u.tag_offset), u.tag_type.id} + assign_int(tag, raw_tag) + return + } + } + return UNSUPPORTED_TYPE } } - + switch &dst in v { // Handle json.Value as an unknown type case Value: @@ -263,7 +323,7 @@ unmarshal_value :: proc(p: ^Parser, v: any) -> (err: Unmarshal_Error) { case .Ident: advance_token(p) if p.spec == .MJSON { - if unmarshal_string_token(p, any{v.data, ti.id}, token.text, ti) { + if unmarshal_string_token(p, any{v.data, ti.id}, token.text, ti) or_return { return nil } } @@ -271,13 +331,18 @@ unmarshal_value :: proc(p: ^Parser, v: any) -> (err: Unmarshal_Error) { case .String: advance_token(p) - str := unquote_string(token, p.spec, p.allocator) or_return - if unmarshal_string_token(p, any{v.data, ti.id}, str, ti) { - return nil + str := unquote_string(token, p.spec, p.allocator) or_return + dest := any{v.data, ti.id} + if !(unmarshal_string_token(p, dest, str, ti) or_return) { + delete(str, p.allocator) + return UNSUPPORTED_TYPE } - delete(str, p.allocator) - return UNSUPPORTED_TYPE + switch destv in dest { + case string, cstring: + case: delete(str, p.allocator) + } + return nil case .Open_Brace: return unmarshal_object(p, v, .Close_Brace) @@ -325,6 +390,16 @@ unmarshal_expect_token :: proc(p: ^Parser, kind: Token_Kind, loc := #caller_loca return prev } +@(private) +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 = value[1 + comma_index:] + } + return +} + @(private) unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unmarshal_Error) { @@ -335,32 +410,38 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm } v := v - v = reflect.any_base(v) - ti := type_info_of(v.id) + ti := reflect.type_info_base(type_info_of(v.id)) #partial switch t in ti.variant { case reflect.Type_Info_Struct: - if t.is_raw_union { + if .raw_union in t.flags { return UNSUPPORTED_TYPE } - + + fields := reflect.struct_fields_zipped(ti.id) + struct_loop: for p.curr_token.kind != end_token { - key, _ := parse_object_key(p, p.allocator) + key := parse_object_key(p, p.allocator) or_return defer delete(key, p.allocator) unmarshal_expect_token(p, .Colon) - fields := reflect.struct_fields_zipped(ti.id) - - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) + field_test :: #force_inline proc "contextless" (field_used: [^]byte, offset: uintptr) -> bool { + prev_set := field_used[offset/8] & byte(offset&7) != 0 + field_used[offset/8] |= byte(offset&7) + return prev_set + } + + field_used_bytes := (reflect.size_of_typeid(ti.id)+7)/8 + field_used := intrinsics.alloca(field_used_bytes + 1, 1) // + 1 to not overflow on size_of 0 types. + intrinsics.mem_zero(field_used, field_used_bytes) - field_used := make([]bool, len(fields), context.temp_allocator) - use_field_idx := -1 for field, field_idx in fields { - tag_value := string(reflect.struct_tag_get(field.tag, "json")) - if key == tag_value { + tag_value := reflect.struct_tag_get(field.tag, "json") + json_name, _ := json_name_from_tag_value(tag_value) + if key == json_name { use_field_idx = field_idx break } @@ -375,14 +456,45 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm } } - if use_field_idx >= 0 { - if field_used[use_field_idx] { + check_children_using_fields :: proc(key: string, parent: typeid) -> ( + offset: uintptr, + type: ^reflect.Type_Info, + found: bool, + ) { + for field in reflect.struct_fields_zipped(parent) { + if field.is_using && field.name == "_" { + offset, type, found = check_children_using_fields(key, field.type.id) + if found { + offset += field.offset + return + } + } + + if field.name == key || (field.tag != "" && reflect.struct_tag_get(field.tag, "json") == key) { + offset = field.offset + type = field.type + found = true + return + } + } + return + } + + offset: uintptr + type: ^reflect.Type_Info + field_found: bool = use_field_idx >= 0 + + if field_found { + offset = fields[use_field_idx].offset + type = fields[use_field_idx].type + } else { + offset, type, field_found = check_children_using_fields(key, ti.id) + } + + if field_found { + if field_test(field_used, offset) { return .Multiple_Use_Field } - field_used[use_field_idx] = true - offset := fields[use_field_idx].offset - type := fields[use_field_idx].type - name := fields[use_field_idx].name field_ptr := rawptr(uintptr(v.data) + offset) field := any{field_ptr, type.id} @@ -394,6 +506,12 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm continue struct_loop } else { // allows skipping unused struct fields + + // NOTE(bill): prevent possible memory leak if a string is unquoted + allocator := p.allocator + defer p.allocator = allocator + p.allocator = mem.nil_allocator() + parse_value(p) or_return if parse_comma(p) { break struct_loop @@ -403,7 +521,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) @@ -420,25 +538,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 err := unmarshal_value(p, map_backing_value); err != nil { + if uerr := unmarshal_value(p, map_backing_value); uerr != nil { delete(key, p.allocator) - return err + 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 @@ -475,7 +607,6 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm } } - return nil case: return UNSUPPORTED_TYPE } diff --git a/core/encoding/json/validator.odin b/core/encoding/json/validator.odin index 961c2dc23..a6873319d 100644 --- a/core/encoding/json/validator.odin +++ b/core/encoding/json/validator.odin @@ -1,4 +1,4 @@ -package json +package encoding_json import "core:mem" 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..f910c33d8 --- /dev/null +++ b/core/encoding/uuid/doc.odin @@ -0,0 +1,47 @@ +/* +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. + +Example: + package main + + 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..7acaa3cd7 --- /dev/null +++ b/core/encoding/uuid/writing.odin @@ -0,0 +1,136 @@ +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 (36 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 36 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 36 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/varint/doc.odin b/core/encoding/varint/doc.odin index 5e4708a59..a00cfed15 100644 --- a/core/encoding/varint/doc.odin +++ b/core/encoding/varint/doc.odin @@ -1,10 +1,11 @@ /* - Implementation of the LEB128 variable integer encoding as used by DWARF encoding and DEX files, among others. +Implementation of the LEB128 variable integer encoding as used by DWARF encoding and DEX files, among others. - Author of this Odin package: Jeroen van Rijn +Author of this Odin package: Jeroen van Rijn + +Example: + package main - Example: - ```odin import "core:encoding/varint" import "core:fmt" @@ -22,7 +23,5 @@ assert(decoded_val == value && decode_size == encode_size && decode_err == .None) fmt.printf("Decoded as %v, using %v byte%v\n", decoded_val, decode_size, "" if decode_size == 1 else "s") } - ``` - */ -package varint \ No newline at end of file +package encoding_varint diff --git a/core/encoding/varint/leb128.odin b/core/encoding/varint/leb128.odin index 1cdbb81b0..606c57ba7 100644 --- a/core/encoding/varint/leb128.odin +++ b/core/encoding/varint/leb128.odin @@ -6,9 +6,7 @@ Jeroen van Rijn: Initial implementation. */ -// package varint implements variable length integer encoding and decoding using -// the LEB128 format as used by DWARF debug info, Android .dex and other file formats. -package varint +package encoding_varint // In theory we should use the bigint package. In practice, varints bigger than this indicate a corrupted file. // Instead we'll set limits on the values we'll encode/decode @@ -160,4 +158,4 @@ encode_ileb128 :: proc(buf: []u8, val: i128) -> (size: int, err: Error) { buf[size - 1] = u8(low) } return -} \ No newline at end of file +} diff --git a/core/encoding/xml/debug_print.odin b/core/encoding/xml/debug_print.odin index b97617a8a..acced262a 100644 --- a/core/encoding/xml/debug_print.odin +++ b/core/encoding/xml/debug_print.odin @@ -1,3 +1,5 @@ +package encoding_xml + /* An XML 1.0 / 1.1 parser @@ -9,7 +11,7 @@ List of contributors: Jeroen van Rijn: Initial implementation. */ -package xml + import "core:io" import "core:fmt" @@ -31,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) } } @@ -40,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 } @@ -81,4 +83,4 @@ print_element :: proc(writer: io.Writer, doc: ^Document, element_id: Element_ID, } return written, .None -} \ No newline at end of file +} diff --git a/core/encoding/xml/doc.odin b/core/encoding/xml/doc.odin new file mode 100644 index 000000000..10d9f78be --- /dev/null +++ b/core/encoding/xml/doc.odin @@ -0,0 +1,23 @@ +/* +XML 1.0 / 1.1 parser + +A from-scratch XML implementation, loosely modelled on the [[ spec; https://www.w3.org/TR/2006/REC-xml11-20060816 ]]. + +Features: +- Supports enough of the XML 1.0/1.1 spec to handle the 99.9% of XML documents in common current usage. +- Simple to understand and use. Small. + +Caveats: +- We do NOT support HTML in this package, as that may or may not be valid XML. + If it works, great. If it doesn't, that's not considered a bug. + +- We do NOT support UTF-16. If you have a UTF-16 XML file, please convert it to UTF-8 first. Also, our condolences. +- <[!ELEMENT and <[!ATTLIST are not supported, and will be either ignored or return an error depending on the parser options. + +MAYBE: +- XML writer? +- Serialize/deserialize Odin types? + +For a full example, see: [[ core/encoding/xml/example; https://github.com/odin-lang/Odin/tree/master/core/encoding/xml/example ]] +*/ +package encoding_xml diff --git a/core/encoding/xml/example/xml_example.odin b/core/encoding/xml/example/xml_example.odin index aebb8d0ea..9648143db 100644 --- a/core/encoding/xml/example/xml_example.odin +++ b/core/encoding/xml/example/xml_example.odin @@ -20,7 +20,7 @@ example :: proc() { xml.destroy(docs[round]) } - DOC :: #load("../../../../tests/core/assets/XML/unicode.xml") + DOC :: #load("../../../../tests/core/assets/XML/utf8.xml") input := DOC for round in 0.. (res: Element_ID, found: bool) { @@ -47,4 +49,4 @@ find_attribute_val_by_key :: proc(doc: ^Document, parent_id: Element_ID, key: st if attr.key == key { return attr.val, true } } return "", false -} \ No newline at end of file +} diff --git a/core/encoding/xml/tokenizer.odin b/core/encoding/xml/tokenizer.odin index cd055475c..a2bbaf28e 100644 --- a/core/encoding/xml/tokenizer.odin +++ b/core/encoding/xml/tokenizer.odin @@ -1,3 +1,5 @@ +package encoding_xml + /* An XML 1.0 / 1.1 parser @@ -9,7 +11,7 @@ List of contributors: Jeroen van Rijn: Initial implementation. */ -package xml + import "core:fmt" import "core:unicode" @@ -124,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 { /* @@ -168,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 { @@ -180,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 { @@ -216,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 } @@ -266,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 } @@ -288,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) @@ -302,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 @@ -317,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 } @@ -340,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) @@ -367,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 } @@ -382,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 @@ -416,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 } @@ -433,4 +414,4 @@ scan :: proc(t: ^Tokenizer) -> Token { lit = string(t.src[offset : t.offset]) } return Token{kind, lit, pos} -} \ No newline at end of file +} diff --git a/core/encoding/xml/xml_reader.odin b/core/encoding/xml/xml_reader.odin index f4f8a4b05..b8c8b13a4 100644 --- a/core/encoding/xml/xml_reader.odin +++ b/core/encoding/xml/xml_reader.odin @@ -1,39 +1,21 @@ /* - An XML 1.0 / 1.1 parser - - Copyright 2021-2022 Jeroen van Rijn . - Made available under Odin's BSD-3 license. - - A from-scratch XML implementation, loosely modelled on the [spec](https://www.w3.org/TR/2006/REC-xml11-20060816). - - Features: - - Supports enough of the XML 1.0/1.1 spec to handle the 99.9% of XML documents in common current usage. - - Simple to understand and use. Small. - - Caveats: - - We do NOT support HTML in this package, as that may or may not be valid XML. - If it works, great. If it doesn't, that's not considered a bug. - - - We do NOT support UTF-16. If you have a UTF-16 XML file, please convert it to UTF-8 first. Also, our condolences. - - <[!ELEMENT and <[!ATTLIST are not supported, and will be either ignored or return an error depending on the parser options. - - MAYBE: - - XML writer? - - Serialize/deserialize Odin types? + 2021-2022 Jeroen van Rijn . + available under Odin's BSD-3 license. List of contributors: - Jeroen van Rijn: Initial implementation. + - Jeroen van Rijn: Initial implementation. */ -package xml + +package encoding_xml // An XML 1.0 / 1.1 parser import "core:bytes" import "core:encoding/entity" -import "core:intrinsics" +import "base:intrinsics" import "core:mem" import "core:os" import "core:strings" -import "core:runtime" +import "base:runtime" likely :: intrinsics.expect @@ -43,48 +25,32 @@ DEFAULT_OPTIONS :: Options{ } Option_Flag :: enum { - /* - If the caller says that input may be modified, we can perform in-situ parsing. - If this flag isn't provided, the XML parser first duplicates the input so that it can. - */ + // If the caller says that input may be modified, we can perform in-situ parsing. + // If this flag isn't provided, the XML parser first duplicates the input so that it can. Input_May_Be_Modified, - /* - Document MUST start with ` (doc: ^Document, err: Error) { data := data context.allocator = allocator opts := validate_options(options) or_return - /* - If `.Input_May_Be_Modified` is not specified, we duplicate the input so that we can modify it in-place. - */ + // If `.Input_May_Be_Modified` is not specified, we duplicate the input so that we can modify it in-place. if .Input_May_Be_Modified not_in opts.flags { data = bytes.clone(data) } @@ -246,16 +185,12 @@ 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 - /* - If a DOCTYPE is present, the root tag has to match. - If an expected DOCTYPE is given in options (i.e. it's non-empty), the DOCTYPE (if present) and root tag have to match. - */ + // If a DOCTYPE is present, the root tag has to match. + // If an expected DOCTYPE is given in options (i.e. it's non-empty), the DOCTYPE (if present) and root tag have to match. expected_doctype := options.expected_doctype loop: for { @@ -263,17 +198,13 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha // NOTE(Jeroen): This is faster as a switch. switch t.ch { case '<': - /* - Consume peeked `<` - */ + // Consume peeked `<` advance_rune(t) open = scan(t) // NOTE(Jeroen): We're not using a switch because this if-else chain ordered by likelihood is 2.5% faster at -o:size and -o:speed. if likely(open.kind, Token_Kind.Ident) == .Ident { - /* - e.g. 0 && expected_doctype != open.text { error(t, t.offset, "Root Tag doesn't match DOCTYPE. Expected: %v, got: %v\n", expected_doctype, open.text) @@ -298,26 +227,20 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha } } - /* - One of these should follow: - - `>`, which means we've just opened this tag and expect a later element to close it. - - `/>`, which means this is an 'empty' or self-closing tag. - */ + // One of these should follow: + // - `>`, which means we've just opened this tag and expect a later element to close it. + // - `/>`, which means this is an 'empty' or self-closing tag. end_token := scan(t) #partial switch end_token.kind { case .Gt: - /* - We're now the new parent. - */ + // We're now the new parent. parent = element case .Slash: - /* - Empty tag. Close it. - */ + // 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) @@ -325,9 +248,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha } } else if open.kind == .Slash { - /* - Close tag. - */ + // Close tag. ident := expect(t, .Ident) or_return _ = expect(t, .Gt) or_return @@ -335,13 +256,11 @@ 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 { - /* - . - The grammar does not allow a comment to end in ---> - */ + // Comment: . + // The grammar does not allow a comment to end in ---> expect(t, .Dash) comment := scan_comment(t) or_return @@ -395,23 +312,17 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha } } else if open.kind == .Question { - /* - 0 { - /* - We've already seen a prologue. - */ + // We've already seen a prologue. return doc, .Too_Many_Prologs } else { - /* - Could be ` (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) @@ -553,7 +460,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 @@ -613,9 +526,7 @@ parse_prologue :: proc(doc: ^Document) -> (err: Error) { doc.encoding = .LATIN_1 case: - /* - Unrecognized encoding, assume UTF-8. - */ + // Unrecognized encoding, assume UTF-8. error(t, offset, "[parse_prologue] Warning: Unrecognized encoding: %v\n", attr.val) } @@ -658,11 +569,11 @@ skip_element :: proc(t: ^Tokenizer) -> (err: Error) { parse_doctype :: proc(doc: ^Document) -> (err: Error) { /* - + - - ]> + + ]> */ assert(doc != nil) context.allocator = doc.allocator @@ -675,9 +586,7 @@ parse_doctype :: proc(doc: ^Document) -> (err: Error) { offset := t.offset skip_element(t) or_return - /* - -1 because the current offset is that of the closing tag, so the rest of the DOCTYPE tag ends just before it. - */ + // -1 because the current offset is that of the closing tag, so the rest of the DOCTYPE tag ends just before it. doc.doctype.rest = string(t.src[offset : t.offset - 1]) return .None } @@ -700,4 +609,4 @@ new_element :: proc(doc: ^Document) -> (id: Element_ID) { cur := doc.element_count doc.element_count += 1 return cur -} \ No newline at end of file +} 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..6f5281928 --- /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 || ODIN_OS == .FreeBSD) + +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..b97547806 --- /dev/null +++ b/core/flags/doc.odin @@ -0,0 +1,171 @@ +/* +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..12ddb876f --- /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 set, valid_index := bit_array.get(&parser.filled_pos, parser.filled_pos.length - 1); set || !valid_index { + // The index below the last one is either set or invalid, which means we're out of space. + // Add one free bit by setting the index above to false. + bit_array.set(&parser.filled_pos, parser.filled_pos.length, 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..4e49f45b0 --- /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..1c559ca55 --- /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..e1286186b --- /dev/null +++ b/core/flags/internal_rtti_nonbsd.odin @@ -0,0 +1,32 @@ +#+private +#+build !netbsd +#+build !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..afd05331c --- /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..6929e9be7 --- /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 402f783cf..49e9f2e6d 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -1,34 +1,20 @@ package fmt +import "base:intrinsics" +import "base:runtime" +import "core:math" import "core:math/bits" import "core:mem" import "core:io" import "core:reflect" -import "core:runtime" import "core:strconv" import "core:strings" import "core:time" import "core:unicode/utf8" -import "core:intrinsics" // 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,27 +140,42 @@ 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 // // *Allocates Using Context's Allocator* // // Inputs: +// - fmt: A format string with placeholders for the provided arguments. +// - args: A variadic list of arguments to be formatted. +// - newline: Whether the string should end with a newline. (See `aprintfln`.) +// +// 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) + return sbprintf(&str, fmt, ..args, newline=newline) +} +// Creates a formatted string using a format string and arguments, followed by a newline. +// +// *Allocates Using Context's Allocator* +// +// Inputs: // - fmt: A format string with placeholders for the provided arguments. // - args: A variadic list of arguments to be formatted. // // Returns: A formatted string. The returned string must be freed accordingly. // -aprintf :: proc(fmt: string, args: ..any, allocator := context.allocator) -> string { - str: strings.Builder - strings.builder_init(&str, allocator) - sbprintf(&str, fmt, ..args) - return strings.to_string(str) +@(require_results) +aprintfln :: proc(fmt: string, args: ..any, allocator := context.allocator) -> string { + return aprintf(fmt, ..args, allocator=allocator, newline=true) } // Creates a formatted string // @@ -168,11 +187,11 @@ aprintf :: proc(fmt: string, args: ..any, allocator := context.allocator) -> str // // 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 // @@ -184,27 +203,42 @@ 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 // // *Allocates Using Context's Temporary Allocator* // // Inputs: +// - fmt: A format string with placeholders for the provided arguments. +// - args: A variadic list of arguments to be formatted. +// - newline: Whether the string should end with a newline. (See `tprintfln`.) +// +// 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) + return sbprintf(&str, fmt, ..args, newline=newline) +} +// Creates a formatted string using a format string and arguments, followed by a newline. +// +// *Allocates Using Context's Temporary Allocator* +// +// Inputs: // - fmt: A format string with placeholders for the provided arguments. // - args: A variadic list of arguments to be formatted. // // Returns: A formatted string. // -tprintf :: proc(fmt: string, args: ..any) -> string { - str: strings.Builder - strings.builder_init(&str, context.temp_allocator) - sbprintf(&str, fmt, ..args) - return strings.to_string(str) +@(require_results) +tprintfln :: proc(fmt: string, args: ..any) -> string { + return tprintf(fmt, ..args, newline=true) } // Creates a formatted string using a supplied buffer as the backing array. Writes into the buffer. // @@ -216,7 +250,7 @@ tprintf :: proc(fmt: string, args: ..any) -> string { // Returns: A formatted string // bprint :: proc(buf: []byte, args: ..any, sep := " ") -> string { - sb := strings.builder_from_bytes(buf[0:len(buf)]) + sb := strings.builder_from_bytes(buf) return sbprint(&sb, ..args, sep=sep) } // Creates a formatted string using a supplied buffer as the backing array, appends newline. Writes into the buffer. @@ -229,7 +263,7 @@ bprint :: proc(buf: []byte, args: ..any, sep := " ") -> string { // Returns: A formatted string with a newline character at the end // bprintln :: proc(buf: []byte, args: ..any, sep := " ") -> string { - sb := strings.builder_from_bytes(buf[0:len(buf)]) + sb := strings.builder_from_bytes(buf) return sbprintln(&sb, ..args, sep=sep) } // Creates a formatted string using a supplied buffer as the backing array. Writes into the buffer. @@ -238,12 +272,25 @@ bprintln :: proc(buf: []byte, args: ..any, sep := " ") -> string { // - buf: The backing buffer // - fmt: A format string with placeholders for the provided arguments // - args: A variadic list of arguments to be formatted +// - newline: Whether the string should end with a newline. (See `bprintfln`.) // // Returns: A formatted string // -bprintf :: proc(buf: []byte, fmt: string, args: ..any) -> string { - sb := strings.builder_from_bytes(buf[0:len(buf)]) - return sbprintf(&sb, fmt, ..args) +bprintf :: proc(buf: []byte, fmt: string, args: ..any, newline := false) -> string { + sb := strings.builder_from_bytes(buf) + return sbprintf(&sb, fmt, ..args, newline=newline) +} +// Creates a formatted string using a supplied buffer as the backing array, followed by a newline. Writes into the buffer. +// +// Inputs: +// - buf: The backing buffer +// - fmt: A format string with placeholders for the provided arguments +// - args: A variadic list of arguments to be formatted +// +// Returns: A formatted string +// +bprintfln :: proc(buf: []byte, fmt: string, args: ..any) -> string { + return bprintf(buf, fmt, ..args, newline=true) } // Runtime assertion with a formatted message // @@ -253,18 +300,24 @@ bprintf :: proc(buf: []byte, fmt: string, args: ..any) -> string { // - args: A variadic list of arguments to be formatted // - loc: The location of the caller // -// Returns: True if the condition is met, otherwise triggers a runtime assertion with a formatted message -// -assertf :: proc(condition: bool, fmt: string, args: ..any, loc := #caller_location) -> bool { +@(disabled=ODIN_DISABLE_ASSERT) +assertf :: proc(condition: bool, fmt: string, args: ..any, loc := #caller_location) { if !condition { - p := context.assertion_failure_proc - if p == nil { - p = runtime.default_assertion_failure_proc + // NOTE(dragos): We are using the same trick as in builtin.assert + // to improve performance to make the CPU not + // execute speculatively, making it about an order of + // magnitude faster + @(cold) + internal :: proc(loc: runtime.Source_Code_Location, fmt: string, args: ..any) { + p := context.assertion_failure_proc + if p == nil { + p = runtime.default_assertion_failure_proc + } + message := tprintf(fmt, ..args) + p("Runtime assertion", message, loc) } - message := tprintf(fmt, ..args) - p("Runtime assertion", message, loc) + internal(loc, fmt, ..args) } - return condition } // Runtime panic with a formatted message // @@ -281,6 +334,27 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! { message := tprintf(fmt, ..args) p("Panic", message, loc) } + +// Creates a formatted C string +// +// *Allocates Using Context's Allocator* +// +// Inputs: +// - args: A variadic list of arguments to be formatted. +// - sep: An optional separator string (default is a single space). +// +// Returns: A formatted C string. +// +@(require_results) +caprint :: proc(args: ..any, sep := " ", allocator := context.allocator) -> cstring { + str: strings.Builder + strings.builder_init(&str, allocator) + sbprint(&str, ..args, sep=sep) + strings.write_byte(&str, 0) + s := strings.to_string(str) + return cstring(raw_data(s)) +} + // Creates a formatted C string // // *Allocates Using Context's Allocator* @@ -288,16 +362,46 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! { // Inputs: // - format: A format string with placeholders for the provided arguments // - args: A variadic list of arguments to be formatted +// - newline: Whether the string should end with a newline. (See `caprintfln`.) +// +// Returns: A formatted C string +// +@(require_results) +caprintf :: proc(format: string, args: ..any, allocator := context.allocator, newline := false) -> cstring { + str: strings.Builder + strings.builder_init(&str, allocator) + sbprintf(&str, format, ..args, newline=newline) + strings.write_byte(&str, 0) + s := strings.to_string(str) + return cstring(raw_data(s)) +} +// Creates a formatted C string, followed by a newline. +// +// *Allocates Using Context's Allocator* +// +// Inputs: +// - format: A format string with placeholders for the provided arguments +// - args: A variadic list of arguments to be formatted // // Returns: A formatted C string // -caprintf :: proc(format: string, args: ..any) -> cstring { - str: strings.Builder - strings.builder_init(&str) - sbprintf(&str, format, ..args) - strings.write_byte(&str, 0) - s := strings.to_string(str) - return cstring(raw_data(s)) +@(require_results) +caprintfln :: proc(format: string, args: ..any, allocator := context.allocator) -> cstring { + return caprintf(format, ..args, allocator=allocator, 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 { + return caprint(args=args, sep=sep, allocator=context.temp_allocator) } // Creates a formatted C string // @@ -306,16 +410,27 @@ caprintf :: proc(format: string, args: ..any) -> cstring { // Inputs: // - format: A format string with placeholders for the provided arguments // - args: A variadic list of arguments to be formatted +// - newline: Whether the string should end with a newline. (See `ctprintfln`.) +// +// Returns: A formatted C string +// +@(require_results) +ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring { + return caprintf(format=format, args=args, allocator=context.temp_allocator, newline=newline) +} +// Creates a formatted C string, followed by a newline. +// +// *Allocates Using Context's Temporary Allocator* +// +// Inputs: +// - format: A format string with placeholders for the provided arguments +// - args: A variadic list of arguments to be formatted // // Returns: A formatted C string // -ctprintf :: proc(format: string, args: ..any) -> cstring { - str: strings.Builder - strings.builder_init(&str, context.temp_allocator) - sbprintf(&str, format, ..args) - strings.write_byte(&str, 0) - s := strings.to_string(str) - return cstring(raw_data(s)) +@(require_results) +ctprintfln :: proc(format: string, args: ..any) -> cstring { + return caprintf(format=format, args=args, allocator=context.temp_allocator, newline=true) } // Formats using the default print settings and writes to the given strings.Builder // @@ -349,13 +464,25 @@ sbprintln :: proc(buf: ^strings.Builder, args: ..any, sep := " ") -> string { // - buf: A pointer to a strings.Builder buffer // - fmt: The format string // - args: A variadic list of arguments to be formatted +// - newline: Whether a trailing newline should be written. (See `sbprintfln`.) // // Returns: The resulting formatted string // -sbprintf :: proc(buf: ^strings.Builder, fmt: string, args: ..any) -> string { - wprintf(strings.to_writer(buf), fmt, ..args, flush=true) +sbprintf :: proc(buf: ^strings.Builder, fmt: string, args: ..any, newline := false) -> string { + wprintf(strings.to_writer(buf), fmt, ..args, flush=true, newline=newline) return strings.to_string(buf^) } +// Formats and writes to a strings.Builder buffer according to the specified format string, followed by a newline. +// +// Inputs: +// - buf: A pointer to a strings.Builder to store the formatted string +// - args: A variadic list of arguments to be formatted +// +// Returns: A formatted string +// +sbprintfln :: proc(buf: ^strings.Builder, format: string, args: ..any) -> string { + return sbprintf(buf, format, ..args, newline=true) +} // Formats and writes to an io.Writer using the default print settings // // Inputs: @@ -429,17 +556,112 @@ wprintln :: proc(w: io.Writer, args: ..any, sep := " ", flush := true) -> int { // - w: An io.Writer to write to // - fmt: The format string // - args: A variadic list of arguments to be formatted +// - newline: Whether a trailing newline should be written. (See `wprintfln`.) // // Returns: The number of bytes written // -wprintf :: proc(w: io.Writer, fmt: string, args: ..any, flush := true) -> int { +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] == '}') { @@ -473,191 +695,65 @@ wprintf :: proc(w: io.Writer, fmt: string, args: ..any, flush := true) -> int { } 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 @@ -676,31 +772,39 @@ wprintf :: proc(w: io.Writer, fmt: string, args: ..any, flush := true) -> int { 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 { + io.write_byte(w, '\n', &fi.n) } if flush { io.flush(w) @@ -708,6 +812,17 @@ wprintf :: proc(w: io.Writer, fmt: string, args: ..any, flush := true) -> int { return fi.n } +// Formats and writes to an io.Writer according to the specified format string, followed by a newline. +// +// Inputs: +// - w: The io.Writer to write to. +// - args: A variadic list of arguments to be formatted. +// +// Returns: The number of bytes written. +// +wprintfln :: proc(w: io.Writer, format: string, args: ..any, flush := true) -> int { + return wprintf(w, format, ..args, flush=flush, newline=true) +} // Writes a ^runtime.Type_Info value to an io.Writer // // Inputs: @@ -768,18 +883,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 @@ -787,30 +900,28 @@ _arg_number :: proc(fi: ^Info, arg_index: int, format: string, offset, arg_count for i in 1..", &fi.n) } @@ -869,7 +980,7 @@ fmt_bad_verb :: proc(fi: ^Info, verb: rune) { // fmt_bool :: proc(fi: ^Info, b: bool, verb: rune) { switch verb { - case 't', 'v': + case 't', 'v', 'w': fmt_string(fi, b ? "true" : "false", 's') case: fmt_bad_verb(fi, verb) @@ -919,6 +1030,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 @@ -944,28 +1082,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 && !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(buf[start:], u, base, is_signed, bit_size, digits, flags) - - if fi.hash && fi.zero && fi.indent == 0 { - c: byte = 0 - switch base { - case 2: c = 'b' - case 8: c = 'o' - case 12: c = 'z' - case 16: c = 'x' - } - if c != 0 { - io.write_byte(fi.writer, '0', &fi.n) - io.write_byte(fi.writer, c, &fi.n) - } - } - prev_zero := fi.zero defer fi.zero = prev_zero fi.zero = false @@ -995,6 +1115,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 @@ -1020,12 +1167,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 { @@ -1096,10 +1240,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:] } } @@ -1120,7 +1264,7 @@ fmt_rune :: proc(fi: ^Info, r: rune, verb: rune) { switch verb { case 'c', 'r', 'v': io.write_rune(fi.writer, r, &fi.n) - case 'q': + case 'q', 'w': fi.n += io.write_quoted_rune(fi.writer, r) case: fmt_int(fi, u64(r), false, 32, verb) @@ -1137,7 +1281,8 @@ fmt_rune :: proc(fi: ^Info, r: rune, verb: rune) { // fmt_int :: proc(fi: ^Info, u: u64, is_signed: bool, bit_size: int, verb: rune) { switch verb { - case 'v': _fmt_int(fi, u, 10, is_signed, bit_size, __DIGITS_LOWER) + case 'v', 'w': + _fmt_int(fi, u, 10, is_signed, bit_size, __DIGITS_LOWER) case 'b': _fmt_int(fi, u, 2, is_signed, bit_size, __DIGITS_LOWER) case 'o': _fmt_int(fi, u, 8, is_signed, bit_size, __DIGITS_LOWER) case 'i', 'd': _fmt_int(fi, u, 10, is_signed, bit_size, __DIGITS_LOWER) @@ -1172,7 +1317,8 @@ fmt_int :: proc(fi: ^Info, u: u64, is_signed: bool, bit_size: int, verb: rune) { // fmt_int_128 :: proc(fi: ^Info, u: u128, is_signed: bool, bit_size: int, verb: rune) { switch verb { - case 'v': _fmt_int_128(fi, u, 10, is_signed, bit_size, __DIGITS_LOWER) + case 'v', 'w': + _fmt_int_128(fi, u, 10, is_signed, bit_size, __DIGITS_LOWER) case 'b': _fmt_int_128(fi, u, 2, is_signed, bit_size, __DIGITS_LOWER) case 'o': _fmt_int_128(fi, u, 8, is_signed, bit_size, __DIGITS_LOWER) case 'i', 'd': _fmt_int_128(fi, u, 10, is_signed, bit_size, __DIGITS_LOWER) @@ -1232,8 +1378,12 @@ _pad :: proc(fi: ^Info, s: string) { // // NOTE: Can return "NaN", "+Inf", "-Inf", "+", or "-". // -_fmt_float_as :: proc(fi: ^Info, v: f64, bit_size: int, verb: rune, float_fmt: byte) { - prec := fi.prec if fi.prec_set else 3 +_fmt_float_as :: proc(fi: ^Info, v: f64, bit_size: int, verb: rune, float_fmt: byte, prec: int) { + prec := prec + if fi.prec_set { + prec = fi.prec + } + buf: [386]byte // Can return "NaN", "+Inf", "-Inf", "+", "-". @@ -1242,7 +1392,7 @@ _fmt_float_as :: proc(fi: ^Info, v: f64, bit_size: int, verb: rune, float_fmt: b if !fi.plus { // Strip sign from "+" but not "+Inf". if str[0] == '+' && str[1] != 'I' { - str = str[1:] + str = str[1:] } } @@ -1258,11 +1408,13 @@ _fmt_float_as :: proc(fi: ^Info, v: f64, bit_size: int, verb: rune, float_fmt: b // fmt_float :: proc(fi: ^Info, v: f64, bit_size: int, verb: rune) { switch verb { - case 'f', 'F', 'g', 'G', 'v': - _fmt_float_as(fi, v, bit_size, verb, 'f') + case 'g', 'G', 'v', 'w': + _fmt_float_as(fi, v, bit_size, verb, 'g', -1) + case 'f', 'F': + _fmt_float_as(fi, v, bit_size, verb, 'f', 3) case 'e', 'E': // BUG(): "%.3e" returns "3.000e+00" - _fmt_float_as(fi, v, bit_size, verb, 'e') + _fmt_float_as(fi, v, bit_size, verb, 'e', 6) case 'h', 'H': prev_fi := fi^ @@ -1319,17 +1471,14 @@ 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) } - case 'q': // quoted string + case 'q', 'w': // quoted string io.write_quoted_string(fi.writer, s, '"', &fi.n) case 'x', 'X': @@ -1372,8 +1521,8 @@ fmt_cstring :: proc(fi: ^Info, s: cstring, verb: rune) { fmt_pointer :: proc(fi: ^Info, p: rawptr, verb: rune) { u := u64(uintptr(p)) switch verb { - case 'p', 'v': - if !fi.hash && verb == 'v' { + case 'p', 'v', 'w': + if !fi.hash { io.write_string(fi.writer, "0x", &fi.n) } _fmt_int(fi, u, 16, false, 8*size_of(rawptr), __DIGITS_UPPER) @@ -1382,7 +1531,7 @@ fmt_pointer :: proc(fi: ^Info, p: rawptr, verb: rune) { case 'o': _fmt_int(fi, u, 8, false, 8*size_of(rawptr), __DIGITS_UPPER) case 'i', 'd': _fmt_int(fi, u, 10, false, 8*size_of(rawptr), __DIGITS_UPPER) case 'z': _fmt_int(fi, u, 12, false, 8*size_of(rawptr), __DIGITS_UPPER) - case 'x': _fmt_int(fi, u, 16, false, 8*size_of(rawptr), __DIGITS_UPPER) + case 'x': _fmt_int(fi, u, 16, false, 8*size_of(rawptr), __DIGITS_LOWER) case 'X': _fmt_int(fi, u, 16, false, 8*size_of(rawptr), __DIGITS_UPPER) case: @@ -1410,34 +1559,9 @@ fmt_soa_pointer :: proc(fi: ^Info, p: runtime.Raw_Soa_Pointer, verb: rune) { // // Returns: The string representation of the enum value and a boolean indicating success. // +@(require_results) enum_value_to_string :: proc(val: any) -> (string, bool) { - v := val - v.id = runtime.typeid_base(v.id) - type_info := type_info_of(v.id) - - #partial switch e in type_info.variant { - case: return "", false - case runtime.Type_Info_Enum: - Enum_Value :: runtime.Type_Info_Enum_Value - - ev_, ok := reflect.as_i64(val) - ev := Enum_Value(ev_) - - if ok { - if len(e.values) == 0 { - return "", true - } else { - for val, idx in e.values { - if val == ev { - return e.names[idx], true - } - } - } - return "", false - } - } - - return "", false + return reflect.enum_name_from_value_any(val) } // Returns the enum value of a string representation. // @@ -1465,7 +1589,7 @@ string_to_enum_value :: proc($T: typeid, s: string) -> (T, bool) { // Inputs: // - fi: Pointer to the Info struct containing format settings. // - v: The enum value to format. -// - verb: The format specifier character (e.g. 'i','d','f','s','v','q'). +// - verb: The format specifier character (e.g. 'i','d','f','s','v','q','w'). // fmt_enum :: proc(fi: ^Info, v: any, verb: rune) { if v.id == nil || v.data == nil { @@ -1489,6 +1613,15 @@ fmt_enum :: proc(fi: ^Info, v: any, verb: rune) { fmt_arg(fi, any{v.data, runtime.type_info_base(e.base).id}, 'i') io.write_string(fi.writer, ")", &fi.n) } + case 'w': + if str, ok := enum_value_to_string(v); ok { + io.write_byte(fi.writer, '.', &fi.n) + io.write_string(fi.writer, str, &fi.n) + } else { + io.write_string(fi.writer, "%!(BAD ENUM VALUE=", &fi.n) + fmt_arg(fi, any{v.data, runtime.type_info_base(e.base).id}, 'i') + io.write_string(fi.writer, ")", &fi.n) + } } } } @@ -1620,10 +1753,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) @@ -1640,9 +1775,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 @@ -1677,8 +1820,8 @@ fmt_write_indent :: proc(fi: ^Info) { // - verb: The formatting verb to be used for the array elements. // fmt_write_array :: proc(fi: ^Info, array_data: rawptr, count: int, elem_size: int, elem_id: typeid, verb: rune) { - io.write_byte(fi.writer, '[', &fi.n) - defer io.write_byte(fi.writer, ']', &fi.n) + io.write_byte(fi.writer, '[' if verb != 'w' else '{', &fi.n) + defer io.write_byte(fi.writer, ']' if verb != 'w' else '}', &fi.n) if count <= 0 { return @@ -1724,12 +1867,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 } @@ -1741,38 +1884,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] == ',' { - 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 := 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 + } + } + + 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': - #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) // @@ -1784,11 +1972,11 @@ handle_tag :: proc(data: rawptr, info: reflect.Type_Info_Struct, idx: int, verb: // - type_name: The name of the type being formatted // fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_Struct, type_name: string) { - if the_verb != 'v' { + if the_verb != 'v' && the_verb != 'w' { 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 { @@ -1801,7 +1989,7 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St is_soa := info.soa_kind != .None io.write_string(fi.writer, type_name, &fi.n) - io.write_byte(fi.writer, '[' if is_soa else '{', &fi.n) + io.write_byte(fi.writer, '[' if is_soa && the_verb == 'v' else '{', &fi.n) fi.record_level += 1 defer fi.record_level -= 1 @@ -1812,14 +2000,16 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St // fi.hash = false; fi.indent += 1 - if 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 { + io.write_byte(fi.writer, '\n', &fi.n) + } for index in 0.. 0 { io.write_string(fi.writer, ", ", &fi.n) } @@ -1854,9 +2047,23 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St if !hash && field_count > 0 { io.write_string(fi.writer, ", ", &fi.n) } + if hash { + fi.indent -= 1 + fmt_write_indent(fi) + fi.indent += 1 + } io.write_string(fi.writer, base_type_name, &fi.n) io.write_byte(fi.writer, '{', &fi.n) - defer io.write_byte(fi.writer, '}', &fi.n) + if hash && !is_empty { io.write_byte(fi.writer, '\n', &fi.n) } + defer { + if hash && !is_empty { + fi.indent -= 1 + fmt_write_indent(fi) + fi.indent += 1 + } + io.write_byte(fi.writer, '}', &fi.n) + if hash { io.write_string(fi.writer, ",\n", &fi.n) } + } fi.record_level += 1 defer fi.record_level -= 1 @@ -1883,7 +2090,7 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St fmt_arg(fi, any{data, t.id}, verb) } } else { - t := info.types[i].variant.(runtime.Type_Info_Pointer).elem + t := info.types[i].variant.(runtime.Type_Info_Multi_Pointer).elem t_size := uintptr(t.size) if reflect.is_any(t) { io.write_string(fi.writer, "any{}", &fi.n) @@ -1897,13 +2104,19 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St if hash { io.write_string(fi.writer, ",\n", &fi.n) } } } + + if hash && n > 0 { + for _ in 0.. 0 { @@ -2060,149 +2276,173 @@ fmt_named :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Named) } // Built-in Custom Formatters for core library types - switch a in v { - case runtime.Source_Code_Location: - io.write_string(fi.writer, a.file_path, &fi.n) + if verb != 'w' { + switch a in v { + case runtime.Source_Code_Location: + io.write_string(fi.writer, a.file_path, &fi.n) - when ODIN_ERROR_POS_STYLE == .Default { - io.write_byte(fi.writer, '(', &fi.n) - io.write_int(fi.writer, int(a.line), 10, &fi.n) - io.write_byte(fi.writer, ':', &fi.n) - io.write_int(fi.writer, int(a.column), 10, &fi.n) - io.write_byte(fi.writer, ')', &fi.n) - } else when ODIN_ERROR_POS_STYLE == .Unix { - io.write_byte(fi.writer, ':', &fi.n) - io.write_int(fi.writer, int(a.line), 10, &fi.n) - io.write_byte(fi.writer, ':', &fi.n) - io.write_int(fi.writer, int(a.column), 10, &fi.n) - io.write_byte(fi.writer, ':', &fi.n) - } else { - #panic("Unhandled ODIN_ERROR_POS_STYLE") - } - return - - case time.Duration: - ffrac :: proc(buf: []byte, v: u64, prec: int) -> (nw: int, nv: u64) { - v := v - w := len(buf) - print := false - for _ in 0.. int { - v := v - w := len(buf) - if v == 0 { - w -= 1 - buf[w] = '0' + io.write_byte(fi.writer, ')', &fi.n) + } else when ODIN_ERROR_POS_STYLE == .Unix { + io.write_byte(fi.writer, ':', &fi.n) + io.write_int(fi.writer, int(a.line), 10, &fi.n) + if a.column != 0 { + io.write_byte(fi.writer, ':', &fi.n) + io.write_int(fi.writer, int(a.column), 10, &fi.n) + } + io.write_byte(fi.writer, ':', &fi.n) } else { - for v > 0 { - w -= 1 - buf[w] = byte(v%10) + '0' + #panic("Unhandled ODIN_ERROR_POS_STYLE") + } + return + + case time.Duration: + ffrac :: proc(buf: []byte, v: u64, prec: int) -> (nw: int, nv: u64) { + v := v + w := len(buf) + print := false + for _ in 0.. int { + v := v + w := len(buf) + if v == 0 { + w -= 1 + buf[w] = '0' + } else { + for v > 0 { + w -= 1 + buf[w] = byte(v%10) + '0' + v /= 10 + } + } + return w } - w, u = ffrac(buf[:w], u, prec) - w = fint(buf[:w], u) - } else { - w -= 1 - buf[w] = 's' - w, u = ffrac(buf[:w], u, 9) - w = fint(buf[:w], u%60) - u /= 60 - if u > 0 { + + buf: [32]byte + w := len(buf) + u := u64(a) + neg := a < 0 + if neg { + u = -u + } + + if u < u64(time.Second) { + prec: int w -= 1 - buf[w] = 'm' + buf[w] = 's' + w -= 1 + switch { + case u == 0: + io.write_string(fi.writer, "0s", &fi.n) + return + case u < u64(time.Microsecond): + prec = 0 + buf[w] = 'n' + case u < u64(time.Millisecond): + prec = 3 + // U+00B5 'µ' micro sign == 0xC2 0xB5 + w -= 1 // Need room for two bytes + copy(buf[w:], "µ") + case: + prec = 6 + buf[w] = 'm' + } + w, u = ffrac(buf[:w], u, prec) + w = fint(buf[:w], u) + } else { + w -= 1 + buf[w] = 's' + w, u = ffrac(buf[:w], u, 9) w = fint(buf[:w], u%60) u /= 60 if u > 0 { w -= 1 - buf[w] = 'h' - w = fint(buf[:w], u) + buf[w] = 'm' + w = fint(buf[:w], u%60) + u /= 60 + if u > 0 { + w -= 1 + buf[w] = 'h' + w = fint(buf[:w], u) + } } } + + if neg { + w -= 1 + buf[w] = '-' + } + io.write_string(fi.writer, string(buf[w:]), &fi.n) + return + + case time.Time: + t := a + y, mon, d := time.date(t) + h, min, s := time.clock(t) + ns := (t._nsec - (t._nsec/1e9 + time.UNIX_TO_ABSOLUTE)*1e9) % 1e9 + write_padded_number(fi, i64(y), 4) + io.write_byte(fi.writer, '-', &fi.n) + write_padded_number(fi, i64(mon), 2) + io.write_byte(fi.writer, '-', &fi.n) + write_padded_number(fi, i64(d), 2) + io.write_byte(fi.writer, ' ', &fi.n) + + write_padded_number(fi, i64(h), 2) + io.write_byte(fi.writer, ':', &fi.n) + write_padded_number(fi, i64(min), 2) + io.write_byte(fi.writer, ':', &fi.n) + write_padded_number(fi, i64(s), 2) + io.write_byte(fi.writer, '.', &fi.n) + write_padded_number(fi, (ns), 9) + io.write_string(fi.writer, " +0000 UTC", &fi.n) + return } - - if neg { - w -= 1 - buf[w] = '-' - } - io.write_string(fi.writer, string(buf[w:]), &fi.n) - return - - case time.Time: - t := a - y, mon, d := time.date(t) - h, min, s := time.clock(t) - ns := (t._nsec - (t._nsec/1e9 + time.UNIX_TO_ABSOLUTE)*1e9) % 1e9 - write_padded_number(fi, i64(y), 4) - io.write_byte(fi.writer, '-', &fi.n) - write_padded_number(fi, i64(mon), 2) - io.write_byte(fi.writer, '-', &fi.n) - write_padded_number(fi, i64(d), 2) - io.write_byte(fi.writer, ' ', &fi.n) - - write_padded_number(fi, i64(h), 2) - io.write_byte(fi.writer, ':', &fi.n) - write_padded_number(fi, i64(min), 2) - io.write_byte(fi.writer, ':', &fi.n) - write_padded_number(fi, i64(s), 2) - io.write_byte(fi.writer, '.', &fi.n) - write_padded_number(fi, (ns), 9) - io.write_string(fi.writer, " +0000 UTC", &fi.n) - return } #partial switch b in info.base.variant { case runtime.Type_Info_Struct: fmt_struct(fi, v, verb, b, info.name) + case runtime.Type_Info_Bit_Field: + fmt_bit_field(fi, v, verb, b, info.name) case runtime.Type_Info_Bit_Set: fmt_bit_set(fi, v, verb = verb) case: + if verb == 'w' { + #partial switch _ in info.base.variant { + case runtime.Type_Info_Array, + runtime.Type_Info_Enumerated_Array, + runtime.Type_Info_Dynamic_Array, + runtime.Type_Info_Slice, + runtime.Type_Info_Struct, + runtime.Type_Info_Enum, + runtime.Type_Info_Map, + runtime.Type_Info_Bit_Set, + runtime.Type_Info_Simd_Vector, + runtime.Type_Info_Matrix, + runtime.Type_Info_Bit_Field: + io.write_string(fi.writer, info.name, &fi.n) + } + } fmt_value(fi, any{v.data, info.base.id}, verb) } } @@ -2269,8 +2509,13 @@ fmt_union :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Union, // - info: A runtime.Type_Info_Matrix struct containing matrix type information. // fmt_matrix :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Matrix) { - io.write_string(fi.writer, "matrix[", &fi.n) - defer io.write_byte(fi.writer, ']', &fi.n) + if verb == 'w' { + io.write_byte(fi.writer, '{', &fi.n) + } else { + io.write_string(fi.writer, "matrix", &fi.n) + io.write_byte(fi.writer, '[', &fi.n) + } + defer io.write_byte(fi.writer, ']' if verb != 'w' else '}', &fi.n) fi.indent += 1 @@ -2282,7 +2527,11 @@ fmt_matrix :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Matrix for col in 0.. 0 { io.write_string(fi.writer, ", ", &fi.n) } - offset := (row + col*info.elem_stride)*info.elem_size + offset: int + switch info.layout { + case .Column_Major: offset = (row + col*info.elem_stride)*info.elem_size + case .Row_Major: offset = (col + row*info.elem_stride)*info.elem_size + } data := uintptr(v.data) + uintptr(offset) fmt_arg(fi, any{rawptr(data), info.elem.id}, verb) @@ -2291,12 +2540,17 @@ fmt_matrix :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Matrix } } else { // Printed in Row-Major layout to match text layout + row_separator := ", " if verb == 'w' else "; " for row 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) } - offset := (row + col*info.elem_stride)*info.elem_size + offset: int + switch info.layout { + case .Column_Major: offset = (row + col*info.elem_stride)*info.elem_size + case .Row_Major: offset = (col + row*info.elem_stride)*info.elem_size + } data := uintptr(v.data) + uintptr(offset) fmt_arg(fi, any{rawptr(data), info.elem.id}, verb) @@ -2310,6 +2564,99 @@ fmt_matrix :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Matrix fmt_write_indent(fi) } } + +fmt_bit_field :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Bit_Field, type_name: string) { + read_bits :: proc(ptr: [^]byte, offset, size: uintptr) -> (res: u64) { + for i in 0.. (do_continue: bool) { + 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 true + } + r, w := utf8.decode_rune_in_string(value) + value = value[w:] + if value == "" || value[0] == ',' { + verb^ = r + } + } + return false + } + + io.write_string(fi.writer, type_name if len(type_name) != 0 || verb == 'w' else "bit_field", &fi.n) + io.write_byte(fi.writer, '{', &fi.n) + + hash := fi.hash; defer fi.hash = hash + indent := fi.indent; defer fi.indent -= 1 + do_trailing_comma := hash + + fi.indent += 1 + + if hash { + io.write_byte(fi.writer, '\n', &fi.n) + } + defer { + if hash { + for _ in 0.. 0 { + io.write_string(fi.writer, ", ") + } + if hash { + fmt_write_indent(fi) + } + + io.write_string(fi.writer, name, &fi.n) + io.write_string(fi.writer, " = ", &fi.n) + + bit_offset := info.bit_offsets[i] + bit_size := info.bit_sizes[i] + + 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 + m := u64(1<<(bit_size-1)) + value = (value ~ m) - m + } + + fmt_value(fi, any{&value, type.id}, field_verb) + if do_trailing_comma { io.write_string(fi.writer, ",\n", &fi.n) } + + } +} + + + // Formats a value based on its type and formatting verb // // Inputs: @@ -2328,7 +2675,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) @@ -2374,7 +2720,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) @@ -2382,7 +2728,8 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { } case runtime.Type_Info_Struct, - runtime.Type_Info_Union: + runtime.Type_Info_Union, + runtime.Type_Info_Bit_Field: if ptr == nil { io.write_string(fi.writer, "", &fi.n) return @@ -2416,9 +2763,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 } @@ -2443,7 +2792,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) @@ -2469,11 +2818,11 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { defer fi.record_level -= 1 if fi.hash { - io.write_string(fi.writer, "[\n", &fi.n) + 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, ']', &fi.n) + io.write_byte(fi.writer, ']' if verb != 'w' else '}', &fi.n) } indent := fi.indent fi.indent += 1 @@ -2497,8 +2846,8 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { io.write_string(fi.writer, ",\n", &fi.n) } } else { - io.write_byte(fi.writer, '[', &fi.n) - defer io.write_byte(fi.writer, ']', &fi.n) + io.write_byte(fi.writer, '[' if verb != 'w' else '{', &fi.n) + defer io.write_byte(fi.writer, ']' if verb != 'w' else '}', &fi.n) for i in 0.. 0 { io.write_string(fi.writer, ", ", &fi.n) } @@ -2520,8 +2869,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 } @@ -2532,8 +2883,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 } @@ -2544,8 +2897,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 } @@ -2563,38 +2918,61 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { case runtime.Type_Info_Map: - if verb != 'v' { + switch verb { + case: fmt_bad_verb(fi, verb) - return - } - - io.write_string(fi.writer, "map[", &fi.n) - defer io.write_byte(fi.writer, ']', &fi.n) - fi.record_level += 1 - defer fi.record_level -= 1 - - m := (^mem.Raw_Map)(v.data) - if m != nil { - if info.map_info == nil { - return + case 'v', 'w': + if verb == 'v' { + io.write_string(fi.writer, "map", &fi.n) } - map_cap := uintptr(runtime.map_cap(m^)) - ks, vs, hs, _, _ := runtime.map_kvh_data_dynamic(m^, info.map_info) - j := 0 - for bucket_index in 0.. 0 { - io.write_string(fi.writer, ", ", &fi.n) + + hash := fi.hash; defer fi.hash = hash + indent := fi.indent; defer fi.indent -= 1 + do_trailing_comma := hash + + fi.indent += 1 + if hash { + io.write_byte(fi.writer, '\n', &fi.n) + } + defer { + if hash { + for _ in 0.. 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: // - fi: A pointer to an Info struct containing formatting information. // - c: The complex128 value to be formatted. // - bits: The number of bits in the complex number (32 or 64). -// - verb: The formatting verb rune ('f', 'F', 'v', 'h', 'H'). +// - verb: The formatting verb rune ('f', 'F', 'v', 'h', 'H', 'w'). // fmt_complex :: proc(fi: ^Info, c: complex128, bits: int, verb: rune) { switch verb { - case 'f', 'F', 'v', 'h', 'H': + 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) @@ -2670,28 +3054,28 @@ fmt_complex :: proc(fi: ^Info, c: complex128, bits: int, verb: rune) { // - fi: A pointer to an Info struct containing formatting information. // - q: The quaternion256 value to be formatted. // - bits: The number of bits in the quaternion number (64, 128, or 256). -// - verb: The formatting verb rune ('f', 'F', 'v', 'h', 'H'). +// - verb: The formatting verb rune ('f', 'F', 'v', 'h', 'H', 'w'). // fmt_quaternion :: proc(fi: ^Info, q: quaternion256, bits: int, verb: rune) { switch verb { - case 'f', 'F', 'v', 'h', 'H': + case 'f', 'F', 'v', 'h', 'H', 'w': r, i, j, k := real(q), imag(q), jmag(q), kmag(q) 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 c70b7c1c0..ce90fbfe7 100644 --- a/core/fmt/fmt_js.odin +++ b/core/fmt/fmt_js.odin @@ -1,7 +1,9 @@ -//+build js +#+build js package fmt +import "core:bufio" import "core:io" +import "core:os" foreign import "odin_env" @@ -31,12 +33,63 @@ 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 println :: proc(args: ..any, sep := " ", flush := true) -> int { return wprintln(w=stdout, args=args, sep=sep, flush=flush) } // printf formats according to the specififed format string and writes to stdout printf :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush) } +// printfln formats according to the specified format string and writes to stdout, followed by a newline. +printfln :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush, newline=true) } // eprint formats using the default print settings and writes to stderr eprint :: proc(args: ..any, sep := " ", flush := true) -> int { return wprint(w=stderr, args=args, sep=sep, flush=flush) } @@ -44,3 +97,5 @@ eprint :: proc(args: ..any, sep := " ", flush := true) -> int { return wprint( eprintln :: proc(args: ..any, sep := " ", flush := true) -> int { return wprintln(w=stderr, args=args, sep=sep, flush=flush) } // eprintf formats according to the specififed format string and writes to stderr eprintf :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stderr, fmt, ..args, flush=flush) } +// eprintfln formats according to the specified format string and writes to stderr, followed by a newline. +eprintfln :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush, newline=true) } diff --git a/core/fmt/fmt_os.odin b/core/fmt/fmt_os.odin index 1f0ccf412..a481061f1 100644 --- a/core/fmt/fmt_os.odin +++ b/core/fmt/fmt_os.odin @@ -1,7 +1,9 @@ -//+build !freestanding !js +#+build !freestanding +#+build !js +#+build !orca package fmt -import "core:runtime" +import "base:runtime" import "core:os" import "core:io" import "core:bufio" @@ -29,7 +31,7 @@ fprintln :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true) -> int { 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) -> int { +fprintf :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true, newline := false) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) @@ -37,7 +39,11 @@ fprintf :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true) -> int { bufio.writer_init_with_buf(&b, os.stream_from_handle(fd), buf[:]) w := bufio.writer_to_writer(&b) - return wprintf(w, fmt, ..args, flush=flush) + 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) -> int { + return fprintf(fd, fmt, ..args, flush=flush, newline=true) } fprint_type :: proc(fd: os.Handle, info: ^runtime.Type_Info, flush := true) -> (n: int, err: io.Error) { buf: [1024]byte @@ -61,15 +67,19 @@ fprint_typeid :: proc(fd: os.Handle, id: typeid, flush := true) -> (n: int, err: } // print formats using the default print settings and writes to os.stdout -print :: proc(args: ..any, sep := " ", flush := true) -> int { return fprint(os.stdout, ..args, sep=sep, flush=flush) } +print :: proc(args: ..any, sep := " ", flush := true) -> int { return fprint(os.stdout, ..args, sep=sep, flush=flush) } // println formats using the default print settings and writes to os.stdout -println :: proc(args: ..any, sep := " ", flush := true) -> int { return fprintln(os.stdout, ..args, sep=sep, flush=flush) } +println :: proc(args: ..any, sep := " ", flush := true) -> int { return fprintln(os.stdout, ..args, sep=sep, flush=flush) } // printf formats according to the specified format string and writes to os.stdout -printf :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(os.stdout, fmt, ..args, flush=flush) } +printf :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(os.stdout, fmt, ..args, flush=flush) } +// printfln formats according to the specified format string and writes to os.stdout, followed by a newline. +printfln :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(os.stdout, fmt, ..args, flush=flush, newline=true) } // eprint formats using the default print settings and writes to os.stderr -eprint :: proc(args: ..any, sep := " ", flush := true) -> int { return fprint(os.stderr, ..args, sep=sep, flush=flush) } +eprint :: proc(args: ..any, sep := " ", flush := true) -> int { return fprint(os.stderr, ..args, sep=sep, flush=flush) } // eprintln formats using the default print settings and writes to os.stderr -eprintln :: proc(args: ..any, sep := " ", flush := true) -> int { return fprintln(os.stderr, ..args, sep=sep, flush=flush) } +eprintln :: proc(args: ..any, sep := " ", flush := true) -> int { return fprintln(os.stderr, ..args, sep=sep, flush=flush) } // eprintf formats according to the specified format string and writes to os.stderr -eprintf :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(os.stderr, fmt, ..args, flush=flush) } +eprintf :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(os.stderr, fmt, ..args, flush=flush) } +// eprintfln formats according to the specified format string and writes to os.stderr, followed by a newline. +eprintfln :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(os.stderr, fmt, ..args, flush=flush, newline=true) } diff --git a/core/hash/crc.odin b/core/hash/crc.odin index 9c0048a0f..68b8f5369 100644 --- a/core/hash/crc.odin +++ b/core/hash/crc.odin @@ -1,7 +1,7 @@ package hash -@(optimization_mode="speed") -crc64_ecma_182 :: proc(data: []byte, seed := u64(0)) -> (result: u64) #no_bounds_check { +@(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 { result = result<<8 ~ _crc64_table_ecma_182[((result>>56) ~ u64(b)) & 0xff] @@ -14,8 +14,8 @@ crc64_ecma_182 :: proc(data: []byte, seed := u64(0)) -> (result: u64) #no_bounds 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") -crc64_xz :: proc(data: []byte, seed := u64(0)) -> u64 #no_bounds_check { +@(optimization_mode="favor_size") +crc64_xz :: proc "contextless" (data: []byte, seed := u64(0)) -> u64 #no_bounds_check { data := data result := ~u64le(seed) @@ -52,8 +52,8 @@ crc64_xz :: proc(data: []byte, seed := u64(0)) -> u64 #no_bounds_check { /* Generator polynomial: x^64 + x^4 + x^3 + x + 1 */ -@(optimization_mode="speed") -crc64_iso_3306 :: proc(data: []byte, seed := u64(0)) -> u64 #no_bounds_check { +@(optimization_mode="favor_size") +crc64_iso_3306 :: proc "contextless" (data: []byte, seed := u64(0)) -> u64 #no_bounds_check { result := seed @@ -70,7 +70,7 @@ crc64_iso_3306 :: proc(data: []byte, seed := u64(0)) -> u64 #no_bounds_check { return result } -crc64_iso_3306_inverse :: proc(data: []byte, seed := u64(0)) -> u64 { +crc64_iso_3306_inverse :: proc "contextless" (data: []byte, seed := u64(0)) -> u64 { result := #force_inline crc64_iso_3306(data, ~seed) return ~result } @@ -738,4 +738,4 @@ crc64_iso_3306_inverse :: proc(data: []byte, seed := u64(0)) -> u64 { 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 fead4d74f..a7f68207e 100644 --- a/core/hash/crc32.odin +++ b/core/hash/crc32.odin @@ -1,9 +1,9 @@ package hash -import "core:intrinsics" +import "base:intrinsics" -@(optimization_mode="speed") -crc32 :: proc(data: []byte, seed := u32(0)) -> u32 #no_bounds_check { +@(optimization_mode="favor_size") +crc32 :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 #no_bounds_check { crc := ~seed buffer := raw_data(data) length := len(data) @@ -323,7 +323,7 @@ crc32_table := [8][256]u32{ /* @(optimization_mode="speed") -crc32 :: proc(data: []byte, seed := u32(0)) -> u32 { +crc32 :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 { result := ~u32(seed); #no_bounds_check for b in data { result = result>>8 ~ _crc32_table[(result ~ u32(b)) & 0xff]; diff --git a/core/hash/hash.odin b/core/hash/hash.odin index 176d17141..45f524d8a 100644 --- a/core/hash/hash.odin +++ b/core/hash/hash.odin @@ -1,10 +1,10 @@ package hash import "core:mem" -import "core:intrinsics" +import "base:intrinsics" -@(optimization_mode="speed") -adler32 :: proc(data: []byte, seed := u32(1)) -> u32 #no_bounds_check { +@(optimization_mode="favor_size") +adler32 :: proc "contextless" (data: []byte, seed := u32(1)) -> u32 #no_bounds_check { ADLER_CONST :: 65521 @@ -46,8 +46,8 @@ adler32 :: proc(data: []byte, seed := u32(1)) -> u32 #no_bounds_check { return (u32(b) << 16) | u32(a) } -@(optimization_mode="speed") -djb2 :: proc(data: []byte, seed := u32(5381)) -> u32 { +@(optimization_mode="favor_size") +djb2 :: proc "contextless" (data: []byte, seed := u32(5381)) -> u32 { hash: u32 = seed for b in data { hash = (hash << 5) + hash + u32(b) // hash * 33 + u32(b) @@ -55,7 +55,7 @@ djb2 :: proc(data: []byte, seed := u32(5381)) -> u32 { return hash } -djbx33a :: proc(data: []byte, seed := u32(5381)) -> (result: [16]byte) #no_bounds_check { +djbx33a :: proc "contextless" (data: []byte, seed := u32(5381)) -> (result: [16]byte) #no_bounds_check { state := [4]u32{seed, seed, seed, seed} s: u32 = 0 @@ -73,8 +73,8 @@ djbx33a :: proc(data: []byte, seed := u32(5381)) -> (result: [16]byte) #no_bound } // If you have a choice, prefer fnv32a -@(optimization_mode="speed") -fnv32_no_a :: proc(data: []byte, seed := u32(0x811c9dc5)) -> u32 { +@(optimization_mode="favor_size") +fnv32_no_a :: proc "contextless" (data: []byte, seed := u32(0x811c9dc5)) -> u32 { h: u32 = seed for b in data { h = (h * 0x01000193) ~ u32(b) @@ -86,16 +86,16 @@ 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") -fnv64_no_a :: proc(data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 { +@(optimization_mode="favor_size") +fnv64_no_a :: proc "contextless" (data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 { h: u64 = seed for b in data { h = (h * 0x100000001b3) ~ u64(b) } return h } -@(optimization_mode="speed") -fnv32a :: proc(data: []byte, seed := u32(0x811c9dc5)) -> u32 { +@(optimization_mode="favor_size") +fnv32a :: proc "contextless" (data: []byte, seed := u32(0x811c9dc5)) -> u32 { h: u32 = seed for b in data { h = (h ~ u32(b)) * 0x01000193 @@ -103,8 +103,8 @@ fnv32a :: proc(data: []byte, seed := u32(0x811c9dc5)) -> u32 { return h } -@(optimization_mode="speed") -fnv64a :: proc(data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 { +@(optimization_mode="favor_size") +fnv64a :: proc "contextless" (data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 { h: u64 = seed for b in data { h = (h ~ u64(b)) * 0x100000001b3 @@ -112,8 +112,8 @@ fnv64a :: proc(data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 { return h } -@(optimization_mode="speed") -jenkins :: proc(data: []byte, seed := u32(0)) -> u32 { +@(optimization_mode="favor_size") +jenkins :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 { hash: u32 = seed for b in data { hash += u32(b) @@ -126,8 +126,8 @@ jenkins :: proc(data: []byte, seed := u32(0)) -> u32 { return hash } -@(optimization_mode="speed") -murmur32 :: proc(data: []byte, seed := u32(0)) -> u32 { +@(optimization_mode="favor_size") +murmur32 :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 { c1_32: u32 : 0xcc9e2d51 c2_32: u32 : 0x1b873593 @@ -177,8 +177,8 @@ murmur32 :: proc(data: []byte, seed := u32(0)) -> u32 { } // See https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp#L96 -@(optimization_mode="speed") -murmur64a :: proc(data: []byte, seed := u64(0x9747b28c)) -> u64 { +@(optimization_mode="favor_size") +murmur64a :: proc "contextless" (data: []byte, seed := u64(0x9747b28c)) -> u64 { m :: 0xc6a4a7935bd1e995 r :: 47 @@ -218,8 +218,8 @@ murmur64a :: proc(data: []byte, seed := u64(0x9747b28c)) -> u64 { } // See https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp#L140 -@(optimization_mode="speed") -murmur64b :: proc(data: []byte, seed := u64(0x9747b28c)) -> u64 { +@(optimization_mode="favor_size") +murmur64b :: proc "contextless" (data: []byte, seed := u64(0x9747b28c)) -> u64 { m :: 0x5bd1e995 r :: 24 @@ -286,8 +286,8 @@ murmur64b :: proc(data: []byte, seed := u64(0x9747b28c)) -> u64 { return u64(h1)<<32 | u64(h2) } -@(optimization_mode="speed") -sdbm :: proc(data: []byte, seed := u32(0)) -> u32 { +@(optimization_mode="favor_size") +sdbm :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 { hash: u32 = seed for b in data { hash = u32(b) + (hash<<6) + (hash<<16) - hash diff --git a/core/hash/mini.odin b/core/hash/mini.odin index 98b1b4ba3..6b476f535 100644 --- a/core/hash/mini.odin +++ b/core/hash/mini.odin @@ -1,6 +1,6 @@ package hash -ginger_hash8 :: proc(x: u8) -> u8 { +ginger_hash8 :: proc "contextless" (x: u8) -> u8 { h := x * 251 h += ~(x << 3) h ~= (x >> 1) @@ -11,7 +11,7 @@ ginger_hash8 :: proc(x: u8) -> u8 { } -ginger_hash16 :: proc(x: u16) -> u16 { +ginger_hash16 :: proc "contextless" (x: u16) -> u16 { z := (x << 8) | (x >> 8) h := z h += ~(z << 5) @@ -24,14 +24,14 @@ ginger_hash16 :: proc(x: u16) -> u16 { } -ginger8 :: proc(data: []byte) -> u8 { +ginger8 :: proc "contextless" (data: []byte) -> u8 { h := ginger_hash8(0) for b in data { h ~= ginger_hash8(b) } return h } -ginger16 :: proc(data: []byte) -> u16 { +ginger16 :: proc "contextless" (data: []byte) -> u16 { h := ginger_hash16(0) for b in data { h ~= ginger_hash16(u16(b)) diff --git a/core/hash/xxhash/common.odin b/core/hash/xxhash/common.odin index 8b34c1e8f..adfc1bac2 100644 --- a/core/hash/xxhash/common.odin +++ b/core/hash/xxhash/common.odin @@ -1,5 +1,4 @@ /* - An implementation of Yann Collet's [xxhash Fast Hash Algorithm](https://cyan4973.github.io/xxHash/). Copyright 2021 Jeroen van Rijn . Made available under Odin's BSD-3 license, based on the original C code. @@ -7,10 +6,12 @@ List of contributors: Jeroen van Rijn: Initial implementation. */ + +// An implementation of Yann Collet's [[ xxhash Fast Hash Algorithm; https://cyan4973.github.io/xxHash/ ]]. package xxhash -import "core:intrinsics" -import "core:runtime" +import "base:intrinsics" +import "base:runtime" mem_copy :: runtime.mem_copy byte_swap :: intrinsics.byte_swap @@ -67,17 +68,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 +90,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 +100,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/streaming.odin b/core/hash/xxhash/streaming.odin index 6f630b042..f77edf3e9 100644 --- a/core/hash/xxhash/streaming.odin +++ b/core/hash/xxhash/streaming.odin @@ -7,10 +7,11 @@ List of contributors: Jeroen van Rijn: Initial implementation. */ + package xxhash import "core:mem" -import "core:intrinsics" +import "base:intrinsics" /* === XXH3 128-bit streaming === @@ -129,7 +130,7 @@ XXH3_create_state :: proc(allocator := context.allocator) -> (res: ^XXH3_state, } XXH3_destroy_state :: proc(state: ^XXH3_state, allocator := context.allocator) -> (err: Error) { - free(state) + free(state, allocator) return .None } @@ -371,4 +372,4 @@ XXH3_generate_secret :: proc(secret_buffer: []u8, custom_seed: []u8) { mem_copy(&secret_buffer[segment_start], &segment, size_of(segment)) } } -} \ No newline at end of file +} diff --git a/core/hash/xxhash/xxhash_3.odin b/core/hash/xxhash/xxhash_3.odin index fa50075f9..293e98528 100644 --- a/core/hash/xxhash/xxhash_3.odin +++ b/core/hash/xxhash/xxhash_3.odin @@ -7,9 +7,10 @@ List of contributors: Jeroen van Rijn: Initial implementation. */ + package xxhash -import "core:intrinsics" +import "base:intrinsics" /* ************************************************************************* @@ -111,13 +112,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 +126,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 +138,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 +167,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 +191,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 +220,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 +262,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 +280,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 +294,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 +324,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 +380,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 +408,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 +416,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 +442,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 +475,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 +520,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 +543,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 +563,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 +580,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 +622,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 +633,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 +666,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 +700,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 +738,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 +755,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 +772,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 +792,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 +805,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 +834,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 +850,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 +869,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 +881,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 +897,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 +925,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 +945,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 5bc87c2c0..28cd4e177 100644 --- a/core/hash/xxhash/xxhash_32.odin +++ b/core/hash/xxhash/xxhash_32.odin @@ -7,9 +7,10 @@ List of contributors: Jeroen van Rijn: Initial implementation. */ + package xxhash -import "core:intrinsics" +import "base:intrinsics" /* 32-bit hash functions @@ -19,15 +20,15 @@ xxh_u32 :: u32 XXH32_DEFAULT_SEED :: XXH32_hash(0) XXH32_state :: struct { - total_len_32: XXH32_hash, /*!< Total length hashed, modulo 2^32 */ - large_len: XXH32_hash, /*!< Whether the hash is >= 16 (handles @ref total_len_32 overflow) */ - v1: XXH32_hash, /*!< First accumulator lane */ - v2: XXH32_hash, /*!< Second accumulator lane */ - v3: XXH32_hash, /*!< Third accumulator lane */ - v4: XXH32_hash, /*!< Fourth accumulator lane */ - mem32: [4]XXH32_hash, /*!< Internal buffer for partial reads. Treated as unsigned char[16]. */ - memsize: XXH32_hash, /*!< Amount of data in @ref mem32 */ - reserved: XXH32_hash, /*!< Reserved field. Do not read or write to it, it may be removed. */ + total_len_32: XXH32_hash, /*!< Total length hashed, modulo 2^32 */ + large_len: XXH32_hash, /*!< Whether the hash is >= 16 (handles @ref total_len_32 overflow) */ + v1: XXH32_hash, /*!< First accumulator lane */ + v2: XXH32_hash, /*!< Second accumulator lane */ + v3: XXH32_hash, /*!< Third accumulator lane */ + v4: XXH32_hash, /*!< Fourth accumulator lane */ + mem32: [4]XXH32_hash, /*!< Internal buffer for partial reads. Treated as unsigned char[16]. */ + memsize: XXH32_hash, /*!< Amount of data in @ref mem32 */ + reserved: XXH32_hash, /*!< Reserved field. Do not read or write to it, it may be removed. */ } XXH32_canonical :: struct { @@ -40,7 +41,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 +54,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 +66,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 +144,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 +319,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 9280e9c59..87e8916d6 100644 --- a/core/hash/xxhash/xxhash_64.odin +++ b/core/hash/xxhash/xxhash_64.odin @@ -7,9 +7,10 @@ List of contributors: Jeroen van Rijn: Initial implementation. */ + package xxhash -import "core:intrinsics" +import "base:intrinsics" /* 64-bit hash functions @@ -19,15 +20,15 @@ xxh_u64 :: u64 XXH64_DEFAULT_SEED :: XXH64_hash(0) XXH64_state :: struct { - total_len: XXH64_hash, /*!< Total length hashed. This is always 64-bit. */ - v1: XXH64_hash, /*!< First accumulator lane */ - v2: XXH64_hash, /*!< Second accumulator lane */ - v3: XXH64_hash, /*!< Third accumulator lane */ - v4: XXH64_hash, /*!< Fourth accumulator lane */ - mem64: [4]XXH64_hash, /*!< Internal buffer for partial reads. Treated as unsigned char[32]. */ - memsize: XXH32_hash, /*!< Amount of data in @ref mem64 */ - reserved32: XXH32_hash, /*!< Reserved field, needed for padding anyways*/ - reserved64: XXH64_hash, /*!< Reserved field. Do not read or write to it, it may be removed. */ + total_len: XXH64_hash, /*!< Total length hashed. This is always 64-bit. */ + v1: XXH64_hash, /*!< First accumulator lane */ + v2: XXH64_hash, /*!< Second accumulator lane */ + v3: XXH64_hash, /*!< Third accumulator lane */ + v4: XXH64_hash, /*!< Fourth accumulator lane */ + mem64: [4]XXH64_hash, /*!< Internal buffer for partial reads. Treated as unsigned char[32]. */ + memsize: XXH32_hash, /*!< Amount of data in @ref mem64 */ + reserved32: XXH32_hash, /*!< Reserved field, needed for padding anyways*/ + reserved64: XXH64_hash, /*!< Reserved field. Do not read or write to it, it may be removed. */ } XXH64_canonical :: struct { @@ -40,7 +41,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 +51,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 +69,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 +101,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 +192,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 +246,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 +293,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 ad01f7e6b..690ebc045 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -12,8 +12,9 @@ package image import "core:bytes" import "core:mem" +import "core:io" import "core:compress" -import "core:runtime" +import "base:runtime" /* 67_108_864 pixels max by default. @@ -62,6 +63,7 @@ Image_Metadata :: union #shared_nil { ^PNG_Info, ^QOI_Info, ^TGA_Info, + ^BMP_Info, } @@ -110,7 +112,8 @@ Image_Option: `.alpha_drop_if_present` If the image has an alpha channel, drop it. - You may want to use `.alpha_premultiply` in this case. + You may want to use `.alpha_ + tiply` in this case. NOTE: For PNG, this also skips handling of the tRNS chunk, if present, unless you select `alpha_premultiply`. @@ -159,11 +162,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 +201,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 */ @@ -461,6 +588,32 @@ Channel :: enum u8 { A = 4, } +// Take a slice of pixels (`[]RGBA_Pixel`, etc), and return an `Image` +// Don't call `destroy` on the resulting `Image`. Instead, delete the original `pixels` slice. +pixels_to_image :: proc(pixels: [][$N]$E, width: int, height: int) -> (img: Image, ok: bool) where E == u8 || E == u16, N >= 1, N <= 4 { + if len(pixels) != width * height { + return {}, false + } + + img.height = height + img.width = width + img.depth = 8 when E == u8 else 16 + img.channels = N + + s := transmute(runtime.Raw_Slice)pixels + d := runtime.Raw_Dynamic_Array{ + data = s.data, + len = s.len * size_of(E) * N, + cap = s.len * size_of(E) * N, + allocator = runtime.nil_allocator(), + } + img.pixels = bytes.Buffer{ + buf = transmute([dynamic]u8)d, + } + + return img, true +} + // When you have an RGB(A) image, but want a particular channel. return_single_channel :: proc(img: ^Image, channel: Channel) -> (res: ^Image, ok: bool) { // Were we actually given a valid image? @@ -651,7 +804,7 @@ alpha_add_if_missing :: proc(img: ^Image, alpha_key := Alpha_Key{}, allocator := // We have keyed alpha. o: GA_Pixel for p in inp { - if p == key.r { + if p.r == key.r { o = GA_Pixel{0, key.g} } else { o = GA_Pixel{p.r, 255} @@ -710,7 +863,7 @@ alpha_add_if_missing :: proc(img: ^Image, alpha_key := Alpha_Key{}, allocator := // We have keyed alpha. o: GA_Pixel_16 for p in inp { - if p == key.r { + if p.r == key.r { o = GA_Pixel_16{0, key.g} } else { o = GA_Pixel_16{p.r, 65535} @@ -842,11 +995,11 @@ alpha_drop_if_present :: proc(img: ^Image, options := Options{}, alpha_key := Al bg := G_Pixel{} if temp_bg, temp_bg_ok := img.background.(RGB_Pixel_16); temp_bg_ok { // Background is RGB 16-bit, take just the red channel's topmost byte. - bg = u8(temp_bg.r >> 8) + bg.r = u8(temp_bg.r >> 8) } for p in inp { - out[0] = bg if p == key else p + out[0] = bg if p.r == key else p out = out[1:] } @@ -865,8 +1018,8 @@ alpha_drop_if_present :: proc(img: ^Image, options := Options{}, alpha_key := Al for p in inp { a := f32(p.g) / 255.0 c := ((1.0 - a) * bg + a * f32(p.r)) - out[0] = u8(c) - out = out[1:] + out[0].r = u8(c) + out = out[1:] } } else if .alpha_premultiply in options { @@ -874,14 +1027,14 @@ alpha_drop_if_present :: proc(img: ^Image, options := Options{}, alpha_key := Al for p in inp { a := f32(p.g) / 255.0 c := f32(p.r) * a - out[0] = u8(c) - out = out[1:] + out[0].r = u8(c) + out = out[1:] } } else { // Just drop alpha on the floor. for p in inp { - out[0] = p.r - out = out[1:] + out[0].r = p.r + out = out[1:] } } @@ -951,11 +1104,11 @@ alpha_drop_if_present :: proc(img: ^Image, options := Options{}, alpha_key := Al bg := G_Pixel_16{} if temp_bg, temp_bg_ok := img.background.(RGB_Pixel_16); temp_bg_ok { // Background is RGB 16-bit, take just the red channel. - bg = temp_bg.r + bg.r = temp_bg.r } for p in inp { - out[0] = bg if p == key else p + out[0] = bg if p.r == key else p out = out[1:] } @@ -974,8 +1127,8 @@ alpha_drop_if_present :: proc(img: ^Image, options := Options{}, alpha_key := Al for p in inp { a := f32(p.g) / 65535.0 c := ((1.0 - a) * bg + a * f32(p.r)) - out[0] = u16(c) - out = out[1:] + out[0].r = u16(c) + out = out[1:] } } else if .alpha_premultiply in options { @@ -983,14 +1136,14 @@ alpha_drop_if_present :: proc(img: ^Image, options := Options{}, alpha_key := Al for p in inp { a := f32(p.g) / 65535.0 c := f32(p.r) * a - out[0] = u16(c) - out = out[1:] + out[0].r = u16(c) + out = out[1:] } } else { // Just drop alpha on the floor. for p in inp { - out[0] = p.r - out = out[1:] + out[0].r = p.r + out = out[1:] } } @@ -1133,6 +1286,76 @@ 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} + +// For all pixels of the image, multiplies R, G and B by Alpha. This is useful mainly for games rendering anti-aliased transparent sprites. +// Grayscale with alpha images are supported as well. +// Note that some image formats like QOI explicitly do NOT support premultiplied alpha, so you will end up with a non-standard file. +premultiply_alpha :: proc(img: ^Image) -> (ok: bool) { + switch { + case img.channels == 2 && img.depth == 8: + pixels := mem.slice_data_cast([]GA_Pixel, img.pixels.buf[:]) + for &pixel in pixels { + pixel.r = u8(u32(pixel.r) * u32(pixel.g) / 0xFF) + } + return true + case img.channels == 2 && img.depth == 16: + pixels := mem.slice_data_cast([]GA_Pixel_16, img.pixels.buf[:]) + for &pixel in pixels { + pixel.r = u16(u32(pixel.r) * u32(pixel.g) / 0xFFFF) + } + return true + case img.channels == 4 && img.depth == 8: + pixels := mem.slice_data_cast([]RGBA_Pixel, img.pixels.buf[:]) + for &pixel in pixels { + pixel.r = u8(u32(pixel.r) * u32(pixel.a) / 0xFF) + pixel.g = u8(u32(pixel.g) * u32(pixel.a) / 0xFF) + pixel.b = u8(u32(pixel.b) * u32(pixel.a) / 0xFF) + } + return true + case img.channels == 4 && img.depth == 16: + pixels := mem.slice_data_cast([]RGBA_Pixel_16, img.pixels.buf[:]) + for &pixel in pixels { + pixel.r = u16(u32(pixel.r) * u32(pixel.a) / 0xFFFF) + pixel.g = u16(u32(pixel.g) * u32(pixel.a) / 0xFFFF) + pixel.b = u16(u32(pixel.b) * u32(pixel.a) / 0xFFFF) + } + return true + case: return false + } +} // Replicates grayscale values into RGB(A) 8- or 16-bit images as appropriate. // Returns early with `false` if already an RGB(A) image. @@ -1153,55 +1376,57 @@ 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. + out = out[1:] } 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. + out = out[1:] + } + + case: + unreachable() + } + + case: + unreachable() } @@ -1216,7 +1441,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 +1450,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/general.odin b/core/image/general.odin index 17a8f35ea..e92b54f18 100644 --- a/core/image/general.odin +++ b/core/image/general.odin @@ -23,7 +23,15 @@ register :: proc(kind: Which_File_Type, loader: Loader_Proc, destroyer: Destroy_ load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { loader := _internal_loaders[which(data)] if loader == nil { - return nil, .Unsupported_Format + + // Check if there is at least one loader, otherwise panic to let the user know about misuse. + for a_loader in _internal_loaders { + if a_loader != nil { + return nil, .Unsupported_Format + } + } + + panic("image.load called when no image loaders are registered. Register a loader by first importing a subpackage (eg: `import \"core:image/png\"`), or with image.register") } return loader(data, options, allocator) } @@ -138,7 +146,7 @@ which_bytes :: proc(data: []byte) -> Which_File_Type { case s[6:10] == "JFIF", s[6:10] == "Exif": return .JPEG case s[:3] == "\xff\xd8\xff": - switch s[4] { + switch s[3] { case 0xdb, 0xee, 0xe1, 0xe0: return .JPEG } @@ -185,7 +193,7 @@ which_bytes :: proc(data: []byte) -> Which_File_Type { return .HDR case s[:4] == "\x38\x42\x50\x53": return .PSD - case s[:4] != "\x53\x80\xF6\x34" && s[88:92] == "PICT": + case s[:4] == "\x53\x80\xF6\x34" && s[88:92] == "PICT": return .PIC case s[:4] == "\x69\x63\x6e\x73": return .ICNS diff --git a/core/image/general_js.odin b/core/image/general_js.odin index 841d9c200..abf9812c0 100644 --- a/core/image/general_js.odin +++ b/core/image/general_js.odin @@ -1,4 +1,4 @@ -//+build js +#+build js package image load :: proc{ diff --git a/core/image/general_os.odin b/core/image/general_os.odin index 144a3470f..98eb5bdbe 100644 --- a/core/image/general_os.odin +++ b/core/image/general_os.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package image import "core:os" @@ -27,7 +27,7 @@ which :: proc{ which_file :: proc(path: string) -> Which_File_Type { f, err := os.open(path) - if err != 0 { + if err != nil { return .Unknown } header: [128]byte diff --git a/core/image/netpbm/doc.odin b/core/image/netpbm/doc.odin index 1b5b46856..7106e023e 100644 --- a/core/image/netpbm/doc.odin +++ b/core/image/netpbm/doc.odin @@ -1,5 +1,6 @@ /* Formats: + PBM (P1, P4): Portable Bit Map, stores black and white images (1 channel) PGM (P2, P5): Portable Gray Map, stores greyscale images (1 channel, 1 or 2 bytes per value) PPM (P3, P6): Portable Pixel Map, stores colour images (3 channel, 1 or 2 bytes per value) @@ -7,27 +8,29 @@ Formats: PFM (Pf, PF): Portable Float Map, stores floating-point images (Pf: 1 channel, PF: 3 channel) Reading: - All formats fill out header fields `format`, `width`, `height`, `channels`, `depth` - Specific formats use more fields - PGM, PPM, and PAM set `maxval` (maximum of 65535) - PAM sets `tupltype` if there is one, and can set `channels` to any value (not just 1 or 3) - PFM sets `scale` (float equivalent of `maxval`) and `little_endian` (endianness of stored floats) - Currently doesn't support reading multiple images from one binary-format file + +- All formats fill out header fields `format`, `width`, `height`, `channels`, `depth`. +- Specific formats use more fields: + PGM, PPM, and PAM set `maxval` (maximum of 65535) + PAM sets `tupltype` if there is one, and can set `channels` to any value (not just 1 or 3) + PFM sets `scale` (float equivalent of `maxval`) and `little_endian` (endianness of stored floats) +- Currently doesn't support reading multiple images from one binary-format file. Writing: - You can use your own `Netpbm_Info` struct to control how images are written - All formats require the header field `format` to be specified - Additional header fields are required for specific formats - PGM, PPM, and PAM require `maxval` (maximum of 65535) - PAM also uses `tupltype`, though it may be left as default (empty or nil string) - PFM requires `scale`, and optionally `little_endian` + +- You can use your own `Netpbm_Info` struct to control how images are written. +- All formats require the header field `format` to be specified. +- Additional header fields are required for specific formats: + PGM, PPM, and PAM require `maxval` (maximum of 65535) + PAM also uses `tupltype`, though it may be left as default (empty or nil string) + PFM requires `scale`, and optionally `little_endian` Some syntax differences from the specifications: - `channels` stores the number of values per pixel, what the PAM specification calls `depth` - `depth` instead is the number of bits for a single value (32 for PFM, 16 or 8 otherwise) - `scale` and `little_endian` are separated, so the `header` will always store a positive `scale` - `little_endian` will only be true for a negative `scale` PFM, every other format will be false - `little_endian` only describes the netpbm data being read/written, the image buffer will be native -*/ +- `channels` stores the number of values per pixel, what the PAM specification calls `depth` +- `depth` instead is the number of bits for a single value (32 for PFM, 16 or 8 otherwise) +- `scale` and `little_endian` are separated, so the `header` will always store a positive `scale` +- `little_endian` will only be true for a negative `scale` PFM, every other format will be false +- `little_endian` only describes the netpbm data being read/written, the image buffer will be native +*/ package netpbm diff --git a/core/image/netpbm/netpbm.odin b/core/image/netpbm/netpbm.odin index 24df76c8e..a9dc6599a 100644 --- a/core/image/netpbm/netpbm.odin +++ b/core/image/netpbm/netpbm.odin @@ -1,4 +1,4 @@ -//+vet !using-stmt +#+vet !using-stmt package netpbm import "core:bytes" @@ -8,7 +8,7 @@ import "core:mem" import "core:strconv" import "core:strings" import "core:unicode" -import "core:runtime" +import "base:runtime" Image :: image.Image Format :: image.Netpbm_Format @@ -199,8 +199,8 @@ save_to_buffer :: proc(img: ^Image, custom_info: Info = {}, allocator := context for x in 0 ..< img.width { i := y * img.width + x for c in 0 ..< img.channels { - i := i * img.channels + c - fmt.sbprintf(&data, "%i ", pixels[i]) + j := i * img.channels + c + fmt.sbprintf(&data, "%i ", pixels[j]) } fmt.sbprint(&data, "\n") } @@ -213,8 +213,8 @@ save_to_buffer :: proc(img: ^Image, custom_info: Info = {}, allocator := context for x in 0 ..< img.width { i := y * img.width + x for c in 0 ..< img.channels { - i := i * img.channels + c - fmt.sbprintf(&data, "%i ", pixels[i]) + j := i * img.channels + c + fmt.sbprintf(&data, "%i ", pixels[j]) } fmt.sbprint(&data, "\n") } @@ -283,7 +283,7 @@ _parse_header_pnm :: proc(data: []byte) -> (header: Header, length: int, err: Er current_field := 0 current_value := header_fields[0] - parse_loop: for d, i in data[SIG_LENGTH:] { + parse_loop: for d in data[SIG_LENGTH:] { length += 1 // handle comments @@ -728,4 +728,4 @@ _register :: proc() { _ = destroy(img) } image.register(.NetPBM, loader, destroyer) -} \ No newline at end of file +} diff --git a/core/image/netpbm/netpbm_js.odin b/core/image/netpbm/netpbm_js.odin index 7db17a05d..7d475cf62 100644 --- a/core/image/netpbm/netpbm_js.odin +++ b/core/image/netpbm/netpbm_js.odin @@ -1,4 +1,4 @@ -//+build js +#+build js package netpbm load :: proc { diff --git a/core/image/netpbm/netpbm_os.odin b/core/image/netpbm/netpbm_os.odin index 609f1ea1f..2cf2439ac 100644 --- a/core/image/netpbm/netpbm_os.odin +++ b/core/image/netpbm/netpbm_os.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package netpbm import "core:os" diff --git a/core/image/png/doc.odin b/core/image/png/doc.odin new file mode 100644 index 000000000..623c13077 --- /dev/null +++ b/core/image/png/doc.odin @@ -0,0 +1,348 @@ +/* +package png implements a PNG image reader + +The PNG specification is at [[ https://www.w3.org/TR/PNG/ ]]. + +Example: + package main + + import "core:image" + // import "core:image/png" + import "core:bytes" + import "core:fmt" + + // For PPM writer + import "core:mem" + import "core:os" + + main :: proc() { + track := mem.Tracking_Allocator{} + mem.tracking_allocator_init(&track, context.allocator) + + context.allocator = mem.tracking_allocator(&track) + + demo() + + if len(track.allocation_map) > 0 { + fmt.println("Leaks:") + for _, v in track.allocation_map { + fmt.printf("\t%v\n\n", v) + } + } + } + + demo :: proc() { + file: string + + options := image.Options{.return_metadata} + err: image.Error + img: ^image.Image + + file = "../../../misc/logo-slim.png" + + img, err = load(file, options) + defer destroy(img) + + if err != nil { + fmt.printf("Trying to read PNG file %v returned %v\n", file, err) + } else { + fmt.printf("Image: %vx%vx%v, %v-bit.\n", img.width, img.height, img.channels, img.depth) + + if v, ok := img.metadata.(^image.PNG_Info); ok { + // Handle ancillary chunks as you wish. + // We provide helper functions for a few types. + for c in v.chunks { + #partial switch c.header.type { + case .tIME: + if t, t_ok := core_time(c); t_ok { + fmt.printf("[tIME]: %v\n", t) + } + case .gAMA: + if gama, gama_ok := gamma(c); gama_ok { + fmt.printf("[gAMA]: %v\n", gama) + } + case .pHYs: + if phys, phys_ok := phys(c); phys_ok { + if phys.unit == .Meter { + xm := f32(img.width) / f32(phys.ppu_x) + ym := f32(img.height) / f32(phys.ppu_y) + dpi_x, dpi_y := phys_to_dpi(phys) + fmt.printf("[pHYs] Image resolution is %v x %v pixels per meter.\n", phys.ppu_x, phys.ppu_y) + fmt.printf("[pHYs] Image resolution is %v x %v DPI.\n", dpi_x, dpi_y) + fmt.printf("[pHYs] Image dimensions are %v x %v meters.\n", xm, ym) + } else { + fmt.printf("[pHYs] x: %v, y: %v pixels per unknown unit.\n", phys.ppu_x, phys.ppu_y) + } + } + case .iTXt, .zTXt, .tEXt: + res, ok_text := text(c) + if ok_text { + if c.header.type == .iTXt { + fmt.printf("[iTXt] %v (%v:%v): %v\n", res.keyword, res.language, res.keyword_localized, res.text) + } else { + fmt.printf("[tEXt/zTXt] %v: %v\n", res.keyword, res.text) + } + } + defer text_destroy(res) + case .bKGD: + fmt.printf("[bKGD] %v\n", img.background) + case .eXIf: + if res, ok_exif := exif(c); ok_exif { + /* + Other than checking the signature and byte order, we don't handle Exif data. + If you wish to interpret it, pass it to an Exif parser. + */ + fmt.printf("[eXIf] %v\n", res) + } + case .PLTE: + if plte, plte_ok := plte(c); plte_ok { + fmt.printf("[PLTE] %v\n", plte) + } else { + fmt.printf("[PLTE] Error\n") + } + case .hIST: + if res, ok_hist := hist(c); ok_hist { + fmt.printf("[hIST] %v\n", res) + } + case .cHRM: + if res, ok_chrm := chrm(c); ok_chrm { + fmt.printf("[cHRM] %v\n", res) + } + case .sPLT: + res, ok_splt := splt(c) + if ok_splt { + fmt.printf("[sPLT] %v\n", res) + } + splt_destroy(res) + case .sBIT: + if res, ok_sbit := sbit(c); ok_sbit { + fmt.printf("[sBIT] %v\n", res) + } + case .iCCP: + res, ok_iccp := iccp(c) + if ok_iccp { + fmt.printf("[iCCP] %v\n", res) + } + iccp_destroy(res) + case .sRGB: + if res, ok_srgb := srgb(c); ok_srgb { + fmt.printf("[sRGB] Rendering intent: %v\n", res) + } + case: + type := c.header.type + name := chunk_type_to_name(&type) + fmt.printf("[%v]: %v\n", name, c.data) + } + } + } + } + + fmt.printf("Done parsing metadata.\n") + + if err == nil && .do_not_decompress_image not_in options && .info not_in options { + if ok := write_image_as_ppm("out.ppm", img); ok { + fmt.println("Saved decoded image.") + } else { + fmt.println("Error saving out.ppm.") + fmt.println(img) + } + } + } + + // Crappy PPM writer used during testing. Don't use in production. + write_image_as_ppm :: proc(filename: string, image: ^image.Image) -> (success: bool) { + + _bg :: proc(bg: Maybe([3]u16), x, y: int, high := true) -> (res: [3]u16) { + if v, ok := bg.?; ok { + res = v + } else { + if high { + l := u16(30 * 256 + 30) + + if (x & 4 == 0) ~ (y & 4 == 0) { + res = [3]u16{l, 0, l} + } else { + res = [3]u16{l >> 1, 0, l >> 1} + } + } else { + if (x & 4 == 0) ~ (y & 4 == 0) { + res = [3]u16{30, 30, 30} + } else { + res = [3]u16{15, 15, 15} + } + } + } + return + } + + // profiler.timed_proc(); + using image + using os + + flags: int = O_WRONLY|O_CREATE|O_TRUNC + + img := image + + // PBM 16-bit images are big endian + when ODIN_ENDIAN == .Little { + if img.depth == 16 { + // The pixel components are in Big Endian. Let's byteswap back. + input := mem.slice_data_cast([]u16, img.pixels.buf[:]) + output := mem.slice_data_cast([]u16be, img.pixels.buf[:]) + #no_bounds_check for v, i in input { + output[i] = u16be(v) + } + } + } + + pix := bytes.buffer_to_bytes(&img.pixels) + + if len(pix) == 0 || len(pix) < image.width * image.height * int(image.channels) { + return false + } + + mode: int = 0 + when ODIN_OS == .Linux || ODIN_OS == .Darwin { + // NOTE(justasd): 644 (owner read, write; group read; others read) + mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH + } + + fd, err := open(filename, flags, mode) + if err != nil { + return false + } + defer close(fd) + + write_string(fd, + fmt.tprintf("P6\n%v %v\n%v\n", width, height, uint(1 << uint(depth) - 1)), + ) + + if channels == 3 { + // We don't handle transparency here... + write_ptr(fd, raw_data(pix), len(pix)) + } else { + bpp := depth == 16 ? 2 : 1 + bytes_needed := width * height * 3 * bpp + + op := bytes.Buffer{} + bytes.buffer_init_allocator(&op, bytes_needed, bytes_needed) + defer bytes.buffer_destroy(&op) + + if channels == 1 { + if depth == 16 { + assert(len(pix) == width * height * 2) + p16 := mem.slice_data_cast([]u16, pix) + o16 := mem.slice_data_cast([]u16, op.buf[:]) + #no_bounds_check for len(p16) != 0 { + r := u16(p16[0]) + o16[0] = r + o16[1] = r + o16[2] = r + p16 = p16[1:] + o16 = o16[3:] + } + } else { + o := 0 + for i := 0; i < len(pix); i += 1 { + r := pix[i] + op.buf[o ] = r + op.buf[o+1] = r + op.buf[o+2] = r + o += 3 + } + } + write_ptr(fd, raw_data(op.buf), len(op.buf)) + } else if channels == 2 { + if depth == 16 { + p16 := mem.slice_data_cast([]u16, pix) + o16 := mem.slice_data_cast([]u16, op.buf[:]) + + bgcol := img.background + + #no_bounds_check for len(p16) != 0 { + r := f64(u16(p16[0])) + bg: f64 + if bgcol != nil { + v := bgcol.([3]u16)[0] + bg = f64(v) + } + a := f64(u16(p16[1])) / 65535.0 + l := (a * r) + (1 - a) * bg + + o16[0] = u16(l) + o16[1] = u16(l) + o16[2] = u16(l) + + p16 = p16[2:] + o16 = o16[3:] + } + } else { + o := 0 + for i := 0; i < len(pix); i += 2 { + r := pix[i]; a := pix[i+1]; a1 := f32(a) / 255.0 + c := u8(f32(r) * a1) + op.buf[o ] = c + op.buf[o+1] = c + op.buf[o+2] = c + o += 3 + } + } + write_ptr(fd, raw_data(op.buf), len(op.buf)) + } else if channels == 4 { + if depth == 16 { + p16 := mem.slice_data_cast([]u16be, pix) + o16 := mem.slice_data_cast([]u16be, op.buf[:]) + + #no_bounds_check for len(p16) != 0 { + + bg := _bg(img.background, 0, 0) + r := f32(p16[0]) + g := f32(p16[1]) + b := f32(p16[2]) + a := f32(p16[3]) / 65535.0 + + lr := (a * r) + (1 - a) * f32(bg[0]) + lg := (a * g) + (1 - a) * f32(bg[1]) + lb := (a * b) + (1 - a) * f32(bg[2]) + + o16[0] = u16be(lr) + o16[1] = u16be(lg) + o16[2] = u16be(lb) + + p16 = p16[4:] + o16 = o16[3:] + } + } else { + o := 0 + + for i := 0; i < len(pix); i += 4 { + + x := (i / 4) % width + y := i / width / 4 + + _b := _bg(img.background, x, y, false) + bgcol := [3]u8{u8(_b[0]), u8(_b[1]), u8(_b[2])} + + r := f32(pix[i]) + g := f32(pix[i+1]) + b := f32(pix[i+2]) + a := f32(pix[i+3]) / 255.0 + + lr := u8(f32(r) * a + (1 - a) * f32(bgcol[0])) + lg := u8(f32(g) * a + (1 - a) * f32(bgcol[1])) + lb := u8(f32(b) * a + (1 - a) * f32(bgcol[2])) + op.buf[o ] = lr + op.buf[o+1] = lg + op.buf[o+2] = lb + o += 3 + } + } + write_ptr(fd, raw_data(op.buf), len(op.buf)) + } else { + return false + } + } + return true + } +*/ +package png diff --git a/core/image/png/example.odin b/core/image/png/example.odin deleted file mode 100644 index cd9788bd3..000000000 --- a/core/image/png/example.odin +++ /dev/null @@ -1,351 +0,0 @@ -/* - Copyright 2021 Jeroen van Rijn . - Made available under Odin's BSD-3 license. - - List of contributors: - Jeroen van Rijn: Initial implementation. - Ginger Bill: Cosmetic changes. - - An example of how to use `load`. -*/ -//+build ignore -package png - -import "core:image" -// import "core:image/png" -import "core:bytes" -import "core:fmt" - -// For PPM writer -import "core:mem" -import "core:os" - -main :: proc() { - track := mem.Tracking_Allocator{} - mem.tracking_allocator_init(&track, context.allocator) - - context.allocator = mem.tracking_allocator(&track) - - demo() - - if len(track.allocation_map) > 0 { - fmt.println("Leaks:") - for _, v in track.allocation_map { - fmt.printf("\t%v\n\n", v) - } - } -} - -demo :: proc() { - file: string - - options := image.Options{.return_metadata} - err: image.Error - img: ^image.Image - - file = "../../../misc/logo-slim.png" - - img, err = load(file, options) - defer destroy(img) - - if err != nil { - fmt.printf("Trying to read PNG file %v returned %v\n", file, err) - } else { - fmt.printf("Image: %vx%vx%v, %v-bit.\n", img.width, img.height, img.channels, img.depth) - - if v, ok := img.metadata.(^image.PNG_Info); ok { - // Handle ancillary chunks as you wish. - // We provide helper functions for a few types. - for c in v.chunks { - #partial switch c.header.type { - case .tIME: - if t, t_ok := core_time(c); t_ok { - fmt.printf("[tIME]: %v\n", t) - } - case .gAMA: - if gama, gama_ok := gamma(c); gama_ok { - fmt.printf("[gAMA]: %v\n", gama) - } - case .pHYs: - if phys, phys_ok := phys(c); phys_ok { - if phys.unit == .Meter { - xm := f32(img.width) / f32(phys.ppu_x) - ym := f32(img.height) / f32(phys.ppu_y) - dpi_x, dpi_y := phys_to_dpi(phys) - fmt.printf("[pHYs] Image resolution is %v x %v pixels per meter.\n", phys.ppu_x, phys.ppu_y) - fmt.printf("[pHYs] Image resolution is %v x %v DPI.\n", dpi_x, dpi_y) - fmt.printf("[pHYs] Image dimensions are %v x %v meters.\n", xm, ym) - } else { - fmt.printf("[pHYs] x: %v, y: %v pixels per unknown unit.\n", phys.ppu_x, phys.ppu_y) - } - } - case .iTXt, .zTXt, .tEXt: - res, ok_text := text(c) - if ok_text { - if c.header.type == .iTXt { - fmt.printf("[iTXt] %v (%v:%v): %v\n", res.keyword, res.language, res.keyword_localized, res.text) - } else { - fmt.printf("[tEXt/zTXt] %v: %v\n", res.keyword, res.text) - } - } - defer text_destroy(res) - case .bKGD: - fmt.printf("[bKGD] %v\n", img.background) - case .eXIf: - if res, ok_exif := exif(c); ok_exif { - /* - Other than checking the signature and byte order, we don't handle Exif data. - If you wish to interpret it, pass it to an Exif parser. - */ - fmt.printf("[eXIf] %v\n", res) - } - case .PLTE: - if plte, plte_ok := plte(c); plte_ok { - fmt.printf("[PLTE] %v\n", plte) - } else { - fmt.printf("[PLTE] Error\n") - } - case .hIST: - if res, ok_hist := hist(c); ok_hist { - fmt.printf("[hIST] %v\n", res) - } - case .cHRM: - if res, ok_chrm := chrm(c); ok_chrm { - fmt.printf("[cHRM] %v\n", res) - } - case .sPLT: - res, ok_splt := splt(c) - if ok_splt { - fmt.printf("[sPLT] %v\n", res) - } - splt_destroy(res) - case .sBIT: - if res, ok_sbit := sbit(c); ok_sbit { - fmt.printf("[sBIT] %v\n", res) - } - case .iCCP: - res, ok_iccp := iccp(c) - if ok_iccp { - fmt.printf("[iCCP] %v\n", res) - } - iccp_destroy(res) - case .sRGB: - if res, ok_srgb := srgb(c); ok_srgb { - fmt.printf("[sRGB] Rendering intent: %v\n", res) - } - case: - type := c.header.type - name := chunk_type_to_name(&type) - fmt.printf("[%v]: %v\n", name, c.data) - } - } - } - } - - fmt.printf("Done parsing metadata.\n") - - if err == nil && .do_not_decompress_image not_in options && .info not_in options { - if ok := write_image_as_ppm("out.ppm", img); ok { - fmt.println("Saved decoded image.") - } else { - fmt.println("Error saving out.ppm.") - fmt.println(img) - } - } -} - -// Crappy PPM writer used during testing. Don't use in production. -write_image_as_ppm :: proc(filename: string, image: ^image.Image) -> (success: bool) { - - _bg :: proc(bg: Maybe([3]u16), x, y: int, high := true) -> (res: [3]u16) { - if v, ok := bg.?; ok { - res = v - } else { - if high { - l := u16(30 * 256 + 30) - - if (x & 4 == 0) ~ (y & 4 == 0) { - res = [3]u16{l, 0, l} - } else { - res = [3]u16{l >> 1, 0, l >> 1} - } - } else { - if (x & 4 == 0) ~ (y & 4 == 0) { - res = [3]u16{30, 30, 30} - } else { - res = [3]u16{15, 15, 15} - } - } - } - return - } - - // profiler.timed_proc(); - using image - using os - - flags: int = O_WRONLY|O_CREATE|O_TRUNC - - img := image - - // PBM 16-bit images are big endian - when ODIN_ENDIAN == .Little { - if img.depth == 16 { - // The pixel components are in Big Endian. Let's byteswap back. - input := mem.slice_data_cast([]u16, img.pixels.buf[:]) - output := mem.slice_data_cast([]u16be, img.pixels.buf[:]) - #no_bounds_check for v, i in input { - output[i] = u16be(v) - } - } - } - - pix := bytes.buffer_to_bytes(&img.pixels) - - if len(pix) == 0 || len(pix) < image.width * image.height * int(image.channels) { - return false - } - - mode: int = 0 - when ODIN_OS == .Linux || ODIN_OS == .Darwin { - // NOTE(justasd): 644 (owner read, write; group read; others read) - mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH - } - - fd, err := open(filename, flags, mode) - if err != 0 { - return false - } - defer close(fd) - - write_string(fd, - fmt.tprintf("P6\n%v %v\n%v\n", width, height, uint(1 << uint(depth) - 1)), - ) - - if channels == 3 { - // We don't handle transparency here... - write_ptr(fd, raw_data(pix), len(pix)) - } else { - bpp := depth == 16 ? 2 : 1 - bytes_needed := width * height * 3 * bpp - - op := bytes.Buffer{} - bytes.buffer_init_allocator(&op, bytes_needed, bytes_needed) - defer bytes.buffer_destroy(&op) - - if channels == 1 { - if depth == 16 { - assert(len(pix) == width * height * 2) - p16 := mem.slice_data_cast([]u16, pix) - o16 := mem.slice_data_cast([]u16, op.buf[:]) - #no_bounds_check for len(p16) != 0 { - r := u16(p16[0]) - o16[0] = r - o16[1] = r - o16[2] = r - p16 = p16[1:] - o16 = o16[3:] - } - } else { - o := 0 - for i := 0; i < len(pix); i += 1 { - r := pix[i] - op.buf[o ] = r - op.buf[o+1] = r - op.buf[o+2] = r - o += 3 - } - } - write_ptr(fd, raw_data(op.buf), len(op.buf)) - } else if channels == 2 { - if depth == 16 { - p16 := mem.slice_data_cast([]u16, pix) - o16 := mem.slice_data_cast([]u16, op.buf[:]) - - bgcol := img.background - - #no_bounds_check for len(p16) != 0 { - r := f64(u16(p16[0])) - bg: f64 - if bgcol != nil { - v := bgcol.([3]u16)[0] - bg = f64(v) - } - a := f64(u16(p16[1])) / 65535.0 - l := (a * r) + (1 - a) * bg - - o16[0] = u16(l) - o16[1] = u16(l) - o16[2] = u16(l) - - p16 = p16[2:] - o16 = o16[3:] - } - } else { - o := 0 - for i := 0; i < len(pix); i += 2 { - r := pix[i]; a := pix[i+1]; a1 := f32(a) / 255.0 - c := u8(f32(r) * a1) - op.buf[o ] = c - op.buf[o+1] = c - op.buf[o+2] = c - o += 3 - } - } - write_ptr(fd, raw_data(op.buf), len(op.buf)) - } else if channels == 4 { - if depth == 16 { - p16 := mem.slice_data_cast([]u16be, pix) - o16 := mem.slice_data_cast([]u16be, op.buf[:]) - - #no_bounds_check for len(p16) != 0 { - - bg := _bg(img.background, 0, 0) - r := f32(p16[0]) - g := f32(p16[1]) - b := f32(p16[2]) - a := f32(p16[3]) / 65535.0 - - lr := (a * r) + (1 - a) * f32(bg[0]) - lg := (a * g) + (1 - a) * f32(bg[1]) - lb := (a * b) + (1 - a) * f32(bg[2]) - - o16[0] = u16be(lr) - o16[1] = u16be(lg) - o16[2] = u16be(lb) - - p16 = p16[4:] - o16 = o16[3:] - } - } else { - o := 0 - - for i := 0; i < len(pix); i += 4 { - - x := (i / 4) % width - y := i / width / 4 - - _b := _bg(img.background, x, y, false) - bgcol := [3]u8{u8(_b[0]), u8(_b[1]), u8(_b[2])} - - r := f32(pix[i]) - g := f32(pix[i+1]) - b := f32(pix[i+2]) - a := f32(pix[i+3]) / 255.0 - - lr := u8(f32(r) * a + (1 - a) * f32(bgcol[0])) - lg := u8(f32(g) * a + (1 - a) * f32(bgcol[1])) - lb := u8(f32(b) * a + (1 - a) * f32(bgcol[2])) - op.buf[o ] = lr - op.buf[o+1] = lg - op.buf[o+2] = lb - o += 3 - } - } - write_ptr(fd, raw_data(op.buf), len(op.buf)) - } else { - return false - } - } - return true -} diff --git a/core/image/png/helpers.odin b/core/image/png/helpers.odin index f0209d4d7..a9495ed4d 100644 --- a/core/image/png/helpers.odin +++ b/core/image/png/helpers.odin @@ -8,6 +8,7 @@ These are a few useful utility functions to work with PNG images. */ + package png import "core:image" @@ -16,7 +17,7 @@ import coretime "core:time" import "core:strings" import "core:bytes" import "core:mem" -import "core:runtime" +import "base:runtime" /* Cleanup of image-specific data. @@ -395,132 +396,4 @@ exif :: proc(c: image.PNG_Chunk) -> (res: Exif, ok: bool) { General helper functions */ -compute_buffer_size :: image.compute_buffer_size - -/* - PNG save helpers -*/ - -when false { - - make_chunk :: proc(c: any, t: Chunk_Type) -> (res: Chunk) { - - data: []u8 - if v, ok := c.([]u8); ok { - data = v - } else { - data = mem.any_to_bytes(c) - } - - res.header.length = u32be(len(data)) - res.header.type = t - res.data = data - - // CRC the type - crc := hash.crc32(mem.any_to_bytes(res.header.type)) - // Extend the CRC with the data - res.crc = u32be(hash.crc32(data, crc)) - return - } - - write_chunk :: proc(fd: os.Handle, chunk: Chunk) { - c := chunk - // Write length + type - os.write_ptr(fd, &c.header, 8) - // Write data - os.write_ptr(fd, mem.raw_data(c.data), int(c.header.length)) - // Write CRC32 - os.write_ptr(fd, &c.crc, 4) - } - - write_image_as_png :: proc(filename: string, image: Image) -> (err: Error) { - profiler.timed_proc() - using image - using os - flags: int = O_WRONLY|O_CREATE|O_TRUNC - - if len(image.pixels) == 0 || len(image.pixels) < image.width * image.height * int(image.channels) { - return .Invalid_Image_Dimensions - } - - mode: int = 0 - when ODIN_OS == .Linux || ODIN_OS == .Darwin { - // NOTE(justasd): 644 (owner read, write; group read; others read) - mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH - } - - fd, fderr := open(filename, flags, mode) - if fderr != 0 { - return .Cannot_Open_File - } - defer close(fd) - - magic := Signature - - write_ptr(fd, &magic, 8) - - ihdr := IHDR{ - width = u32be(width), - height = u32be(height), - bit_depth = depth, - compression_method = 0, - filter_method = 0, - interlace_method = .None, - } - - switch channels { - case 1: ihdr.color_type = Color_Type{} - case 2: ihdr.color_type = Color_Type{.Alpha} - case 3: ihdr.color_type = Color_Type{.Color} - case 4: ihdr.color_type = Color_Type{.Color, .Alpha} - case:// Unhandled - return .Unknown_Color_Type - } - h := make_chunk(ihdr, .IHDR) - write_chunk(fd, h) - - bytes_needed := width * height * int(channels) + height - filter_bytes := mem.make_dynamic_array_len_cap([dynamic]u8, bytes_needed, bytes_needed, context.allocator) - defer delete(filter_bytes) - - i := 0; j := 0 - // Add a filter byte 0 per pixel row - for y := 0; y < height; y += 1 { - filter_bytes[j] = 0; j += 1 - for x := 0; x < width; x += 1 { - for z := 0; z < channels; z += 1 { - filter_bytes[j+z] = image.pixels[i+z] - } - i += channels; j += channels - } - } - assert(j == bytes_needed) - - a: []u8 = filter_bytes[:] - - out_buf: ^[dynamic]u8 - defer free(out_buf) - - ctx := zlib.ZLIB_Context{ - in_buf = &a, - out_buf = out_buf, - } - err = zlib.write_zlib_stream_from_memory(&ctx) - - b: []u8 - if err == nil { - b = ctx.out_buf[:] - } else { - return err - } - - idat := make_chunk(b, .IDAT) - - write_chunk(fd, idat) - - iend := make_chunk([]u8{}, .IEND) - write_chunk(fd, iend) - - return nil - } -} +compute_buffer_size :: image.compute_buffer_size \ No newline at end of file diff --git a/core/image/png/png.odin b/core/image/png/png.odin index 1821e55cd..2d3665e94 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -8,10 +8,7 @@ */ -// package png implements a PNG image reader -// -// The PNG specification is at https://www.w3.org/TR/PNG/. -//+vet !using-stmt +#+vet !using-stmt package png import "core:compress" @@ -22,8 +19,8 @@ import "core:hash" import "core:bytes" import "core:io" import "core:mem" -import "core:intrinsics" -import "core:runtime" +import "base:intrinsics" +import "base:runtime" // Limit chunk sizes. // By default: IDAT = 8k x 8k x 16-bits + 8k filter bytes. @@ -341,7 +338,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 +351,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 +532,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 +594,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 +732,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 +1001,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,8 +1613,7 @@ 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) -} \ No newline at end of file +} diff --git a/core/image/png/png_js.odin b/core/image/png/png_js.odin index 57c27fc64..dd9e74526 100644 --- a/core/image/png/png_js.odin +++ b/core/image/png/png_js.odin @@ -1,4 +1,4 @@ -//+build js +#+build js package png load :: proc{load_from_bytes, load_from_context} diff --git a/core/image/png/png_os.odin b/core/image/png/png_os.odin index cc65e7b42..8e0706206 100644 --- a/core/image/png/png_os.odin +++ b/core/image/png/png_os.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package png import "core:os" diff --git a/core/image/qoi/qoi.odin b/core/image/qoi/qoi.odin index dfdf1875a..6b6149e60 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/qoi/qoi_js.odin b/core/image/qoi/qoi_js.odin index 2c23cc17a..4a69e98a0 100644 --- a/core/image/qoi/qoi_js.odin +++ b/core/image/qoi/qoi_js.odin @@ -1,4 +1,4 @@ -//+build js +#+build js package qoi save :: proc{save_to_buffer} diff --git a/core/image/qoi/qoi_os.odin b/core/image/qoi/qoi_os.odin index efcec6c52..c85fdd839 100644 --- a/core/image/qoi/qoi_os.odin +++ b/core/image/qoi/qoi_os.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package qoi import "core:os" 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/image/tga/tga_js.odin b/core/image/tga/tga_js.odin index d98b241a7..9261be8c6 100644 --- a/core/image/tga/tga_js.odin +++ b/core/image/tga/tga_js.odin @@ -1,4 +1,4 @@ -//+build js +#+build js package tga save :: proc{save_to_buffer} diff --git a/core/image/tga/tga_os.odin b/core/image/tga/tga_os.odin index 12747a684..a78998105 100644 --- a/core/image/tga/tga_os.odin +++ b/core/image/tga/tga_os.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package tga import "core:os" diff --git a/core/io/io.odin b/core/io/io.odin index 566e13c54..6072aec6d 100644 --- a/core/io/io.odin +++ b/core/io/io.odin @@ -3,7 +3,7 @@ // operations into an abstracted stream interface. package io -import "core:intrinsics" +import "base:intrinsics" import "core:unicode/utf8" // Seek whence values @@ -29,12 +29,12 @@ Error :: enum i32 { // Invalid_Write means that a write returned an impossible count Invalid_Write, - // Short_Buffer means that a read required a longer buffer than was provided + // Short_Buffer means that a read/write required a longer buffer than was provided Short_Buffer, // No_Progress is returned by some implementations of `io.Reader` when many calls // to `read` have failed to return any data or error. - // This is usually a signed of a broken `io.Reader` implementation + // This is usually a sign of a broken `io.Reader` implementation No_Progress, Invalid_Whence, @@ -359,6 +359,25 @@ read_at_least :: proc(r: Reader, buf: []byte, min: int) -> (n: int, err: Error) return } +// write_full writes until the entire contents of `buf` has been written or an error occurs. +write_full :: proc(w: Writer, buf: []byte) -> (n: int, err: Error) { + return write_at_least(w, buf, len(buf)) +} + +// write_at_least writes at least `buf[:min]` to the writer and returns the amount written. +// If an error occurs before writing everything it is returned. +write_at_least :: proc(w: Writer, buf: []byte, min: int) -> (n: int, err: Error) { + if len(buf) < min { + return 0, .Short_Buffer + } + for n < min && err == nil { + nn: int + nn, err = write(w, buf[n:]) + n += nn + } + return +} + // copy copies from src to dst till either EOF is reached on src or an error occurs // It returns the number of bytes copied and the first error that occurred whilst copying, if any. copy :: proc(dst: Writer, src: Reader) -> (written: i64, err: Error) { diff --git a/core/io/multi.odin b/core/io/multi.odin index e85114a7a..e25e8133e 100644 --- a/core/io/multi.odin +++ b/core/io/multi.odin @@ -4,7 +4,6 @@ Multi_Reader :: struct { readers: [dynamic]Reader, } -@(private) _multi_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, offset: i64, whence: Seek_From) -> (n: i64, err: Error) { if mode == .Query { return query_utility({.Read, .Query}) @@ -58,7 +57,6 @@ Multi_Writer :: struct { writers: [dynamic]Writer, } -@(private) _multi_writer_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, offset: i64, whence: Seek_From) -> (n: i64, err: Error) { if mode == .Query { return query_utility({.Write, .Query}) diff --git a/core/io/util.odin b/core/io/util.odin index c24eb99c5..fdbbd5b9f 100644 --- a/core/io/util.odin +++ b/core/io/util.odin @@ -132,9 +132,13 @@ write_encoded_rune :: proc(w: Writer, r: rune, write_quote := true, n_written: ^ buf: [2]byte s := strconv.append_bits(buf[:], u64(r), 16, true, 64, strconv.digits, nil) switch len(s) { - case 0: write_string(w, "00", &n) or_return - case 1: write_byte(w, '0', &n) or_return - case 2: write_string(w, s, &n) or_return + case 0: + write_string(w, "00", &n) or_return + case 1: + write_byte(w, '0', &n) or_return + fallthrough + case 2: + write_string(w, s, &n) or_return } } else { write_rune(w, r, &n) or_return @@ -225,7 +229,7 @@ write_escaped_rune :: proc(w: Writer, r: rune, quote: byte, html_safe := false, } else { write_byte(w, '\\', &n) or_return write_byte(w, 'U', &n) or_return - for s := 24; s >= 0; s -= 4 { + for s := 28; s >= 0; s -= 4 { write_byte(w, DIGITS_LOWER[c>>uint(s) & 0xf], &n) or_return } } @@ -340,6 +344,9 @@ _limited_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, l := (^Limited_Reader)(stream_data) #partial switch mode { case .Read: + if len(p) == 0 { + return 0, nil + } if l.n <= 0 { return 0, .EOF } @@ -376,11 +383,12 @@ Section_Reader :: struct { limit: i64, } -section_reader_init :: proc(s: ^Section_Reader, r: Reader_At, off: i64, n: i64) { +section_reader_init :: proc(s: ^Section_Reader, r: Reader_At, off: i64, n: i64) -> Reader { s.r = r + s.base = off s.off = off s.limit = off + n - return + return section_reader_to_stream(s) } section_reader_to_stream :: proc(s: ^Section_Reader) -> (out: Stream) { out.data = s @@ -393,6 +401,9 @@ _section_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, s := (^Section_Reader)(stream_data) #partial switch mode { case .Read: + if len(p) == 0 { + return 0, nil + } if s.off >= s.limit { return 0, .EOF } @@ -404,6 +415,9 @@ _section_reader_proc :: proc(stream_data: rawptr, mode: Stream_Mode, p: []byte, s.off += i64(n) return case .Read_At: + if len(p) == 0 { + return 0, nil + } p, off := p, offset if off < 0 || off >= s.limit - s.base { diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin index bf537a161..6d93fb879 100644 --- a/core/log/file_console_logger.odin +++ b/core/log/file_console_logger.odin @@ -1,5 +1,8 @@ +#+build !freestanding +#+build !orca package log +import "core:encoding/ansi" import "core:fmt" import "core:strings" import "core:os" @@ -34,30 +37,30 @@ File_Console_Logger_Data :: struct { ident: string, } -create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "") -> Logger { - data := new(File_Console_Logger_Data) +create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger { + data := new(File_Console_Logger_Data, allocator) data.file_handle = h data.ident = ident return Logger{file_console_logger_proc, data, lowest, opt} } -destroy_file_logger :: proc(log: ^Logger) { +destroy_file_logger :: proc(log: Logger, allocator := context.allocator) { data := cast(^File_Console_Logger_Data)log.data if data.file_handle != os.INVALID_HANDLE { os.close(data.file_handle) } - free(data) + free(data, allocator) } -create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logger_Opts, ident := "") -> Logger { - data := new(File_Console_Logger_Data) +create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logger_Opts, ident := "", allocator := context.allocator) -> Logger { + data := new(File_Console_Logger_Data, allocator) data.file_handle = os.INVALID_HANDLE data.ident = ident return Logger{file_console_logger_proc, data, lowest, opt} } -destroy_console_logger :: proc(log: Logger) { - free(log.data) +destroy_console_logger :: proc(log: Logger, allocator := context.allocator) { + free(log.data, allocator) } file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) { @@ -69,18 +72,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) @@ -98,12 +93,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 { @@ -124,6 +119,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/log.odin b/core/log/log.odin index f3554791b..cbb2e922b 100644 --- a/core/log/log.odin +++ b/core/log/log.odin @@ -1,6 +1,6 @@ package log -import "core:runtime" +import "base:runtime" import "core:fmt" @@ -60,16 +60,14 @@ Logger_Proc :: runtime.Logger_Proc /* Logger :: struct { procedure: Logger_Proc, - data: rawptr, + data: rawptr, lowest_level: Level, - options: Logger_Options, + options: Logger_Options, } */ Logger :: runtime.Logger -nil_logger_proc :: proc(data: rawptr, level: Level, text: string, options: Options, location := #caller_location) { - // Do nothing -} +nil_logger_proc :: runtime.default_logger_proc nil_logger :: proc() -> Logger { return Logger{nil_logger_proc, nil, Level.Debug, nil} @@ -116,29 +114,67 @@ panicf :: proc(fmt_str: string, args: ..any, location := #caller_location) -> ! runtime.panic("log.panicf", location) } +@(disabled=ODIN_DISABLE_ASSERT) +assert :: proc(condition: bool, message := "", loc := #caller_location) { + if !condition { + @(cold) + internal :: proc(message: string, loc: runtime.Source_Code_Location) { + p := context.assertion_failure_proc + if p == nil { + p = runtime.default_assertion_failure_proc + } + log(.Fatal, message, location=loc) + p("runtime assertion", message, loc) + } + internal(message, loc) + } +} + +@(disabled=ODIN_DISABLE_ASSERT) +assertf :: proc(condition: bool, fmt_str: string, args: ..any, loc := #caller_location) { + if !condition { + // NOTE(dragos): We are using the same trick as in builtin.assert + // to improve performance to make the CPU not + // execute speculatively, making it about an order of + // magnitude faster + @(cold) + internal :: proc(loc: runtime.Source_Code_Location, fmt_str: string, args: ..any) { + p := context.assertion_failure_proc + if p == nil { + p = runtime.default_assertion_failure_proc + } + message := fmt.tprintf(fmt_str, ..args) + log(.Fatal, message, location=loc) + p("Runtime assertion", message, loc) + } + internal(loc, fmt_str, ..args) + } +} log :: proc(level: Level, args: ..any, sep := " ", location := #caller_location) { logger := context.logger - if logger.procedure == nil { + if logger.procedure == nil || logger.procedure == nil_logger_proc { return } if level < logger.lowest_level { return } + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() str := fmt.tprint(..args, sep=sep) //NOTE(Hoej): While tprint isn't thread-safe, no logging is. logger.procedure(logger.data, level, str, logger.options, location) } logf :: proc(level: Level, fmt_str: string, args: ..any, location := #caller_location) { logger := context.logger - if logger.procedure == nil { + if logger.procedure == nil || logger.procedure == nil_logger_proc { return } if level < logger.lowest_level { return } + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() str := fmt.tprintf(fmt_str, ..args) logger.procedure(logger.data, level, str, logger.options, location) } diff --git a/core/log/log_allocator.odin b/core/log/log_allocator.odin index 934f0d643..16f1abe31 100644 --- a/core/log/log_allocator.odin +++ b/core/log/log_allocator.odin @@ -1,19 +1,28 @@ package log -import "core:runtime" +import "base:runtime" +import "core:fmt" + +Log_Allocator_Format :: enum { + Bytes, // Actual number of bytes. + Human, // Bytes in human units like bytes, kibibytes, etc. as appropriate. +} Log_Allocator :: struct { allocator: runtime.Allocator, level: Level, prefix: string, locked: bool, + size_fmt: Log_Allocator_Format, } -log_allocator_init :: proc(la: ^Log_Allocator, level: Level, allocator := context.allocator, prefix := "") { +log_allocator_init :: proc(la: ^Log_Allocator, level: Level, size_fmt := Log_Allocator_Format.Bytes, + allocator := context.allocator, prefix := "") { la.allocator = allocator la.level = level la.prefix = prefix la.locked = false + la.size_fmt = size_fmt } @@ -29,71 +38,80 @@ log_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode, old_memory: rawptr, old_size: int, location := #caller_location) -> ([]byte, runtime.Allocator_Error) { la := (^Log_Allocator)(allocator_data) + if context.logger.procedure == nil || la.level < context.logger.lowest_level { + return la.allocator.procedure(la.allocator.data, mode, size, alignment, old_memory, old_size, location) + } + padding := " " if la.prefix != "" else "" + buf: [256]byte = --- + if !la.locked { la.locked = true defer la.locked = false switch mode { case .Alloc: - logf( - la.level, - "%s%s>>> ALLOCATOR(mode=.Alloc, size=%d, alignment=%d)", - la.prefix, padding, size, alignment, - location = location, - ) + format: string + switch la.size_fmt { + case .Bytes: format = "%s%s>>> ALLOCATOR(mode=.Alloc, size=%d, alignment=%d)" + case .Human: format = "%s%s>>> ALLOCATOR(mode=.Alloc, size=%m, alignment=%d)" + } + str := fmt.bprintf(buf[:], format, la.prefix, padding, size, alignment) + context.logger.procedure(context.logger.data, la.level, str, context.logger.options, location) + case .Alloc_Non_Zeroed: - logf( - la.level, - "%s%s>>> ALLOCATOR(mode=.Alloc_Non_Zeroed, size=%d, alignment=%d)", - la.prefix, padding, size, alignment, - location = location, - ) + format: string + switch la.size_fmt { + case .Bytes: format = "%s%s>>> ALLOCATOR(mode=.Alloc_Non_Zeroed, size=%d, alignment=%d)" + case .Human: format = "%s%s>>> ALLOCATOR(mode=.Alloc_Non_Zeroed, size=%m, alignment=%d)" + } + str := fmt.bprintf(buf[:], format, la.prefix, padding, size, alignment) + context.logger.procedure(context.logger.data, la.level, str, context.logger.options, location) + case .Free: if old_size != 0 { - logf( - la.level, - "%s%s<<< ALLOCATOR(mode=.Free, ptr=%p, size=%d)", - la.prefix, padding, old_memory, old_size, - location = location, - ) + format: string + switch la.size_fmt { + case .Bytes: format = "%s%s<<< ALLOCATOR(mode=.Free, ptr=%p, size=%d)" + case .Human: format = "%s%s<<< ALLOCATOR(mode=.Free, ptr=%p, size=%m)" + } + str := fmt.bprintf(buf[:], format, la.prefix, padding, old_memory, old_size) + context.logger.procedure(context.logger.data, la.level, str, context.logger.options, location) } else { - logf( - la.level, - "%s%s<<< ALLOCATOR(mode=.Free, ptr=%p)", - la.prefix, padding, old_memory, - location = location, - ) + str := fmt.bprintf(buf[:], "%s%s<<< ALLOCATOR(mode=.Free, ptr=%p)", la.prefix, padding, old_memory) + context.logger.procedure(context.logger.data, la.level, str, context.logger.options, location) } + case .Free_All: - logf( - la.level, - "%s%s<<< ALLOCATOR(mode=.Free_All)", - la.prefix, padding, - location = location, - ) + str := fmt.bprintf(buf[:], "%s%s<<< ALLOCATOR(mode=.Free_All)", la.prefix, padding) + context.logger.procedure(context.logger.data, la.level, str, context.logger.options, location) + case .Resize: - logf( - la.level, - "%s%s>>> ALLOCATOR(mode=.Resize, ptr=%p, old_size=%d, size=%d, alignment=%d)", - la.prefix, padding, old_memory, old_size, size, alignment, - location = location, - ) + format: string + switch la.size_fmt { + case .Bytes: format = "%s%s>>> ALLOCATOR(mode=.Resize, ptr=%p, old_size=%d, size=%d, alignment=%d)" + case .Human: format = "%s%s>>> ALLOCATOR(mode=.Resize, ptr=%p, old_size=%m, size=%m, alignment=%d)" + } + str := fmt.bprintf(buf[:], format, la.prefix, padding, old_memory, old_size, size, alignment) + context.logger.procedure(context.logger.data, la.level, str, context.logger.options, location) + + case .Resize_Non_Zeroed: + format: string + switch la.size_fmt { + case .Bytes: format = "%s%s>>> ALLOCATOR(mode=.Resize_Non_Zeroed, ptr=%p, old_size=%d, size=%d, alignment=%d)" + case .Human: format = "%s%s>>> ALLOCATOR(mode=.Resize_Non_Zeroed, ptr=%p, old_size=%m, size=%m, alignment=%d)" + } + str := fmt.bprintf(buf[:], format, la.prefix, padding, old_memory, old_size, size, alignment) + context.logger.procedure(context.logger.data, la.level, str, context.logger.options, location) + case .Query_Features: - logf( - la.level, - "%s%ALLOCATOR(mode=.Query_Features)", - la.prefix, padding, - location = location, - ) + str := fmt.bprintf(buf[:], "%s%sALLOCATOR(mode=.Query_Features)", la.prefix, padding) + context.logger.procedure(context.logger.data, la.level, str, context.logger.options, location) + case .Query_Info: - logf( - la.level, - "%s%ALLOCATOR(mode=.Query_Info)", - la.prefix, padding, - location = location, - ) + str := fmt.bprintf(buf[:], "%s%sALLOCATOR(mode=.Query_Info)", la.prefix, padding) + context.logger.procedure(context.logger.data, la.level, str, context.logger.options, location) } } @@ -102,13 +120,9 @@ log_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode, la.locked = true defer la.locked = false if err != nil { - logf( - la.level, - "%s%ALLOCATOR ERROR=%v", - la.prefix, padding, error, - location = location, - ) + str := fmt.bprintf(buf[:], "%s%sALLOCATOR ERROR=%v", la.prefix, padding, err) + context.logger.procedure(context.logger.data, la.level, str, context.logger.options, location) } } return data, err -} \ No newline at end of file +} diff --git a/core/log/multi_logger.odin b/core/log/multi_logger.odin index 55c0f1436..6122554bb 100644 --- a/core/log/multi_logger.odin +++ b/core/log/multi_logger.odin @@ -5,18 +5,17 @@ Multi_Logger_Data :: struct { loggers: []Logger, } -create_multi_logger :: proc(logs: ..Logger) -> Logger { - data := new(Multi_Logger_Data) - data.loggers = make([]Logger, len(logs)) +create_multi_logger :: proc(logs: ..Logger, allocator := context.allocator) -> Logger { + data := new(Multi_Logger_Data, allocator) + data.loggers = make([]Logger, len(logs), allocator) copy(data.loggers, logs) return Logger{multi_logger_proc, data, Level.Debug, nil} } -destroy_multi_logger :: proc(log : ^Logger) { +destroy_multi_logger :: proc(log: Logger, allocator := context.allocator) { data := (^Multi_Logger_Data)(log.data) - delete(data.loggers) - free(log.data) - log^ = nil_logger() + delete(data.loggers, allocator) + free(data, allocator) } 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/common.odin b/core/math/big/common.odin index 74a641d83..5428b00ee 100644 --- a/core/math/big/common.odin +++ b/core/math/big/common.odin @@ -6,7 +6,7 @@ package math_big -import "core:intrinsics" +import "base:intrinsics" /* TODO: Make the tunables runtime adjustable where practical. @@ -195,7 +195,7 @@ Error_String :: #sparse[Error]string{ } Primality_Flag :: enum u8 { - Blum_Blum_Shub = 0, // Make prime congruent to 3 mod 4 + Blum_Blum_Shub = 0, // Make prime congruent to 3 mod 4 Safe = 1, // Make sure (p-1)/2 is prime as well (implies .Blum_Blum_Shub) Second_MSB_On = 3, // Make the 2nd highest bit one } diff --git a/core/math/big/helpers.odin b/core/math/big/helpers.odin index a4313a244..ee09bb2c7 100644 --- a/core/math/big/helpers.odin +++ b/core/math/big/helpers.odin @@ -6,7 +6,7 @@ package math_big -import "core:intrinsics" +import "base:intrinsics" import rnd "core:math/rand" /* @@ -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, } @@ -777,6 +777,11 @@ int_from_bytes_little_python :: proc(a: ^Int, buf: []u8, signed := false, alloca */ INT_ONE, INT_ZERO, INT_MINUS_ONE, INT_INF, INT_MINUS_INF, INT_NAN := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{} +@(init, private) +_init_constants :: proc() { + initialize_constants() +} + initialize_constants :: proc() -> (res: int) { internal_set( INT_ZERO, 0); INT_ZERO.flags = {.Immutable} internal_set( INT_ONE, 1); INT_ONE.flags = {.Immutable} @@ -788,7 +793,7 @@ initialize_constants :: proc() -> (res: int) { */ internal_set( INT_NAN, 1); INT_NAN.flags = {.Immutable, .NaN} internal_set( INT_INF, 1); INT_INF.flags = {.Immutable, .Inf} - internal_set( INT_INF, -1); INT_MINUS_INF.flags = {.Immutable, .Inf} + internal_set(INT_MINUS_INF, -1); INT_MINUS_INF.flags = {.Immutable, .Inf} return _DEFAULT_MUL_KARATSUBA_CUTOFF } diff --git a/core/math/big/internal.odin b/core/math/big/internal.odin index 968a26f8f..c9b331e55 100644 --- a/core/math/big/internal.odin +++ b/core/math/big/internal.odin @@ -28,9 +28,9 @@ package math_big import "core:mem" -import "core:intrinsics" +import "base:intrinsics" import rnd "core:math/rand" -import "core:builtin" +import "base:builtin" /* Low-level addition, unsigned. Handbook of Applied Cryptography, algorithm 14.7. @@ -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{} @@ -1181,28 +1181,18 @@ internal_cmp_digit :: internal_compare_digit */ internal_int_compare_magnitude :: #force_inline proc(a, b: ^Int) -> (comparison: int) { assert_if_nil(a, b) - /* - Compare based on used digits. - */ + + // Compare based on used digits. if a.used != b.used { - if a.used > b.used { - return +1 - } - return -1 + return +1 if a.used > b.used else -1 } - /* - Same number of used digits, compare based on their value. - */ + // Same number of used digits, compare based on their value. #no_bounds_check for n := a.used - 1; n >= 0; n -= 1 { if a.digit[n] != b.digit[n] { - if a.digit[n] > b.digit[n] { - return +1 - } - return -1 + return +1 if a.digit[n] > b.digit[n] else -1 } } - return 0 } internal_compare_magnitude :: proc { internal_int_compare_magnitude, } @@ -2046,9 +2036,9 @@ internal_int_inverse_modulo :: proc(dest, a, b: ^Int, allocator := context.alloc if internal_is_positive(a) && internal_eq(b, 1) { return internal_zero(dest) } /* - `b` cannot be negative and has to be > 1 + `b` cannot be negative and b has to be > 1 */ - if internal_is_negative(b) || internal_gt(b, 1) { return .Invalid_Argument } + if internal_is_negative(b) || !internal_gt(b, 1) { return .Invalid_Argument } /* If the modulus is odd we can use a faster routine instead. @@ -2057,6 +2047,7 @@ internal_int_inverse_modulo :: proc(dest, a, b: ^Int, allocator := context.alloc return _private_inverse_modulo(dest, a, b) } +internal_int_invmod :: internal_int_inverse_modulo internal_invmod :: proc{ internal_int_inverse_modulo, } /* @@ -2187,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. @@ -2815,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.") } @@ -2833,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 @@ -2850,13 +2846,13 @@ 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) } dest.used = digits - return nil + return internal_clamp(dest) } internal_random :: proc { internal_int_random, } @@ -2954,4 +2950,4 @@ internal_zero_unused :: proc { internal_int_zero_unused, } /* ========================== End of low-level routines ========================== -*/ \ No newline at end of file +*/ diff --git a/core/math/big/prime.odin b/core/math/big/prime.odin index 110c10239..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. @@ -44,7 +42,7 @@ internal_int_prime_is_divisible :: proc(a: ^Int, allocator := context.allocator) Computes res == G**X mod P. Assumes `res`, `G`, `X` and `P` to not be `nil` and for `G`, `X` and `P` to have been initialized. */ -internal_int_exponent_mod :: proc(res, G, X, P: ^Int, allocator := context.allocator) -> (err: Error) { +internal_int_power_modulo :: proc(res, G, X, P: ^Int, allocator := context.allocator) -> (err: Error) { context.allocator = allocator dr: int @@ -112,6 +110,9 @@ internal_int_exponent_mod :: proc(res, G, X, P: ^Int, allocator := context.alloc */ return _private_int_exponent_mod(res, G, X, P, 0) } +internal_int_exponent_mod :: internal_int_power_modulo +internal_int_powmod :: internal_int_power_modulo +internal_powmod :: proc { internal_int_power_modulo, } /* Kronecker/Legendre symbol (a|p) @@ -312,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 @@ -458,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. @@ -1109,7 +1110,7 @@ internal_int_prime_next_prime :: proc(a: ^Int, trials: int, bbs_style: bool, all Generate the restable. */ for x := 1; x < _PRIME_TAB_SIZE; x += 1 { - res_tab = internal_mod(a, _private_prime_table[x]) or_return + res_tab = cast(type_of(res_tab))(internal_mod(a, _private_prime_table[x]) or_return) } for { @@ -1180,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. */ @@ -1244,6 +1242,20 @@ internal_random_prime :: proc(a: ^Int, size_in_bits: int, trials: int, flags := a.digit[0] |= 3 } if .Second_MSB_On in flags { + /* + Ensure there's enough space for the bit to be set. + */ + if a.used * _DIGIT_BITS < size_in_bits - 1 { + new_size := (size_in_bits - 1) / _DIGIT_BITS + + if new_size % _DIGIT_BITS > 0 { + new_size += 1 + } + + internal_grow(a, new_size) or_return + a.used = new_size + } + internal_int_bitfield_set_single(a, size_in_bits - 2) or_return } @@ -1411,4 +1423,4 @@ number_of_rabin_miller_trials :: proc(bit_size: int) -> (number_of_trials: int) case: return 2 /* For keysizes bigger than 10_240 use always at least 2 Rounds */ } -} \ No newline at end of file +} diff --git a/core/math/big/private.odin b/core/math/big/private.odin index d41e66343..bb6b9497c 100644 --- a/core/math/big/private.odin +++ b/core/math/big/private.odin @@ -1,3402 +1,3355 @@ -/* - Copyright 2021 Jeroen van Rijn . - Made available under Odin's BSD-3 license. - - An arbitrary precision mathematics implementation in Odin. - For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3. - The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks. - - ============================= Private procedures ============================= - - Private procedures used by the above low-level routines follow. - - Don't call these yourself unless you really know what you're doing. - They include implementations that are optimimal for certain ranges of input only. - - These aren't exported for the same reasons. -*/ - - -package math_big - -import "core:intrinsics" -import "core:mem" - -/* - Multiplies |a| * |b| and only computes upto digs digits of result. - HAC pp. 595, Algorithm 14.12 Modified so you can control how - many digits of output are created. -*/ -_private_int_mul :: proc(dest, a, b: ^Int, digits: int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - /* - Can we use the fast multiplier? - */ - if digits < _WARRAY && min(a.used, b.used) < _MAX_COMBA { - return #force_inline _private_int_mul_comba(dest, a, b, digits) - } - - /* - Set up temporary output `Int`, which we'll swap for `dest` when done. - */ - - t := &Int{} - - internal_grow(t, max(digits, _DEFAULT_DIGIT_COUNT)) or_return - t.used = digits - - /* - Compute the digits of the product directly. - */ - pa := a.used - for ix := 0; ix < pa; ix += 1 { - /* - Limit ourselves to `digits` DIGITs of output. - */ - pb := min(b.used, digits - ix) - carry := _WORD(0) - iy := 0 - - /* - Compute the column of the output and propagate the carry. - */ - #no_bounds_check for iy = 0; iy < pb; iy += 1 { - /* - Compute the column as a _WORD. - */ - column := _WORD(t.digit[ix + iy]) + _WORD(a.digit[ix]) * _WORD(b.digit[iy]) + carry - - /* - The new column is the lower part of the result. - */ - t.digit[ix + iy] = DIGIT(column & _WORD(_MASK)) - - /* - Get the carry word from the result. - */ - carry = column >> _DIGIT_BITS - } - /* - Set carry if it is placed below digits - */ - if ix + iy < digits { - t.digit[ix + pb] = DIGIT(carry) - } - } - - internal_swap(dest, t) - internal_destroy(t) - return internal_clamp(dest) -} - - -/* - Multiplication using the Toom-Cook 3-way algorithm. - - Much more complicated than Karatsuba but has a lower asymptotic running time of O(N**1.464). - This algorithm is only particularly useful on VERY large inputs. - (We're talking 1000s of digits here...). - - This file contains code from J. Arndt's book "Matters Computational" - and the accompanying FXT-library with permission of the author. - - Setup from: - Chung, Jaewook, and M. Anwar Hasan. "Asymmetric squaring formulae." - 18th IEEE Symposium on Computer Arithmetic (ARITH'07). IEEE, 2007. - - The interpolation from above needed one temporary variable more than the interpolation here: - - Bodrato, Marco, and Alberto Zanoni. "What about Toom-Cook matrices optimality." - Centro Vito Volterra Universita di Roma Tor Vergata (2006) -*/ -_private_int_mul_toom :: proc(dest, a, b: ^Int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - S1, S2, T1, a0, a1, a2, b0, b1, b2 := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{} - defer internal_destroy(S1, S2, T1, a0, a1, a2, b0, b1, b2) - - /* - Init temps. - */ - internal_init_multi(S1, S2, T1) or_return - - /* - B - */ - B := min(a.used, b.used) / 3 - - /* - a = a2 * x^2 + a1 * x + a0; - */ - internal_grow(a0, B) or_return - internal_grow(a1, B) or_return - internal_grow(a2, a.used - 2 * B) or_return - - a0.used, a1.used = B, B - a2.used = a.used - 2 * B - - internal_copy_digits(a0, a, a0.used) or_return - internal_copy_digits(a1, a, a1.used, B) or_return - internal_copy_digits(a2, a, a2.used, 2 * B) or_return - - internal_clamp(a0) - internal_clamp(a1) - internal_clamp(a2) - - /* - b = b2 * x^2 + b1 * x + b0; - */ - internal_grow(b0, B) or_return - internal_grow(b1, B) or_return - internal_grow(b2, b.used - 2 * B) or_return - - b0.used, b1.used = B, B - b2.used = b.used - 2 * B - - internal_copy_digits(b0, b, b0.used) or_return - internal_copy_digits(b1, b, b1.used, B) or_return - internal_copy_digits(b2, b, b2.used, 2 * B) or_return - - internal_clamp(b0) - internal_clamp(b1) - internal_clamp(b2) - - - /* - \\ S1 = (a2+a1+a0) * (b2+b1+b0); - */ - internal_add(T1, a2, a1) or_return /* T1 = a2 + a1; */ - internal_add(S2, T1, a0) or_return /* S2 = T1 + a0; */ - internal_add(dest, b2, b1) or_return /* dest = b2 + b1; */ - internal_add(S1, dest, b0) or_return /* S1 = c + b0; */ - internal_mul(S1, S1, S2) or_return /* S1 = S1 * S2; */ - - /* - \\S2 = (4*a2+2*a1+a0) * (4*b2+2*b1+b0); - */ - internal_add(T1, T1, a2) or_return /* T1 = T1 + a2; */ - internal_int_shl1(T1, T1) or_return /* T1 = T1 << 1; */ - internal_add(T1, T1, a0) or_return /* T1 = T1 + a0; */ - internal_add(dest, dest, b2) or_return /* c = c + b2; */ - internal_int_shl1(dest, dest) or_return /* c = c << 1; */ - internal_add(dest, dest, b0) or_return /* c = c + b0; */ - internal_mul(S2, T1, dest) or_return /* S2 = T1 * c; */ - - /* - \\S3 = (a2-a1+a0) * (b2-b1+b0); - */ - internal_sub(a1, a2, a1) or_return /* a1 = a2 - a1; */ - internal_add(a1, a1, a0) or_return /* a1 = a1 + a0; */ - internal_sub(b1, b2, b1) or_return /* b1 = b2 - b1; */ - internal_add(b1, b1, b0) or_return /* b1 = b1 + b0; */ - internal_mul(a1, a1, b1) or_return /* a1 = a1 * b1; */ - internal_mul(b1, a2, b2) or_return /* b1 = a2 * b2; */ - - /* - \\S2 = (S2 - S3) / 3; - */ - internal_sub(S2, S2, a1) or_return /* S2 = S2 - a1; */ - _private_int_div_3(S2, S2) or_return /* S2 = S2 / 3; \\ this is an exact division */ - internal_sub(a1, S1, a1) or_return /* a1 = S1 - a1; */ - internal_int_shr1(a1, a1) or_return /* a1 = a1 >> 1; */ - internal_mul(a0, a0, b0) or_return /* a0 = a0 * b0; */ - internal_sub(S1, S1, a0) or_return /* S1 = S1 - a0; */ - internal_sub(S2, S2, S1) or_return /* S2 = S2 - S1; */ - internal_int_shr1(S2, S2) or_return /* S2 = S2 >> 1; */ - internal_sub(S1, S1, a1) or_return /* S1 = S1 - a1; */ - internal_sub(S1, S1, b1) or_return /* S1 = S1 - b1; */ - internal_int_shl1(T1, b1) or_return /* T1 = b1 << 1; */ - internal_sub(S2, S2, T1) or_return /* S2 = S2 - T1; */ - internal_sub(a1, a1, S2) or_return /* a1 = a1 - S2; */ - - /* - P = b1*x^4+ S2*x^3+ S1*x^2+ a1*x + a0; - */ - _private_int_shl_leg(b1, 4 * B) or_return - _private_int_shl_leg(S2, 3 * B) or_return - internal_add(b1, b1, S2) or_return - _private_int_shl_leg(S1, 2 * B) or_return - internal_add(b1, b1, S1) or_return - _private_int_shl_leg(a1, 1 * B) or_return - internal_add(b1, b1, a1) or_return - internal_add(dest, b1, a0) or_return - - /* - a * b - P - */ - return nil -} - -/* - product = |a| * |b| using Karatsuba Multiplication using three half size multiplications. - - Let `B` represent the radix [e.g. 2**_DIGIT_BITS] and let `n` represent - half of the number of digits in the min(a,b) - - `a` = `a1` * `B`**`n` + `a0` - `b` = `b`1 * `B`**`n` + `b0` - - Then, a * b => 1b1 * B**2n + ((a1 + a0)(b1 + b0) - (a0b0 + a1b1)) * B + a0b0 - - Note that a1b1 and a0b0 are used twice and only need to be computed once. - So in total three half size (half # of digit) multiplications are performed, - a0b0, a1b1 and (a1+b1)(a0+b0) - - Note that a multiplication of half the digits requires 1/4th the number of - single precision multiplications, so in total after one call 25% of the - single precision multiplications are saved. - - Note also that the call to `internal_mul` can end up back in this function - if the a0, a1, b0, or b1 are above the threshold. - - This is known as divide-and-conquer and leads to the famous O(N**lg(3)) or O(N**1.584) - work which is asymptopically lower than the standard O(N**2) that the - baseline/comba methods use. Generally though, the overhead of this method doesn't pay off - until a certain size is reached, of around 80 used DIGITs. -*/ -_private_int_mul_karatsuba :: proc(dest, a, b: ^Int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - x0, x1, y0, y1, t1, x0y0, x1y1 := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{} - defer internal_destroy(x0, x1, y0, y1, t1, x0y0, x1y1) - - /* - min # of digits, divided by two. - */ - B := min(a.used, b.used) >> 1 - - /* - Init all the temps. - */ - internal_grow(x0, B) or_return - internal_grow(x1, a.used - B) or_return - internal_grow(y0, B) or_return - internal_grow(y1, b.used - B) or_return - internal_grow(t1, B * 2) or_return - internal_grow(x0y0, B * 2) or_return - internal_grow(x1y1, B * 2) or_return - - /* - Now shift the digits. - */ - x0.used, y0.used = B, B - x1.used = a.used - B - y1.used = b.used - B - - /* - We copy the digits directly instead of using higher level functions - since we also need to shift the digits. - */ - internal_copy_digits(x0, a, x0.used) - internal_copy_digits(y0, b, y0.used) - internal_copy_digits(x1, a, x1.used, B) - internal_copy_digits(y1, b, y1.used, B) - - /* - Only need to clamp the lower words since by definition the - upper words x1/y1 must have a known number of digits. - */ - clamp(x0) - clamp(y0) - - /* - Now calc the products x0y0 and x1y1, - after this x0 is no longer required, free temp [x0==t2]! - */ - internal_mul(x0y0, x0, y0) or_return /* x0y0 = x0*y0 */ - internal_mul(x1y1, x1, y1) or_return /* x1y1 = x1*y1 */ - internal_add(t1, x1, x0) or_return /* now calc x1+x0 and */ - internal_add(x0, y1, y0) or_return /* t2 = y1 + y0 */ - internal_mul(t1, t1, x0) or_return /* t1 = (x1 + x0) * (y1 + y0) */ - - /* - Add x0y0. - */ - internal_add(x0, x0y0, x1y1) or_return /* t2 = x0y0 + x1y1 */ - internal_sub(t1, t1, x0) or_return /* t1 = (x1+x0)*(y1+y0) - (x1y1 + x0y0) */ - - /* - shift by B. - */ - _private_int_shl_leg(t1, B) or_return /* t1 = (x0y0 + x1y1 - (x1-x0)*(y1-y0))< (err: Error) { - context.allocator = allocator - - /* - Set up array. - */ - W: [_WARRAY]DIGIT = --- - - /* - Grow the destination as required. - */ - internal_grow(dest, digits) or_return - - /* - Number of output digits to produce. - */ - pa := min(digits, a.used + b.used) - - /* - Clear the carry - */ - _W := _WORD(0) - - ix: int - for ix = 0; ix < pa; ix += 1 { - tx, ty, iy, iz: int - - /* - Get offsets into the two bignums. - */ - ty = min(b.used - 1, ix) - tx = ix - ty - - /* - This is the number of times the loop will iterate, essentially. - while (tx++ < a->used && ty-- >= 0) { ... } - */ - - iy = min(a.used - tx, ty + 1) - - /* - Execute loop. - */ - #no_bounds_check for iz = 0; iz < iy; iz += 1 { - _W += _WORD(a.digit[tx + iz]) * _WORD(b.digit[ty - iz]) - } - - /* - Store term. - */ - W[ix] = DIGIT(_W) & _MASK - - /* - Make next carry. - */ - _W = _W >> _WORD(_DIGIT_BITS) - } - - /* - Setup dest. - */ - old_used := dest.used - dest.used = pa - - /* - Now extract the previous digit [below the carry]. - */ - copy_slice(dest.digit[0:], W[:pa]) - - /* - Clear unused digits [that existed in the old copy of dest]. - */ - internal_zero_unused(dest, old_used) - - /* - Adjust dest.used based on leading zeroes. - */ - - return internal_clamp(dest) -} - -/* - Multiplies |a| * |b| and does not compute the lower digs digits - [meant to get the higher part of the product] -*/ -_private_int_mul_high :: proc(dest, a, b: ^Int, digits: int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - /* - Can we use the fast multiplier? - */ - if a.used + b.used + 1 < _WARRAY && min(a.used, b.used) < _MAX_COMBA { - return _private_int_mul_high_comba(dest, a, b, digits) - } - - internal_grow(dest, a.used + b.used + 1) or_return - dest.used = a.used + b.used + 1 - - pa := a.used - pb := b.used - for ix := 0; ix < pa; ix += 1 { - carry := DIGIT(0) - - for iy := digits - ix; iy < pb; iy += 1 { - /* - Calculate the double precision result. - */ - r := _WORD(dest.digit[ix + iy]) + _WORD(a.digit[ix]) * _WORD(b.digit[iy]) + _WORD(carry) - - /* - Get the lower part. - */ - dest.digit[ix + iy] = DIGIT(r & _WORD(_MASK)) - - /* - Carry the carry. - */ - carry = DIGIT(r >> _WORD(_DIGIT_BITS)) - } - dest.digit[ix + pb] = carry - } - return internal_clamp(dest) -} - -/* - This is a modified version of `_private_int_mul_comba` that only produces output digits *above* `digits`. - See the comments for `_private_int_mul_comba` to see how it works. - - This is used in the Barrett reduction since for one of the multiplications - only the higher digits were needed. This essentially halves the work. - - Based on Algorithm 14.12 on pp.595 of HAC. -*/ -_private_int_mul_high_comba :: proc(dest, a, b: ^Int, digits: int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - W: [_WARRAY]DIGIT = --- - _W: _WORD = 0 - - /* - Number of output digits to produce. Grow the destination as required. - */ - pa := a.used + b.used - internal_grow(dest, pa) or_return - - ix: int - for ix = digits; ix < pa; ix += 1 { - /* - Get offsets into the two bignums. - */ - ty := min(b.used - 1, ix) - tx := ix - ty - - /* - This is the number of times the loop will iterrate, essentially it's - while (tx++ < a->used && ty-- >= 0) { ... } - */ - iy := min(a.used - tx, ty + 1) - - /* - Execute loop. - */ - for iz := 0; iz < iy; iz += 1 { - _W += _WORD(a.digit[tx + iz]) * _WORD(b.digit[ty - iz]) - } - - /* - Store term. - */ - W[ix] = DIGIT(_W) & DIGIT(_MASK) - - /* - Make next carry. - */ - _W = _W >> _WORD(_DIGIT_BITS) - } - - /* - Setup dest - */ - old_used := dest.used - dest.used = pa - - for ix = digits; ix < pa; ix += 1 { - /* - Now extract the previous digit [below the carry]. - */ - dest.digit[ix] = W[ix] - } - - /* - Zero remainder. - */ - internal_zero_unused(dest, old_used) - - /* - Adjust dest.used based on leading zeroes. - */ - return internal_clamp(dest) -} - -/* - Single-digit multiplication with the smaller number as the single-digit. -*/ -_private_int_mul_balance :: proc(dest, a, b: ^Int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - a, b := a, b - - a0, tmp, r := &Int{}, &Int{}, &Int{} - defer internal_destroy(a0, tmp, r) - - b_size := min(a.used, b.used) - n_blocks := max(a.used, b.used) / b_size - - internal_grow(a0, b_size + 2) or_return - internal_init_multi(tmp, r) or_return - - /* - Make sure that `a` is the larger one. - */ - if a.used < b.used { - a, b = b, a - } - assert(a.used >= b.used) - - i, j := 0, 0 - for ; i < n_blocks; i += 1 { - /* - Cut a slice off of `a`. - */ - - a0.used = b_size - internal_copy_digits(a0, a, a0.used, j) - j += a0.used - internal_clamp(a0) - - /* - Multiply with `b`. - */ - internal_mul(tmp, a0, b) or_return - - /* - Shift `tmp` to the correct position. - */ - _private_int_shl_leg(tmp, b_size * i) or_return - - /* - Add to output. No carry needed. - */ - internal_add(r, r, tmp) or_return - } - - /* - The left-overs; there are always left-overs. - */ - if j < a.used { - a0.used = a.used - j - internal_copy_digits(a0, a, a0.used, j) - j += a0.used - internal_clamp(a0) - - internal_mul(tmp, a0, b) or_return - _private_int_shl_leg(tmp, b_size * i) or_return - internal_add(r, r, tmp) or_return - } - - internal_swap(dest, r) - return -} - -/* - Low level squaring, b = a*a, HAC pp.596-597, Algorithm 14.16 - Assumes `dest` and `src` to not be `nil`, and `src` to have been initialized. -*/ -_private_int_sqr :: proc(dest, src: ^Int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - pa := src.used - - t := &Int{}; ix, iy: int - /* - Grow `t` to maximum needed size, or `_DEFAULT_DIGIT_COUNT`, whichever is bigger. - */ - internal_grow(t, max((2 * pa) + 1, _DEFAULT_DIGIT_COUNT)) or_return - t.used = (2 * pa) + 1 - - #no_bounds_check for ix = 0; ix < pa; ix += 1 { - carry := DIGIT(0) - /* - First calculate the digit at 2*ix; calculate double precision result. - */ - r := _WORD(t.digit[ix+ix]) + (_WORD(src.digit[ix]) * _WORD(src.digit[ix])) - - /* - Store lower part in result. - */ - t.digit[ix+ix] = DIGIT(r & _WORD(_MASK)) - /* - Get the carry. - */ - carry = DIGIT(r >> _DIGIT_BITS) - - #no_bounds_check for iy = ix + 1; iy < pa; iy += 1 { - /* - First calculate the product. - */ - r = _WORD(src.digit[ix]) * _WORD(src.digit[iy]) - - /* Now calculate the double precision result. Nóte we use - * addition instead of *2 since it's easier to optimize - */ - r = _WORD(t.digit[ix+iy]) + r + r + _WORD(carry) - - /* - Store lower part. - */ - t.digit[ix+iy] = DIGIT(r & _WORD(_MASK)) - - /* - Get carry. - */ - carry = DIGIT(r >> _DIGIT_BITS) - } - /* - Propagate upwards. - */ - #no_bounds_check for carry != 0 { - r = _WORD(t.digit[ix+iy]) + _WORD(carry) - t.digit[ix+iy] = DIGIT(r & _WORD(_MASK)) - carry = DIGIT(r >> _WORD(_DIGIT_BITS)) - iy += 1 - } - } - - err = internal_clamp(t) - internal_swap(dest, t) - internal_destroy(t) - return err -} - -/* - The jist of squaring... - You do like mult except the offset of the tmpx [one that starts closer to zero] can't equal the offset of tmpy. - So basically you set up iy like before then you min it with (ty-tx) so that it never happens. - You double all those you add in the inner loop. After that loop you do the squares and add them in. - - Assumes `dest` and `src` not to be `nil` and `src` to have been initialized. -*/ -_private_int_sqr_comba :: proc(dest, src: ^Int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - W: [_WARRAY]DIGIT = --- - - /* - Grow the destination as required. - */ - pa := uint(src.used) + uint(src.used) - internal_grow(dest, int(pa)) or_return - - /* - Number of output digits to produce. - */ - W1 := _WORD(0) - _W : _WORD = --- - ix := uint(0) - - #no_bounds_check for ; ix < pa; ix += 1 { - /* - Clear counter. - */ - _W = {} - - /* - Get offsets into the two bignums. - */ - ty := min(uint(src.used) - 1, ix) - tx := ix - ty - - /* - This is the number of times the loop will iterate, - essentially while (tx++ < a->used && ty-- >= 0) { ... } - */ - iy := min(uint(src.used) - tx, ty + 1) - - /* - Now for squaring, tx can never equal ty. - We halve the distance since they approach at a rate of 2x, - and we have to round because odd cases need to be executed. - */ - iy = min(iy, ((ty - tx) + 1) >> 1 ) - - /* - Execute loop. - */ - #no_bounds_check for iz := uint(0); iz < iy; iz += 1 { - _W += _WORD(src.digit[tx + iz]) * _WORD(src.digit[ty - iz]) - } - - /* - Double the inner product and add carry. - */ - _W = _W + _W + W1 - - /* - Even columns have the square term in them. - */ - if ix & 1 == 0 { - _W += _WORD(src.digit[ix >> 1]) * _WORD(src.digit[ix >> 1]) - } - - /* - Store it. - */ - W[ix] = DIGIT(_W & _WORD(_MASK)) - - /* - Make next carry. - */ - W1 = _W >> _DIGIT_BITS - } - - /* - Setup dest. - */ - old_used := dest.used - dest.used = src.used + src.used - - #no_bounds_check for ix = 0; ix < pa; ix += 1 { - dest.digit[ix] = W[ix] & _MASK - } - - /* - Clear unused digits [that existed in the old copy of dest]. - */ - internal_zero_unused(dest, old_used) - - return internal_clamp(dest) -} - -/* - 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. -*/ -_private_int_sqr_karatsuba :: proc(dest, src: ^Int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - x0, x1, t1, t2, x0x0, x1x1 := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{} - defer internal_destroy(x0, x1, t1, t2, x0x0, x1x1) - - /* - Min # of digits, divided by two. - */ - B := src.used >> 1 - - /* - Init temps. - */ - internal_grow(x0, B) or_return - internal_grow(x1, src.used - B) or_return - internal_grow(t1, src.used * 2) or_return - internal_grow(t2, src.used * 2) or_return - internal_grow(x0x0, B * 2 ) or_return - internal_grow(x1x1, (src.used - B) * 2) or_return - - /* - Now shift the digits. - */ - x0.used = B - x1.used = src.used - B - - #force_inline internal_copy_digits(x0, src, x0.used) - #force_inline mem.copy_non_overlapping(&x1.digit[0], &src.digit[B], size_of(DIGIT) * x1.used) - #force_inline internal_clamp(x0) - - /* - Now calc the products x0*x0 and x1*x1. - */ - internal_sqr(x0x0, x0) or_return - internal_sqr(x1x1, x1) or_return - - /* - Now calc (x1+x0)^2 - */ - internal_add(t1, x0, x1) or_return - internal_sqr(t1, t1) or_return - - /* - Add x0y0 - */ - internal_add(t2, x0x0, x1x1) or_return - internal_sub(t1, t1, t2) or_return - - /* - Shift by B. - */ - _private_int_shl_leg(t1, B) or_return - _private_int_shl_leg(x1x1, B * 2) or_return - internal_add(t1, t1, x0x0) or_return - internal_add(dest, t1, x1x1) or_return - - return #force_inline internal_clamp(dest) -} - -/* - Squaring using Toom-Cook 3-way algorithm. - - Setup and interpolation from algorithm SQR_3 in Chung, Jaewook, and M. Anwar Hasan. "Asymmetric squaring formulae." - 18th IEEE Symposium on Computer Arithmetic (ARITH'07). IEEE, 2007. -*/ -_private_int_sqr_toom :: proc(dest, src: ^Int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - S0, a0, a1, a2 := &Int{}, &Int{}, &Int{}, &Int{} - defer internal_destroy(S0, a0, a1, a2) - - /* - Init temps. - */ - internal_zero(S0) or_return - - /* - B - */ - B := src.used / 3 - - /* - a = a2 * x^2 + a1 * x + a0; - */ - internal_grow(a0, B) or_return - internal_grow(a1, B) or_return - internal_grow(a2, src.used - (2 * B)) or_return - - a0.used = B - a1.used = B - a2.used = src.used - 2 * B - - #force_inline mem.copy_non_overlapping(&a0.digit[0], &src.digit[ 0], size_of(DIGIT) * a0.used) - #force_inline mem.copy_non_overlapping(&a1.digit[0], &src.digit[ B], size_of(DIGIT) * a1.used) - #force_inline mem.copy_non_overlapping(&a2.digit[0], &src.digit[2 * B], size_of(DIGIT) * a2.used) - - internal_clamp(a0) - internal_clamp(a1) - internal_clamp(a2) - - /** S0 = a0^2; */ - internal_sqr(S0, a0) or_return - - /** \\S1 = (a2 + a1 + a0)^2 */ - /** \\S2 = (a2 - a1 + a0)^2 */ - /** \\S1 = a0 + a2; */ - /** a0 = a0 + a2; */ - internal_add(a0, a0, a2) or_return - /** \\S2 = S1 - a1; */ - /** b = a0 - a1; */ - internal_sub(dest, a0, a1) or_return - /** \\S1 = S1 + a1; */ - /** a0 = a0 + a1; */ - internal_add(a0, a0, a1) or_return - /** \\S1 = S1^2; */ - /** a0 = a0^2; */ - internal_sqr(a0, a0) or_return - /** \\S2 = S2^2; */ - /** b = b^2; */ - internal_sqr(dest, dest) or_return - /** \\ S3 = 2 * a1 * a2 */ - /** \\S3 = a1 * a2; */ - /** a1 = a1 * a2; */ - internal_mul(a1, a1, a2) or_return - /** \\S3 = S3 << 1; */ - /** a1 = a1 << 1; */ - internal_shl(a1, a1, 1) or_return - /** \\S4 = a2^2; */ - /** a2 = a2^2; */ - internal_sqr(a2, a2) or_return - /** \\ tmp = (S1 + S2)/2 */ - /** \\tmp = S1 + S2; */ - /** b = a0 + b; */ - internal_add(dest, a0, dest) or_return - /** \\tmp = tmp >> 1; */ - /** b = b >> 1; */ - internal_shr(dest, dest, 1) or_return - /** \\ S1 = S1 - tmp - S3 */ - /** \\S1 = S1 - tmp; */ - /** a0 = a0 - b; */ - internal_sub(a0, a0, dest) or_return - /** \\S1 = S1 - S3; */ - /** a0 = a0 - a1; */ - internal_sub(a0, a0, a1) or_return - /** \\S2 = tmp - S4 -S0 */ - /** \\S2 = tmp - S4; */ - /** b = b - a2; */ - internal_sub(dest, dest, a2) or_return - /** \\S2 = S2 - S0; */ - /** b = b - S0; */ - internal_sub(dest, dest, S0) or_return - /** \\P = S4*x^4 + S3*x^3 + S2*x^2 + S1*x + S0; */ - /** P = a2*x^4 + a1*x^3 + b*x^2 + a0*x + S0; */ - _private_int_shl_leg( a2, 4 * B) or_return - _private_int_shl_leg( a1, 3 * B) or_return - _private_int_shl_leg(dest, 2 * B) or_return - _private_int_shl_leg( a0, 1 * B) or_return - - internal_add(a2, a2, a1) or_return - internal_add(dest, dest, a2) or_return - internal_add(dest, dest, a0) or_return - internal_add(dest, dest, S0) or_return - /** a^2 - P */ - - return #force_inline internal_clamp(dest) -} - -/* - Divide by three (based on routine from MPI and the GMP manual). -*/ -_private_int_div_3 :: proc(quotient, numerator: ^Int, allocator := context.allocator) -> (remainder: DIGIT, err: Error) { - context.allocator = allocator - - /* - b = 2^_DIGIT_BITS / 3 - */ - b := _WORD(1) << _WORD(_DIGIT_BITS) / _WORD(3) - - q := &Int{} - internal_grow(q, numerator.used) or_return - q.used = numerator.used - q.sign = numerator.sign - - w, t: _WORD - #no_bounds_check for ix := numerator.used; ix >= 0; ix -= 1 { - w = (w << _WORD(_DIGIT_BITS)) | _WORD(numerator.digit[ix]) - if w >= 3 { - /* - Multiply w by [1/3]. - */ - t = (w * b) >> _WORD(_DIGIT_BITS) - - /* - Now subtract 3 * [w/3] from w, to get the remainder. - */ - w -= t+t+t - - /* - Fixup the remainder as required since the optimization is not exact. - */ - for w >= 3 { - t += 1 - w -= 3 - } - } else { - t = 0 - } - q.digit[ix] = DIGIT(t) - } - remainder = DIGIT(w) - - /* - [optional] store the quotient. - */ - if quotient != nil { - err = clamp(q) - internal_swap(q, quotient) - } - internal_destroy(q) - return remainder, nil -} - -/* - Signed Integer Division - - c*b + d == a [i.e. a/b, c=quotient, d=remainder], HAC pp.598 Algorithm 14.20 - - Note that the description in HAC is horribly incomplete. - For example, it doesn't consider the case where digits are removed from 'x' in - the inner loop. - - It also doesn't consider the case that y has fewer than three digits, etc. - The overall algorithm is as described as 14.20 from HAC but fixed to treat these cases. -*/ -_private_int_div_school :: proc(quotient, remainder, numerator, denominator: ^Int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - error_if_immutable(quotient, remainder) or_return - - q, x, y, t1, t2 := &Int{}, &Int{}, &Int{}, &Int{}, &Int{} - defer internal_destroy(q, x, y, t1, t2) - - internal_grow(q, numerator.used + 2) or_return - q.used = numerator.used + 2 - - internal_init_multi(t1, t2) or_return - internal_copy(x, numerator) or_return - internal_copy(y, denominator) or_return - - /* - Fix the sign. - */ - neg := numerator.sign != denominator.sign - x.sign = .Zero_or_Positive - y.sign = .Zero_or_Positive - - /* - Normalize both x and y, ensure that y >= b/2, [b == 2**MP_DIGIT_BIT] - */ - norm := internal_count_bits(y) % _DIGIT_BITS - - if norm < _DIGIT_BITS - 1 { - norm = (_DIGIT_BITS - 1) - norm - internal_shl(x, x, norm) or_return - internal_shl(y, y, norm) or_return - } else { - norm = 0 - } - - /* - Note: HAC does 0 based, so if used==5 then it's 0,1,2,3,4, i.e. use 4 - */ - n := x.used - 1 - t := y.used - 1 - - /* - while (x >= y*b**n-t) do { q[n-t] += 1; x -= y*b**{n-t} } - y = y*b**{n-t} - */ - - _private_int_shl_leg(y, n - t) or_return - - gte := internal_gte(x, y) - for gte { - q.digit[n - t] += 1 - internal_sub(x, x, y) or_return - gte = internal_gte(x, y) - } - - /* - Reset y by shifting it back down. - */ - _private_int_shr_leg(y, n - t) - - /* - Step 3. for i from n down to (t + 1). - */ - #no_bounds_check for i := n; i >= (t + 1); i -= 1 { - if i > x.used { continue } - - /* - step 3.1 if xi == yt then set q{i-t-1} to b-1, otherwise set q{i-t-1} to (xi*b + x{i-1})/yt - */ - if x.digit[i] == y.digit[t] { - q.digit[(i - t) - 1] = 1 << (_DIGIT_BITS - 1) - } else { - - tmp := _WORD(x.digit[i]) << _DIGIT_BITS - tmp |= _WORD(x.digit[i - 1]) - tmp /= _WORD(y.digit[t]) - if tmp > _WORD(_MASK) { - tmp = _WORD(_MASK) - } - q.digit[(i - t) - 1] = DIGIT(tmp & _WORD(_MASK)) - } - - /* while (q{i-t-1} * (yt * b + y{t-1})) > - xi * b**2 + xi-1 * b + xi-2 - - do q{i-t-1} -= 1; - */ - - iter := 0 - - q.digit[(i - t) - 1] = (q.digit[(i - t) - 1] + 1) & _MASK - #no_bounds_check for { - q.digit[(i - t) - 1] = (q.digit[(i - t) - 1] - 1) & _MASK - - /* - Find left hand. - */ - internal_zero(t1) - t1.digit[0] = ((t - 1) < 0) ? 0 : y.digit[t - 1] - t1.digit[1] = y.digit[t] - t1.used = 2 - internal_mul(t1, t1, q.digit[(i - t) - 1]) or_return - - /* - Find right hand. - */ - t2.digit[0] = ((i - 2) < 0) ? 0 : x.digit[i - 2] - t2.digit[1] = x.digit[i - 1] /* i >= 1 always holds */ - t2.digit[2] = x.digit[i] - t2.used = 3 - - if internal_lte(t1, t2) { - break - } - iter += 1; if iter > 100 { - return .Max_Iterations_Reached - } - } - - /* - Step 3.3 x = x - q{i-t-1} * y * b**{i-t-1} - */ - int_mul_digit(t1, y, q.digit[(i - t) - 1]) or_return - _private_int_shl_leg(t1, (i - t) - 1) or_return - internal_sub(x, x, t1) or_return - - /* - if x < 0 then { x = x + y*b**{i-t-1}; q{i-t-1} -= 1; } - */ - if x.sign == .Negative { - internal_copy(t1, y) or_return - _private_int_shl_leg(t1, (i - t) - 1) or_return - internal_add(x, x, t1) or_return - - q.digit[(i - t) - 1] = (q.digit[(i - t) - 1] - 1) & _MASK - } - } - - /* - Now q is the quotient and x is the remainder, [which we have to normalize] - Get sign before writing to c. - */ - z, _ := is_zero(x) - x.sign = .Zero_or_Positive if z else numerator.sign - - if quotient != nil { - internal_clamp(q) - internal_swap(q, quotient) - quotient.sign = .Negative if neg else .Zero_or_Positive - } - - if remainder != nil { - internal_shr(x, x, norm) or_return - internal_swap(x, remainder) - } - - return nil -} - -/* - Direct implementation of algorithms 1.8 "RecursiveDivRem" and 1.9 "UnbalancedDivision" from: - - Brent, Richard P., and Paul Zimmermann. "Modern computer arithmetic" - Vol. 18. Cambridge University Press, 2010 - Available online at https://arxiv.org/pdf/1004.4710 - - pages 19ff. in the above online document. -*/ -_private_div_recursion :: proc(quotient, remainder, a, b: ^Int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - A1, A2, B1, B0, Q1, Q0, R1, R0, t := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{} - defer internal_destroy(A1, A2, B1, B0, Q1, Q0, R1, R0, t) - - m := a.used - b.used - k := m / 2 - - if m < MUL_KARATSUBA_CUTOFF { - return _private_int_div_school(quotient, remainder, a, b) - } - - internal_init_multi(A1, A2, B1, B0, Q1, Q0, R1, R0, t) or_return - - /* - `B1` = `b` / `beta`^`k`, `B0` = `b` % `beta`^`k` - */ - internal_shrmod(B1, B0, b, k * _DIGIT_BITS) or_return - - /* - (Q1, R1) = RecursiveDivRem(A / beta^(2k), B1) - */ - internal_shrmod(A1, t, a, 2 * k * _DIGIT_BITS) or_return - _private_div_recursion(Q1, R1, A1, B1) or_return - - /* - A1 = (R1 * beta^(2k)) + (A % beta^(2k)) - (Q1 * B0 * beta^k) - */ - _private_int_shl_leg(R1, 2 * k) or_return - internal_add(A1, R1, t) or_return - internal_mul(t, Q1, B0) or_return - - /* - While A1 < 0 do Q1 = Q1 - 1, A1 = A1 + (beta^k * B) - */ - if internal_lt(A1, 0) { - internal_shl(t, b, k * _DIGIT_BITS) or_return - - for { - internal_decr(Q1) or_return - internal_add(A1, A1, t) or_return - if internal_gte(A1, 0) { break } - } - } - - /* - (Q0, R0) = RecursiveDivRem(A1 / beta^(k), B1) - */ - internal_shrmod(A1, t, A1, k * _DIGIT_BITS) or_return - _private_div_recursion(Q0, R0, A1, B1) or_return - - /* - A2 = (R0*beta^k) + (A1 % beta^k) - (Q0*B0) - */ - _private_int_shl_leg(R0, k) or_return - internal_add(A2, R0, t) or_return - internal_mul(t, Q0, B0) or_return - internal_sub(A2, A2, t) or_return - - /* - While A2 < 0 do Q0 = Q0 - 1, A2 = A2 + B. - */ - for internal_is_negative(A2) { // internal_lt(A2, 0) { - internal_decr(Q0) or_return - internal_add(A2, A2, b) or_return - } - - /* - Return q = (Q1*beta^k) + Q0, r = A2. - */ - _private_int_shl_leg(Q1, k) or_return - internal_add(quotient, Q1, Q0) or_return - - return internal_copy(remainder, A2) -} - -_private_int_div_recursive :: proc(quotient, remainder, a, b: ^Int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - A, B, Q, Q1, R, A_div, A_mod := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{} - defer internal_destroy(A, B, Q, Q1, R, A_div, A_mod) - - internal_init_multi(A, B, Q, Q1, R, A_div, A_mod) or_return - - /* - Most significant bit of a limb. - Assumes _DIGIT_MAX < (sizeof(DIGIT) * sizeof(u8)). - */ - msb := (_DIGIT_MAX + DIGIT(1)) >> 1 - sigma := 0 - msb_b := b.digit[b.used - 1] - for msb_b < msb { - sigma += 1 - msb_b <<= 1 - } - - /* - Use that sigma to normalize B. - */ - internal_shl(B, b, sigma) or_return - internal_shl(A, a, sigma) or_return - - /* - Fix the sign. - */ - neg := a.sign != b.sign - A.sign = .Zero_or_Positive; B.sign = .Zero_or_Positive - - /* - If the magnitude of "A" is not more more than twice that of "B" we can work - on them directly, otherwise we need to work at "A" in chunks. - */ - n := B.used - m := A.used - B.used - - /* - Q = 0. We already ensured that when we called `internal_init_multi`. - */ - for m > n { - /* - (q, r) = RecursiveDivRem(A / (beta^(m-n)), B) - */ - j := (m - n) * _DIGIT_BITS - internal_shrmod(A_div, A_mod, A, j) or_return - _private_div_recursion(Q1, R, A_div, B) or_return - - /* - Q = (Q*beta!(n)) + q - */ - internal_shl(Q, Q, n * _DIGIT_BITS) or_return - internal_add(Q, Q, Q1) or_return - - /* - A = (r * beta^(m-n)) + (A % beta^(m-n)) - */ - internal_shl(R, R, (m - n) * _DIGIT_BITS) or_return - internal_add(A, R, A_mod) or_return - - /* - m = m - n - */ - m -= n - } - - /* - (q, r) = RecursiveDivRem(A, B) - */ - _private_div_recursion(Q1, R, A, B) or_return - - /* - Q = (Q * beta^m) + q, R = r - */ - internal_shl(Q, Q, m * _DIGIT_BITS) or_return - internal_add(Q, Q, Q1) or_return - - /* - Get sign before writing to dest. - */ - R.sign = .Zero_or_Positive if internal_is_zero(Q) else a.sign - - if quotient != nil { - swap(quotient, Q) - quotient.sign = .Negative if neg else .Zero_or_Positive - } - if remainder != nil { - /* - De-normalize the remainder. - */ - internal_shrmod(R, nil, R, sigma) or_return - swap(remainder, R) - } - return nil -} - -/* - Slower bit-bang division... also smaller. -*/ -@(deprecated="Use `_int_div_school`, it's 3.5x faster.") -_private_int_div_small :: proc(quotient, remainder, numerator, denominator: ^Int) -> (err: Error) { - - ta, tb, tq, q := &Int{}, &Int{}, &Int{}, &Int{} - - defer internal_destroy(ta, tb, tq, q) - - for { - internal_one(tq) or_return - - num_bits, _ := count_bits(numerator) - den_bits, _ := count_bits(denominator) - n := num_bits - den_bits - - abs(ta, numerator) or_return - abs(tb, denominator) or_return - shl(tb, tb, n) or_return - shl(tq, tq, n) or_return - - for n >= 0 { - if internal_gte(ta, tb) { - // ta -= tb - sub(ta, ta, tb) or_return - // q += tq - add( q, q, tq) or_return - } - shr1(tb, tb) or_return - shr1(tq, tq) or_return - - n -= 1 - } - - /* - Now q == quotient and ta == remainder. - */ - neg := numerator.sign != denominator.sign - if quotient != nil { - swap(quotient, q) - z, _ := is_zero(quotient) - quotient.sign = .Negative if neg && !z else .Zero_or_Positive - } - if remainder != nil { - swap(remainder, ta) - z, _ := is_zero(numerator) - remainder.sign = .Zero_or_Positive if z else numerator.sign - } - - break - } - return err -} - - - -/* - Binary split factorial algo due to: http://www.luschny.de/math/factorial/binarysplitfact.html -*/ -_private_int_factorial_binary_split :: proc(res: ^Int, n: int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - inner, outer, start, stop, temp := &Int{}, &Int{}, &Int{}, &Int{}, &Int{} - defer internal_destroy(inner, outer, start, stop, temp) - - internal_one(inner, false) or_return - internal_one(outer, false) or_return - - bits_used := ilog2(n) - - for i := bits_used; i >= 0; i -= 1 { - start := (n >> (uint(i) + 1)) + 1 | 1 - stop := (n >> uint(i)) + 1 | 1 - _private_int_recursive_product(temp, start, stop, 0) or_return - internal_mul(inner, inner, temp) or_return - internal_mul(outer, outer, inner) or_return - } - shift := n - intrinsics.count_ones(n) - - return internal_shl(res, outer, int(shift)) -} - -/* - Recursive product used by binary split factorial algorithm. -*/ -_private_int_recursive_product :: proc(res: ^Int, start, stop: int, level := int(0), allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - t1, t2 := &Int{}, &Int{} - defer internal_destroy(t1, t2) - - if level > FACTORIAL_BINARY_SPLIT_MAX_RECURSIONS { - return .Max_Iterations_Reached - } - - num_factors := (stop - start) >> 1 - if num_factors == 2 { - internal_set(t1, start, false) or_return - when true { - internal_grow(t2, t1.used + 1, false) or_return - internal_add(t2, t1, 2) or_return - } else { - internal_add(t2, t1, 2) or_return - } - return internal_mul(res, t1, t2) - } - - if num_factors > 1 { - mid := (start + num_factors) | 1 - _private_int_recursive_product(t1, start, mid, level + 1) or_return - _private_int_recursive_product(t2, mid, stop, level + 1) or_return - return internal_mul(res, t1, t2) - } - - if num_factors == 1 { - return #force_inline internal_set(res, start, true) - } - - return #force_inline internal_one(res, true) -} - -/* - Internal function computing both GCD using the binary method, - and, if target isn't `nil`, also LCM. - - Expects the `a` and `b` to have been initialized - and one or both of `res_gcd` or `res_lcm` not to be `nil`. - - If both `a` and `b` are zero, return zero. - If either `a` or `b`, return the other one. - - The `gcd` and `lcm` wrappers have already done this test, - but `gcd_lcm` wouldn't have, so we still need to perform it. - - If neither result is wanted, we have nothing to do. -*/ -_private_int_gcd_lcm :: proc(res_gcd, res_lcm, a, b: ^Int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - if res_gcd == nil && res_lcm == nil { - return nil - } - - /* - We need a temporary because `res_gcd` is allowed to be `nil`. - */ - if a.used == 0 && b.used == 0 { - /* - GCD(0, 0) and LCM(0, 0) are both 0. - */ - if res_gcd != nil { - internal_zero(res_gcd) or_return - } - if res_lcm != nil { - internal_zero(res_lcm) or_return - } - return nil - } else if a.used == 0 { - /* - We can early out with GCD = B and LCM = 0 - */ - if res_gcd != nil { - internal_abs(res_gcd, b) or_return - } - if res_lcm != nil { - internal_zero(res_lcm) or_return - } - return nil - } else if b.used == 0 { - /* - We can early out with GCD = A and LCM = 0 - */ - if res_gcd != nil { - internal_abs(res_gcd, a) or_return - } - if res_lcm != nil { - internal_zero(res_lcm) or_return - } - return nil - } - - temp_gcd_res := &Int{} - defer internal_destroy(temp_gcd_res) - - /* - If neither `a` or `b` was zero, we need to compute `gcd`. - 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. - */ - 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) - - if k > 0 { - /* - Divide the power of two out. - */ - internal_shr(u, u, k) or_return - internal_shr(v, v, k) or_return - } - - /* - Divide any remaining factors of two out. - */ - if u_lsb != k { - internal_shr(u, u, u_lsb - k) or_return - } - if v_lsb != k { - internal_shr(v, v, v_lsb - k) or_return - } - - for v.used != 0 { - /* - Make sure `v` is the largest. - */ - if internal_gt(u, v) { - /* - Swap `u` and `v` to make sure `v` is >= `u`. - */ - internal_swap(u, v) - } - - /* - Subtract smallest from largest. - */ - internal_sub(v, v, u) or_return - - /* - Divide out all factors of two. - */ - b, _ := internal_count_lsb(v) - 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 - - /* - We've computed `gcd`, either the long way, or because one of the inputs was zero. - If we don't want `lcm`, we're done. - */ - if res_lcm == nil { - internal_swap(temp_gcd_res, res_gcd) - return nil - } - - /* - Computes least common multiple as `|a*b|/gcd(a,b)` - Divide the smallest by the GCD. - */ - if internal_lt_abs(a, b) { - /* - Store quotient in `t2` such that `t2 * b` is the LCM. - */ - internal_div(res_lcm, a, temp_gcd_res) or_return - err = internal_mul(res_lcm, res_lcm, b) - } else { - /* - Store quotient in `t2` such that `t2 * a` is the LCM. - */ - internal_div(res_lcm, b, temp_gcd_res) or_return - err = internal_mul(res_lcm, res_lcm, a) - } - - if res_gcd != nil { - internal_swap(temp_gcd_res, res_gcd) - } - - /* - Fix the sign to positive and return. - */ - res_lcm.sign = .Zero_or_Positive - return err -} - -/* - Internal implementation of log. - Assumes `a` not to be `nil` and to have been initialized. -*/ -_private_int_log :: proc(a: ^Int, base: DIGIT, allocator := context.allocator) -> (res: int, err: Error) { - bracket_low, bracket_high, bracket_mid, t, bi_base := &Int{}, &Int{}, &Int{}, &Int{}, &Int{} - defer internal_destroy(bracket_low, bracket_high, bracket_mid, t, bi_base) - - ic := #force_inline internal_cmp(a, base) - if ic == -1 || ic == 0 { - return 1 if ic == 0 else 0, nil - } - defer if err != nil { - res = -1 - } - - internal_set(bi_base, base, true, allocator) or_return - internal_clear(bracket_mid, false, allocator) or_return - internal_clear(t, false, allocator) or_return - internal_one(bracket_low, false, allocator) or_return - internal_set(bracket_high, base, false, allocator) or_return - - low := 0; high := 1 - - /* - A kind of Giant-step/baby-step algorithm. - Idea shamelessly stolen from https://programmingpraxis.com/2010/05/07/integer-logarithms/2/ - The effect is asymptotic, hence needs benchmarks to test if the Giant-step should be skipped - for small n. - */ - - for { - /* - Iterate until `a` is bracketed between low + high. - */ - if #force_inline internal_gte(bracket_high, a) { break } - - low = high - #force_inline internal_copy(bracket_low, bracket_high) or_return - high <<= 1 - #force_inline internal_sqr(bracket_high, bracket_high) or_return - } - - for (high - low) > 1 { - mid := (high + low) >> 1 - - #force_inline internal_pow(t, bi_base, mid - low) or_return - - #force_inline internal_mul(bracket_mid, bracket_low, t) or_return - - mc := #force_inline internal_cmp(a, bracket_mid) - switch mc { - case -1: - high = mid - internal_swap(bracket_mid, bracket_high) - case 0: - return mid, nil - case 1: - low = mid - internal_swap(bracket_mid, bracket_low) - } - } - - fc := #force_inline internal_cmp(bracket_high, a) - res = high if fc == 0 else low - - return -} - -/* - Computes xR**-1 == x (mod N) via Montgomery Reduction. - This is an optimized implementation of `internal_montgomery_reduce` - which uses the comba method to quickly calculate the columns of the reduction. - Based on Algorithm 14.32 on pp.601 of HAC. -*/ -_private_montgomery_reduce_comba :: proc(x, n: ^Int, rho: DIGIT, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - W: [_WARRAY]_WORD = --- - - if x.used > _WARRAY { return .Invalid_Argument } - - /* - Get old used count. - */ - old_used := x.used - - /* - Grow `x` as required. - */ - internal_grow(x, n.used + 1) or_return - - /* - First we have to get the digits of the input into an array of double precision words W[...] - Copy the digits of `x` into W[0..`x.used` - 1] - */ - ix: int - for ix = 0; ix < x.used; ix += 1 { - W[ix] = _WORD(x.digit[ix]) - } - - /* - Zero the high words of W[a->used..m->used*2]. - */ - zero_upper := (n.used * 2) + 1 - if ix < zero_upper { - for ix = x.used; ix < zero_upper; ix += 1 { - W[ix] = {} - } - } - - /* - Now we proceed to zero successive digits from the least significant upwards. - */ - for ix = 0; ix < n.used; ix += 1 { - /* - `mu = ai * m' mod b` - - We avoid a double precision multiplication (which isn't required) - by casting the value down to a DIGIT. Note this requires - that W[ix-1] have the carry cleared (see after the inner loop) - */ - mu := ((W[ix] & _WORD(_MASK)) * _WORD(rho)) & _WORD(_MASK) - - /* - `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. - - Note the comba method normally doesn't handle carries in the - inner loop In this case we fix the carry from the previous - column since the Montgomery reduction requires digits of the - result (so far) [see above] to work. - - This is handled by fixing up one carry after the inner loop. - The carry fixups are done in order so after these loops the - first m->used words of W[] have the carries fixed. - */ - for iy := 0; iy < n.used; iy += 1 { - W[ix + iy] += mu * _WORD(n.digit[iy]) - } - - /* - Now fix carry for next digit, W[ix+1]. - */ - W[ix + 1] += (W[ix] >> _DIGIT_BITS) - } - - /* - Now we have to propagate the carries and shift the words downward - [all those least significant digits we zeroed]. - */ - - for ; ix < n.used * 2; ix += 1 { - W[ix + 1] += (W[ix] >> _DIGIT_BITS) - } - - /* copy out, A = A/b**n - * - * The result is A/b**n but instead of converting from an - * array of mp_word to mp_digit than calling mp_rshd - * we just copy them in the right order - */ - - for ix = 0; ix < (n.used + 1); ix += 1 { - x.digit[ix] = DIGIT(W[n.used + ix] & _WORD(_MASK)) - } - - /* - Set the max used. - */ - x.used = n.used + 1 - - /* - Zero old_used digits, if the input a was larger than m->used+1 we'll have to clear the digits. - */ - internal_zero_unused(x, old_used) - internal_clamp(x) - - /* - if A >= m then A = A - m - */ - if internal_gte_abs(x, n) { - return internal_sub(x, x, n) - } - return nil -} - -/* - Computes xR**-1 == x (mod N) via Montgomery Reduction. - Assumes `x` and `n` not to be nil. -*/ -_private_int_montgomery_reduce :: proc(x, n: ^Int, rho: DIGIT, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - /* - Can the fast reduction [comba] method be used? - Note that unlike in mul, you're safely allowed *less* than the available columns [255 per default], - since carries are fixed up in the inner loop. - */ - internal_clear_if_uninitialized(x, n) or_return - - digs := (n.used * 2) + 1 - if digs < _WARRAY && x.used <= _WARRAY && n.used < _MAX_COMBA { - return _private_montgomery_reduce_comba(x, n, rho) - } - - /* - Grow the input as required - */ - internal_grow(x, digs) or_return - x.used = digs - - for ix := 0; ix < n.used; ix += 1 { - /* - `mu = ai * rho mod b` - The value of rho must be precalculated via `int_montgomery_setup()`, - such that it equals -1/n0 mod b this allows the following inner loop - to reduce the input one digit at a time. - */ - - mu := DIGIT((_WORD(x.digit[ix]) * _WORD(rho)) & _WORD(_MASK)) - - /* - a = a + mu * m * b**i - Multiply and add in place. - */ - u := DIGIT(0) - iy := int(0) - for ; iy < n.used; iy += 1 { - /* - Compute product and sum. - */ - r := (_WORD(mu) * _WORD(n.digit[iy]) + _WORD(u) + _WORD(x.digit[ix + iy])) - - /* - Get carry. - */ - u = DIGIT(r >> _DIGIT_BITS) - - /* - Fix digit. - */ - x.digit[ix + iy] = DIGIT(r & _WORD(_MASK)) - } - - /* - At this point the ix'th digit of x should be zero. - Propagate carries upwards as required. - */ - for u != 0 { - x.digit[ix + iy] += u - u = x.digit[ix + iy] >> _DIGIT_BITS - x.digit[ix + iy] &= _MASK - iy += 1 - } - } - - /* - At this point the n.used'th least significant digits of x are all zero, - which means we can shift x to the right by n.used digits and the - residue is unchanged. - - x = x/b**n.used. - */ - internal_clamp(x) - _private_int_shr_leg(x, n.used) - - /* - if x >= n then x = x - n - */ - if internal_gte_abs(x, n) { - return internal_sub(x, x, n) - } - - return nil -} - -/* - Shifts with subtractions when the result is greater than b. - - The method is slightly modified to shift B unconditionally upto just under - the leading bit of b. This saves alot of multiple precision shifting. - - Assumes `a` and `b` not to be `nil`. -*/ -_private_int_montgomery_calc_normalization :: proc(a, b: ^Int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - /* - How many bits of last digit does b use. - */ - internal_clear_if_uninitialized(a, b) or_return - - bits := internal_count_bits(b) % _DIGIT_BITS - - if b.used > 1 { - power := ((b.used - 1) * _DIGIT_BITS) + bits - 1 - internal_int_power_of_two(a, power) or_return - } else { - internal_one(a) or_return - bits = 1 - } - - /* - Now compute C = A * B mod b. - */ - for x := bits - 1; x < _DIGIT_BITS; x += 1 { - internal_int_shl1(a, a) or_return - if internal_gte_abs(a, b) { - internal_sub(a, a, b) or_return - } - } - return nil -} - -/* - Sets up the Montgomery reduction stuff. -*/ -_private_int_montgomery_setup :: proc(n: ^Int, allocator := context.allocator) -> (rho: DIGIT, err: Error) { - /* - Fast inversion mod 2**k - Based on the fact that: - - XA = 1 (mod 2**n) => (X(2-XA)) A = 1 (mod 2**2n) - => 2*X*A - X*X*A*A = 1 - => 2*(1) - (1) = 1 - */ - internal_clear_if_uninitialized(n, allocator) or_return - - b := n.digit[0] - if b & 1 == 0 { return 0, .Invalid_Argument } - - x := (((b + 2) & 4) << 1) + b /* here x*a==1 mod 2**4 */ - x *= 2 - (b * x) /* here x*a==1 mod 2**8 */ - x *= 2 - (b * x) /* here x*a==1 mod 2**16 */ - - when _DIGIT_TYPE_BITS == 64 { - x *= 2 - (b * x) /* here x*a==1 mod 2**32 */ - x *= 2 - (b * x) /* here x*a==1 mod 2**64 */ - } - - /* - rho = -1/m mod b - */ - rho = DIGIT(((_WORD(1) << _WORD(_DIGIT_BITS)) - _WORD(x)) & _WORD(_MASK)) - return rho, nil -} - -/* - Reduces `x` mod `m`, assumes 0 < x < m**2, mu is precomputed via reduce_setup. - From HAC pp.604 Algorithm 14.42 - - Assumes `x`, `m` and `mu` all not to be `nil` and have been initialized. -*/ -_private_int_reduce :: proc(x, m, mu: ^Int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - q := &Int{} - defer internal_destroy(q) - um := m.used - - /* - q = x - */ - internal_copy(q, x) or_return - - /* - q1 = x / b**(k-1) - */ - _private_int_shr_leg(q, um - 1) - - /* - According to HAC this optimization is ok. - */ - if DIGIT(um) > DIGIT(1) << (_DIGIT_BITS - 1) { - internal_mul(q, q, mu) or_return - } else { - _private_int_mul_high(q, q, mu, um) or_return - } - - /* - q3 = q2 / b**(k+1) - */ - _private_int_shr_leg(q, um + 1) - - /* - x = x mod b**(k+1), quick (no division) - */ - internal_int_mod_bits(x, x, _DIGIT_BITS * (um + 1)) or_return - - /* - q = q * m mod b**(k+1), quick (no division) - */ - _private_int_mul(q, q, m, um + 1) or_return - - /* - x = x - q - */ - internal_sub(x, x, q) or_return - - /* - If x < 0, add b**(k+1) to it. - */ - if internal_is_negative(x) { - internal_set(q, 1) or_return - _private_int_shl_leg(q, um + 1) or_return - internal_add(x, x, q) or_return - } - - /* - Back off if it's too big. - */ - for internal_gte(x, m) { - internal_sub(x, x, m) or_return - } - - return nil -} - -/* - Reduces `a` modulo `n`, where `n` is of the form 2**p - d. -*/ -_private_int_reduce_2k :: proc(a, n: ^Int, d: DIGIT, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - q := &Int{} - defer internal_destroy(q) - - internal_zero(q) or_return - - p := internal_count_bits(n) - - for { - /* - q = a/2**p, a = a mod 2**p - */ - internal_shrmod(q, a, a, p) or_return - - if d != 1 { - /* - q = q * d - */ - internal_mul(q, q, d) or_return - } - - /* - a = a + q - */ - internal_add(a, a, q) or_return - if internal_lt_abs(a, n) { break } - internal_sub(a, a, n) or_return - } - - return nil -} - -/* - Reduces `a` modulo `n` where `n` is of the form 2**p - d - This differs from reduce_2k since "d" can be larger than a single digit. -*/ -_private_int_reduce_2k_l :: proc(a, n, d: ^Int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - q := &Int{} - defer internal_destroy(q) - - internal_zero(q) or_return - - p := internal_count_bits(n) - - for { - /* - q = a/2**p, a = a mod 2**p - */ - internal_shrmod(q, a, a, p) or_return - - /* - q = q * d - */ - internal_mul(q, q, d) or_return - - /* - a = a + q - */ - internal_add(a, a, q) or_return - if internal_lt_abs(a, n) { break } - internal_sub(a, a, n) or_return - } - - return nil -} - -/* - Determines if `internal_int_reduce_2k` can be used. - Asssumes `a` not to be `nil` and to have been initialized. -*/ -_private_int_reduce_is_2k :: proc(a: ^Int) -> (reducible: bool, err: Error) { - assert_if_nil(a) - - if internal_is_zero(a) { - return false, nil - } else if a.used == 1 { - return true, nil - } else if a.used > 1 { - iy := internal_count_bits(a) - iw := 1 - iz := DIGIT(1) - - /* - Test every bit from the second digit up, must be 1. - */ - for ix := _DIGIT_BITS; ix < iy; ix += 1 { - if a.digit[iw] & iz == 0 { - return false, nil - } - - iz <<= 1 - if iz > _DIGIT_MAX { - iw += 1 - iz = 1 - } - } - return true, nil - } else { - return true, nil - } -} - -/* - Determines if `internal_int_reduce_2k_l` can be used. - Asssumes `a` not to be `nil` and to have been initialized. -*/ -_private_int_reduce_is_2k_l :: proc(a: ^Int) -> (reducible: bool, err: Error) { - assert_if_nil(a) - - if internal_int_is_zero(a) { - return false, nil - } else if a.used == 1 { - return true, nil - } else if a.used > 1 { - /* - If more than half of the digits are -1 we're sold. - */ - ix := 0 - iy := 0 - - for ; ix < a.used; ix += 1 { - if a.digit[ix] == _DIGIT_MAX { - iy += 1 - } - } - return iy >= (a.used / 2), nil - } else { - return false, nil - } -} - -/* - Determines the setup value. - Assumes `a` is not `nil`. -*/ -_private_int_reduce_2k_setup :: proc(a: ^Int, allocator := context.allocator) -> (d: DIGIT, err: Error) { - context.allocator = allocator - - tmp := &Int{} - defer internal_destroy(tmp) - internal_zero(tmp) or_return - - internal_int_power_of_two(tmp, internal_count_bits(a)) or_return - internal_sub(tmp, tmp, a) or_return - - return tmp.digit[0], nil -} - -/* - Determines the setup value. - Assumes `mu` and `P` are not `nil`. - - d := (1 << a.bits) - a; -*/ -_private_int_reduce_2k_setup_l :: proc(mu, P: ^Int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - tmp := &Int{} - defer internal_destroy(tmp) - internal_zero(tmp) or_return - - internal_int_power_of_two(tmp, internal_count_bits(P)) or_return - internal_sub(mu, tmp, P) or_return - - return nil -} - -/* - Pre-calculate the value required for Barrett reduction. - For a given modulus "P" it calulates the value required in "mu" - Assumes `mu` and `P` are not `nil`. -*/ -_private_int_reduce_setup :: proc(mu, P: ^Int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - internal_int_power_of_two(mu, P.used * 2 * _DIGIT_BITS) or_return - return internal_int_div(mu, mu, P) -} - -/* - Determines the setup value. - Assumes `a` to not be `nil` and to have been initialized. -*/ -_private_int_dr_setup :: proc(a: ^Int) -> (d: DIGIT) { - /* - The casts are required if _DIGIT_BITS is one less than - the number of bits in a DIGIT [e.g. _DIGIT_BITS==31]. - */ - return DIGIT((1 << _DIGIT_BITS) - a.digit[0]) -} - -/* - Determines if a number is a valid DR modulus. - Assumes `a` to not be `nil` and to have been initialized. -*/ -_private_dr_is_modulus :: proc(a: ^Int) -> (res: bool) { - /* - Must be at least two digits. - */ - if a.used < 2 { return false } - - /* - Must be of the form b**k - a [a <= b] so all but the first digit must be equal to -1 (mod b). - */ - for ix := 1; ix < a.used; ix += 1 { - if a.digit[ix] != _MASK { - return false - } - } - return true -} - -/* - Reduce "x" in place modulo "n" using the Diminished Radix algorithm. - Based on algorithm from the paper - - "Generating Efficient Primes for Discrete Log Cryptosystems" - Chae Hoon Lim, Pil Joong Lee, - POSTECH Information Research Laboratories - - The modulus must be of a special format [see manual]. - Has been modified to use algorithm 7.10 from the LTM book instead - - Input x must be in the range 0 <= x <= (n-1)**2 - Assumes `x` and `n` to not be `nil` and to have been initialized. -*/ -_private_int_dr_reduce :: proc(x, n: ^Int, k: DIGIT, allocator := context.allocator) -> (err: Error) { - /* - m = digits in modulus. - */ - m := n.used - - /* - Ensure that "x" has at least 2m digits. - */ - internal_grow(x, m + m) or_return - - /* - Top of loop, this is where the code resumes if another reduction pass is required. - */ - for { - i: int - mu := DIGIT(0) - - /* - Compute (x mod B**m) + k * [x/B**m] inline and inplace. - */ - for i = 0; i < m; i += 1 { - r := _WORD(x.digit[i + m]) * _WORD(k) + _WORD(x.digit[i] + mu) - x.digit[i] = DIGIT(r & _WORD(_MASK)) - mu = DIGIT(r >> _WORD(_DIGIT_BITS)) - } - - /* - Set final carry. - */ - x.digit[i] = mu - - /* - Zero words above m. - */ - mem.zero_slice(x.digit[m + 1:][:x.used - m]) - - /* - Clamp, sub and return. - */ - internal_clamp(x) or_return - - /* - If x >= n then subtract and reduce again. - Each successive "recursion" makes the input smaller and smaller. - */ - if internal_lt_abs(x, n) { break } - - internal_sub(x, x, n) or_return - } - return nil -} - -/* - Computes res == G**X mod P. - Assumes `res`, `G`, `X` and `P` to not be `nil` and for `G`, `X` and `P` to have been initialized. -*/ -_private_int_exponent_mod :: proc(res, G, X, P: ^Int, redmode: int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - M := [_TAB_SIZE]Int{} - winsize: uint - - /* - Use a pointer to the reduction algorithm. - This allows us to use one of many reduction algorithms without modding the guts of the code with if statements everywhere. - */ - redux: #type proc(x, m, mu: ^Int, allocator := context.allocator) -> (err: Error) - - defer { - internal_destroy(&M[1]) - for x := 1 << (winsize - 1); x < (1 << winsize); x += 1 { - internal_destroy(&M[x]) - } - } - - /* - Find window size. - */ - x := internal_count_bits(X) - switch { - case x <= 7: - winsize = 2 - case x <= 36: - winsize = 3 - case x <= 140: - winsize = 4 - case x <= 450: - winsize = 5 - case x <= 1303: - winsize = 6 - case x <= 3529: - winsize = 7 - case: - winsize = 8 - } - - winsize = min(_MAX_WIN_SIZE, winsize) if _MAX_WIN_SIZE > 0 else winsize - - /* - Init M array. - Init first cell. - */ - internal_zero(&M[1]) or_return - - /* - Now init the second half of the array. - */ - for x = 1 << (winsize - 1); x < (1 << winsize); x += 1 { - internal_zero(&M[x]) or_return - } - - /* - Create `mu`, used for Barrett reduction. - */ - mu := &Int{} - defer internal_destroy(mu) - internal_zero(mu) or_return - - if redmode == 0 { - _private_int_reduce_setup(mu, P) or_return - redux = _private_int_reduce - } else { - _private_int_reduce_2k_setup_l(mu, P) or_return - redux = _private_int_reduce_2k_l - } - - /* - Create M table. - - The M table contains powers of the base, e.g. M[x] = G**x mod P. - The first half of the table is not computed, though, except for M[0] and M[1]. - */ - internal_int_mod(&M[1], G, P) or_return - - /* - Compute the value at M[1<<(winsize-1)] by squaring M[1] (winsize-1) times. - - TODO: This can probably be replaced by computing the power and using `pow` to raise to it - instead of repeated squaring. - */ - slot := 1 << (winsize - 1) - internal_copy(&M[slot], &M[1]) or_return - - for x = 0; x < int(winsize - 1); x += 1 { - /* - Square it. - */ - internal_sqr(&M[slot], &M[slot]) or_return - - /* - Reduce modulo P - */ - redux(&M[slot], P, mu) or_return - } - - /* - Create upper table, that is M[x] = M[x-1] * M[1] (mod P) - for x = (2**(winsize - 1) + 1) to (2**winsize - 1) - */ - for x = slot + 1; x < (1 << winsize); x += 1 { - internal_mul(&M[x], &M[x - 1], &M[1]) or_return - redux(&M[x], P, mu) or_return - } - - /* - Setup result. - */ - internal_one(res) or_return - - /* - Set initial mode and bit cnt. - */ - mode := 0 - bitcnt := 1 - buf := DIGIT(0) - digidx := X.used - 1 - bitcpy := uint(0) - bitbuf := DIGIT(0) - - for { - /* - Grab next digit as required. - */ - bitcnt -= 1 - if bitcnt == 0 { - /* - If digidx == -1 we are out of digits. - */ - if digidx == -1 { break } - - /* - Read next digit and reset the bitcnt. - */ - buf = X.digit[digidx] - digidx -= 1 - bitcnt = _DIGIT_BITS - } - - /* - Grab the next msb from the exponent. - */ - y := buf >> (_DIGIT_BITS - 1) & 1 - buf <<= 1 - - /* - If the bit is zero and mode == 0 then we ignore it. - These represent the leading zero bits before the first 1 bit - in the exponent. Technically this opt is not required but it - does lower the # of trivial squaring/reductions used. - */ - if mode == 0 && y == 0 { - continue - } - - /* - If the bit is zero and mode == 1 then we square. - */ - if mode == 1 && y == 0 { - internal_sqr(res, res) or_return - redux(res, P, mu) or_return - continue - } - - /* - Else we add it to the window. - */ - bitcpy += 1 - bitbuf |= (y << (winsize - bitcpy)) - mode = 2 - - if (bitcpy == winsize) { - /* - Window is filled so square as required and multiply. - Square first. - */ - for x = 0; x < int(winsize); x += 1 { - internal_sqr(res, res) or_return - redux(res, P, mu) or_return - } - - /* - Then multiply. - */ - internal_mul(res, res, &M[bitbuf]) or_return - redux(res, P, mu) or_return - - /* - Empty window and reset. - */ - bitcpy = 0 - bitbuf = 0 - mode = 1 - } - } - - /* - If bits remain then square/multiply. - */ - if mode == 2 && bitcpy > 0 { - /* - Square then multiply if the bit is set. - */ - for x = 0; x < int(bitcpy); x += 1 { - internal_sqr(res, res) or_return - redux(res, P, mu) or_return - - bitbuf <<= 1 - if ((bitbuf & (1 << winsize)) != 0) { - /* - Then multiply. - */ - internal_mul(res, res, &M[1]) or_return - redux(res, P, mu) or_return - } - } - } - return err -} - -/* - Computes Y == G**X mod P, HAC pp.616, Algorithm 14.85 - - Uses a left-to-right `k`-ary sliding window to compute the modular exponentiation. - The value of `k` changes based on the size of the exponent. - - Uses Montgomery or Diminished Radix reduction [whichever appropriate] - - Assumes `res`, `G`, `X` and `P` to not be `nil` and for `G`, `X` and `P` to have been initialized. -*/ -_private_int_exponent_mod_fast :: proc(res, G, X, P: ^Int, redmode: int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - M := [_TAB_SIZE]Int{} - winsize: uint - - /* - Use a pointer to the reduction algorithm. - This allows us to use one of many reduction algorithms without modding the guts of the code with if statements everywhere. - */ - redux: #type proc(x, n: ^Int, rho: DIGIT, allocator := context.allocator) -> (err: Error) - - defer { - internal_destroy(&M[1]) - for x := 1 << (winsize - 1); x < (1 << winsize); x += 1 { - internal_destroy(&M[x]) - } - } - - /* - Find window size. - */ - x := internal_count_bits(X) - switch { - case x <= 7: - winsize = 2 - case x <= 36: - winsize = 3 - case x <= 140: - winsize = 4 - case x <= 450: - winsize = 5 - case x <= 1303: - winsize = 6 - case x <= 3529: - winsize = 7 - case: - winsize = 8 - } - - winsize = min(_MAX_WIN_SIZE, winsize) if _MAX_WIN_SIZE > 0 else winsize - - /* - Init M array - Init first cell. - */ - cap := internal_int_allocated_cap(P) - internal_grow(&M[1], cap) or_return - - /* - Now init the second half of the array. - */ - for x = 1 << (winsize - 1); x < (1 << winsize); x += 1 { - internal_grow(&M[x], cap) or_return - } - - /* - Determine and setup reduction code. - */ - rho: DIGIT - - if redmode == 0 { - /* - Now setup Montgomery. - */ - rho = _private_int_montgomery_setup(P) or_return - - /* - Automatically pick the comba one if available (saves quite a few calls/ifs). - */ - if ((P.used * 2) + 1) < _WARRAY && P.used < _MAX_COMBA { - redux = _private_montgomery_reduce_comba - } else { - /* - Use slower baseline Montgomery method. - */ - redux = _private_int_montgomery_reduce - } - } else if redmode == 1 { - /* - Setup DR reduction for moduli of the form B**k - b. - */ - rho = _private_int_dr_setup(P) - redux = _private_int_dr_reduce - } else { - /* - Setup DR reduction for moduli of the form 2**k - b. - */ - rho = _private_int_reduce_2k_setup(P) or_return - redux = _private_int_reduce_2k - } - - /* - Setup result. - */ - internal_grow(res, cap) or_return - - /* - Create M table - The first half of the table is not computed, though, except for M[0] and M[1] - */ - - if redmode == 0 { - /* - Now we need R mod m. - */ - _private_int_montgomery_calc_normalization(res, P) or_return - - /* - Now set M[1] to G * R mod m. - */ - internal_mulmod(&M[1], G, res, P) or_return - } else { - internal_one(res) or_return - internal_mod(&M[1], G, P) or_return - } - - /* - Compute the value at M[1<<(winsize-1)] by squaring M[1] (winsize-1) times. - */ - slot := 1 << (winsize - 1) - internal_copy(&M[slot], &M[1]) or_return - - for x = 0; x < int(winsize - 1); x += 1 { - internal_sqr(&M[slot], &M[slot]) or_return - redux(&M[slot], P, rho) or_return - } - - /* - Create upper table. - */ - for x = (1 << (winsize - 1)) + 1; x < (1 << winsize); x += 1 { - internal_mul(&M[x], &M[x - 1], &M[1]) or_return - redux(&M[x], P, rho) or_return - } - - /* - Set initial mode and bit cnt. - */ - mode := 0 - bitcnt := 1 - buf := DIGIT(0) - digidx := X.used - 1 - bitcpy := 0 - bitbuf := DIGIT(0) - - for { - /* - Grab next digit as required. - */ - bitcnt -= 1 - if bitcnt == 0 { - /* - If digidx == -1 we are out of digits so break. - */ - if digidx == -1 { break } - - /* - Read next digit and reset the bitcnt. - */ - buf = X.digit[digidx] - digidx -= 1 - bitcnt = _DIGIT_BITS - } - - /* - Grab the next msb from the exponent. - */ - y := (buf >> (_DIGIT_BITS - 1)) & 1 - buf <<= 1 - - /* - If the bit is zero and mode == 0 then we ignore it. - These represent the leading zero bits before the first 1 bit in the exponent. - Technically this opt is not required but it does lower the # of trivial squaring/reductions used. - */ - if mode == 0 && y == 0 { continue } - - /* - If the bit is zero and mode == 1 then we square. - */ - if mode == 1 && y == 0 { - internal_sqr(res, res) or_return - redux(res, P, rho) or_return - continue - } - - /* - Else we add it to the window. - */ - bitcpy += 1 - bitbuf |= (y << (winsize - uint(bitcpy))) - mode = 2 - - if bitcpy == int(winsize) { - /* - Window is filled so square as required and multiply - Square first. - */ - for x = 0; x < int(winsize); x += 1 { - internal_sqr(res, res) or_return - redux(res, P, rho) or_return - } - - /* - Then multiply. - */ - internal_mul(res, res, &M[bitbuf]) or_return - redux(res, P, rho) or_return - - /* - Empty window and reset. - */ - bitcpy = 0 - bitbuf = 0 - mode = 1 - } - } - - /* - If bits remain then square/multiply. - */ - if mode == 2 && bitcpy > 0 { - /* - Square then multiply if the bit is set. - */ - for x = 0; x < bitcpy; x += 1 { - internal_sqr(res, res) or_return - redux(res, P, rho) or_return - - /* - Get next bit of the window. - */ - bitbuf <<= 1 - if bitbuf & (1 << winsize) != 0 { - /* - Then multiply. - */ - internal_mul(res, res, &M[1]) or_return - redux(res, P, rho) or_return - } - } - } - - if redmode == 0 { - /* - Fixup result if Montgomery reduction is used. - Recall that any value in a Montgomery system is actually multiplied by R mod n. - So we have to reduce one more time to cancel out the factor of R. - */ - redux(res, P, rho) or_return - } - - return nil -} - -/* - hac 14.61, pp608 -*/ -_private_inverse_modulo :: proc(dest, a, b: ^Int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - x, y, u, v, A, B, C, D := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{} - defer internal_destroy(x, y, u, v, A, B, C, D) - - /* - `b` cannot be negative. - */ - if b.sign == .Negative || internal_is_zero(b) { - return .Invalid_Argument - } - - /* - init temps. - */ - internal_init_multi(x, y, u, v, A, B, C, D) or_return - - /* - `x` = `a` % `b`, `y` = `b` - */ - internal_mod(x, a, b) or_return - internal_copy(y, b) or_return - - /* - 2. [modified] if x,y are both even then return an error! - */ - if internal_is_even(x) && internal_is_even(y) { - return .Invalid_Argument - } - - /* - 3. u=x, v=y, A=1, B=0, C=0, D=1 - */ - internal_copy(u, x) or_return - internal_copy(v, y) or_return - internal_one(A) or_return - internal_one(D) or_return - - for { - /* - 4. while `u` is even do: - */ - for internal_is_even(u) { - /* - 4.1 `u` = `u` / 2 - */ - internal_int_shr1(u, u) or_return - - /* - 4.2 if `A` or `B` is odd then: - */ - if internal_is_odd(A) || internal_is_odd(B) { - /* - `A` = (`A`+`y`) / 2, `B` = (`B`-`x`) / 2 - */ - internal_add(A, A, y) or_return - internal_add(B, B, x) or_return - } - /* - `A` = `A` / 2, `B` = `B` / 2 - */ - internal_int_shr1(A, A) or_return - internal_int_shr1(B, B) or_return - } - - /* - 5. while `v` is even do: - */ - for internal_is_even(v) { - /* - 5.1 `v` = `v` / 2 - */ - internal_int_shr1(v, v) or_return - - /* - 5.2 if `C` or `D` is odd then: - */ - if internal_is_odd(C) || internal_is_odd(D) { - /* - `C` = (`C`+`y`) / 2, `D` = (`D`-`x`) / 2 - */ - internal_add(C, C, y) or_return - internal_add(D, D, x) or_return - } - /* - `C` = `C` / 2, `D` = `D` / 2 - */ - internal_int_shr1(C, C) or_return - internal_int_shr1(D, D) or_return - } - - /* - 6. if `u` >= `v` then: - */ - if internal_cmp(u, v) != -1 { - /* - `u` = `u` - `v`, `A` = `A` - `C`, `B` = `B` - `D` - */ - internal_sub(u, u, v) or_return - internal_sub(A, A, C) or_return - internal_sub(B, B, D) or_return - } else { - /* v - v - u, C = C - A, D = D - B */ - internal_sub(v, v, u) or_return - internal_sub(C, C, A) or_return - internal_sub(D, D, B) or_return - } - - /* - If not zero goto step 4 - */ - if internal_is_zero(u) { - break - } - } - - /* - Now `a` = `C`, `b` = `D`, `gcd` == `g`*`v` - */ - - /* - If `v` != `1` then there is no inverse. - */ - if !internal_eq(v, 1) { - return .Invalid_Argument - } - - /* - If its too low. - */ - if internal_is_negative(C) { - internal_add(C, C, b) or_return - } - - /* - Too big. - */ - if internal_gte(C, 0) { - internal_sub(C, C, b) or_return - } - - /* - `C` is now the inverse. - */ - swap(dest, C) - - return -} - -/* - Computes the modular inverse via binary extended Euclidean algorithm, that is `dest` = 1 / `a` mod `b`. - - Based on slow invmod except this is optimized for the case where `b` is odd, - as per HAC Note 14.64 on pp. 610. -*/ -_private_inverse_modulo_odd :: proc(dest, a, b: ^Int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - x, y, u, v, B, D := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{} - defer internal_destroy(x, y, u, v, B, D) - - sign: Sign - - /* - 2. [modified] `b` must be odd. - */ - if internal_is_even(b) { return .Invalid_Argument } - - /* - Init all our temps. - */ - internal_init_multi(x, y, u, v, B, D) or_return - - /* - `x` == modulus, `y` == value to invert. - */ - internal_copy(x, b) or_return - - /* - We need `y` = `|a|`. - */ - internal_mod(y, a, b) or_return - - /* - If one of `x`, `y` is zero return an error! - */ - if internal_is_zero(x) || internal_is_zero(y) { return .Invalid_Argument } - - /* - 3. `u` = `x`, `v` = `y`, `A` = 1, `B` = 0, `C` = 0, `D` = 1 - */ - internal_copy(u, x) or_return - internal_copy(v, y) or_return - - internal_one(D) or_return - - for { - /* - 4. while `u` is even do. - */ - for internal_is_even(u) { - /* - 4.1 `u` = `u` / 2 - */ - internal_int_shr1(u, u) or_return - - /* - 4.2 if `B` is odd then: - */ - if internal_is_odd(B) { - /* - `B` = (`B` - `x`) / 2 - */ - internal_sub(B, B, x) or_return - } - - /* - `B` = `B` / 2 - */ - internal_int_shr1(B, B) or_return - } - - /* - 5. while `v` is even do: - */ - for internal_is_even(v) { - /* - 5.1 `v` = `v` / 2 - */ - internal_int_shr1(v, v) or_return - - /* - 5.2 if `D` is odd then: - */ - if internal_is_odd(D) { - /* - `D` = (`D` - `x`) / 2 - */ - internal_sub(D, D, x) or_return - } - /* - `D` = `D` / 2 - */ - internal_int_shr1(D, D) or_return - } - - /* - 6. if `u` >= `v` then: - */ - if internal_cmp(u, v) != -1 { - /* - `u` = `u` - `v`, `B` = `B` - `D` - */ - internal_sub(u, u, v) or_return - internal_sub(B, B, D) or_return - } else { - /* - `v` - `v` - `u`, `D` = `D` - `B` - */ - internal_sub(v, v, u) or_return - internal_sub(D, D, B) or_return - } - - /* - If not zero goto step 4. - */ - if internal_is_zero(u) { break } - } - - /* - Now `a` = C, `b` = D, gcd == g*v - */ - - /* - if `v` != 1 then there is no inverse - */ - if internal_cmp(v, 1) != 0 { - return .Invalid_Argument - } - - /* - `b` is now the inverse. - */ - sign = a.sign - for internal_int_is_negative(D) { - internal_add(D, D, b) or_return - } - - /* - Too big. - */ - for internal_gte_abs(D, b) { - internal_sub(D, D, b) or_return - } - - swap(dest, D) - dest.sign = sign - return nil -} - - -/* - Returns the log2 of an `Int`. - Assumes `a` not to be `nil` and to have been initialized. - Also assumes `base` is a power of two. -*/ -_private_log_power_of_two :: proc(a: ^Int, base: DIGIT) -> (log: int, err: Error) { - base := base - y: int - for y = 0; base & 1 == 0; { - y += 1 - base >>= 1 - } - log = internal_count_bits(a) - return (log - 1) / y, err -} - -/* - Copies DIGITs from `src` to `dest`. - Assumes `src` and `dest` to not be `nil` and have been initialized. -*/ -_private_copy_digits :: proc(dest, src: ^Int, digits: int, offset := int(0)) -> (err: Error) { - digits := digits - /* - If dest == src, do nothing - */ - if dest == src { - return nil - } - - digits = min(digits, len(src.digit), len(dest.digit)) - mem.copy_non_overlapping(&dest.digit[0], &src.digit[offset], size_of(DIGIT) * digits) - return nil -} - - -/* - Shift left by `digits` * _DIGIT_BITS bits. -*/ -_private_int_shl_leg :: proc(quotient: ^Int, digits: int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - if digits <= 0 { return nil } - - /* - No need to shift a zero. - */ - if #force_inline internal_is_zero(quotient) { - return nil - } - - /* - Resize `quotient` to accomodate extra digits. - */ - #force_inline internal_grow(quotient, quotient.used + digits) or_return - - /* - Increment the used by the shift amount then copy upwards. - */ - - /* - Much like `_private_int_shr_leg`, this is implemented using a sliding window, - except the window goes the other way around. - */ - #no_bounds_check for x := quotient.used; x > 0; x -= 1 { - quotient.digit[x+digits-1] = quotient.digit[x-1] - } - - quotient.used += digits - mem.zero_slice(quotient.digit[:digits]) - return nil -} - -/* - Shift right by `digits` * _DIGIT_BITS bits. -*/ -_private_int_shr_leg :: proc(quotient: ^Int, digits: int, allocator := context.allocator) -> (err: Error) { - context.allocator = allocator - - if digits <= 0 { return nil } - - /* - If digits > used simply zero and return. - */ - if digits > quotient.used { return internal_zero(quotient) } - - /* - Much like `int_shl_digit`, this is implemented using a sliding window, - except the window goes the other way around. - - b-2 | b-1 | b0 | b1 | b2 | ... | bb | ----> - /\ | ----> - \-------------------/ ----> - */ - - #no_bounds_check for x := 0; x < (quotient.used - digits); x += 1 { - quotient.digit[x] = quotient.digit[x + digits] - } - quotient.used -= digits - internal_zero_unused(quotient) - return internal_clamp(quotient) -} - -/* - ======================== End of private procedures ======================= - - =============================== Private tables =============================== - - Tables used by `internal_*` and `_*`. -*/ - -_private_int_rem_128 := [?]DIGIT{ - 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, - 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, - 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, - 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, - 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, - 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, - 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, - 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, -} -#assert(128 * size_of(DIGIT) == size_of(_private_int_rem_128)) - -_private_int_rem_105 := [?]DIGIT{ - 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, - 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, - 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, - 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, - 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, - 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, - 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, -} -#assert(105 * size_of(DIGIT) == size_of(_private_int_rem_105)) - -_PRIME_TAB_SIZE :: 256 -_private_prime_table := [_PRIME_TAB_SIZE]DIGIT{ - 0x0002, 0x0003, 0x0005, 0x0007, 0x000B, 0x000D, 0x0011, 0x0013, - 0x0017, 0x001D, 0x001F, 0x0025, 0x0029, 0x002B, 0x002F, 0x0035, - 0x003B, 0x003D, 0x0043, 0x0047, 0x0049, 0x004F, 0x0053, 0x0059, - 0x0061, 0x0065, 0x0067, 0x006B, 0x006D, 0x0071, 0x007F, 0x0083, - 0x0089, 0x008B, 0x0095, 0x0097, 0x009D, 0x00A3, 0x00A7, 0x00AD, - 0x00B3, 0x00B5, 0x00BF, 0x00C1, 0x00C5, 0x00C7, 0x00D3, 0x00DF, - 0x00E3, 0x00E5, 0x00E9, 0x00EF, 0x00F1, 0x00FB, 0x0101, 0x0107, - 0x010D, 0x010F, 0x0115, 0x0119, 0x011B, 0x0125, 0x0133, 0x0137, - - 0x0139, 0x013D, 0x014B, 0x0151, 0x015B, 0x015D, 0x0161, 0x0167, - 0x016F, 0x0175, 0x017B, 0x017F, 0x0185, 0x018D, 0x0191, 0x0199, - 0x01A3, 0x01A5, 0x01AF, 0x01B1, 0x01B7, 0x01BB, 0x01C1, 0x01C9, - 0x01CD, 0x01CF, 0x01D3, 0x01DF, 0x01E7, 0x01EB, 0x01F3, 0x01F7, - 0x01FD, 0x0209, 0x020B, 0x021D, 0x0223, 0x022D, 0x0233, 0x0239, - 0x023B, 0x0241, 0x024B, 0x0251, 0x0257, 0x0259, 0x025F, 0x0265, - 0x0269, 0x026B, 0x0277, 0x0281, 0x0283, 0x0287, 0x028D, 0x0293, - 0x0295, 0x02A1, 0x02A5, 0x02AB, 0x02B3, 0x02BD, 0x02C5, 0x02CF, - - 0x02D7, 0x02DD, 0x02E3, 0x02E7, 0x02EF, 0x02F5, 0x02F9, 0x0301, - 0x0305, 0x0313, 0x031D, 0x0329, 0x032B, 0x0335, 0x0337, 0x033B, - 0x033D, 0x0347, 0x0355, 0x0359, 0x035B, 0x035F, 0x036D, 0x0371, - 0x0373, 0x0377, 0x038B, 0x038F, 0x0397, 0x03A1, 0x03A9, 0x03AD, - 0x03B3, 0x03B9, 0x03C7, 0x03CB, 0x03D1, 0x03D7, 0x03DF, 0x03E5, - 0x03F1, 0x03F5, 0x03FB, 0x03FD, 0x0407, 0x0409, 0x040F, 0x0419, - 0x041B, 0x0425, 0x0427, 0x042D, 0x043F, 0x0443, 0x0445, 0x0449, - 0x044F, 0x0455, 0x045D, 0x0463, 0x0469, 0x047F, 0x0481, 0x048B, - - 0x0493, 0x049D, 0x04A3, 0x04A9, 0x04B1, 0x04BD, 0x04C1, 0x04C7, - 0x04CD, 0x04CF, 0x04D5, 0x04E1, 0x04EB, 0x04FD, 0x04FF, 0x0503, - 0x0509, 0x050B, 0x0511, 0x0515, 0x0517, 0x051B, 0x0527, 0x0529, - 0x052F, 0x0551, 0x0557, 0x055D, 0x0565, 0x0577, 0x0581, 0x058F, - 0x0593, 0x0595, 0x0599, 0x059F, 0x05A7, 0x05AB, 0x05AD, 0x05B3, - 0x05BF, 0x05C9, 0x05CB, 0x05CF, 0x05D1, 0x05D5, 0x05DB, 0x05E7, - 0x05F3, 0x05FB, 0x0607, 0x060D, 0x0611, 0x0617, 0x061F, 0x0623, - 0x062B, 0x062F, 0x063D, 0x0641, 0x0647, 0x0649, 0x064D, 0x0653, -} -#assert(_PRIME_TAB_SIZE * size_of(DIGIT) == size_of(_private_prime_table)) - -when MATH_BIG_FORCE_64_BIT || (!MATH_BIG_FORCE_32_BIT && size_of(rawptr) == 8) { - _factorial_table := [35]_WORD{ -/* f(00): */ 1, -/* f(01): */ 1, -/* f(02): */ 2, -/* f(03): */ 6, -/* f(04): */ 24, -/* f(05): */ 120, -/* f(06): */ 720, -/* f(07): */ 5_040, -/* f(08): */ 40_320, -/* f(09): */ 362_880, -/* f(10): */ 3_628_800, -/* f(11): */ 39_916_800, -/* f(12): */ 479_001_600, -/* f(13): */ 6_227_020_800, -/* f(14): */ 87_178_291_200, -/* f(15): */ 1_307_674_368_000, -/* f(16): */ 20_922_789_888_000, -/* f(17): */ 355_687_428_096_000, -/* f(18): */ 6_402_373_705_728_000, -/* f(19): */ 121_645_100_408_832_000, -/* f(20): */ 2_432_902_008_176_640_000, -/* f(21): */ 51_090_942_171_709_440_000, -/* f(22): */ 1_124_000_727_777_607_680_000, -/* f(23): */ 25_852_016_738_884_976_640_000, -/* f(24): */ 620_448_401_733_239_439_360_000, -/* f(25): */ 15_511_210_043_330_985_984_000_000, -/* f(26): */ 403_291_461_126_605_635_584_000_000, -/* f(27): */ 10_888_869_450_418_352_160_768_000_000, -/* f(28): */ 304_888_344_611_713_860_501_504_000_000, -/* f(29): */ 8_841_761_993_739_701_954_543_616_000_000, -/* f(30): */ 265_252_859_812_191_058_636_308_480_000_000, -/* f(31): */ 8_222_838_654_177_922_817_725_562_880_000_000, -/* f(32): */ 263_130_836_933_693_530_167_218_012_160_000_000, -/* f(33): */ 8_683_317_618_811_886_495_518_194_401_280_000_000, -/* f(34): */ 295_232_799_039_604_140_847_618_609_643_520_000_000, - } -} else { - _factorial_table := [21]_WORD{ -/* f(00): */ 1, -/* f(01): */ 1, -/* f(02): */ 2, -/* f(03): */ 6, -/* f(04): */ 24, -/* f(05): */ 120, -/* f(06): */ 720, -/* f(07): */ 5_040, -/* f(08): */ 40_320, -/* f(09): */ 362_880, -/* f(10): */ 3_628_800, -/* f(11): */ 39_916_800, -/* f(12): */ 479_001_600, -/* f(13): */ 6_227_020_800, -/* f(14): */ 87_178_291_200, -/* f(15): */ 1_307_674_368_000, -/* f(16): */ 20_922_789_888_000, -/* f(17): */ 355_687_428_096_000, -/* f(18): */ 6_402_373_705_728_000, -/* f(19): */ 121_645_100_408_832_000, -/* f(20): */ 2_432_902_008_176_640_000, - } -} - -/* - ========================= End of private tables ======================== +/* + Copyright 2021 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + An arbitrary precision mathematics implementation in Odin. + For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3. + The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks. + + ============================= Private procedures ============================= + + Private procedures used by the above low-level routines follow. + + Don't call these yourself unless you really know what you're doing. + They include implementations that are optimimal for certain ranges of input only. + + These aren't exported for the same reasons. +*/ + + +package math_big + +import "base:intrinsics" +import "core:mem" + +/* + Multiplies |a| * |b| and only computes upto digs digits of result. + HAC pp. 595, Algorithm 14.12 Modified so you can control how + many digits of output are created. +*/ +_private_int_mul :: proc(dest, a, b: ^Int, digits: int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + /* + Can we use the fast multiplier? + */ + if digits < _WARRAY && min(a.used, b.used) < _MAX_COMBA { + return #force_inline _private_int_mul_comba(dest, a, b, digits) + } + + /* + Set up temporary output `Int`, which we'll swap for `dest` when done. + */ + + t := &Int{} + + internal_grow(t, max(digits, _DEFAULT_DIGIT_COUNT)) or_return + t.used = digits + + /* + Compute the digits of the product directly. + */ + pa := a.used + for ix := 0; ix < pa; ix += 1 { + /* + Limit ourselves to `digits` DIGITs of output. + */ + pb := min(b.used, digits - ix) + carry := _WORD(0) + iy := 0 + + /* + Compute the column of the output and propagate the carry. + */ + #no_bounds_check for iy = 0; iy < pb; iy += 1 { + /* + Compute the column as a _WORD. + */ + column := _WORD(t.digit[ix + iy]) + _WORD(a.digit[ix]) * _WORD(b.digit[iy]) + carry + + /* + The new column is the lower part of the result. + */ + t.digit[ix + iy] = DIGIT(column & _WORD(_MASK)) + + /* + Get the carry word from the result. + */ + carry = column >> _DIGIT_BITS + } + /* + Set carry if it is placed below digits + */ + if ix + iy < digits { + t.digit[ix + pb] = DIGIT(carry) + } + } + + internal_swap(dest, t) + internal_destroy(t) + return internal_clamp(dest) +} + + +/* + Multiplication using the Toom-Cook 3-way algorithm. + + Much more complicated than Karatsuba but has a lower asymptotic running time of O(N**1.464). + This algorithm is only particularly useful on VERY large inputs. + (We're talking 1000s of digits here...). + + This file contains code from J. Arndt's book "Matters Computational" + and the accompanying FXT-library with permission of the author. + + Setup from: + Chung, Jaewook, and M. Anwar Hasan. "Asymmetric squaring formulae." + 18th IEEE Symposium on Computer Arithmetic (ARITH'07). IEEE, 2007. + + The interpolation from above needed one temporary variable more than the interpolation here: + + Bodrato, Marco, and Alberto Zanoni. "What about Toom-Cook matrices optimality." + Centro Vito Volterra Universita di Roma Tor Vergata (2006) +*/ +_private_int_mul_toom :: proc(dest, a, b: ^Int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + S1, S2, T1, a0, a1, a2, b0, b1, b2 := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{} + defer internal_destroy(S1, S2, T1, a0, a1, a2, b0, b1, b2) + + /* + Init temps. + */ + internal_init_multi(S1, S2, T1) or_return + + /* + B + */ + B := min(a.used, b.used) / 3 + + /* + a = a2 * x^2 + a1 * x + a0; + */ + internal_grow(a0, B) or_return + internal_grow(a1, B) or_return + internal_grow(a2, a.used - 2 * B) or_return + + a0.used, a1.used = B, B + a2.used = a.used - 2 * B + + internal_copy_digits(a0, a, a0.used) or_return + internal_copy_digits(a1, a, a1.used, B) or_return + internal_copy_digits(a2, a, a2.used, 2 * B) or_return + + internal_clamp(a0) + internal_clamp(a1) + internal_clamp(a2) + + /* + b = b2 * x^2 + b1 * x + b0; + */ + internal_grow(b0, B) or_return + internal_grow(b1, B) or_return + internal_grow(b2, b.used - 2 * B) or_return + + b0.used, b1.used = B, B + b2.used = b.used - 2 * B + + internal_copy_digits(b0, b, b0.used) or_return + internal_copy_digits(b1, b, b1.used, B) or_return + internal_copy_digits(b2, b, b2.used, 2 * B) or_return + + internal_clamp(b0) + internal_clamp(b1) + internal_clamp(b2) + + + /* + \\ S1 = (a2+a1+a0) * (b2+b1+b0); + */ + internal_add(T1, a2, a1) or_return /* T1 = a2 + a1; */ + internal_add(S2, T1, a0) or_return /* S2 = T1 + a0; */ + internal_add(dest, b2, b1) or_return /* dest = b2 + b1; */ + internal_add(S1, dest, b0) or_return /* S1 = c + b0; */ + internal_mul(S1, S1, S2) or_return /* S1 = S1 * S2; */ + + /* + \\S2 = (4*a2+2*a1+a0) * (4*b2+2*b1+b0); + */ + internal_add(T1, T1, a2) or_return /* T1 = T1 + a2; */ + internal_int_shl1(T1, T1) or_return /* T1 = T1 << 1; */ + internal_add(T1, T1, a0) or_return /* T1 = T1 + a0; */ + internal_add(dest, dest, b2) or_return /* c = c + b2; */ + internal_int_shl1(dest, dest) or_return /* c = c << 1; */ + internal_add(dest, dest, b0) or_return /* c = c + b0; */ + internal_mul(S2, T1, dest) or_return /* S2 = T1 * c; */ + + /* + \\S3 = (a2-a1+a0) * (b2-b1+b0); + */ + internal_sub(a1, a2, a1) or_return /* a1 = a2 - a1; */ + internal_add(a1, a1, a0) or_return /* a1 = a1 + a0; */ + internal_sub(b1, b2, b1) or_return /* b1 = b2 - b1; */ + internal_add(b1, b1, b0) or_return /* b1 = b1 + b0; */ + internal_mul(a1, a1, b1) or_return /* a1 = a1 * b1; */ + internal_mul(b1, a2, b2) or_return /* b1 = a2 * b2; */ + + /* + \\S2 = (S2 - S3) / 3; + */ + internal_sub(S2, S2, a1) or_return /* S2 = S2 - a1; */ + _private_int_div_3(S2, S2) or_return /* S2 = S2 / 3; \\ this is an exact division */ + internal_sub(a1, S1, a1) or_return /* a1 = S1 - a1; */ + internal_int_shr1(a1, a1) or_return /* a1 = a1 >> 1; */ + internal_mul(a0, a0, b0) or_return /* a0 = a0 * b0; */ + internal_sub(S1, S1, a0) or_return /* S1 = S1 - a0; */ + internal_sub(S2, S2, S1) or_return /* S2 = S2 - S1; */ + internal_int_shr1(S2, S2) or_return /* S2 = S2 >> 1; */ + internal_sub(S1, S1, a1) or_return /* S1 = S1 - a1; */ + internal_sub(S1, S1, b1) or_return /* S1 = S1 - b1; */ + internal_int_shl1(T1, b1) or_return /* T1 = b1 << 1; */ + internal_sub(S2, S2, T1) or_return /* S2 = S2 - T1; */ + internal_sub(a1, a1, S2) or_return /* a1 = a1 - S2; */ + + /* + P = b1*x^4+ S2*x^3+ S1*x^2+ a1*x + a0; + */ + _private_int_shl_leg(b1, 4 * B) or_return + _private_int_shl_leg(S2, 3 * B) or_return + internal_add(b1, b1, S2) or_return + _private_int_shl_leg(S1, 2 * B) or_return + internal_add(b1, b1, S1) or_return + _private_int_shl_leg(a1, 1 * B) or_return + internal_add(b1, b1, a1) or_return + internal_add(dest, b1, a0) or_return + + /* + a * b - P + */ + return nil +} + +/* + product = |a| * |b| using Karatsuba Multiplication using three half size multiplications. + + Let `B` represent the radix [e.g. 2**_DIGIT_BITS] and let `n` represent + half of the number of digits in the min(a,b) + + `a` = `a1` * `B`**`n` + `a0` + `b` = `b`1 * `B`**`n` + `b0` + + Then, a * b => 1b1 * B**2n + ((a1 + a0)(b1 + b0) - (a0b0 + a1b1)) * B + a0b0 + + Note that a1b1 and a0b0 are used twice and only need to be computed once. + So in total three half size (half # of digit) multiplications are performed, + a0b0, a1b1 and (a1+b1)(a0+b0) + + Note that a multiplication of half the digits requires 1/4th the number of + single precision multiplications, so in total after one call 25% of the + single precision multiplications are saved. + + Note also that the call to `internal_mul` can end up back in this function + if the a0, a1, b0, or b1 are above the threshold. + + This is known as divide-and-conquer and leads to the famous O(N**lg(3)) or O(N**1.584) + work which is asymptopically lower than the standard O(N**2) that the + baseline/comba methods use. Generally though, the overhead of this method doesn't pay off + until a certain size is reached, of around 80 used DIGITs. +*/ +_private_int_mul_karatsuba :: proc(dest, a, b: ^Int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + x0, x1, y0, y1, t1, x0y0, x1y1 := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{} + defer internal_destroy(x0, x1, y0, y1, t1, x0y0, x1y1) + + /* + min # of digits, divided by two. + */ + B := min(a.used, b.used) >> 1 + + /* + Init all the temps. + */ + internal_grow(x0, B) or_return + internal_grow(x1, a.used - B) or_return + internal_grow(y0, B) or_return + internal_grow(y1, b.used - B) or_return + internal_grow(t1, B * 2) or_return + internal_grow(x0y0, B * 2) or_return + internal_grow(x1y1, B * 2) or_return + + /* + Now shift the digits. + */ + x0.used, y0.used = B, B + x1.used = a.used - B + y1.used = b.used - B + + /* + We copy the digits directly instead of using higher level functions + since we also need to shift the digits. + */ + internal_copy_digits(x0, a, x0.used) + internal_copy_digits(y0, b, y0.used) + internal_copy_digits(x1, a, x1.used, B) + internal_copy_digits(y1, b, y1.used, B) + + /* + Only need to clamp the lower words since by definition the + upper words x1/y1 must have a known number of digits. + */ + clamp(x0) + clamp(y0) + + /* + Now calc the products x0y0 and x1y1, + after this x0 is no longer required, free temp [x0==t2]! + */ + internal_mul(x0y0, x0, y0) or_return /* x0y0 = x0*y0 */ + internal_mul(x1y1, x1, y1) or_return /* x1y1 = x1*y1 */ + internal_add(t1, x1, x0) or_return /* now calc x1+x0 and */ + internal_add(x0, y1, y0) or_return /* t2 = y1 + y0 */ + internal_mul(t1, t1, x0) or_return /* t1 = (x1 + x0) * (y1 + y0) */ + + /* + Add x0y0. + */ + internal_add(x0, x0y0, x1y1) or_return /* t2 = x0y0 + x1y1 */ + internal_sub(t1, t1, x0) or_return /* t1 = (x1+x0)*(y1+y0) - (x1y1 + x0y0) */ + + /* + shift by B. + */ + _private_int_shl_leg(t1, B) or_return /* t1 = (x0y0 + x1y1 - (x1-x0)*(y1-y0))< (err: Error) { + context.allocator = allocator + + /* + Set up array. + */ + W: [_WARRAY]DIGIT = --- + + /* + Grow the destination as required. + */ + internal_grow(dest, digits) or_return + + /* + Number of output digits to produce. + */ + pa := min(digits, a.used + b.used) + + /* + Clear the carry + */ + _W := _WORD(0) + + ix: int + for ix = 0; ix < pa; ix += 1 { + tx, ty, iy, iz: int + + /* + Get offsets into the two bignums. + */ + ty = min(b.used - 1, ix) + tx = ix - ty + + /* + This is the number of times the loop will iterate, essentially. + while (tx++ < a->used && ty-- >= 0) { ... } + */ + + iy = min(a.used - tx, ty + 1) + + /* + Execute loop. + */ + #no_bounds_check for iz = 0; iz < iy; iz += 1 { + _W += _WORD(a.digit[tx + iz]) * _WORD(b.digit[ty - iz]) + } + + /* + Store term. + */ + W[ix] = DIGIT(_W) & _MASK + + /* + Make next carry. + */ + _W = _W >> _WORD(_DIGIT_BITS) + } + + /* + Setup dest. + */ + old_used := dest.used + dest.used = pa + + /* + Now extract the previous digit [below the carry]. + */ + copy_slice(dest.digit[0:], W[:pa]) + + /* + Clear unused digits [that existed in the old copy of dest]. + */ + internal_zero_unused(dest, old_used) + + /* + Adjust dest.used based on leading zeroes. + */ + + return internal_clamp(dest) +} + +/* + Multiplies |a| * |b| and does not compute the lower digs digits + [meant to get the higher part of the product] +*/ +_private_int_mul_high :: proc(dest, a, b: ^Int, digits: int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + /* + Can we use the fast multiplier? + */ + if a.used + b.used + 1 < _WARRAY && min(a.used, b.used) < _MAX_COMBA { + return _private_int_mul_high_comba(dest, a, b, digits) + } + + internal_grow(dest, a.used + b.used + 1) or_return + dest.used = a.used + b.used + 1 + + pa := a.used + pb := b.used + for ix := 0; ix < pa; ix += 1 { + carry := DIGIT(0) + + for iy := digits - ix; iy < pb; iy += 1 { + /* + Calculate the double precision result. + */ + r := _WORD(dest.digit[ix + iy]) + _WORD(a.digit[ix]) * _WORD(b.digit[iy]) + _WORD(carry) + + /* + Get the lower part. + */ + dest.digit[ix + iy] = DIGIT(r & _WORD(_MASK)) + + /* + Carry the carry. + */ + carry = DIGIT(r >> _WORD(_DIGIT_BITS)) + } + dest.digit[ix + pb] = carry + } + return internal_clamp(dest) +} + +/* + This is a modified version of `_private_int_mul_comba` that only produces output digits *above* `digits`. + See the comments for `_private_int_mul_comba` to see how it works. + + This is used in the Barrett reduction since for one of the multiplications + only the higher digits were needed. This essentially halves the work. + + Based on Algorithm 14.12 on pp.595 of HAC. +*/ +_private_int_mul_high_comba :: proc(dest, a, b: ^Int, digits: int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + W: [_WARRAY]DIGIT = --- + _W: _WORD = 0 + + /* + Number of output digits to produce. Grow the destination as required. + */ + pa := a.used + b.used + internal_grow(dest, pa) or_return + + ix: int + for ix = digits; ix < pa; ix += 1 { + /* + Get offsets into the two bignums. + */ + ty := min(b.used - 1, ix) + tx := ix - ty + + /* + This is the number of times the loop will iterrate, essentially it's + while (tx++ < a->used && ty-- >= 0) { ... } + */ + iy := min(a.used - tx, ty + 1) + + /* + Execute loop. + */ + for iz := 0; iz < iy; iz += 1 { + _W += _WORD(a.digit[tx + iz]) * _WORD(b.digit[ty - iz]) + } + + /* + Store term. + */ + W[ix] = DIGIT(_W) & DIGIT(_MASK) + + /* + Make next carry. + */ + _W = _W >> _WORD(_DIGIT_BITS) + } + + /* + Setup dest + */ + old_used := dest.used + dest.used = pa + + for ix = digits; ix < pa; ix += 1 { + /* + Now extract the previous digit [below the carry]. + */ + dest.digit[ix] = W[ix] + } + + /* + Zero remainder. + */ + internal_zero_unused(dest, old_used) + + /* + Adjust dest.used based on leading zeroes. + */ + return internal_clamp(dest) +} + +/* + Single-digit multiplication with the smaller number as the single-digit. +*/ +_private_int_mul_balance :: proc(dest, a, b: ^Int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + a, b := a, b + + a0, tmp, r := &Int{}, &Int{}, &Int{} + defer internal_destroy(a0, tmp, r) + + b_size := min(a.used, b.used) + n_blocks := max(a.used, b.used) / b_size + + internal_grow(a0, b_size + 2) or_return + internal_init_multi(tmp, r) or_return + + /* + Make sure that `a` is the larger one. + */ + if a.used < b.used { + a, b = b, a + } + assert(a.used >= b.used) + + i, j := 0, 0 + for ; i < n_blocks; i += 1 { + /* + Cut a slice off of `a`. + */ + + a0.used = b_size + internal_copy_digits(a0, a, a0.used, j) + j += a0.used + internal_clamp(a0) + + /* + Multiply with `b`. + */ + internal_mul(tmp, a0, b) or_return + + /* + Shift `tmp` to the correct position. + */ + _private_int_shl_leg(tmp, b_size * i) or_return + + /* + Add to output. No carry needed. + */ + internal_add(r, r, tmp) or_return + } + + /* + The left-overs; there are always left-overs. + */ + if j < a.used { + a0.used = a.used - j + internal_copy_digits(a0, a, a0.used, j) + j += a0.used + internal_clamp(a0) + + internal_mul(tmp, a0, b) or_return + _private_int_shl_leg(tmp, b_size * i) or_return + internal_add(r, r, tmp) or_return + } + + internal_swap(dest, r) + return +} + +/* + Low level squaring, b = a*a, HAC pp.596-597, Algorithm 14.16 + Assumes `dest` and `src` to not be `nil`, and `src` to have been initialized. +*/ +_private_int_sqr :: proc(dest, src: ^Int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + pa := src.used + + t := &Int{}; ix, iy: int + /* + Grow `t` to maximum needed size, or `_DEFAULT_DIGIT_COUNT`, whichever is bigger. + */ + internal_grow(t, max((2 * pa) + 1, _DEFAULT_DIGIT_COUNT)) or_return + t.used = (2 * pa) + 1 + + #no_bounds_check for ix = 0; ix < pa; ix += 1 { + carry := DIGIT(0) + /* + First calculate the digit at 2*ix; calculate double precision result. + */ + r := _WORD(t.digit[ix+ix]) + (_WORD(src.digit[ix]) * _WORD(src.digit[ix])) + + /* + Store lower part in result. + */ + t.digit[ix+ix] = DIGIT(r & _WORD(_MASK)) + /* + Get the carry. + */ + carry = DIGIT(r >> _DIGIT_BITS) + + #no_bounds_check for iy = ix + 1; iy < pa; iy += 1 { + /* + First calculate the product. + */ + r = _WORD(src.digit[ix]) * _WORD(src.digit[iy]) + + /* Now calculate the double precision result. Nóte we use + * addition instead of *2 since it's easier to optimize + */ + r = _WORD(t.digit[ix+iy]) + r + r + _WORD(carry) + + /* + Store lower part. + */ + t.digit[ix+iy] = DIGIT(r & _WORD(_MASK)) + + /* + Get carry. + */ + carry = DIGIT(r >> _DIGIT_BITS) + } + /* + Propagate upwards. + */ + #no_bounds_check for carry != 0 { + r = _WORD(t.digit[ix+iy]) + _WORD(carry) + t.digit[ix+iy] = DIGIT(r & _WORD(_MASK)) + carry = DIGIT(r >> _WORD(_DIGIT_BITS)) + iy += 1 + } + } + + err = internal_clamp(t) + internal_swap(dest, t) + internal_destroy(t) + return err +} + +/* + The jist of squaring... + You do like mult except the offset of the tmpx [one that starts closer to zero] can't equal the offset of tmpy. + So basically you set up iy like before then you min it with (ty-tx) so that it never happens. + You double all those you add in the inner loop. After that loop you do the squares and add them in. + + Assumes `dest` and `src` not to be `nil` and `src` to have been initialized. +*/ +_private_int_sqr_comba :: proc(dest, src: ^Int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + W: [_WARRAY]DIGIT = --- + + /* + Grow the destination as required. + */ + pa := uint(src.used) + uint(src.used) + internal_grow(dest, int(pa)) or_return + + /* + Number of output digits to produce. + */ + W1 := _WORD(0) + _W : _WORD = --- + ix := uint(0) + + #no_bounds_check for ; ix < pa; ix += 1 { + /* + Clear counter. + */ + _W = {} + + /* + Get offsets into the two bignums. + */ + ty := min(uint(src.used) - 1, ix) + tx := ix - ty + + /* + This is the number of times the loop will iterate, + essentially while (tx++ < a->used && ty-- >= 0) { ... } + */ + iy := min(uint(src.used) - tx, ty + 1) + + /* + Now for squaring, tx can never equal ty. + We halve the distance since they approach at a rate of 2x, + and we have to round because odd cases need to be executed. + */ + iy = min(iy, ((ty - tx) + 1) >> 1 ) + + /* + Execute loop. + */ + #no_bounds_check for iz := uint(0); iz < iy; iz += 1 { + _W += _WORD(src.digit[tx + iz]) * _WORD(src.digit[ty - iz]) + } + + /* + Double the inner product and add carry. + */ + _W = _W + _W + W1 + + /* + Even columns have the square term in them. + */ + if ix & 1 == 0 { + _W += _WORD(src.digit[ix >> 1]) * _WORD(src.digit[ix >> 1]) + } + + /* + Store it. + */ + W[ix] = DIGIT(_W & _WORD(_MASK)) + + /* + Make next carry. + */ + W1 = _W >> _DIGIT_BITS + } + + /* + Setup dest. + */ + old_used := dest.used + dest.used = src.used + src.used + + #no_bounds_check for ix = 0; ix < pa; ix += 1 { + dest.digit[ix] = W[ix] & _MASK + } + + /* + Clear unused digits [that existed in the old copy of dest]. + */ + internal_zero_unused(dest, old_used) + + return internal_clamp(dest) +} + +/* + 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. +*/ +_private_int_sqr_karatsuba :: proc(dest, src: ^Int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + x0, x1, t1, t2, x0x0, x1x1 := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{} + defer internal_destroy(x0, x1, t1, t2, x0x0, x1x1) + + /* + Min # of digits, divided by two. + */ + B := src.used >> 1 + + /* + Init temps. + */ + internal_grow(x0, B) or_return + internal_grow(x1, src.used - B) or_return + internal_grow(t1, src.used * 2) or_return + internal_grow(t2, src.used * 2) or_return + internal_grow(x0x0, B * 2 ) or_return + internal_grow(x1x1, (src.used - B) * 2) or_return + + /* + Now shift the digits. + */ + x0.used = B + x1.used = src.used - B + + #force_inline internal_copy_digits(x0, src, x0.used) + #force_inline mem.copy_non_overlapping(&x1.digit[0], &src.digit[B], size_of(DIGIT) * x1.used) + #force_inline internal_clamp(x0) + + /* + Now calc the products x0*x0 and x1*x1. + */ + internal_sqr(x0x0, x0) or_return + internal_sqr(x1x1, x1) or_return + + /* + Now calc (x1+x0)^2 + */ + internal_add(t1, x0, x1) or_return + internal_sqr(t1, t1) or_return + + /* + Add x0y0 + */ + internal_add(t2, x0x0, x1x1) or_return + internal_sub(t1, t1, t2) or_return + + /* + Shift by B. + */ + _private_int_shl_leg(t1, B) or_return + _private_int_shl_leg(x1x1, B * 2) or_return + internal_add(t1, t1, x0x0) or_return + internal_add(dest, t1, x1x1) or_return + + return #force_inline internal_clamp(dest) +} + +/* + Squaring using Toom-Cook 3-way algorithm. + + Setup and interpolation from algorithm SQR_3 in Chung, Jaewook, and M. Anwar Hasan. "Asymmetric squaring formulae." + 18th IEEE Symposium on Computer Arithmetic (ARITH'07). IEEE, 2007. +*/ +_private_int_sqr_toom :: proc(dest, src: ^Int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + S0, a0, a1, a2 := &Int{}, &Int{}, &Int{}, &Int{} + defer internal_destroy(S0, a0, a1, a2) + + /* + Init temps. + */ + internal_zero(S0) or_return + + /* + B + */ + B := src.used / 3 + + /* + a = a2 * x^2 + a1 * x + a0; + */ + internal_grow(a0, B) or_return + internal_grow(a1, B) or_return + internal_grow(a2, src.used - (2 * B)) or_return + + a0.used = B + a1.used = B + a2.used = src.used - 2 * B + + #force_inline mem.copy_non_overlapping(&a0.digit[0], &src.digit[ 0], size_of(DIGIT) * a0.used) + #force_inline mem.copy_non_overlapping(&a1.digit[0], &src.digit[ B], size_of(DIGIT) * a1.used) + #force_inline mem.copy_non_overlapping(&a2.digit[0], &src.digit[2 * B], size_of(DIGIT) * a2.used) + + internal_clamp(a0) + internal_clamp(a1) + internal_clamp(a2) + + /** S0 = a0^2; */ + internal_sqr(S0, a0) or_return + + /** \\S1 = (a2 + a1 + a0)^2 */ + /** \\S2 = (a2 - a1 + a0)^2 */ + /** \\S1 = a0 + a2; */ + /** a0 = a0 + a2; */ + internal_add(a0, a0, a2) or_return + /** \\S2 = S1 - a1; */ + /** b = a0 - a1; */ + internal_sub(dest, a0, a1) or_return + /** \\S1 = S1 + a1; */ + /** a0 = a0 + a1; */ + internal_add(a0, a0, a1) or_return + /** \\S1 = S1^2; */ + /** a0 = a0^2; */ + internal_sqr(a0, a0) or_return + /** \\S2 = S2^2; */ + /** b = b^2; */ + internal_sqr(dest, dest) or_return + /** \\ S3 = 2 * a1 * a2 */ + /** \\S3 = a1 * a2; */ + /** a1 = a1 * a2; */ + internal_mul(a1, a1, a2) or_return + /** \\S3 = S3 << 1; */ + /** a1 = a1 << 1; */ + internal_shl(a1, a1, 1) or_return + /** \\S4 = a2^2; */ + /** a2 = a2^2; */ + internal_sqr(a2, a2) or_return + /** \\ tmp = (S1 + S2)/2 */ + /** \\tmp = S1 + S2; */ + /** b = a0 + b; */ + internal_add(dest, a0, dest) or_return + /** \\tmp = tmp >> 1; */ + /** b = b >> 1; */ + internal_shr(dest, dest, 1) or_return + /** \\ S1 = S1 - tmp - S3 */ + /** \\S1 = S1 - tmp; */ + /** a0 = a0 - b; */ + internal_sub(a0, a0, dest) or_return + /** \\S1 = S1 - S3; */ + /** a0 = a0 - a1; */ + internal_sub(a0, a0, a1) or_return + /** \\S2 = tmp - S4 -S0 */ + /** \\S2 = tmp - S4; */ + /** b = b - a2; */ + internal_sub(dest, dest, a2) or_return + /** \\S2 = S2 - S0; */ + /** b = b - S0; */ + internal_sub(dest, dest, S0) or_return + /** \\P = S4*x^4 + S3*x^3 + S2*x^2 + S1*x + S0; */ + /** P = a2*x^4 + a1*x^3 + b*x^2 + a0*x + S0; */ + _private_int_shl_leg( a2, 4 * B) or_return + _private_int_shl_leg( a1, 3 * B) or_return + _private_int_shl_leg(dest, 2 * B) or_return + _private_int_shl_leg( a0, 1 * B) or_return + + internal_add(a2, a2, a1) or_return + internal_add(dest, dest, a2) or_return + internal_add(dest, dest, a0) or_return + internal_add(dest, dest, S0) or_return + /** a^2 - P */ + + return #force_inline internal_clamp(dest) +} + +/* + Divide by three (based on routine from MPI and the GMP manual). +*/ +_private_int_div_3 :: proc(quotient, numerator: ^Int, allocator := context.allocator) -> (remainder: DIGIT, err: Error) { + context.allocator = allocator + + /* + b = 2^_DIGIT_BITS / 3 + */ + b := _WORD(1) << _WORD(_DIGIT_BITS) / _WORD(3) + + q := &Int{} + internal_grow(q, numerator.used) or_return + q.used = numerator.used + q.sign = numerator.sign + + w, t: _WORD + #no_bounds_check for ix := numerator.used - 1; ix >= 0; ix -= 1 { + w = (w << _WORD(_DIGIT_BITS)) | _WORD(numerator.digit[ix]) + if w >= 3 { + /* + Multiply w by [1/3]. + */ + t = (w * b) >> _WORD(_DIGIT_BITS) + + /* + Now subtract 3 * [w/3] from w, to get the remainder. + */ + w -= t+t+t + + /* + Fixup the remainder as required since the optimization is not exact. + */ + for w >= 3 { + t += 1 + w -= 3 + } + } else { + t = 0 + } + q.digit[ix] = DIGIT(t) + } + remainder = DIGIT(w) + + /* + [optional] store the quotient. + */ + if quotient != nil { + err = clamp(q) + internal_swap(q, quotient) + } + internal_destroy(q) + return remainder, nil +} + +/* + Signed Integer Division + + c*b + d == a [i.e. a/b, c=quotient, d=remainder], HAC pp.598 Algorithm 14.20 + + Note that the description in HAC is horribly incomplete. + For example, it doesn't consider the case where digits are removed from 'x' in + the inner loop. + + It also doesn't consider the case that y has fewer than three digits, etc. + The overall algorithm is as described as 14.20 from HAC but fixed to treat these cases. +*/ +_private_int_div_school :: proc(quotient, remainder, numerator, denominator: ^Int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + error_if_immutable(quotient, remainder) or_return + + q, x, y, t1, t2 := &Int{}, &Int{}, &Int{}, &Int{}, &Int{} + defer internal_destroy(q, x, y, t1, t2) + + internal_grow(q, numerator.used + 2) or_return + q.used = numerator.used + 2 + + internal_init_multi(t1, t2) or_return + internal_copy(x, numerator) or_return + internal_copy(y, denominator) or_return + + /* + Fix the sign. + */ + neg := numerator.sign != denominator.sign + x.sign = .Zero_or_Positive + y.sign = .Zero_or_Positive + + /* + Normalize both x and y, ensure that y >= b/2, [b == 2**MP_DIGIT_BIT] + */ + norm := internal_count_bits(y) % _DIGIT_BITS + + if norm < _DIGIT_BITS - 1 { + norm = (_DIGIT_BITS - 1) - norm + internal_shl(x, x, norm) or_return + internal_shl(y, y, norm) or_return + } else { + norm = 0 + } + + /* + Note: HAC does 0 based, so if used==5 then it's 0,1,2,3,4, i.e. use 4 + */ + n := x.used - 1 + t := y.used - 1 + + /* + while (x >= y*b**n-t) do { q[n-t] += 1; x -= y*b**{n-t} } + y = y*b**{n-t} + */ + + _private_int_shl_leg(y, n - t) or_return + + gte := internal_gte(x, y) + for gte { + q.digit[n - t] += 1 + internal_sub(x, x, y) or_return + gte = internal_gte(x, y) + } + + /* + Reset y by shifting it back down. + */ + _private_int_shr_leg(y, n - t) + + /* + Step 3. for i from n down to (t + 1). + */ + #no_bounds_check for i := n; i >= (t + 1); i -= 1 { + if i > x.used { continue } + + /* + step 3.1 if xi == yt then set q{i-t-1} to b-1, otherwise set q{i-t-1} to (xi*b + x{i-1})/yt + */ + if x.digit[i] == y.digit[t] { + q.digit[(i - t) - 1] = 1 << (_DIGIT_BITS - 1) + } else { + + tmp := _WORD(x.digit[i]) << _DIGIT_BITS + tmp |= _WORD(x.digit[i - 1]) + tmp /= _WORD(y.digit[t]) + if tmp > _WORD(_MASK) { + tmp = _WORD(_MASK) + } + q.digit[(i - t) - 1] = DIGIT(tmp & _WORD(_MASK)) + } + + /* while (q{i-t-1} * (yt * b + y{t-1})) > + xi * b**2 + xi-1 * b + xi-2 + + do q{i-t-1} -= 1; + */ + + iter := 0 + + q.digit[(i - t) - 1] = (q.digit[(i - t) - 1] + 1) & _MASK + #no_bounds_check for { + q.digit[(i - t) - 1] = (q.digit[(i - t) - 1] - 1) & _MASK + + /* + Find left hand. + */ + internal_zero(t1) + t1.digit[0] = ((t - 1) < 0) ? 0 : y.digit[t - 1] + t1.digit[1] = y.digit[t] + t1.used = 2 + internal_mul(t1, t1, q.digit[(i - t) - 1]) or_return + + /* + Find right hand. + */ + t2.digit[0] = ((i - 2) < 0) ? 0 : x.digit[i - 2] + t2.digit[1] = x.digit[i - 1] /* i >= 1 always holds */ + t2.digit[2] = x.digit[i] + t2.used = 3 + + if internal_lte(t1, t2) { + break + } + iter += 1; if iter > 100 { + return .Max_Iterations_Reached + } + } + + /* + Step 3.3 x = x - q{i-t-1} * y * b**{i-t-1} + */ + int_mul_digit(t1, y, q.digit[(i - t) - 1]) or_return + _private_int_shl_leg(t1, (i - t) - 1) or_return + internal_sub(x, x, t1) or_return + + /* + if x < 0 then { x = x + y*b**{i-t-1}; q{i-t-1} -= 1; } + */ + if x.sign == .Negative { + internal_copy(t1, y) or_return + _private_int_shl_leg(t1, (i - t) - 1) or_return + internal_add(x, x, t1) or_return + + q.digit[(i - t) - 1] = (q.digit[(i - t) - 1] - 1) & _MASK + } + } + + /* + Now q is the quotient and x is the remainder, [which we have to normalize] + Get sign before writing to c. + */ + z, _ := is_zero(x) + x.sign = .Zero_or_Positive if z else numerator.sign + + if quotient != nil { + internal_clamp(q) + internal_swap(q, quotient) + quotient.sign = .Negative if neg else .Zero_or_Positive + } + + if remainder != nil { + internal_shr(x, x, norm) or_return + internal_swap(x, remainder) + } + + return nil +} + +/* + Direct implementation of algorithms 1.8 "RecursiveDivRem" and 1.9 "UnbalancedDivision" from: + + Brent, Richard P., and Paul Zimmermann. "Modern computer arithmetic" + Vol. 18. Cambridge University Press, 2010 + Available online at https://arxiv.org/pdf/1004.4710 + + pages 19ff. in the above online document. +*/ +_private_div_recursion :: proc(quotient, remainder, a, b: ^Int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + A1, A2, B1, B0, Q1, Q0, R1, R0, t := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{} + defer internal_destroy(A1, A2, B1, B0, Q1, Q0, R1, R0, t) + + m := a.used - b.used + k := m / 2 + + if m < MUL_KARATSUBA_CUTOFF { + return _private_int_div_school(quotient, remainder, a, b) + } + + internal_init_multi(A1, A2, B1, B0, Q1, Q0, R1, R0, t) or_return + + /* + `B1` = `b` / `beta`^`k`, `B0` = `b` % `beta`^`k` + */ + internal_shrmod(B1, B0, b, k * _DIGIT_BITS) or_return + + /* + (Q1, R1) = RecursiveDivRem(A / beta^(2k), B1) + */ + internal_shrmod(A1, t, a, 2 * k * _DIGIT_BITS) or_return + _private_div_recursion(Q1, R1, A1, B1) or_return + + /* + A1 = (R1 * beta^(2k)) + (A % beta^(2k)) - (Q1 * B0 * beta^k) + */ + _private_int_shl_leg(R1, 2 * k) or_return + internal_add(A1, R1, t) or_return + internal_mul(t, Q1, B0) or_return + + /* + While A1 < 0 do Q1 = Q1 - 1, A1 = A1 + (beta^k * B) + */ + if internal_lt(A1, 0) { + internal_shl(t, b, k * _DIGIT_BITS) or_return + + for { + internal_decr(Q1) or_return + internal_add(A1, A1, t) or_return + if internal_gte(A1, 0) { break } + } + } + + /* + (Q0, R0) = RecursiveDivRem(A1 / beta^(k), B1) + */ + internal_shrmod(A1, t, A1, k * _DIGIT_BITS) or_return + _private_div_recursion(Q0, R0, A1, B1) or_return + + /* + A2 = (R0*beta^k) + (A1 % beta^k) - (Q0*B0) + */ + _private_int_shl_leg(R0, k) or_return + internal_add(A2, R0, t) or_return + internal_mul(t, Q0, B0) or_return + internal_sub(A2, A2, t) or_return + + /* + While A2 < 0 do Q0 = Q0 - 1, A2 = A2 + B. + */ + for internal_is_negative(A2) { // internal_lt(A2, 0) { + internal_decr(Q0) or_return + internal_add(A2, A2, b) or_return + } + + /* + Return q = (Q1*beta^k) + Q0, r = A2. + */ + _private_int_shl_leg(Q1, k) or_return + internal_add(quotient, Q1, Q0) or_return + + return internal_copy(remainder, A2) +} + +_private_int_div_recursive :: proc(quotient, remainder, a, b: ^Int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + A, B, Q, Q1, R, A_div, A_mod := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{} + defer internal_destroy(A, B, Q, Q1, R, A_div, A_mod) + + internal_init_multi(A, B, Q, Q1, R, A_div, A_mod) or_return + + /* + Most significant bit of a limb. + Assumes _DIGIT_MAX < (sizeof(DIGIT) * sizeof(u8)). + */ + msb := (_DIGIT_MAX + DIGIT(1)) >> 1 + sigma := 0 + msb_b := b.digit[b.used - 1] + for msb_b < msb { + sigma += 1 + msb_b <<= 1 + } + + /* + Use that sigma to normalize B. + */ + internal_shl(B, b, sigma) or_return + internal_shl(A, a, sigma) or_return + + /* + Fix the sign. + */ + neg := a.sign != b.sign + A.sign = .Zero_or_Positive; B.sign = .Zero_or_Positive + + /* + If the magnitude of "A" is not more more than twice that of "B" we can work + on them directly, otherwise we need to work at "A" in chunks. + */ + n := B.used + m := A.used - B.used + + /* + Q = 0. We already ensured that when we called `internal_init_multi`. + */ + for m > n { + /* + (q, r) = RecursiveDivRem(A / (beta^(m-n)), B) + */ + j := (m - n) * _DIGIT_BITS + internal_shrmod(A_div, A_mod, A, j) or_return + _private_div_recursion(Q1, R, A_div, B) or_return + + /* + Q = (Q*beta!(n)) + q + */ + internal_shl(Q, Q, n * _DIGIT_BITS) or_return + internal_add(Q, Q, Q1) or_return + + /* + A = (r * beta^(m-n)) + (A % beta^(m-n)) + */ + internal_shl(R, R, (m - n) * _DIGIT_BITS) or_return + internal_add(A, R, A_mod) or_return + + /* + m = m - n + */ + m -= n + } + + /* + (q, r) = RecursiveDivRem(A, B) + */ + _private_div_recursion(Q1, R, A, B) or_return + + /* + Q = (Q * beta^m) + q, R = r + */ + internal_shl(Q, Q, m * _DIGIT_BITS) or_return + internal_add(Q, Q, Q1) or_return + + /* + Get sign before writing to dest. + */ + R.sign = .Zero_or_Positive if internal_is_zero(Q) else a.sign + + if quotient != nil { + swap(quotient, Q) + quotient.sign = .Negative if neg else .Zero_or_Positive + } + if remainder != nil { + /* + De-normalize the remainder. + */ + internal_shrmod(R, nil, R, sigma) or_return + swap(remainder, R) + } + return nil +} + +/* + Slower bit-bang division... also smaller. +*/ +@(deprecated="Use `_int_div_school`, it's 3.5x faster.") +_private_int_div_small :: proc(quotient, remainder, numerator, denominator: ^Int) -> (err: Error) { + + ta, tb, tq, q := &Int{}, &Int{}, &Int{}, &Int{} + + defer internal_destroy(ta, tb, tq, q) + + for { + internal_one(tq) or_return + + num_bits, _ := count_bits(numerator) + den_bits, _ := count_bits(denominator) + n := num_bits - den_bits + + abs(ta, numerator) or_return + abs(tb, denominator) or_return + shl(tb, tb, n) or_return + shl(tq, tq, n) or_return + + for n >= 0 { + if internal_gte(ta, tb) { + // ta -= tb + sub(ta, ta, tb) or_return + // q += tq + add( q, q, tq) or_return + } + shr1(tb, tb) or_return + shr1(tq, tq) or_return + + n -= 1 + } + + /* + Now q == quotient and ta == remainder. + */ + neg := numerator.sign != denominator.sign + if quotient != nil { + swap(quotient, q) + z, _ := is_zero(quotient) + quotient.sign = .Negative if neg && !z else .Zero_or_Positive + } + if remainder != nil { + swap(remainder, ta) + z, _ := is_zero(numerator) + remainder.sign = .Zero_or_Positive if z else numerator.sign + } + + break + } + return err +} + + + +/* + Binary split factorial algo due to: http://www.luschny.de/math/factorial/binarysplitfact.html +*/ +_private_int_factorial_binary_split :: proc(res: ^Int, n: int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + inner, outer, start, stop, temp := &Int{}, &Int{}, &Int{}, &Int{}, &Int{} + defer internal_destroy(inner, outer, start, stop, temp) + + internal_one(inner, false) or_return + internal_one(outer, false) or_return + + bits_used := ilog2(n) + + for i := bits_used; i >= 0; i -= 1 { + start := (n >> (uint(i) + 1)) + 1 | 1 + stop := (n >> uint(i)) + 1 | 1 + _private_int_recursive_product(temp, start, stop, 0) or_return + internal_mul(inner, inner, temp) or_return + internal_mul(outer, outer, inner) or_return + } + shift := n - intrinsics.count_ones(n) + + return internal_shl(res, outer, int(shift)) +} + +/* + Recursive product used by binary split factorial algorithm. +*/ +_private_int_recursive_product :: proc(res: ^Int, start, stop: int, level := int(0), allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + t1, t2 := &Int{}, &Int{} + defer internal_destroy(t1, t2) + + if level > FACTORIAL_BINARY_SPLIT_MAX_RECURSIONS { + return .Max_Iterations_Reached + } + + num_factors := (stop - start) >> 1 + if num_factors == 2 { + internal_set(t1, start, false) or_return + when true { + internal_grow(t2, t1.used + 1, false) or_return + internal_add(t2, t1, 2) or_return + } else { + internal_add(t2, t1, 2) or_return + } + return internal_mul(res, t1, t2) + } + + if num_factors > 1 { + mid := (start + num_factors) | 1 + _private_int_recursive_product(t1, start, mid, level + 1) or_return + _private_int_recursive_product(t2, mid, stop, level + 1) or_return + return internal_mul(res, t1, t2) + } + + if num_factors == 1 { + return #force_inline internal_set(res, start, true) + } + + return #force_inline internal_one(res, true) +} + +/* + Internal function computing both GCD using the binary method, + and, if target isn't `nil`, also LCM. + + Expects the `a` and `b` to have been initialized + and one or both of `res_gcd` or `res_lcm` not to be `nil`. + + If both `a` and `b` are zero, return zero. + If either `a` or `b`, return the other one. + + The `gcd` and `lcm` wrappers have already done this test, + but `gcd_lcm` wouldn't have, so we still need to perform it. + + If neither result is wanted, we have nothing to do. +*/ +_private_int_gcd_lcm :: proc(res_gcd, res_lcm, a, b: ^Int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + if res_gcd == nil && res_lcm == nil { + return nil + } + + /* + We need a temporary because `res_gcd` is allowed to be `nil`. + */ + if a.used == 0 && b.used == 0 { + /* + GCD(0, 0) and LCM(0, 0) are both 0. + */ + if res_gcd != nil { + internal_zero(res_gcd) or_return + } + if res_lcm != nil { + internal_zero(res_lcm) or_return + } + return nil + } else if a.used == 0 { + /* + We can early out with GCD = B and LCM = 0 + */ + if res_gcd != nil { + internal_abs(res_gcd, b) or_return + } + if res_lcm != nil { + internal_zero(res_lcm) or_return + } + return nil + } else if b.used == 0 { + /* + We can early out with GCD = A and LCM = 0 + */ + if res_gcd != nil { + internal_abs(res_gcd, a) or_return + } + if res_lcm != nil { + internal_zero(res_lcm) or_return + } + return nil + } + + temp_gcd_res := &Int{} + defer internal_destroy(temp_gcd_res) + + /* + If neither `a` or `b` was zero, we need to compute `gcd`. + 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. + */ + 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) + + if k > 0 { + /* + Divide the power of two out. + */ + internal_shr(u, u, k) or_return + internal_shr(v, v, k) or_return + } + + /* + Divide any remaining factors of two out. + */ + if u_lsb != k { + internal_shr(u, u, u_lsb - k) or_return + } + if v_lsb != k { + internal_shr(v, v, v_lsb - k) or_return + } + + for v.used != 0 { + /* + Make sure `v` is the largest. + */ + if internal_gt(u, v) { + /* + Swap `u` and `v` to make sure `v` is >= `u`. + */ + internal_swap(u, v) + } + + /* + Subtract smallest from largest. + */ + internal_sub(v, v, u) or_return + + /* + Divide out all factors of two. + */ + b, _ := internal_count_lsb(v) + 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 + + /* + We've computed `gcd`, either the long way, or because one of the inputs was zero. + If we don't want `lcm`, we're done. + */ + if res_lcm == nil { + internal_swap(temp_gcd_res, res_gcd) + return nil + } + + /* + Computes least common multiple as `|a*b|/gcd(a,b)` + Divide the smallest by the GCD. + */ + if internal_lt_abs(a, b) { + /* + Store quotient in `t2` such that `t2 * b` is the LCM. + */ + internal_div(res_lcm, a, temp_gcd_res) or_return + err = internal_mul(res_lcm, res_lcm, b) + } else { + /* + Store quotient in `t2` such that `t2 * a` is the LCM. + */ + internal_div(res_lcm, b, temp_gcd_res) or_return + err = internal_mul(res_lcm, res_lcm, a) + } + + if res_gcd != nil { + internal_swap(temp_gcd_res, res_gcd) + } + + /* + Fix the sign to positive and return. + */ + res_lcm.sign = .Zero_or_Positive + return err +} + +/* + Internal implementation of log. + Assumes `a` not to be `nil` and to have been initialized. +*/ +_private_int_log :: proc(a: ^Int, base: DIGIT, allocator := context.allocator) -> (res: int, err: Error) { + bracket_low, bracket_high, bracket_mid, t, bi_base := &Int{}, &Int{}, &Int{}, &Int{}, &Int{} + defer internal_destroy(bracket_low, bracket_high, bracket_mid, t, bi_base) + + ic := #force_inline internal_cmp(a, base) + if ic == -1 || ic == 0 { + return 1 if ic == 0 else 0, nil + } + defer if err != nil { + res = -1 + } + + internal_set(bi_base, base, true, allocator) or_return + internal_clear(bracket_mid, false, allocator) or_return + internal_clear(t, false, allocator) or_return + internal_one(bracket_low, false, allocator) or_return + internal_set(bracket_high, base, false, allocator) or_return + + low := 0; high := 1 + + /* + A kind of Giant-step/baby-step algorithm. + Idea shamelessly stolen from https://programmingpraxis.com/2010/05/07/integer-logarithms/2/ + The effect is asymptotic, hence needs benchmarks to test if the Giant-step should be skipped + for small n. + */ + + for { + /* + Iterate until `a` is bracketed between low + high. + */ + if #force_inline internal_gte(bracket_high, a) { break } + + low = high + #force_inline internal_copy(bracket_low, bracket_high) or_return + high <<= 1 + #force_inline internal_sqr(bracket_high, bracket_high) or_return + } + + for (high - low) > 1 { + mid := (high + low) >> 1 + + #force_inline internal_pow(t, bi_base, mid - low) or_return + + #force_inline internal_mul(bracket_mid, bracket_low, t) or_return + + mc := #force_inline internal_cmp(a, bracket_mid) + switch mc { + case -1: + high = mid + internal_swap(bracket_mid, bracket_high) + case 0: + return mid, nil + case 1: + low = mid + internal_swap(bracket_mid, bracket_low) + } + } + + fc := #force_inline internal_cmp(bracket_high, a) + res = high if fc == 0 else low + + return +} + +/* + Computes xR**-1 == x (mod N) via Montgomery Reduction. + This is an optimized implementation of `internal_montgomery_reduce` + which uses the comba method to quickly calculate the columns of the reduction. + Based on Algorithm 14.32 on pp.601 of HAC. +*/ +_private_montgomery_reduce_comba :: proc(x, n: ^Int, rho: DIGIT, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + W: [_WARRAY]_WORD = --- + + if x.used > _WARRAY { return .Invalid_Argument } + + /* + Get old used count. + */ + old_used := x.used + + /* + Grow `x` as required. + */ + internal_grow(x, n.used + 1) or_return + + /* + First we have to get the digits of the input into an array of double precision words W[...] + Copy the digits of `x` into W[0..`x.used` - 1] + */ + ix: int + for ix = 0; ix < x.used; ix += 1 { + W[ix] = _WORD(x.digit[ix]) + } + + /* + Zero the high words of W[a->used..m->used*2]. + */ + zero_upper := (n.used * 2) + 1 + if ix < zero_upper { + for ix = x.used; ix < zero_upper; ix += 1 { + W[ix] = {} + } + } + + /* + Now we proceed to zero successive digits from the least significant upwards. + */ + for ix = 0; ix < n.used; ix += 1 { + /* + `mu = ai * m' mod b` + + We avoid a double precision multiplication (which isn't required) + by casting the value down to a DIGIT. Note this requires + that W[ix-1] have the carry cleared (see after the inner loop) + */ + mu := ((W[ix] & _WORD(_MASK)) * _WORD(rho)) & _WORD(_MASK) + + /* + `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. + + Note the comba method normally doesn't handle carries in the + inner loop In this case we fix the carry from the previous + column since the Montgomery reduction requires digits of the + result (so far) [see above] to work. + + This is handled by fixing up one carry after the inner loop. + The carry fixups are done in order so after these loops the + first m->used words of W[] have the carries fixed. + */ + for iy := 0; iy < n.used; iy += 1 { + W[ix + iy] += mu * _WORD(n.digit[iy]) + } + + /* + Now fix carry for next digit, W[ix+1]. + */ + W[ix + 1] += (W[ix] >> _DIGIT_BITS) + } + + /* + Now we have to propagate the carries and shift the words downward + [all those least significant digits we zeroed]. + */ + + for ; ix < n.used * 2; ix += 1 { + W[ix + 1] += (W[ix] >> _DIGIT_BITS) + } + + /* copy out, A = A/b**n + * + * The result is A/b**n but instead of converting from an + * array of mp_word to mp_digit than calling mp_rshd + * we just copy them in the right order + */ + + for ix = 0; ix < (n.used + 1); ix += 1 { + x.digit[ix] = DIGIT(W[n.used + ix] & _WORD(_MASK)) + } + + /* + Set the max used. + */ + x.used = n.used + 1 + + /* + Zero old_used digits, if the input a was larger than m->used+1 we'll have to clear the digits. + */ + internal_zero_unused(x, old_used) + internal_clamp(x) + + /* + if A >= m then A = A - m + */ + if internal_gte_abs(x, n) { + return internal_sub(x, x, n) + } + return nil +} + +/* + Computes xR**-1 == x (mod N) via Montgomery Reduction. + Assumes `x` and `n` not to be nil. +*/ +_private_int_montgomery_reduce :: proc(x, n: ^Int, rho: DIGIT, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + /* + Can the fast reduction [comba] method be used? + Note that unlike in mul, you're safely allowed *less* than the available columns [255 per default], + since carries are fixed up in the inner loop. + */ + internal_clear_if_uninitialized(x, n) or_return + + digs := (n.used * 2) + 1 + if digs < _WARRAY && x.used <= _WARRAY && n.used < _MAX_COMBA { + return _private_montgomery_reduce_comba(x, n, rho) + } + + /* + Grow the input as required + */ + internal_grow(x, digs) or_return + x.used = digs + + for ix := 0; ix < n.used; ix += 1 { + /* + `mu = ai * rho mod b` + The value of rho must be precalculated via `int_montgomery_setup()`, + such that it equals -1/n0 mod b this allows the following inner loop + to reduce the input one digit at a time. + */ + + mu := DIGIT((_WORD(x.digit[ix]) * _WORD(rho)) & _WORD(_MASK)) + + /* + a = a + mu * m * b**i + Multiply and add in place. + */ + u := DIGIT(0) + iy := int(0) + for ; iy < n.used; iy += 1 { + /* + Compute product and sum. + */ + r := (_WORD(mu) * _WORD(n.digit[iy]) + _WORD(u) + _WORD(x.digit[ix + iy])) + + /* + Get carry. + */ + u = DIGIT(r >> _DIGIT_BITS) + + /* + Fix digit. + */ + x.digit[ix + iy] = DIGIT(r & _WORD(_MASK)) + } + + /* + At this point the ix'th digit of x should be zero. + Propagate carries upwards as required. + */ + for u != 0 { + x.digit[ix + iy] += u + u = x.digit[ix + iy] >> _DIGIT_BITS + x.digit[ix + iy] &= _MASK + iy += 1 + } + } + + /* + At this point the n.used'th least significant digits of x are all zero, + which means we can shift x to the right by n.used digits and the + residue is unchanged. + + x = x/b**n.used. + */ + internal_clamp(x) + _private_int_shr_leg(x, n.used) + + /* + if x >= n then x = x - n + */ + if internal_gte_abs(x, n) { + return internal_sub(x, x, n) + } + + return nil +} + +/* + Shifts with subtractions when the result is greater than b. + + The method is slightly modified to shift B unconditionally upto just under + the leading bit of b. This saves alot of multiple precision shifting. + + Assumes `a` and `b` not to be `nil`. +*/ +_private_int_montgomery_calc_normalization :: proc(a, b: ^Int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + /* + How many bits of last digit does b use. + */ + internal_clear_if_uninitialized(a, b) or_return + + bits := internal_count_bits(b) % _DIGIT_BITS + + if b.used > 1 { + power := ((b.used - 1) * _DIGIT_BITS) + bits - 1 + internal_int_power_of_two(a, power) or_return + } else { + internal_one(a) or_return + bits = 1 + } + + /* + Now compute C = A * B mod b. + */ + for x := bits - 1; x < _DIGIT_BITS; x += 1 { + internal_int_shl1(a, a) or_return + if internal_gte_abs(a, b) { + internal_sub(a, a, b) or_return + } + } + return nil +} + +/* + Sets up the Montgomery reduction stuff. +*/ +_private_int_montgomery_setup :: proc(n: ^Int, allocator := context.allocator) -> (rho: DIGIT, err: Error) { + /* + Fast inversion mod 2**k + Based on the fact that: + + XA = 1 (mod 2**n) => (X(2-XA)) A = 1 (mod 2**2n) + => 2*X*A - X*X*A*A = 1 + => 2*(1) - (1) = 1 + */ + internal_clear_if_uninitialized(n, allocator) or_return + + b := n.digit[0] + if b & 1 == 0 { return 0, .Invalid_Argument } + + x := (((b + 2) & 4) << 1) + b /* here x*a==1 mod 2**4 */ + x *= 2 - (b * x) /* here x*a==1 mod 2**8 */ + x *= 2 - (b * x) /* here x*a==1 mod 2**16 */ + + when _DIGIT_TYPE_BITS == 64 { + x *= 2 - (b * x) /* here x*a==1 mod 2**32 */ + x *= 2 - (b * x) /* here x*a==1 mod 2**64 */ + } + + /* + rho = -1/m mod b + */ + rho = DIGIT(((_WORD(1) << _WORD(_DIGIT_BITS)) - _WORD(x)) & _WORD(_MASK)) + return rho, nil +} + +/* + Reduces `x` mod `m`, assumes 0 < x < m**2, mu is precomputed via reduce_setup. + From HAC pp.604 Algorithm 14.42 + + Assumes `x`, `m` and `mu` all not to be `nil` and have been initialized. +*/ +_private_int_reduce :: proc(x, m, mu: ^Int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + q := &Int{} + defer internal_destroy(q) + um := m.used + + /* + q = x + */ + internal_copy(q, x) or_return + + /* + q1 = x / b**(k-1) + */ + _private_int_shr_leg(q, um - 1) + + /* + According to HAC this optimization is ok. + */ + if DIGIT(um) > DIGIT(1) << (_DIGIT_BITS - 1) { + internal_mul(q, q, mu) or_return + } else { + _private_int_mul_high(q, q, mu, um) or_return + } + + /* + q3 = q2 / b**(k+1) + */ + _private_int_shr_leg(q, um + 1) + + /* + x = x mod b**(k+1), quick (no division) + */ + internal_int_mod_bits(x, x, _DIGIT_BITS * (um + 1)) or_return + + /* + q = q * m mod b**(k+1), quick (no division) + */ + _private_int_mul(q, q, m, um + 1) or_return + + /* + x = x - q + */ + internal_sub(x, x, q) or_return + + /* + If x < 0, add b**(k+1) to it. + */ + if internal_is_negative(x) { + internal_set(q, 1) or_return + _private_int_shl_leg(q, um + 1) or_return + internal_add(x, x, q) or_return + } + + /* + Back off if it's too big. + */ + for internal_gte(x, m) { + internal_sub(x, x, m) or_return + } + + return nil +} + +/* + Reduces `a` modulo `n`, where `n` is of the form 2**p - d. +*/ +_private_int_reduce_2k :: proc(a, n: ^Int, d: DIGIT, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + q := &Int{} + defer internal_destroy(q) + + internal_zero(q) or_return + + p := internal_count_bits(n) + + for { + /* + q = a/2**p, a = a mod 2**p + */ + internal_shrmod(q, a, a, p) or_return + + if d != 1 { + /* + q = q * d + */ + internal_mul(q, q, d) or_return + } + + /* + a = a + q + */ + internal_add(a, a, q) or_return + if internal_lt_abs(a, n) { break } + internal_sub(a, a, n) or_return + } + + return nil +} + +/* + Reduces `a` modulo `n` where `n` is of the form 2**p - d + This differs from reduce_2k since "d" can be larger than a single digit. +*/ +_private_int_reduce_2k_l :: proc(a, n, d: ^Int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + q := &Int{} + defer internal_destroy(q) + + internal_zero(q) or_return + + p := internal_count_bits(n) + + for { + /* + q = a/2**p, a = a mod 2**p + */ + internal_shrmod(q, a, a, p) or_return + + /* + q = q * d + */ + internal_mul(q, q, d) or_return + + /* + a = a + q + */ + internal_add(a, a, q) or_return + if internal_lt_abs(a, n) { break } + internal_sub(a, a, n) or_return + } + + return nil +} + +/* + Determines if `internal_int_reduce_2k` can be used. + Asssumes `a` not to be `nil` and to have been initialized. +*/ +_private_int_reduce_is_2k :: proc(a: ^Int) -> (reducible: bool, err: Error) { + assert_if_nil(a) + + if internal_is_zero(a) { + return false, nil + } else if a.used == 1 { + return true, nil + } else if a.used > 1 { + iy := internal_count_bits(a) + iw := 1 + iz := DIGIT(1) + + /* + Test every bit from the second digit up, must be 1. + */ + for ix := _DIGIT_BITS; ix < iy; ix += 1 { + if a.digit[iw] & iz == 0 { + return false, nil + } + + iz <<= 1 + if iz > _DIGIT_MAX { + iw += 1 + iz = 1 + } + } + return true, nil + } else { + return true, nil + } +} + +/* + Determines if `internal_int_reduce_2k_l` can be used. + Asssumes `a` not to be `nil` and to have been initialized. +*/ +_private_int_reduce_is_2k_l :: proc(a: ^Int) -> (reducible: bool, err: Error) { + assert_if_nil(a) + + if internal_int_is_zero(a) { + return false, nil + } else if a.used == 1 { + return true, nil + } else if a.used > 1 { + /* + If more than half of the digits are -1 we're sold. + */ + ix := 0 + iy := 0 + + for ; ix < a.used; ix += 1 { + if a.digit[ix] == _DIGIT_MAX { + iy += 1 + } + } + return iy >= (a.used / 2), nil + } else { + return false, nil + } +} + +/* + Determines the setup value. + Assumes `a` is not `nil`. +*/ +_private_int_reduce_2k_setup :: proc(a: ^Int, allocator := context.allocator) -> (d: DIGIT, err: Error) { + context.allocator = allocator + + tmp := &Int{} + defer internal_destroy(tmp) + internal_zero(tmp) or_return + + internal_int_power_of_two(tmp, internal_count_bits(a)) or_return + internal_sub(tmp, tmp, a) or_return + + return tmp.digit[0], nil +} + +/* + Determines the setup value. + Assumes `mu` and `P` are not `nil`. + + d := (1 << a.bits) - a; +*/ +_private_int_reduce_2k_setup_l :: proc(mu, P: ^Int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + tmp := &Int{} + defer internal_destroy(tmp) + internal_zero(tmp) or_return + + internal_int_power_of_two(tmp, internal_count_bits(P)) or_return + internal_sub(mu, tmp, P) or_return + + return nil +} + +/* + Pre-calculate the value required for Barrett reduction. + For a given modulus "P" it calulates the value required in "mu" + Assumes `mu` and `P` are not `nil`. +*/ +_private_int_reduce_setup :: proc(mu, P: ^Int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + internal_int_power_of_two(mu, P.used * 2 * _DIGIT_BITS) or_return + return internal_int_div(mu, mu, P) +} + +/* + Determines the setup value. + Assumes `a` to not be `nil` and to have been initialized. +*/ +_private_int_dr_setup :: proc(a: ^Int) -> (d: DIGIT) { + /* + The casts are required if _DIGIT_BITS is one less than + the number of bits in a DIGIT [e.g. _DIGIT_BITS==31]. + */ + return DIGIT((1 << _DIGIT_BITS) - a.digit[0]) +} + +/* + Determines if a number is a valid DR modulus. + Assumes `a` to not be `nil` and to have been initialized. +*/ +_private_dr_is_modulus :: proc(a: ^Int) -> (res: bool) { + /* + Must be at least two digits. + */ + if a.used < 2 { return false } + + /* + Must be of the form b**k - a [a <= b] so all but the first digit must be equal to -1 (mod b). + */ + for ix := 1; ix < a.used; ix += 1 { + if a.digit[ix] != _MASK { + return false + } + } + return true +} + +/* + Reduce "x" in place modulo "n" using the Diminished Radix algorithm. + Based on algorithm from the paper + + "Generating Efficient Primes for Discrete Log Cryptosystems" + Chae Hoon Lim, Pil Joong Lee, + POSTECH Information Research Laboratories + + The modulus must be of a special format [see manual]. + Has been modified to use algorithm 7.10 from the LTM book instead + + Input x must be in the range 0 <= x <= (n-1)**2 + Assumes `x` and `n` to not be `nil` and to have been initialized. +*/ +_private_int_dr_reduce :: proc(x, n: ^Int, k: DIGIT, allocator := context.allocator) -> (err: Error) { + /* + m = digits in modulus. + */ + m := n.used + + /* + Ensure that "x" has at least 2m digits. + */ + internal_grow(x, m + m) or_return + + /* + Top of loop, this is where the code resumes if another reduction pass is required. + */ + for { + i: int + mu := DIGIT(0) + + /* + Compute (x mod B**m) + k * [x/B**m] inline and inplace. + */ + for i = 0; i < m; i += 1 { + r := _WORD(x.digit[i + m]) * _WORD(k) + _WORD(x.digit[i] + mu) + x.digit[i] = DIGIT(r & _WORD(_MASK)) + mu = DIGIT(r >> _WORD(_DIGIT_BITS)) + } + + /* + Set final carry. + */ + x.digit[i] = mu + + /* + Zero words above m. + */ + mem.zero_slice(x.digit[m + 1:][:x.used - m]) + + /* + Clamp, sub and return. + */ + internal_clamp(x) or_return + + /* + If x >= n then subtract and reduce again. + Each successive "recursion" makes the input smaller and smaller. + */ + if internal_lt_abs(x, n) { break } + + internal_sub(x, x, n) or_return + } + return nil +} + +/* + Computes res == G**X mod P. + Assumes `res`, `G`, `X` and `P` to not be `nil` and for `G`, `X` and `P` to have been initialized. +*/ +_private_int_exponent_mod :: proc(res, G, X, P: ^Int, redmode: int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + M := [_TAB_SIZE]Int{} + winsize: uint + + /* + Use a pointer to the reduction algorithm. + This allows us to use one of many reduction algorithms without modding the guts of the code with if statements everywhere. + */ + redux: #type proc(x, m, mu: ^Int, allocator := context.allocator) -> (err: Error) + + defer { + internal_destroy(&M[1]) + for x := 1 << (winsize - 1); x < (1 << winsize); x += 1 { + internal_destroy(&M[x]) + } + } + + /* + Find window size. + */ + x := internal_count_bits(X) + switch { + case x <= 7: + winsize = 2 + case x <= 36: + winsize = 3 + case x <= 140: + winsize = 4 + case x <= 450: + winsize = 5 + case x <= 1303: + winsize = 6 + case x <= 3529: + winsize = 7 + case: + winsize = 8 + } + + winsize = min(_MAX_WIN_SIZE, winsize) if _MAX_WIN_SIZE > 0 else winsize + + /* + Init M array. + Init first cell. + */ + internal_zero(&M[1]) or_return + + /* + Now init the second half of the array. + */ + for x = 1 << (winsize - 1); x < (1 << winsize); x += 1 { + internal_zero(&M[x]) or_return + } + + /* + Create `mu`, used for Barrett reduction. + */ + mu := &Int{} + defer internal_destroy(mu) + internal_zero(mu) or_return + + if redmode == 0 { + _private_int_reduce_setup(mu, P) or_return + redux = _private_int_reduce + } else { + _private_int_reduce_2k_setup_l(mu, P) or_return + redux = _private_int_reduce_2k_l + } + + /* + Create M table. + + The M table contains powers of the base, e.g. M[x] = G**x mod P. + The first half of the table is not computed, though, except for M[0] and M[1]. + */ + internal_int_mod(&M[1], G, P) or_return + + /* + Compute the value at M[1<<(winsize-1)] by squaring M[1] (winsize-1) times. + + TODO: This can probably be replaced by computing the power and using `pow` to raise to it + instead of repeated squaring. + */ + slot := 1 << (winsize - 1) + internal_copy(&M[slot], &M[1]) or_return + + for x = 0; x < int(winsize - 1); x += 1 { + /* + Square it. + */ + internal_sqr(&M[slot], &M[slot]) or_return + + /* + Reduce modulo P + */ + redux(&M[slot], P, mu) or_return + } + + /* + Create upper table, that is M[x] = M[x-1] * M[1] (mod P) + for x = (2**(winsize - 1) + 1) to (2**winsize - 1) + */ + for x = slot + 1; x < (1 << winsize); x += 1 { + internal_mul(&M[x], &M[x - 1], &M[1]) or_return + redux(&M[x], P, mu) or_return + } + + /* + Setup result. + */ + internal_one(res) or_return + + /* + Set initial mode and bit cnt. + */ + mode := 0 + bitcnt := 1 + buf := DIGIT(0) + digidx := X.used - 1 + bitcpy := uint(0) + bitbuf := DIGIT(0) + + for { + /* + Grab next digit as required. + */ + bitcnt -= 1 + if bitcnt == 0 { + /* + If digidx == -1 we are out of digits. + */ + if digidx == -1 { break } + + /* + Read next digit and reset the bitcnt. + */ + buf = X.digit[digidx] + digidx -= 1 + bitcnt = _DIGIT_BITS + } + + /* + Grab the next msb from the exponent. + */ + y := buf >> (_DIGIT_BITS - 1) & 1 + buf <<= 1 + + /* + If the bit is zero and mode == 0 then we ignore it. + These represent the leading zero bits before the first 1 bit + in the exponent. Technically this opt is not required but it + does lower the # of trivial squaring/reductions used. + */ + if mode == 0 && y == 0 { + continue + } + + /* + If the bit is zero and mode == 1 then we square. + */ + if mode == 1 && y == 0 { + internal_sqr(res, res) or_return + redux(res, P, mu) or_return + continue + } + + /* + Else we add it to the window. + */ + bitcpy += 1 + bitbuf |= (y << (winsize - bitcpy)) + mode = 2 + + if (bitcpy == winsize) { + /* + Window is filled so square as required and multiply. + Square first. + */ + for x = 0; x < int(winsize); x += 1 { + internal_sqr(res, res) or_return + redux(res, P, mu) or_return + } + + /* + Then multiply. + */ + internal_mul(res, res, &M[bitbuf]) or_return + redux(res, P, mu) or_return + + /* + Empty window and reset. + */ + bitcpy = 0 + bitbuf = 0 + mode = 1 + } + } + + /* + If bits remain then square/multiply. + */ + if mode == 2 && bitcpy > 0 { + /* + Square then multiply if the bit is set. + */ + for x = 0; x < int(bitcpy); x += 1 { + internal_sqr(res, res) or_return + redux(res, P, mu) or_return + + bitbuf <<= 1 + if ((bitbuf & (1 << winsize)) != 0) { + /* + Then multiply. + */ + internal_mul(res, res, &M[1]) or_return + redux(res, P, mu) or_return + } + } + } + return err +} + +/* + Computes Y == G**X mod P, HAC pp.616, Algorithm 14.85 + + Uses a left-to-right `k`-ary sliding window to compute the modular exponentiation. + The value of `k` changes based on the size of the exponent. + + Uses Montgomery or Diminished Radix reduction [whichever appropriate] + + Assumes `res`, `G`, `X` and `P` to not be `nil` and for `G`, `X` and `P` to have been initialized. +*/ +_private_int_exponent_mod_fast :: proc(res, G, X, P: ^Int, redmode: int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + M := [_TAB_SIZE]Int{} + winsize: uint + + /* + Use a pointer to the reduction algorithm. + This allows us to use one of many reduction algorithms without modding the guts of the code with if statements everywhere. + */ + redux: #type proc(x, n: ^Int, rho: DIGIT, allocator := context.allocator) -> (err: Error) + + defer { + internal_destroy(&M[1]) + for x := 1 << (winsize - 1); x < (1 << winsize); x += 1 { + internal_destroy(&M[x]) + } + } + + /* + Find window size. + */ + x := internal_count_bits(X) + switch { + case x <= 7: + winsize = 2 + case x <= 36: + winsize = 3 + case x <= 140: + winsize = 4 + case x <= 450: + winsize = 5 + case x <= 1303: + winsize = 6 + case x <= 3529: + winsize = 7 + case: + winsize = 8 + } + + winsize = min(_MAX_WIN_SIZE, winsize) if _MAX_WIN_SIZE > 0 else winsize + + /* + Init M array + Init first cell. + */ + cap := internal_int_allocated_cap(P) + internal_grow(&M[1], cap) or_return + + /* + Now init the second half of the array. + */ + for x = 1 << (winsize - 1); x < (1 << winsize); x += 1 { + internal_grow(&M[x], cap) or_return + } + + /* + Determine and setup reduction code. + */ + rho: DIGIT + + if redmode == 0 { + /* + Now setup Montgomery. + */ + rho = _private_int_montgomery_setup(P) or_return + + /* + Automatically pick the comba one if available (saves quite a few calls/ifs). + */ + if ((P.used * 2) + 1) < _WARRAY && P.used < _MAX_COMBA { + redux = _private_montgomery_reduce_comba + } else { + /* + Use slower baseline Montgomery method. + */ + redux = _private_int_montgomery_reduce + } + } else if redmode == 1 { + /* + Setup DR reduction for moduli of the form B**k - b. + */ + rho = _private_int_dr_setup(P) + redux = _private_int_dr_reduce + } else { + /* + Setup DR reduction for moduli of the form 2**k - b. + */ + rho = _private_int_reduce_2k_setup(P) or_return + redux = _private_int_reduce_2k + } + + /* + Setup result. + */ + internal_grow(res, cap) or_return + + /* + Create M table + The first half of the table is not computed, though, except for M[0] and M[1] + */ + + if redmode == 0 { + /* + Now we need R mod m. + */ + _private_int_montgomery_calc_normalization(res, P) or_return + + /* + Now set M[1] to G * R mod m. + */ + internal_mulmod(&M[1], G, res, P) or_return + } else { + internal_one(res) or_return + internal_mod(&M[1], G, P) or_return + } + + /* + Compute the value at M[1<<(winsize-1)] by squaring M[1] (winsize-1) times. + */ + slot := 1 << (winsize - 1) + internal_copy(&M[slot], &M[1]) or_return + + for x = 0; x < int(winsize - 1); x += 1 { + internal_sqr(&M[slot], &M[slot]) or_return + redux(&M[slot], P, rho) or_return + } + + /* + Create upper table. + */ + for x = (1 << (winsize - 1)) + 1; x < (1 << winsize); x += 1 { + internal_mul(&M[x], &M[x - 1], &M[1]) or_return + redux(&M[x], P, rho) or_return + } + + /* + Set initial mode and bit cnt. + */ + mode := 0 + bitcnt := 1 + buf := DIGIT(0) + digidx := X.used - 1 + bitcpy := 0 + bitbuf := DIGIT(0) + + for { + /* + Grab next digit as required. + */ + bitcnt -= 1 + if bitcnt == 0 { + /* + If digidx == -1 we are out of digits so break. + */ + if digidx == -1 { break } + + /* + Read next digit and reset the bitcnt. + */ + buf = X.digit[digidx] + digidx -= 1 + bitcnt = _DIGIT_BITS + } + + /* + Grab the next msb from the exponent. + */ + y := (buf >> (_DIGIT_BITS - 1)) & 1 + buf <<= 1 + + /* + If the bit is zero and mode == 0 then we ignore it. + These represent the leading zero bits before the first 1 bit in the exponent. + Technically this opt is not required but it does lower the # of trivial squaring/reductions used. + */ + if mode == 0 && y == 0 { continue } + + /* + If the bit is zero and mode == 1 then we square. + */ + if mode == 1 && y == 0 { + internal_sqr(res, res) or_return + redux(res, P, rho) or_return + continue + } + + /* + Else we add it to the window. + */ + bitcpy += 1 + bitbuf |= (y << (winsize - uint(bitcpy))) + mode = 2 + + if bitcpy == int(winsize) { + /* + Window is filled so square as required and multiply + Square first. + */ + for x = 0; x < int(winsize); x += 1 { + internal_sqr(res, res) or_return + redux(res, P, rho) or_return + } + + /* + Then multiply. + */ + internal_mul(res, res, &M[bitbuf]) or_return + redux(res, P, rho) or_return + + /* + Empty window and reset. + */ + bitcpy = 0 + bitbuf = 0 + mode = 1 + } + } + + /* + If bits remain then square/multiply. + */ + if mode == 2 && bitcpy > 0 { + /* + Square then multiply if the bit is set. + */ + for x = 0; x < bitcpy; x += 1 { + internal_sqr(res, res) or_return + redux(res, P, rho) or_return + + /* + Get next bit of the window. + */ + bitbuf <<= 1 + if bitbuf & (1 << winsize) != 0 { + /* + Then multiply. + */ + internal_mul(res, res, &M[1]) or_return + redux(res, P, rho) or_return + } + } + } + + if redmode == 0 { + /* + Fixup result if Montgomery reduction is used. + Recall that any value in a Montgomery system is actually multiplied by R mod n. + So we have to reduce one more time to cancel out the factor of R. + */ + redux(res, P, rho) or_return + } + + return nil +} + +/* + hac 14.61, pp608 +*/ +_private_inverse_modulo :: proc(dest, a, b: ^Int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + x, y, u, v, A, B, C, D := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{} + defer internal_destroy(x, y, u, v, A, B, C, D) + + // `b` cannot be negative. + if b.sign == .Negative || internal_is_zero(b) { + return .Invalid_Argument + } + + // init temps. + internal_init_multi(x, y, u, v, A, B, C, D) or_return + + // `x` = `a` % `b`, `y` = `b` + internal_mod(x, a, b) or_return + internal_copy(y, b) or_return + + // 2. [modified] if x,y are both even then return an error! + if internal_is_even(x) && internal_is_even(y) { + return .Invalid_Argument + } + + // 3. u=x, v=y, A=1, B=0, C=0, D=1 + internal_copy(u, x) or_return + internal_copy(v, y) or_return + internal_one(A) or_return + internal_one(D) or_return + + for { + // 4. while `u` is even do: + for internal_is_even(u) { + // 4.1 `u` = `u` / 2 + internal_int_shr1(u, u) or_return + + // 4.2 if `A` or `B` is odd then: + if internal_is_odd(A) || internal_is_odd(B) { + // `A` = (`A`+`y`) / 2, `B` = (`B`-`x`) / 2 + internal_add(A, A, y) or_return + internal_sub(B, B, x) or_return + } + // `A` = `A` / 2, `B` = `B` / 2 + internal_int_shr1(A, A) or_return + internal_int_shr1(B, B) or_return + } + + // 5. while `v` is even do: + for internal_is_even(v) { + // 5.1 `v` = `v` / 2 + internal_int_shr1(v, v) or_return + + // 5.2 if `C` or `D` is odd then: + if internal_is_odd(C) || internal_is_odd(D) { + // `C` = (`C`+`y`) / 2, `D` = (`D`-`x`) / 2 + internal_add(C, C, y) or_return + internal_sub(D, D, x) or_return + } + // `C` = `C` / 2, `D` = `D` / 2 + internal_int_shr1(C, C) or_return + internal_int_shr1(D, D) or_return + } + + // 6. if `u` >= `v` then: + if internal_cmp(u, v) != -1 { + // `u` = `u` - `v`, `A` = `A` - `C`, `B` = `B` - `D` + internal_sub(u, u, v) or_return + internal_sub(A, A, C) or_return + internal_sub(B, B, D) or_return + } else { + // v - v - u, C = C - A, D = D - B + internal_sub(v, v, u) or_return + internal_sub(C, C, A) or_return + internal_sub(D, D, B) or_return + } + + // If not zero goto step 4 + if internal_is_zero(u) { + break + } + } + + // Now `a` = `C`, `b` = `D`, `gcd` == `g`*`v` + + // If `v` != `1` then there is no inverse. + if !internal_eq(v, 1) { + return .Invalid_Argument + } + + // If its too low. + for internal_is_negative(C) { + internal_add(C, C, b) or_return + } + + // Too big. + for internal_cmp_mag(C, b) > -1 { + internal_sub(C, C, b) or_return + } + + // `C` is now the inverse. + swap(dest, C) + return +} + +/* + Computes the modular inverse via binary extended Euclidean algorithm, that is `dest` = 1 / `a` mod `b`. + + Based on slow invmod except this is optimized for the case where `b` is odd, + as per HAC Note 14.64 on pp. 610. +*/ +_private_inverse_modulo_odd :: proc(dest, a, b: ^Int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + x, y, u, v, B, D := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{} + defer internal_destroy(x, y, u, v, B, D) + + sign: Sign + + /* + 2. [modified] `b` must be odd. + */ + if internal_is_even(b) { return .Invalid_Argument } + + /* + Init all our temps. + */ + internal_init_multi(x, y, u, v, B, D) or_return + + /* + `x` == modulus, `y` == value to invert. + */ + internal_copy(x, b) or_return + + /* + We need `y` = `|a|`. + */ + internal_mod(y, a, b) or_return + + /* + If one of `x`, `y` is zero return an error! + */ + if internal_is_zero(x) || internal_is_zero(y) { return .Invalid_Argument } + + /* + 3. `u` = `x`, `v` = `y`, `A` = 1, `B` = 0, `C` = 0, `D` = 1 + */ + internal_copy(u, x) or_return + internal_copy(v, y) or_return + + internal_one(D) or_return + + for { + /* + 4. while `u` is even do. + */ + for internal_is_even(u) { + /* + 4.1 `u` = `u` / 2 + */ + internal_int_shr1(u, u) or_return + + /* + 4.2 if `B` is odd then: + */ + if internal_is_odd(B) { + /* + `B` = (`B` - `x`) / 2 + */ + internal_sub(B, B, x) or_return + } + + /* + `B` = `B` / 2 + */ + internal_int_shr1(B, B) or_return + } + + /* + 5. while `v` is even do: + */ + for internal_is_even(v) { + /* + 5.1 `v` = `v` / 2 + */ + internal_int_shr1(v, v) or_return + + /* + 5.2 if `D` is odd then: + */ + if internal_is_odd(D) { + /* + `D` = (`D` - `x`) / 2 + */ + internal_sub(D, D, x) or_return + } + /* + `D` = `D` / 2 + */ + internal_int_shr1(D, D) or_return + } + + /* + 6. if `u` >= `v` then: + */ + if internal_cmp(u, v) != -1 { + /* + `u` = `u` - `v`, `B` = `B` - `D` + */ + internal_sub(u, u, v) or_return + internal_sub(B, B, D) or_return + } else { + /* + `v` - `v` - `u`, `D` = `D` - `B` + */ + internal_sub(v, v, u) or_return + internal_sub(D, D, B) or_return + } + + /* + If not zero goto step 4. + */ + if internal_is_zero(u) { break } + } + + /* + Now `a` = C, `b` = D, gcd == g*v + */ + + /* + if `v` != 1 then there is no inverse + */ + if internal_cmp(v, 1) != 0 { + return .Invalid_Argument + } + + /* + `b` is now the inverse. + */ + sign = a.sign + for internal_int_is_negative(D) { + internal_add(D, D, b) or_return + } + + /* + Too big. + */ + for internal_gte_abs(D, b) { + internal_sub(D, D, b) or_return + } + + swap(dest, D) + dest.sign = sign + return nil +} + + +/* + Returns the log2 of an `Int`. + Assumes `a` not to be `nil` and to have been initialized. + Also assumes `base` is a power of two. +*/ +_private_log_power_of_two :: proc(a: ^Int, base: DIGIT) -> (log: int, err: Error) { + base := base + y: int + for y = 0; base & 1 == 0; { + y += 1 + base >>= 1 + } + log = internal_count_bits(a) + return (log - 1) / y, err +} + +/* + Copies DIGITs from `src` to `dest`. + Assumes `src` and `dest` to not be `nil` and have been initialized. +*/ +_private_copy_digits :: proc(dest, src: ^Int, digits: int, offset := int(0)) -> (err: Error) { + digits := digits + /* + If dest == src, do nothing + */ + if dest == src { + return nil + } + + digits = min(digits, len(src.digit), len(dest.digit)) + mem.copy_non_overlapping(&dest.digit[0], &src.digit[offset], size_of(DIGIT) * digits) + return nil +} + + +/* + Shift left by `digits` * _DIGIT_BITS bits. +*/ +_private_int_shl_leg :: proc(quotient: ^Int, digits: int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + if digits <= 0 { return nil } + + /* + No need to shift a zero. + */ + if #force_inline internal_is_zero(quotient) { + return nil + } + + /* + Resize `quotient` to accomodate extra digits. + */ + #force_inline internal_grow(quotient, quotient.used + digits) or_return + + /* + Increment the used by the shift amount then copy upwards. + */ + + /* + Much like `_private_int_shr_leg`, this is implemented using a sliding window, + except the window goes the other way around. + */ + #no_bounds_check for x := quotient.used; x > 0; x -= 1 { + quotient.digit[x+digits-1] = quotient.digit[x-1] + } + + quotient.used += digits + mem.zero_slice(quotient.digit[:digits]) + return nil +} + +/* + Shift right by `digits` * _DIGIT_BITS bits. +*/ +_private_int_shr_leg :: proc(quotient: ^Int, digits: int, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + if digits <= 0 { return nil } + + /* + If digits > used simply zero and return. + */ + if digits > quotient.used { return internal_zero(quotient) } + + /* + Much like `int_shl_digit`, this is implemented using a sliding window, + except the window goes the other way around. + + b-2 | b-1 | b0 | b1 | b2 | ... | bb | ----> + /\ | ----> + \-------------------/ ----> + */ + + #no_bounds_check for x := 0; x < (quotient.used - digits); x += 1 { + quotient.digit[x] = quotient.digit[x + digits] + } + quotient.used -= digits + internal_zero_unused(quotient) + return internal_clamp(quotient) +} + +/* + ======================== End of private procedures ======================= + + =============================== Private tables =============================== + + Tables used by `internal_*` and `_*`. +*/ + +_private_int_rem_128 := [?]DIGIT{ + 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, +} +#assert(128 * size_of(DIGIT) == size_of(_private_int_rem_128)) + +_private_int_rem_105 := [?]DIGIT{ + 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, + 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, + 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, + 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, + 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, +} +#assert(105 * size_of(DIGIT) == size_of(_private_int_rem_105)) + +_PRIME_TAB_SIZE :: 256 +_private_prime_table := [_PRIME_TAB_SIZE]DIGIT{ + 0x0002, 0x0003, 0x0005, 0x0007, 0x000B, 0x000D, 0x0011, 0x0013, + 0x0017, 0x001D, 0x001F, 0x0025, 0x0029, 0x002B, 0x002F, 0x0035, + 0x003B, 0x003D, 0x0043, 0x0047, 0x0049, 0x004F, 0x0053, 0x0059, + 0x0061, 0x0065, 0x0067, 0x006B, 0x006D, 0x0071, 0x007F, 0x0083, + 0x0089, 0x008B, 0x0095, 0x0097, 0x009D, 0x00A3, 0x00A7, 0x00AD, + 0x00B3, 0x00B5, 0x00BF, 0x00C1, 0x00C5, 0x00C7, 0x00D3, 0x00DF, + 0x00E3, 0x00E5, 0x00E9, 0x00EF, 0x00F1, 0x00FB, 0x0101, 0x0107, + 0x010D, 0x010F, 0x0115, 0x0119, 0x011B, 0x0125, 0x0133, 0x0137, + + 0x0139, 0x013D, 0x014B, 0x0151, 0x015B, 0x015D, 0x0161, 0x0167, + 0x016F, 0x0175, 0x017B, 0x017F, 0x0185, 0x018D, 0x0191, 0x0199, + 0x01A3, 0x01A5, 0x01AF, 0x01B1, 0x01B7, 0x01BB, 0x01C1, 0x01C9, + 0x01CD, 0x01CF, 0x01D3, 0x01DF, 0x01E7, 0x01EB, 0x01F3, 0x01F7, + 0x01FD, 0x0209, 0x020B, 0x021D, 0x0223, 0x022D, 0x0233, 0x0239, + 0x023B, 0x0241, 0x024B, 0x0251, 0x0257, 0x0259, 0x025F, 0x0265, + 0x0269, 0x026B, 0x0277, 0x0281, 0x0283, 0x0287, 0x028D, 0x0293, + 0x0295, 0x02A1, 0x02A5, 0x02AB, 0x02B3, 0x02BD, 0x02C5, 0x02CF, + + 0x02D7, 0x02DD, 0x02E3, 0x02E7, 0x02EF, 0x02F5, 0x02F9, 0x0301, + 0x0305, 0x0313, 0x031D, 0x0329, 0x032B, 0x0335, 0x0337, 0x033B, + 0x033D, 0x0347, 0x0355, 0x0359, 0x035B, 0x035F, 0x036D, 0x0371, + 0x0373, 0x0377, 0x038B, 0x038F, 0x0397, 0x03A1, 0x03A9, 0x03AD, + 0x03B3, 0x03B9, 0x03C7, 0x03CB, 0x03D1, 0x03D7, 0x03DF, 0x03E5, + 0x03F1, 0x03F5, 0x03FB, 0x03FD, 0x0407, 0x0409, 0x040F, 0x0419, + 0x041B, 0x0425, 0x0427, 0x042D, 0x043F, 0x0443, 0x0445, 0x0449, + 0x044F, 0x0455, 0x045D, 0x0463, 0x0469, 0x047F, 0x0481, 0x048B, + + 0x0493, 0x049D, 0x04A3, 0x04A9, 0x04B1, 0x04BD, 0x04C1, 0x04C7, + 0x04CD, 0x04CF, 0x04D5, 0x04E1, 0x04EB, 0x04FD, 0x04FF, 0x0503, + 0x0509, 0x050B, 0x0511, 0x0515, 0x0517, 0x051B, 0x0527, 0x0529, + 0x052F, 0x0551, 0x0557, 0x055D, 0x0565, 0x0577, 0x0581, 0x058F, + 0x0593, 0x0595, 0x0599, 0x059F, 0x05A7, 0x05AB, 0x05AD, 0x05B3, + 0x05BF, 0x05C9, 0x05CB, 0x05CF, 0x05D1, 0x05D5, 0x05DB, 0x05E7, + 0x05F3, 0x05FB, 0x0607, 0x060D, 0x0611, 0x0617, 0x061F, 0x0623, + 0x062B, 0x062F, 0x063D, 0x0641, 0x0647, 0x0649, 0x064D, 0x0653, +} +#assert(_PRIME_TAB_SIZE * size_of(DIGIT) == size_of(_private_prime_table)) + +when MATH_BIG_FORCE_64_BIT || (!MATH_BIG_FORCE_32_BIT && size_of(rawptr) == 8) { + _factorial_table := [35]_WORD{ +/* f(00): */ 1, +/* f(01): */ 1, +/* f(02): */ 2, +/* f(03): */ 6, +/* f(04): */ 24, +/* f(05): */ 120, +/* f(06): */ 720, +/* f(07): */ 5_040, +/* f(08): */ 40_320, +/* f(09): */ 362_880, +/* f(10): */ 3_628_800, +/* f(11): */ 39_916_800, +/* f(12): */ 479_001_600, +/* f(13): */ 6_227_020_800, +/* f(14): */ 87_178_291_200, +/* f(15): */ 1_307_674_368_000, +/* f(16): */ 20_922_789_888_000, +/* f(17): */ 355_687_428_096_000, +/* f(18): */ 6_402_373_705_728_000, +/* f(19): */ 121_645_100_408_832_000, +/* f(20): */ 2_432_902_008_176_640_000, +/* f(21): */ 51_090_942_171_709_440_000, +/* f(22): */ 1_124_000_727_777_607_680_000, +/* f(23): */ 25_852_016_738_884_976_640_000, +/* f(24): */ 620_448_401_733_239_439_360_000, +/* f(25): */ 15_511_210_043_330_985_984_000_000, +/* f(26): */ 403_291_461_126_605_635_584_000_000, +/* f(27): */ 10_888_869_450_418_352_160_768_000_000, +/* f(28): */ 304_888_344_611_713_860_501_504_000_000, +/* f(29): */ 8_841_761_993_739_701_954_543_616_000_000, +/* f(30): */ 265_252_859_812_191_058_636_308_480_000_000, +/* f(31): */ 8_222_838_654_177_922_817_725_562_880_000_000, +/* f(32): */ 263_130_836_933_693_530_167_218_012_160_000_000, +/* f(33): */ 8_683_317_618_811_886_495_518_194_401_280_000_000, +/* f(34): */ 295_232_799_039_604_140_847_618_609_643_520_000_000, + } +} else { + _factorial_table := [21]_WORD{ +/* f(00): */ 1, +/* f(01): */ 1, +/* f(02): */ 2, +/* f(03): */ 6, +/* f(04): */ 24, +/* f(05): */ 120, +/* f(06): */ 720, +/* f(07): */ 5_040, +/* f(08): */ 40_320, +/* f(09): */ 362_880, +/* f(10): */ 3_628_800, +/* f(11): */ 39_916_800, +/* f(12): */ 479_001_600, +/* f(13): */ 6_227_020_800, +/* f(14): */ 87_178_291_200, +/* f(15): */ 1_307_674_368_000, +/* f(16): */ 20_922_789_888_000, +/* f(17): */ 355_687_428_096_000, +/* f(18): */ 6_402_373_705_728_000, +/* f(19): */ 121_645_100_408_832_000, +/* f(20): */ 2_432_902_008_176_640_000, + } +} + +/* + ========================= End of private tables ======================== */ \ No newline at end of file diff --git a/core/math/big/public.odin b/core/math/big/public.odin index 3227d7bc4..070c45283 100644 --- a/core/math/big/public.odin +++ b/core/math/big/public.odin @@ -12,7 +12,7 @@ package math_big -import "core:intrinsics" +import "base:intrinsics" /* =========================== diff --git a/core/math/big/radix.odin b/core/math/big/radix.odin index d15ce0e98..a5100e478 100644 --- a/core/math/big/radix.odin +++ b/core/math/big/radix.odin @@ -16,7 +16,7 @@ package math_big -import "core:intrinsics" +import "base:intrinsics" import "core:mem" import "core:os" @@ -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/big/rat.odin b/core/math/big/rat.odin index 35618affb..e0e58b80f 100644 --- a/core/math/big/rat.odin +++ b/core/math/big/rat.odin @@ -1,7 +1,7 @@ package math_big -import "core:builtin" -import "core:intrinsics" +import "base:builtin" +import "base:intrinsics" import "core:math" Rat :: struct { diff --git a/core/math/big/tune.odin b/core/math/big/tune.odin index ec1ef9a5b..eab36b951 100644 --- a/core/math/big/tune.odin +++ b/core/math/big/tune.odin @@ -7,11 +7,11 @@ The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks. */ -//+build ignore +#+build ignore package math_big import "core:time" -import "core:runtime" +import "base:runtime" print_value :: proc(name: string, value: i64) { runtime.print_string("\t") diff --git a/core/math/bits/bits.odin b/core/math/bits/bits.odin index 959b5536f..154b5a142 100644 --- a/core/math/bits/bits.odin +++ b/core/math/bits/bits.odin @@ -1,6 +1,6 @@ package math_bits -import "core:intrinsics" +import "base:intrinsics" U8_MIN :: 0 U16_MIN :: 0 diff --git a/core/math/cmplx/cmplx.odin b/core/math/cmplx/cmplx.odin index c029be30c..d1c70ca61 100644 --- a/core/math/cmplx/cmplx.odin +++ b/core/math/cmplx/cmplx.odin @@ -1,6 +1,6 @@ package math_cmplx -import "core:builtin" +import "base:builtin" import "core:math" // The original C code, the long comment, and the constants @@ -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 a746a370f..40a8493bc 100644 --- a/core/math/cmplx/cmplx_invtrig.odin +++ b/core/math/cmplx/cmplx_invtrig.odin @@ -1,6 +1,6 @@ package math_cmplx -import "core:builtin" +import "base:builtin" import "core:math" // The original C code, the long comment, and the constants @@ -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/ease/ease.odin b/core/math/ease/ease.odin index 0e6569bca..5ed0dd56a 100644 --- a/core/math/ease/ease.odin +++ b/core/math/ease/ease.odin @@ -2,7 +2,7 @@ package ease import "core:math" -import "core:intrinsics" +import "base:intrinsics" import "core:time" @(private) PI_2 :: math.PI / 2 diff --git a/core/math/fixed/fixed.odin b/core/math/fixed/fixed.odin index 8567fd0ed..b23090307 100644 --- a/core/math/fixed/fixed.odin +++ b/core/math/fixed/fixed.odin @@ -2,7 +2,7 @@ package math_fixed import "core:math" import "core:strconv" -import "core:intrinsics" +import "base:intrinsics" _, _, _ :: intrinsics, strconv, math Fixed :: struct($Backing: typeid, $Fraction_Width: uint) @@ -33,14 +33,15 @@ init_from_f64 :: proc(x: ^$T/Fixed($Backing, $Fraction_Width), val: f64) { x.i = Backing(f * (1< f64 { @@ -86,50 +87,66 @@ div_sat :: proc(x, y: $T/Fixed($Backing, $Fraction_Width)) -> (z: T) { @(require_results) floor :: proc(x: $T/Fixed($Backing, $Fraction_Width)) -> Backing { - return x.i >> Fraction_Width + if x.i >= 0 { + return x.i >> Fraction_Width + } else { + return (x.i - (1 << (Fraction_Width - 1)) + (1 << (Fraction_Width - 2))) >> Fraction_Width + } } @(require_results) ceil :: proc(x: $T/Fixed($Backing, $Fraction_Width)) -> Backing { - Integer :: 8*size_of(Backing) - Fraction_Width - return (x.i + (1 << Integer-1)) >> Fraction_Width + return (x.i + (1 << Fraction_Width - 1)) >> Fraction_Width } @(require_results) round :: proc(x: $T/Fixed($Backing, $Fraction_Width)) -> Backing { - Integer :: 8*size_of(Backing) - Fraction_Width - return (x.i + (1 << (Integer - 1))) >> Fraction_Width + return (x.i + (1 << (Fraction_Width - 1))) >> Fraction_Width } - - @(require_results) append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string { + Integer_Width :: 8*size_of(Backing) - Fraction_Width + x := x buf: [48]byte i := 0 - if x.i < 0 { + + if !intrinsics.type_is_unsigned(Backing) && x.i == min(Backing) { + // edge case handling for signed numbers buf[i] = '-' i += 1 - x.i = -x.i - } - - integer := x.i >> Fraction_Width - fraction := x.i & (1< 0 { - fraction *= 10 - buf[i] = byte('0' + (fraction>>Fraction_Width)) + i += copy(buf[i:], _power_of_two_table[Integer_Width]) + } else { + if x.i < 0 { + buf[i] = '-' i += 1 - fraction &= 1<> Fraction_Width + fraction := T(x.i) & (1< 0 { + fraction *= 10 + buf[i] = byte('0' + (fraction>>Fraction_Width) % 10) + i += 1 + fraction &= 1< (out: T) where IS_FLOAT(ELEM_TYPE(T)) { @(require_results) log2 :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) { + INVLN2 :: 1.4426950408889634073599246810018921374266459541529859341354494069 when IS_ARRAY(T) { for i in 0.. (out: T) where IS_FLOAT(ELEM_TYPE(T)) { @(require_results) log10 :: proc "contextless" (x: $T) -> (out: T) where IS_FLOAT(ELEM_TYPE(T)) { + INVLN10 :: 0.4342944819032518276511289189166050822943970058036665661144537831 when IS_ARRAY(T) { for i in 0.. (out: T) where IS_FLOAT(ELEM_TYPE(T)) { out[i] = math.ln(x[i]) / math.ln(cast(ELEM_TYPE(T))b[i]) } } else { - out = INVLN10 * math.ln(x) / math.ln(cast(ELEM_TYPE(T))b) + out = math.ln(x) / math.ln(cast(ELEM_TYPE(T))b) } return } diff --git a/core/math/linalg/general.odin b/core/math/linalg/general.odin index b58305c63..4a0150972 100644 --- a/core/math/linalg/general.odin +++ b/core/math/linalg/general.odin @@ -1,8 +1,9 @@ package linalg import "core:math" -import "core:builtin" -import "core:intrinsics" +import "base:builtin" +import "base:intrinsics" +@require import "base:runtime" // Generic @@ -52,25 +53,25 @@ vector_dot :: proc "contextless" (a, b: $T/[$N]$E) -> (c: E) where IS_NUMERIC(E) } @(require_results) quaternion64_dot :: proc "contextless" (a, b: $T/quaternion64) -> (c: f16) { - return a.w*a.w + a.x*b.x + a.y*b.y + a.z*b.z + return a.w*b.w + a.x*b.x + a.y*b.y + a.z*b.z } @(require_results) quaternion128_dot :: proc "contextless" (a, b: $T/quaternion128) -> (c: f32) { - return a.w*a.w + a.x*b.x + a.y*b.y + a.z*b.z + return a.w*b.w + a.x*b.x + a.y*b.y + a.z*b.z } @(require_results) quaternion256_dot :: proc "contextless" (a, b: $T/quaternion256) -> (c: f64) { - return a.w*a.w + a.x*b.x + a.y*b.y + a.z*b.z + return a.w*b.w + a.x*b.x + a.y*b.y + a.z*b.z } dot :: proc{scalar_dot, vector_dot, quaternion64_dot, quaternion128_dot, quaternion256_dot} inner_product :: dot -outer_product :: builtin.outer_product +outer_product :: intrinsics.outer_product @(require_results) quaternion_inverse :: proc "contextless" (q: $Q) -> Q where IS_QUATERNION(Q) { - return conj(q) * quaternion(1.0/dot(q, q), 0, 0, 0) + return conj(q) * quaternion(w=1.0/dot(q, q), x=0, y=0, z=0) } @@ -166,21 +167,42 @@ vector_triple_product :: proc "contextless" (a, b, c: $T/[$N]$E) -> T where IS_N length :: proc{vector_length, quaternion_length} length2 :: proc{vector_length2, quaternion_length2} + +@(require_results) +clamp_length :: proc "contextless" (v: $T/[$N]$E, a: E) -> T where IS_FLOAT(E) { + if a <= 0 { + return 0 + } + + m2 := length2(v) + return v if (m2 <= a*a) else (v / sqrt(m2) * a) // returns original when m2 is 0 +} + + @(require_results) projection :: proc "contextless" (x, normal: $T/[$N]$E) -> T where IS_NUMERIC(E) { return dot(x, normal) / dot(normal, normal) * normal } @(require_results) -identity :: proc "contextless" ($T: typeid/[$N][N]$E) -> (m: T) #no_bounds_check { +identity_array_based_matrix :: proc "contextless" ($T: typeid/[$N][N]$E) -> (m: T) #no_bounds_check { for i in 0.. T #no_bounds_check { + return 1 +} + +identity :: proc{ + identity_array_based_matrix, + identity_matrix, +} + +transpose :: intrinsics.transpose @(require_results) matrix_mul :: proc "contextless" (a, b: $M/matrix[$N, N]$E) -> (c: M) @@ -214,33 +236,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(runtime.Raw_Quaternion64_Vector_Scalar)q + v := v - q := transmute(Raw_Quaternion)q - v := transmute([3]f16)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(runtime.Raw_Quaternion128_Vector_Scalar)q + v := v - q := transmute(Raw_Quaternion)q - v := transmute([3]f32)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(runtime.Raw_Quaternion256_Vector_Scalar)q + v := v - q := transmute(Raw_Quaternion)q - v := transmute([3]f64)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} @@ -268,11 +284,37 @@ to_ptr :: proc{vector_to_ptr, matrix_to_ptr} +vector_angle_between :: proc "contextless" (a, b: $V/[$N]$E) -> E { + a0 := normalize0(a) + b0 := normalize0(b) + d := clamp(dot(a0, b0), -1, +1) + return math.acos(d) +} +quaternion64_angle_between :: proc "contextless" (a, b: $Q/quaternion64) -> f16 { + c := normalize0(conj(a) * b) + return math.acos(c.w) +} +quaternion128_angle_between :: proc "contextless" (a, b: $Q/quaternion128) -> f32 { + c := normalize0(conj(a) * b) + return math.acos(c.w) +} +quaternion256_angle_between :: proc "contextless" (a, b: $Q/quaternion256) -> f64 { + c := normalize0(conj(a) * b) + return math.acos(c.w) +} +angle_between :: proc{ + vector_angle_between, + quaternion64_angle_between, + quaternion128_angle_between, + quaternion256_angle_between, +} + + // Splines @(require_results) -vector_slerp :: proc "contextless" (x, y: $T/[$N]$E, a: E) -> T { +vector_slerp :: proc "contextless" (x, y: $T/[$N]$E, a: E) -> T #no_bounds_check { cos_alpha := dot(x, y) alpha := math.acos(cos_alpha) sin_alpha := math.sin(alpha) @@ -284,7 +326,7 @@ vector_slerp :: proc "contextless" (x, y: $T/[$N]$E, a: E) -> T { } @(require_results) -catmull_rom :: proc "contextless" (v1, v2, v3, v4: $T/[$N]$E, s: E) -> T { +catmull_rom :: proc "contextless" (v1, v2, v3, v4: $T/[$N]$E, s: E) -> T #no_bounds_check { s2 := s*s s3 := s2*s @@ -297,7 +339,7 @@ catmull_rom :: proc "contextless" (v1, v2, v3, v4: $T/[$N]$E, s: E) -> T { } @(require_results) -hermite :: proc "contextless" (v1, t1, v2, t2: $T/[$N]$E, s: E) -> T { +hermite :: proc "contextless" (v1, t1, v2, t2: $T/[$N]$E, s: E) -> T #no_bounds_check { s2 := s*s s3 := s2*s @@ -310,7 +352,7 @@ hermite :: proc "contextless" (v1, t1, v2, t2: $T/[$N]$E, s: E) -> T { } @(require_results) -cubic :: proc "contextless" (v1, v2, v3, v4: $T/[$N]$E, s: E) -> T { +cubic :: proc "contextless" (v1, v2, v3, v4: $T/[$N]$E, s: E) -> T #no_bounds_check { return ((v1 * s + v2) * s + v3) * s + v4 } @@ -355,3 +397,321 @@ matrix_cast :: proc "contextless" (v: $A/matrix[$M, $N]$T, $Elem_Type: typeid) - @(require_results) to_quaternion64 :: #force_inline proc(v: $A/[$N]$T) -> [N]quaternion64 { return array_cast(v, quaternion64) } @(require_results) to_quaternion128 :: #force_inline proc(v: $A/[$N]$T) -> [N]quaternion128 { return array_cast(v, quaternion128) } @(require_results) to_quaternion256 :: #force_inline proc(v: $A/[$N]$T) -> [N]quaternion256 { return array_cast(v, quaternion256) } + + +hadamard_product :: intrinsics.hadamard_product +matrix_flatten :: intrinsics.matrix_flatten + + +determinant :: proc{ + matrix1x1_determinant, + matrix2x2_determinant, + matrix3x3_determinant, + matrix4x4_determinant, +} + +adjugate :: proc{ + matrix1x1_adjugate, + matrix2x2_adjugate, + matrix3x3_adjugate, + matrix4x4_adjugate, +} + +cofactor :: proc{ + matrix1x1_cofactor, + matrix2x2_cofactor, + matrix3x3_cofactor, + matrix4x4_cofactor, +} + +inverse_transpose :: proc{ + matrix1x1_inverse_transpose, + matrix2x2_inverse_transpose, + matrix3x3_inverse_transpose, + matrix4x4_inverse_transpose, +} + + +inverse :: proc{ + matrix1x1_inverse, + matrix2x2_inverse, + matrix3x3_inverse, + matrix4x4_inverse, +} + +@(require_results) +hermitian_adjoint :: proc "contextless" (m: $M/matrix[$N, N]$T) -> M where intrinsics.type_is_complex(T), N >= 1 #no_bounds_check { + return conj(transpose(m)) +} + +@(require_results) +trace :: proc "contextless" (m: $M/matrix[$N, N]$T) -> (trace: T) #no_bounds_check { + for i in 0.. (minor: T) where N > 1 #no_bounds_check { + K :: int(N-1) + cut_down: matrix[K, K]T + for col_idx in 0..= column) + for row_idx in 0..= row) + cut_down[row_idx, col_idx] = m[i, j] + } + } + return determinant(cut_down) +} + + + +@(require_results) +matrix1x1_determinant :: proc "contextless" (m: $M/matrix[1, 1]$T) -> (det: T) #no_bounds_check { + return m[0, 0] +} + +@(require_results) +matrix2x2_determinant :: proc "contextless" (m: $M/matrix[2, 2]$T) -> (det: T) #no_bounds_check { + return m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] +} +@(require_results) +matrix3x3_determinant :: proc "contextless" (m: $M/matrix[3, 3]$T) -> (det: T) #no_bounds_check { + a := +m[0, 0] * (m[1, 1] * m[2, 2] - m[1, 2] * m[2, 1]) + b := -m[0, 1] * (m[1, 0] * m[2, 2] - m[1, 2] * m[2, 0]) + c := +m[0, 2] * (m[1, 0] * m[2, 1] - m[1, 1] * m[2, 0]) + return a + b + c +} +@(require_results) +matrix4x4_determinant :: proc "contextless" (m: $M/matrix[4, 4]$T) -> (det: T) #no_bounds_check { + c := cofactor(m) + for i in 0..<4 { + det += m[0, i] * c[0, i] + } + return +} + + + + +@(require_results) +matrix1x1_adjugate :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) #no_bounds_check { + y = x + return +} + +@(require_results) +matrix2x2_adjugate :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) #no_bounds_check { + y[0, 0] = +x[1, 1] + y[0, 1] = -x[0, 1] + y[1, 0] = -x[1, 0] + y[1, 1] = +x[0, 0] + return +} + +@(require_results) +matrix3x3_adjugate :: proc "contextless" (m: $M/matrix[3, 3]$T) -> (y: M) #no_bounds_check { + y[0, 0] = +(m[1, 1] * m[2, 2] - m[2, 1] * m[1, 2]) + y[1, 0] = -(m[1, 0] * m[2, 2] - m[2, 0] * m[1, 2]) + y[2, 0] = +(m[1, 0] * m[2, 1] - m[2, 0] * m[1, 1]) + y[0, 1] = -(m[0, 1] * m[2, 2] - m[2, 1] * m[0, 2]) + y[1, 1] = +(m[0, 0] * m[2, 2] - m[2, 0] * m[0, 2]) + y[2, 1] = -(m[0, 0] * m[2, 1] - m[2, 0] * m[0, 1]) + y[0, 2] = +(m[0, 1] * m[1, 2] - m[1, 1] * m[0, 2]) + y[1, 2] = -(m[0, 0] * m[1, 2] - m[1, 0] * m[0, 2]) + y[2, 2] = +(m[0, 0] * m[1, 1] - m[1, 0] * m[0, 1]) + return +} + +@(require_results) +matrix4x4_adjugate :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) #no_bounds_check { + for i in 0..<4 { + for j in 0..<4 { + sign: T = 1 if (i + j) % 2 == 0 else -1 + y[i, j] = sign * matrix_minor(x, j, i) + } + } + return +} + + +@(require_results) +matrix1x1_cofactor :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) #no_bounds_check { + y = x + return +} + +@(require_results) +matrix2x2_cofactor :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) #no_bounds_check { + y[0, 0] = +x[1, 1] + y[0, 1] = -x[1, 0] + y[1, 0] = -x[0, 1] + y[1, 1] = +x[0, 0] + return +} + +@(require_results) +matrix3x3_cofactor :: proc "contextless" (m: $M/matrix[3, 3]$T) -> (y: M) #no_bounds_check { + y[0, 0] = +(m[1, 1] * m[2, 2] - m[2, 1] * m[1, 2]) + y[0, 1] = -(m[1, 0] * m[2, 2] - m[2, 0] * m[1, 2]) + y[0, 2] = +(m[1, 0] * m[2, 1] - m[2, 0] * m[1, 1]) + y[1, 0] = -(m[0, 1] * m[2, 2] - m[2, 1] * m[0, 2]) + y[1, 1] = +(m[0, 0] * m[2, 2] - m[2, 0] * m[0, 2]) + y[1, 2] = -(m[0, 0] * m[2, 1] - m[2, 0] * m[0, 1]) + y[2, 0] = +(m[0, 1] * m[1, 2] - m[1, 1] * m[0, 2]) + y[2, 1] = -(m[0, 0] * m[1, 2] - m[1, 0] * m[0, 2]) + y[2, 2] = +(m[0, 0] * m[1, 1] - m[1, 0] * m[0, 1]) + return +} + + +@(require_results) +matrix4x4_cofactor :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) #no_bounds_check { + for i in 0..<4 { + for j in 0..<4 { + sign: T = 1 if (i + j) % 2 == 0 else -1 + y[i, j] = sign * matrix_minor(x, i, j) + } + } + return +} + +@(require_results) +matrix1x1_inverse_transpose :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) #no_bounds_check { + y[0, 0] = 1/x[0, 0] + return +} + +@(require_results) +matrix2x2_inverse_transpose :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) #no_bounds_check { + d := x[0, 0]*x[1, 1] - x[0, 1]*x[1, 0] + when intrinsics.type_is_integer(T) { + y[0, 0] = +x[1, 1] / d + y[1, 0] = -x[0, 1] / d + y[0, 1] = -x[1, 0] / d + y[1, 1] = +x[0, 0] / d + } else { + id := 1 / d + y[0, 0] = +x[1, 1] * id + y[1, 0] = -x[0, 1] * id + y[0, 1] = -x[1, 0] * id + y[1, 1] = +x[0, 0] * id + } + return +} + +@(require_results) +matrix3x3_inverse_transpose :: proc "contextless" (x: $M/matrix[3, 3]$T) -> (y: M) #no_bounds_check { + c := cofactor(x) + d := determinant(x) + when intrinsics.type_is_integer(T) { + for i in 0..<3 { + for j in 0..<3 { + y[i, j] = c[i, j] / d + } + } + } else { + id := 1/d + for i in 0..<3 { + for j in 0..<3 { + y[i, j] = c[i, j] * id + } + } + } + return +} + +@(require_results) +matrix4x4_inverse_transpose :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) #no_bounds_check { + c := cofactor(x) + d: T + for i in 0..<4 { + d += x[0, i] * c[0, i] + } + when intrinsics.type_is_integer(T) { + for i in 0..<4 { + for j in 0..<4 { + y[i, j] = c[i, j] / d + } + } + } else { + id := 1/d + for i in 0..<4 { + for j in 0..<4 { + y[i, j] = c[i, j] * id + } + } + } + return +} + +@(require_results) +matrix1x1_inverse :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) #no_bounds_check { + y[0, 0] = 1/x[0, 0] + return +} + +@(require_results) +matrix2x2_inverse :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) #no_bounds_check { + d := x[0, 0]*x[1, 1] - x[0, 1]*x[1, 0] + when intrinsics.type_is_integer(T) { + y[0, 0] = +x[1, 1] / d + y[0, 1] = -x[0, 1] / d + y[1, 0] = -x[1, 0] / d + y[1, 1] = +x[0, 0] / d + } else { + id := 1 / d + y[0, 0] = +x[1, 1] * id + y[0, 1] = -x[0, 1] * id + y[1, 0] = -x[1, 0] * id + y[1, 1] = +x[0, 0] * id + } + return +} + +@(require_results) +matrix3x3_inverse :: proc "contextless" (x: $M/matrix[3, 3]$T) -> (y: M) #no_bounds_check { + c := cofactor(x) + d := determinant(x) + when intrinsics.type_is_integer(T) { + for i in 0..<3 { + for j in 0..<3 { + y[i, j] = c[j, i] / d + } + } + } else { + id := 1/d + for i in 0..<3 { + for j in 0..<3 { + y[i, j] = c[j, i] * id + } + } + } + return +} + +@(require_results) +matrix4x4_inverse :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) #no_bounds_check { + c := cofactor(x) + d: T + for i in 0..<4 { + d += x[0, i] * c[0, i] + } + when intrinsics.type_is_integer(T) { + for i in 0..<4 { + for j in 0..<4 { + y[i, j] = c[j, i] / d + } + } + } else { + id := 1/d + for i in 0..<4 { + for j in 0..<4 { + y[i, j] = c[j, i] * id + } + } + } + return +} diff --git a/core/math/linalg/glsl/linalg_glsl.odin b/core/math/linalg/glsl/linalg_glsl.odin index 0d91ad4a3..bd2cf416a 100644 --- a/core/math/linalg/glsl/linalg_glsl.odin +++ b/core/math/linalg/glsl/linalg_glsl.odin @@ -1,7 +1,8 @@ // core:math/linalg/glsl implements a GLSL-like mathematics library plus numerous other utility procedures package math_linalg_glsl -import "core:builtin" +import "base:builtin" +import "base:intrinsics" TAU :: 6.28318530717958647692528676655900576 PI :: 3.14159265358979323846264338327950288 @@ -21,9 +22,9 @@ F32_EPSILON :: 1e-7 F64_EPSILON :: 1e-15 // Odin matrices are stored internally as Column-Major, which matches OpenGL/GLSL by default -mat2 :: distinct matrix[2, 2]f32 -mat3 :: distinct matrix[3, 3]f32 -mat4 :: distinct matrix[4, 4]f32 +mat2 :: matrix[2, 2]f32 +mat3 :: matrix[3, 3]f32 +mat4 :: matrix[4, 4]f32 mat2x2 :: mat2 mat3x3 :: mat3 mat4x4 :: mat4 @@ -32,52 +33,52 @@ mat4x4 :: mat4 // but they match how GLSL and OpenGL defines them in name // Odin: matrix[R, C]f32 // GLSL: matCxR -mat3x2 :: distinct matrix[2, 3]f32 -mat4x2 :: distinct matrix[2, 4]f32 -mat2x3 :: distinct matrix[3, 2]f32 -mat4x3 :: distinct matrix[3, 4]f32 -mat2x4 :: distinct matrix[4, 2]f32 -mat3x4 :: distinct matrix[4, 3]f32 +mat3x2 :: matrix[2, 3]f32 +mat4x2 :: matrix[2, 4]f32 +mat2x3 :: matrix[3, 2]f32 +mat4x3 :: matrix[3, 4]f32 +mat2x4 :: matrix[4, 2]f32 +mat3x4 :: matrix[4, 3]f32 -vec2 :: distinct [2]f32 -vec3 :: distinct [3]f32 -vec4 :: distinct [4]f32 +vec2 :: [2]f32 +vec3 :: [3]f32 +vec4 :: [4]f32 -ivec2 :: distinct [2]i32 -ivec3 :: distinct [3]i32 -ivec4 :: distinct [4]i32 +ivec2 :: [2]i32 +ivec3 :: [3]i32 +ivec4 :: [4]i32 -uvec2 :: distinct [2]u32 -uvec3 :: distinct [3]u32 -uvec4 :: distinct [4]u32 +uvec2 :: [2]u32 +uvec3 :: [3]u32 +uvec4 :: [4]u32 -bvec2 :: distinct [2]bool -bvec3 :: distinct [3]bool -bvec4 :: distinct [4]bool +bvec2 :: [2]bool +bvec3 :: [3]bool +bvec4 :: [4]bool -quat :: distinct quaternion128 +quat :: quaternion128 // Double Precision (f64) Floating Point Types -dmat2 :: distinct matrix[2, 2]f64 -dmat3 :: distinct matrix[3, 3]f64 -dmat4 :: distinct matrix[4, 4]f64 +dmat2 :: matrix[2, 2]f64 +dmat3 :: matrix[3, 3]f64 +dmat4 :: matrix[4, 4]f64 dmat2x2 :: dmat2 dmat3x3 :: dmat3 dmat4x4 :: dmat4 -dmat3x2 :: distinct matrix[2, 3]f64 -dmat4x2 :: distinct matrix[2, 4]f64 -dmat2x3 :: distinct matrix[3, 2]f64 -dmat4x3 :: distinct matrix[3, 4]f64 -dmat2x4 :: distinct matrix[4, 2]f64 -dmat3x4 :: distinct matrix[4, 3]f64 +dmat3x2 :: matrix[2, 3]f64 +dmat4x2 :: matrix[2, 4]f64 +dmat2x3 :: matrix[3, 2]f64 +dmat4x3 :: matrix[3, 4]f64 +dmat2x4 :: matrix[4, 2]f64 +dmat3x4 :: matrix[4, 3]f64 -dvec2 :: distinct [2]f64 -dvec3 :: distinct [3]f64 -dvec4 :: distinct [4]f64 +dvec2 :: [2]f64 +dvec3 :: [3]f64 +dvec4 :: [4]f64 -dquat :: distinct quaternion256 +dquat :: quaternion256 cos :: proc{ cos_f32, @@ -472,6 +473,22 @@ floor :: proc{ @(require_results) floor_dvec3 :: proc "c" (x: dvec3) -> dvec3 { return {floor(x.x), floor(x.y), floor(x.z)} } @(require_results) floor_dvec4 :: proc "c" (x: dvec4) -> dvec4 { return {floor(x.x), floor(x.y), floor(x.z), floor(x.w)} } +trunc :: proc{ + trunc_f32, + trunc_f64, + trunc_vec2, + trunc_vec3, + trunc_vec4, + trunc_dvec2, + trunc_dvec3, + trunc_dvec4, +} +@(require_results) trunc_vec2 :: proc "c" (x: vec2) -> vec2 { return {trunc(x.x), trunc(x.y)} } +@(require_results) trunc_vec3 :: proc "c" (x: vec3) -> vec3 { return {trunc(x.x), trunc(x.y), trunc(x.z)} } +@(require_results) trunc_vec4 :: proc "c" (x: vec4) -> vec4 { return {trunc(x.x), trunc(x.y), trunc(x.z), trunc(x.w)} } +@(require_results) trunc_dvec2 :: proc "c" (x: dvec2) -> dvec2 { return {trunc(x.x), trunc(x.y)} } +@(require_results) trunc_dvec3 :: proc "c" (x: dvec3) -> dvec3 { return {trunc(x.x), trunc(x.y), trunc(x.z)} } +@(require_results) trunc_dvec4 :: proc "c" (x: dvec4) -> dvec4 { return {trunc(x.x), trunc(x.y), trunc(x.z), trunc(x.w)} } round :: proc{ @@ -1723,7 +1740,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) } @@ -1831,37 +1848,336 @@ 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) } -@(require_results) inverse_mat2 :: proc "c" (m: mat2) -> mat2 { return builtin.inverse(m) } -@(require_results) inverse_mat3 :: proc "c" (m: mat3) -> mat3 { return builtin.inverse(m) } -@(require_results) inverse_mat4 :: proc "c" (m: mat4) -> mat4 { return builtin.inverse(m) } -@(require_results) inverse_dmat2 :: proc "c" (m: dmat2) -> dmat2 { return builtin.inverse(m) } -@(require_results) inverse_dmat3 :: proc "c" (m: dmat3) -> dmat3 { return builtin.inverse(m) } -@(require_results) inverse_dmat4 :: proc "c" (m: dmat4) -> dmat4 { return builtin.inverse(m) } +@(require_results) inverse_mat2 :: proc "c" (m: mat2) -> mat2 { return inverse_matrix2x2(m) } +@(require_results) inverse_mat3 :: proc "c" (m: mat3) -> mat3 { return inverse_matrix3x3(m) } +@(require_results) inverse_mat4 :: proc "c" (m: mat4) -> mat4 { return inverse_matrix4x4(m) } +@(require_results) inverse_dmat2 :: proc "c" (m: dmat2) -> dmat2 { return inverse_matrix2x2(m) } +@(require_results) inverse_dmat3 :: proc "c" (m: dmat3) -> dmat3 { return inverse_matrix3x3(m) } +@(require_results) inverse_dmat4 :: proc "c" (m: dmat4) -> dmat4 { return inverse_matrix4x4(m) } @(require_results) inverse_quat :: proc "c" (q: quat) -> quat { return 1/q } @(require_results) inverse_dquat :: proc "c" (q: dquat) -> dquat { return 1/q } -inverse :: proc{ - inverse_mat2, - inverse_mat3, - inverse_mat4, - inverse_dmat2, - inverse_dmat3, - inverse_dmat4, - inverse_quat, - inverse_dquat, + +transpose :: intrinsics.transpose + + +determinant :: proc{ + determinant_matrix1x1, + determinant_matrix2x2, + determinant_matrix3x3, + determinant_matrix4x4, +} + +adjugate :: proc{ + adjugate_matrix1x1, + adjugate_matrix2x2, + adjugate_matrix3x3, + adjugate_matrix4x4, +} + +cofactor :: proc{ + cofactor_matrix1x1, + cofactor_matrix2x2, + cofactor_matrix3x3, + cofactor_matrix4x4, +} + +inverse_transpose :: proc{ + inverse_transpose_matrix1x1, + inverse_transpose_matrix2x2, + inverse_transpose_matrix3x3, + inverse_transpose_matrix4x4, +} + + +inverse :: proc{ + inverse_matrix1x1, + inverse_matrix2x2, + inverse_matrix3x3, + inverse_matrix4x4, +} + +@(require_results) +hermitian_adjoint :: proc "contextless" (m: $M/matrix[$N, N]$T) -> M where intrinsics.type_is_complex(T), N >= 1 { + return conj(transpose(m)) +} + +@(require_results) +trace :: proc "contextless" (m: $M/matrix[$N, N]$T) -> (trace: T) { + for i in 0.. (minor: T) where N > 1 { + K :: int(N-1) + cut_down: matrix[K, K]T + for col_idx in 0..= column) + for row_idx in 0..= row) + cut_down[row_idx, col_idx] = m[i, j] + } + } + return determinant(cut_down) +} + + + +@(require_results) +determinant_matrix1x1 :: proc "contextless" (m: $M/matrix[1, 1]$T) -> (det: T) { + return m[0, 0] +} + +@(require_results) +determinant_matrix2x2 :: proc "contextless" (m: $M/matrix[2, 2]$T) -> (det: T) { + return m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] +} +@(require_results) +determinant_matrix3x3 :: proc "contextless" (m: $M/matrix[3, 3]$T) -> (det: T) { + a := +m[0, 0] * (m[1, 1] * m[2, 2] - m[1, 2] * m[2, 1]) + b := -m[0, 1] * (m[1, 0] * m[2, 2] - m[1, 2] * m[2, 0]) + c := +m[0, 2] * (m[1, 0] * m[2, 1] - m[1, 1] * m[2, 0]) + return a + b + c +} +@(require_results) +determinant_matrix4x4 :: proc "contextless" (m: $M/matrix[4, 4]$T) -> (det: T) { + c := cofactor(m) + #no_bounds_check for i in 0..<4 { + det += m[0, i] * c[0, i] + } + return +} + + + + +@(require_results) +adjugate_matrix1x1 :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) { + y = x + return +} + +@(require_results) +adjugate_matrix2x2 :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) { + y[0, 0] = +x[1, 1] + y[0, 1] = -x[0, 1] + y[1, 0] = -x[1, 0] + y[1, 1] = +x[0, 0] + return +} + +@(require_results) +adjugate_matrix3x3 :: proc "contextless" (m: $M/matrix[3, 3]$T) -> (y: M) { + y[0, 0] = +(m[1, 1] * m[2, 2] - m[2, 1] * m[1, 2]) + y[1, 0] = -(m[1, 0] * m[2, 2] - m[2, 0] * m[1, 2]) + y[2, 0] = +(m[1, 0] * m[2, 1] - m[2, 0] * m[1, 1]) + y[0, 1] = -(m[0, 1] * m[2, 2] - m[2, 1] * m[0, 2]) + y[1, 1] = +(m[0, 0] * m[2, 2] - m[2, 0] * m[0, 2]) + y[2, 1] = -(m[0, 0] * m[2, 1] - m[2, 0] * m[0, 1]) + y[0, 2] = +(m[0, 1] * m[1, 2] - m[1, 1] * m[0, 2]) + y[1, 2] = -(m[0, 0] * m[1, 2] - m[1, 0] * m[0, 2]) + y[2, 2] = +(m[0, 0] * m[1, 1] - m[1, 0] * m[0, 1]) + return +} + +@(require_results) +adjugate_matrix4x4 :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) { + for i in 0..<4 { + for j in 0..<4 { + sign: T = 1 if (i + j) % 2 == 0 else -1 + y[i, j] = sign * matrix_minor(x, j, i) + } + } + return +} + + +@(require_results) +cofactor_matrix1x1 :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) { + y = x + return +} + +@(require_results) +cofactor_matrix2x2 :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) { + y[0, 0] = +x[1, 1] + y[0, 1] = -x[1, 0] + y[1, 0] = -x[0, 1] + y[1, 1] = +x[0, 0] + return +} + +@(require_results) +cofactor_matrix3x3 :: proc "contextless" (m: $M/matrix[3, 3]$T) -> (y: M) { + y[0, 0] = +(m[1, 1] * m[2, 2] - m[2, 1] * m[1, 2]) + y[0, 1] = -(m[1, 0] * m[2, 2] - m[2, 0] * m[1, 2]) + y[0, 2] = +(m[1, 0] * m[2, 1] - m[2, 0] * m[1, 1]) + y[1, 0] = -(m[0, 1] * m[2, 2] - m[2, 1] * m[0, 2]) + y[1, 1] = +(m[0, 0] * m[2, 2] - m[2, 0] * m[0, 2]) + y[1, 2] = -(m[0, 0] * m[2, 1] - m[2, 0] * m[0, 1]) + y[2, 0] = +(m[0, 1] * m[1, 2] - m[1, 1] * m[0, 2]) + y[2, 1] = -(m[0, 0] * m[1, 2] - m[1, 0] * m[0, 2]) + y[2, 2] = +(m[0, 0] * m[1, 1] - m[1, 0] * m[0, 1]) + return +} + + +@(require_results) +cofactor_matrix4x4 :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) { + for i in 0..<4 { + for j in 0..<4 { + sign: T = 1 if (i + j) % 2 == 0 else -1 + y[i, j] = sign * matrix_minor(x, i, j) + } + } + return +} + +@(require_results) +inverse_transpose_matrix1x1 :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) { + y[0, 0] = 1/x[0, 0] + return +} + +@(require_results) +inverse_transpose_matrix2x2 :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) { + d := x[0, 0]*x[1, 1] - x[0, 1]*x[1, 0] + when intrinsics.type_is_integer(T) { + y[0, 0] = +x[1, 1] / d + y[1, 0] = -x[0, 1] / d + y[0, 1] = -x[1, 0] / d + y[1, 1] = +x[0, 0] / d + } else { + id := 1 / d + y[0, 0] = +x[1, 1] * id + y[1, 0] = -x[0, 1] * id + y[0, 1] = -x[1, 0] * id + y[1, 1] = +x[0, 0] * id + } + return +} + +@(require_results) +inverse_transpose_matrix3x3 :: proc "contextless" (x: $M/matrix[3, 3]$T) -> (y: M) #no_bounds_check { + c := cofactor(x) + d := determinant(x) + when intrinsics.type_is_integer(T) { + for i in 0..<3 { + for j in 0..<3 { + y[i, j] = c[i, j] / d + } + } + } else { + id := 1/d + for i in 0..<3 { + for j in 0..<3 { + y[i, j] = c[i, j] * id + } + } + } + return +} + +@(require_results) +inverse_transpose_matrix4x4 :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) #no_bounds_check { + c := cofactor(x) + d: T + for i in 0..<4 { + d += x[0, i] * c[0, i] + } + when intrinsics.type_is_integer(T) { + for i in 0..<4 { + for j in 0..<4 { + y[i, j] = c[i, j] / d + } + } + } else { + id := 1/d + for i in 0..<4 { + for j in 0..<4 { + y[i, j] = c[i, j] * id + } + } + } + return +} + +@(require_results) +inverse_matrix1x1 :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) { + y[0, 0] = 1/x[0, 0] + return +} + +@(require_results) +inverse_matrix2x2 :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) { + d := x[0, 0]*x[1, 1] - x[0, 1]*x[1, 0] + when intrinsics.type_is_integer(T) { + y[0, 0] = +x[1, 1] / d + y[0, 1] = -x[0, 1] / d + y[1, 0] = -x[1, 0] / d + y[1, 1] = +x[0, 0] / d + } else { + id := 1 / d + y[0, 0] = +x[1, 1] * id + y[0, 1] = -x[0, 1] * id + y[1, 0] = -x[1, 0] * id + y[1, 1] = +x[0, 0] * id + } + return +} + +@(require_results) +inverse_matrix3x3 :: proc "contextless" (x: $M/matrix[3, 3]$T) -> (y: M) #no_bounds_check { + c := cofactor(x) + d := determinant(x) + when intrinsics.type_is_integer(T) { + for i in 0..<3 { + for j in 0..<3 { + y[i, j] = c[j, i] / d + } + } + } else { + id := 1/d + for i in 0..<3 { + for j in 0..<3 { + y[i, j] = c[j, i] * id + } + } + } + return +} + +@(require_results) +inverse_matrix4x4 :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) #no_bounds_check { + c := cofactor(x) + d: T + for i in 0..<4 { + d += x[0, i] * c[0, i] + } + when intrinsics.type_is_integer(T) { + for i in 0..<4 { + for j in 0..<4 { + y[i, j] = c[j, i] / d + } + } + } else { + id := 1/d + for i in 0..<4 { + for j in 0..<4 { + y[i, j] = c[j, i] * id + } + } + } + return } -transpose :: builtin.transpose -inverse_transpose :: builtin.inverse_transpose -adjugate :: builtin.adjugate -hermitian_adjoint :: builtin.hermitian_adjoint -minor :: builtin.matrix_minor -determinant :: builtin.determinant -trace :: builtin.matrix_trace \ No newline at end of file diff --git a/core/math/linalg/glsl/linalg_glsl_math.odin b/core/math/linalg/glsl/linalg_glsl_math.odin index 82b1857ab..b4461ca3b 100644 --- a/core/math/linalg/glsl/linalg_glsl_math.odin +++ b/core/math/linalg/glsl/linalg_glsl_math.odin @@ -23,6 +23,7 @@ import "core:math" @(require_results) exp2_f32 :: proc "c" (x: f32) -> f32 { return math.pow(f32(2), x) } @(require_results) sign_f32 :: proc "c" (x: f32) -> f32 { return math.sign(x) } @(require_results) floor_f32 :: proc "c" (x: f32) -> f32 { return math.floor(x) } +@(require_results) trunc_f32 :: proc "c" (x: f32) -> f32 { return math.trunc(x) } @(require_results) round_f32 :: proc "c" (x: f32) -> f32 { return math.round(x) } @(require_results) ceil_f32 :: proc "c" (x: f32) -> f32 { return math.ceil(x) } @(require_results) mod_f32 :: proc "c" (x, y: f32) -> f32 { return math.mod(x, y) } @@ -55,6 +56,7 @@ fract_f32 :: proc "c" (x: f32) -> f32 { @(require_results) exp2_f64 :: proc "c" (x: f64) -> f64 { return math.pow(f64(2), x) } @(require_results) sign_f64 :: proc "c" (x: f64) -> f64 { return math.sign(x) } @(require_results) floor_f64 :: proc "c" (x: f64) -> f64 { return math.floor(x) } +@(require_results) trunc_f64 :: proc "c" (x: f64) -> f64 { return math.trunc(x) } @(require_results) round_f64 :: proc "c" (x: f64) -> f64 { return math.round(x) } @(require_results) ceil_f64 :: proc "c" (x: f64) -> f64 { return math.ceil(x) } @(require_results) mod_f64 :: proc "c" (x, y: f64) -> f64 { return math.mod(x, y) } diff --git a/core/math/linalg/hlsl/linalg_hlsl.odin b/core/math/linalg/hlsl/linalg_hlsl.odin index 351aa7ea3..cca70f9c8 100644 --- a/core/math/linalg/hlsl/linalg_hlsl.odin +++ b/core/math/linalg/hlsl/linalg_hlsl.odin @@ -1,7 +1,8 @@ // core:math/linalg/hlsl implements a HLSL-like mathematics library plus numerous other utility procedures package math_linalg_hlsl -import "core:builtin" +import "base:builtin" +import "base:intrinsics" TAU :: 6.28318530717958647692528676655900576 PI :: 3.14159265358979323846264338327950288 @@ -20,89 +21,89 @@ LN10 :: 2.30258509299404568401799145468436421 FLOAT_EPSILON :: 1e-7 DOUBLE_EPSILON :: 1e-15 -// Aliases (not distinct) of types +// Aliases (not distict) of types float :: f32 double :: f64 int :: builtin.i32 uint :: builtin.u32 // Odin matrices are stored internally as Column-Major, which matches the internal layout of HLSL by default -float1x1 :: distinct matrix[1, 1]float -float2x2 :: distinct matrix[2, 2]float -float3x3 :: distinct matrix[3, 3]float -float4x4 :: distinct matrix[4, 4]float +float1x1 :: matrix[1, 1]float +float2x2 :: matrix[2, 2]float +float3x3 :: matrix[3, 3]float +float4x4 :: matrix[4, 4]float -float1x2 :: distinct matrix[1, 2]float -float1x3 :: distinct matrix[1, 3]float -float1x4 :: distinct matrix[1, 4]float -float2x1 :: distinct matrix[2, 1]float -float2x3 :: distinct matrix[2, 3]float -float2x4 :: distinct matrix[2, 4]float -float3x1 :: distinct matrix[3, 1]float -float3x2 :: distinct matrix[3, 2]float -float3x4 :: distinct matrix[3, 4]float -float4x1 :: distinct matrix[4, 1]float -float4x2 :: distinct matrix[4, 2]float -float4x3 :: distinct matrix[4, 3]float +float1x2 :: matrix[1, 2]float +float1x3 :: matrix[1, 3]float +float1x4 :: matrix[1, 4]float +float2x1 :: matrix[2, 1]float +float2x3 :: matrix[2, 3]float +float2x4 :: matrix[2, 4]float +float3x1 :: matrix[3, 1]float +float3x2 :: matrix[3, 2]float +float3x4 :: matrix[3, 4]float +float4x1 :: matrix[4, 1]float +float4x2 :: matrix[4, 2]float +float4x3 :: matrix[4, 3]float -float2 :: distinct [2]float -float3 :: distinct [3]float -float4 :: distinct [4]float +float2 :: [2]float +float3 :: [3]float +float4 :: [4]float -int2 :: distinct [2]int -int3 :: distinct [3]int -int4 :: distinct [4]int +int2 :: [2]int +int3 :: [3]int +int4 :: [4]int -uint2 :: distinct [2]uint -uint3 :: distinct [3]uint -uint4 :: distinct [4]uint +uint2 :: [2]uint +uint3 :: [3]uint +uint4 :: [4]uint -bool2 :: distinct [2]bool -bool3 :: distinct [3]bool -bool4 :: distinct [4]bool +bool2 :: [2]bool +bool3 :: [3]bool +bool4 :: [4]bool // Double Precision (double) Floating Point Types -double1x1 :: distinct matrix[1, 1]double -double2x2 :: distinct matrix[2, 2]double -double3x3 :: distinct matrix[3, 3]double -double4x4 :: distinct matrix[4, 4]double +double1x1 :: matrix[1, 1]double +double2x2 :: matrix[2, 2]double +double3x3 :: matrix[3, 3]double +double4x4 :: matrix[4, 4]double -double1x2 :: distinct matrix[1, 2]double -double1x3 :: distinct matrix[1, 3]double -double1x4 :: distinct matrix[1, 4]double -double2x1 :: distinct matrix[2, 1]double -double2x3 :: distinct matrix[2, 3]double -double2x4 :: distinct matrix[2, 4]double -double3x1 :: distinct matrix[3, 1]double -double3x2 :: distinct matrix[3, 2]double -double3x4 :: distinct matrix[3, 4]double -double4x1 :: distinct matrix[4, 1]double -double4x2 :: distinct matrix[4, 2]double -double4x3 :: distinct matrix[4, 3]double +double1x2 :: matrix[1, 2]double +double1x3 :: matrix[1, 3]double +double1x4 :: matrix[1, 4]double +double2x1 :: matrix[2, 1]double +double2x3 :: matrix[2, 3]double +double2x4 :: matrix[2, 4]double +double3x1 :: matrix[3, 1]double +double3x2 :: matrix[3, 2]double +double3x4 :: matrix[3, 4]double +double4x1 :: matrix[4, 1]double +double4x2 :: matrix[4, 2]double +double4x3 :: matrix[4, 3]double -double2 :: distinct [2]double -double3 :: distinct [3]double -double4 :: distinct [4]double +double2 :: [2]double +double3 :: [3]double +double4 :: [4]double -int1x1 :: distinct matrix[1, 1]int -int2x2 :: distinct matrix[2, 2]int -int3x3 :: distinct matrix[3, 3]int -int4x4 :: distinct matrix[4, 4]int +int1x1 :: matrix[1, 1]int +int2x2 :: matrix[2, 2]int +int3x3 :: matrix[3, 3]int +int4x4 :: matrix[4, 4]int -int1x2 :: distinct matrix[1, 2]int -int1x3 :: distinct matrix[1, 3]int -int1x4 :: distinct matrix[1, 4]int -int2x1 :: distinct matrix[2, 1]int -int2x3 :: distinct matrix[2, 3]int -int2x4 :: distinct matrix[2, 4]int -int3x1 :: distinct matrix[3, 1]int -int3x2 :: distinct matrix[3, 2]int -int3x4 :: distinct matrix[3, 4]int -int4x1 :: distinct matrix[4, 1]int -int4x2 :: distinct matrix[4, 2]int -int4x3 :: distinct matrix[4, 3]int +int1x2 :: matrix[1, 2]int +int1x3 :: matrix[1, 3]int +int1x4 :: matrix[1, 4]int +int2x1 :: matrix[2, 1]int +int2x3 :: matrix[2, 3]int +int2x4 :: matrix[2, 4]int +int3x1 :: matrix[3, 1]int +int3x2 :: matrix[3, 2]int +int3x4 :: matrix[3, 4]int +int4x1 :: matrix[4, 1]int +int4x2 :: matrix[4, 2]int +int4x3 :: matrix[4, 3]int cos :: proc{ cos_float, @@ -1471,14 +1472,14 @@ not :: proc{ -@(require_results) inverse_float1x1 :: proc "c" (m: float1x1) -> float1x1 { return builtin.inverse(m) } -@(require_results) inverse_float2x2 :: proc "c" (m: float2x2) -> float2x2 { return builtin.inverse(m) } -@(require_results) inverse_float3x3 :: proc "c" (m: float3x3) -> float3x3 { return builtin.inverse(m) } -@(require_results) inverse_float4x4 :: proc "c" (m: float4x4) -> float4x4 { return builtin.inverse(m) } -@(require_results) inverse_double1x1 :: proc "c" (m: double1x1) -> double1x1 { return builtin.inverse(m) } -@(require_results) inverse_double2x2 :: proc "c" (m: double2x2) -> double2x2 { return builtin.inverse(m) } -@(require_results) inverse_double3x3 :: proc "c" (m: double3x3) -> double3x3 { return builtin.inverse(m) } -@(require_results) inverse_double4x4 :: proc "c" (m: double4x4) -> double4x4 { return builtin.inverse(m) } +@(require_results) inverse_float1x1 :: proc "c" (m: float1x1) -> float1x1 { return inverse_matrix1x1(m) } +@(require_results) inverse_float2x2 :: proc "c" (m: float2x2) -> float2x2 { return inverse_matrix2x2(m) } +@(require_results) inverse_float3x3 :: proc "c" (m: float3x3) -> float3x3 { return inverse_matrix3x3(m) } +@(require_results) inverse_float4x4 :: proc "c" (m: float4x4) -> float4x4 { return inverse_matrix4x4(m) } +@(require_results) inverse_double1x1 :: proc "c" (m: double1x1) -> double1x1 { return inverse_matrix1x1(m) } +@(require_results) inverse_double2x2 :: proc "c" (m: double2x2) -> double2x2 { return inverse_matrix2x2(m) } +@(require_results) inverse_double3x3 :: proc "c" (m: double3x3) -> double3x3 { return inverse_matrix3x3(m) } +@(require_results) inverse_double4x4 :: proc "c" (m: double4x4) -> double4x4 { return inverse_matrix4x4(m) } inverse :: proc{ inverse_float1x1, @@ -1489,15 +1490,323 @@ inverse :: proc{ inverse_double2x2, inverse_double3x3, inverse_double4x4, + + inverse_matrix1x1, + inverse_matrix2x2, + inverse_matrix3x3, + inverse_matrix4x4, } -transpose :: builtin.transpose -inverse_transpose :: builtin.inverse_transpose -adjugate :: builtin.adjugate -hermitian_adjoint :: builtin.hermitian_adjoint -minor :: builtin.matrix_minor -determinant :: builtin.determinant -trace :: builtin.matrix_trace +transpose :: intrinsics.transpose + + +determinant :: proc{ + determinant_matrix1x1, + determinant_matrix2x2, + determinant_matrix3x3, + determinant_matrix4x4, +} + +adjugate :: proc{ + adjugate_matrix1x1, + adjugate_matrix2x2, + adjugate_matrix3x3, + adjugate_matrix4x4, +} + +cofactor :: proc{ + cofactor_matrix1x1, + cofactor_matrix2x2, + cofactor_matrix3x3, + cofactor_matrix4x4, +} + +inverse_transpose :: proc{ + inverse_transpose_matrix1x1, + inverse_transpose_matrix2x2, + inverse_transpose_matrix3x3, + inverse_transpose_matrix4x4, +} + +@(require_results) +hermitian_adjoint :: proc "contextless" (m: $M/matrix[$N, N]$T) -> M where intrinsics.type_is_complex(T), N >= 1 { + return conj(transpose(m)) +} + +@(require_results) +trace :: proc "contextless" (m: $M/matrix[$N, N]$T) -> (trace: T) { + for i in 0.. (minor: T) where N > 1 { + K :: int(N-1) + cut_down: matrix[K, K]T + for col_idx in 0..= column) + for row_idx in 0..= row) + cut_down[row_idx, col_idx] = m[i, j] + } + } + return determinant(cut_down) +} + + + +@(require_results) +determinant_matrix1x1 :: proc "contextless" (m: $M/matrix[1, 1]$T) -> (det: T) { + return m[0, 0] +} + +@(require_results) +determinant_matrix2x2 :: proc "contextless" (m: $M/matrix[2, 2]$T) -> (det: T) { + return m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] +} +@(require_results) +determinant_matrix3x3 :: proc "contextless" (m: $M/matrix[3, 3]$T) -> (det: T) { + a := +m[0, 0] * (m[1, 1] * m[2, 2] - m[1, 2] * m[2, 1]) + b := -m[0, 1] * (m[1, 0] * m[2, 2] - m[1, 2] * m[2, 0]) + c := +m[0, 2] * (m[1, 0] * m[2, 1] - m[1, 1] * m[2, 0]) + return a + b + c +} +@(require_results) +determinant_matrix4x4 :: proc "contextless" (m: $M/matrix[4, 4]$T) -> (det: T) { + c := cofactor(m) + #no_bounds_check for i in 0..<4 { + det += m[0, i] * c[0, i] + } + return +} + + + + +@(require_results) +adjugate_matrix1x1 :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) { + y = x + return +} + +@(require_results) +adjugate_matrix2x2 :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) { + y[0, 0] = +x[1, 1] + y[0, 1] = -x[0, 1] + y[1, 0] = -x[1, 0] + y[1, 1] = +x[0, 0] + return +} + +@(require_results) +adjugate_matrix3x3 :: proc "contextless" (m: $M/matrix[3, 3]$T) -> (y: M) { + y[0, 0] = +(m[1, 1] * m[2, 2] - m[2, 1] * m[1, 2]) + y[1, 0] = -(m[1, 0] * m[2, 2] - m[2, 0] * m[1, 2]) + y[2, 0] = +(m[1, 0] * m[2, 1] - m[2, 0] * m[1, 1]) + y[0, 1] = -(m[0, 1] * m[2, 2] - m[2, 1] * m[0, 2]) + y[1, 1] = +(m[0, 0] * m[2, 2] - m[2, 0] * m[0, 2]) + y[2, 1] = -(m[0, 0] * m[2, 1] - m[2, 0] * m[0, 1]) + y[0, 2] = +(m[0, 1] * m[1, 2] - m[1, 1] * m[0, 2]) + y[1, 2] = -(m[0, 0] * m[1, 2] - m[1, 0] * m[0, 2]) + y[2, 2] = +(m[0, 0] * m[1, 1] - m[1, 0] * m[0, 1]) + return +} + +@(require_results) +adjugate_matrix4x4 :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) { + for i in 0..<4 { + for j in 0..<4 { + sign: T = 1 if (i + j) % 2 == 0 else -1 + y[i, j] = sign * matrix_minor(x, j, i) + } + } + return +} + + +@(require_results) +cofactor_matrix1x1 :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) { + y = x + return +} + +@(require_results) +cofactor_matrix2x2 :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) { + y[0, 0] = +x[1, 1] + y[0, 1] = -x[1, 0] + y[1, 0] = -x[0, 1] + y[1, 1] = +x[0, 0] + return +} + +@(require_results) +cofactor_matrix3x3 :: proc "contextless" (m: $M/matrix[3, 3]$T) -> (y: M) { + y[0, 0] = +(m[1, 1] * m[2, 2] - m[2, 1] * m[1, 2]) + y[0, 1] = -(m[1, 0] * m[2, 2] - m[2, 0] * m[1, 2]) + y[0, 2] = +(m[1, 0] * m[2, 1] - m[2, 0] * m[1, 1]) + y[1, 0] = -(m[0, 1] * m[2, 2] - m[2, 1] * m[0, 2]) + y[1, 1] = +(m[0, 0] * m[2, 2] - m[2, 0] * m[0, 2]) + y[1, 2] = -(m[0, 0] * m[2, 1] - m[2, 0] * m[0, 1]) + y[2, 0] = +(m[0, 1] * m[1, 2] - m[1, 1] * m[0, 2]) + y[2, 1] = -(m[0, 0] * m[1, 2] - m[1, 0] * m[0, 2]) + y[2, 2] = +(m[0, 0] * m[1, 1] - m[1, 0] * m[0, 1]) + return +} + + +@(require_results) +cofactor_matrix4x4 :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) { + for i in 0..<4 { + for j in 0..<4 { + sign: T = 1 if (i + j) % 2 == 0 else -1 + y[i, j] = sign * matrix_minor(x, i, j) + } + } + return +} + +@(require_results) +inverse_transpose_matrix1x1 :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) { + y[0, 0] = 1/x[0, 0] + return +} + +@(require_results) +inverse_transpose_matrix2x2 :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) { + d := x[0, 0]*x[1, 1] - x[0, 1]*x[1, 0] + when intrinsics.type_is_integer(T) { + y[0, 0] = +x[1, 1] / d + y[1, 0] = -x[0, 1] / d + y[0, 1] = -x[1, 0] / d + y[1, 1] = +x[0, 0] / d + } else { + id := 1 / d + y[0, 0] = +x[1, 1] * id + y[1, 0] = -x[0, 1] * id + y[0, 1] = -x[1, 0] * id + y[1, 1] = +x[0, 0] * id + } + return +} + +@(require_results) +inverse_transpose_matrix3x3 :: proc "contextless" (x: $M/matrix[3, 3]$T) -> (y: M) #no_bounds_check { + c := cofactor(x) + d := determinant(x) + when intrinsics.type_is_integer(T) { + for i in 0..<3 { + for j in 0..<3 { + y[i, j] = c[i, j] / d + } + } + } else { + id := 1/d + for i in 0..<3 { + for j in 0..<3 { + y[i, j] = c[i, j] * id + } + } + } + return +} + +@(require_results) +inverse_transpose_matrix4x4 :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) #no_bounds_check { + c := cofactor(x) + d: T + for i in 0..<4 { + d += x[0, i] * c[0, i] + } + when intrinsics.type_is_integer(T) { + for i in 0..<4 { + for j in 0..<4 { + y[i, j] = c[i, j] / d + } + } + } else { + id := 1/d + for i in 0..<4 { + for j in 0..<4 { + y[i, j] = c[i, j] * id + } + } + } + return +} + +@(require_results) +inverse_matrix1x1 :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) { + y[0, 0] = 1/x[0, 0] + return +} + +@(require_results) +inverse_matrix2x2 :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) { + d := x[0, 0]*x[1, 1] - x[0, 1]*x[1, 0] + when intrinsics.type_is_integer(T) { + y[0, 0] = +x[1, 1] / d + y[0, 1] = -x[0, 1] / d + y[1, 0] = -x[1, 0] / d + y[1, 1] = +x[0, 0] / d + } else { + id := 1 / d + y[0, 0] = +x[1, 1] * id + y[0, 1] = -x[0, 1] * id + y[1, 0] = -x[1, 0] * id + y[1, 1] = +x[0, 0] * id + } + return +} + +@(require_results) +inverse_matrix3x3 :: proc "contextless" (x: $M/matrix[3, 3]$T) -> (y: M) #no_bounds_check { + c := cofactor(x) + d := determinant(x) + when intrinsics.type_is_integer(T) { + for i in 0..<3 { + for j in 0..<3 { + y[i, j] = c[j, i] / d + } + } + } else { + id := 1/d + for i in 0..<3 { + for j in 0..<3 { + y[i, j] = c[j, i] * id + } + } + } + return +} + +@(require_results) +inverse_matrix4x4 :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) #no_bounds_check { + c := cofactor(x) + d: T + for i in 0..<4 { + d += x[0, i] * c[0, i] + } + when intrinsics.type_is_integer(T) { + for i in 0..<4 { + for j in 0..<4 { + y[i, j] = c[j, i] / d + } + } + } else { + id := 1/d + for i in 0..<4 { + for j in 0..<4 { + y[i, j] = c[j, i] * id + } + } + } + return +} + + + asfloat :: proc{ asfloat_float, diff --git a/core/math/linalg/specific.odin b/core/math/linalg/specific.odin index 6607b241f..b841f0610 100644 --- a/core/math/linalg/specific.odin +++ b/core/math/linalg/specific.odin @@ -1,102 +1,102 @@ package linalg -import "core:builtin" +import "base:builtin" import "core:math" F16_EPSILON :: 1e-3 F32_EPSILON :: 1e-7 F64_EPSILON :: 1e-15 -Vector2f16 :: distinct [2]f16 -Vector3f16 :: distinct [3]f16 -Vector4f16 :: distinct [4]f16 +Vector2f16 :: [2]f16 +Vector3f16 :: [3]f16 +Vector4f16 :: [4]f16 -Matrix1x1f16 :: distinct matrix[1, 1]f16 -Matrix1x2f16 :: distinct matrix[1, 2]f16 -Matrix1x3f16 :: distinct matrix[1, 3]f16 -Matrix1x4f16 :: distinct matrix[1, 4]f16 +Matrix1x1f16 :: matrix[1, 1]f16 +Matrix1x2f16 :: matrix[1, 2]f16 +Matrix1x3f16 :: matrix[1, 3]f16 +Matrix1x4f16 :: matrix[1, 4]f16 -Matrix2x1f16 :: distinct matrix[2, 1]f16 -Matrix2x2f16 :: distinct matrix[2, 2]f16 -Matrix2x3f16 :: distinct matrix[2, 3]f16 -Matrix2x4f16 :: distinct matrix[2, 4]f16 +Matrix2x1f16 :: matrix[2, 1]f16 +Matrix2x2f16 :: matrix[2, 2]f16 +Matrix2x3f16 :: matrix[2, 3]f16 +Matrix2x4f16 :: matrix[2, 4]f16 -Matrix3x1f16 :: distinct matrix[3, 1]f16 -Matrix3x2f16 :: distinct matrix[3, 2]f16 -Matrix3x3f16 :: distinct matrix[3, 3]f16 -Matrix3x4f16 :: distinct matrix[3, 4]f16 +Matrix3x1f16 :: matrix[3, 1]f16 +Matrix3x2f16 :: matrix[3, 2]f16 +Matrix3x3f16 :: matrix[3, 3]f16 +Matrix3x4f16 :: matrix[3, 4]f16 -Matrix4x1f16 :: distinct matrix[4, 1]f16 -Matrix4x2f16 :: distinct matrix[4, 2]f16 -Matrix4x3f16 :: distinct matrix[4, 3]f16 -Matrix4x4f16 :: distinct matrix[4, 4]f16 +Matrix4x1f16 :: matrix[4, 1]f16 +Matrix4x2f16 :: matrix[4, 2]f16 +Matrix4x3f16 :: matrix[4, 3]f16 +Matrix4x4f16 :: matrix[4, 4]f16 Matrix1f16 :: Matrix1x1f16 Matrix2f16 :: Matrix2x2f16 Matrix3f16 :: Matrix3x3f16 Matrix4f16 :: Matrix4x4f16 -Vector2f32 :: distinct [2]f32 -Vector3f32 :: distinct [3]f32 -Vector4f32 :: distinct [4]f32 +Vector2f32 :: [2]f32 +Vector3f32 :: [3]f32 +Vector4f32 :: [4]f32 -Matrix1x1f32 :: distinct matrix[1, 1]f32 -Matrix1x2f32 :: distinct matrix[1, 2]f32 -Matrix1x3f32 :: distinct matrix[1, 3]f32 -Matrix1x4f32 :: distinct matrix[1, 4]f32 +Matrix1x1f32 :: matrix[1, 1]f32 +Matrix1x2f32 :: matrix[1, 2]f32 +Matrix1x3f32 :: matrix[1, 3]f32 +Matrix1x4f32 :: matrix[1, 4]f32 -Matrix2x1f32 :: distinct matrix[2, 1]f32 -Matrix2x2f32 :: distinct matrix[2, 2]f32 -Matrix2x3f32 :: distinct matrix[2, 3]f32 -Matrix2x4f32 :: distinct matrix[2, 4]f32 +Matrix2x1f32 :: matrix[2, 1]f32 +Matrix2x2f32 :: matrix[2, 2]f32 +Matrix2x3f32 :: matrix[2, 3]f32 +Matrix2x4f32 :: matrix[2, 4]f32 -Matrix3x1f32 :: distinct matrix[3, 1]f32 -Matrix3x2f32 :: distinct matrix[3, 2]f32 -Matrix3x3f32 :: distinct matrix[3, 3]f32 -Matrix3x4f32 :: distinct matrix[3, 4]f32 +Matrix3x1f32 :: matrix[3, 1]f32 +Matrix3x2f32 :: matrix[3, 2]f32 +Matrix3x3f32 :: matrix[3, 3]f32 +Matrix3x4f32 :: matrix[3, 4]f32 -Matrix4x1f32 :: distinct matrix[4, 1]f32 -Matrix4x2f32 :: distinct matrix[4, 2]f32 -Matrix4x3f32 :: distinct matrix[4, 3]f32 -Matrix4x4f32 :: distinct matrix[4, 4]f32 +Matrix4x1f32 :: matrix[4, 1]f32 +Matrix4x2f32 :: matrix[4, 2]f32 +Matrix4x3f32 :: matrix[4, 3]f32 +Matrix4x4f32 :: matrix[4, 4]f32 Matrix1f32 :: Matrix1x1f32 Matrix2f32 :: Matrix2x2f32 Matrix3f32 :: Matrix3x3f32 Matrix4f32 :: Matrix4x4f32 -Vector2f64 :: distinct [2]f64 -Vector3f64 :: distinct [3]f64 -Vector4f64 :: distinct [4]f64 +Vector2f64 :: [2]f64 +Vector3f64 :: [3]f64 +Vector4f64 :: [4]f64 -Matrix1x1f64 :: distinct matrix[1, 1]f64 -Matrix1x2f64 :: distinct matrix[1, 2]f64 -Matrix1x3f64 :: distinct matrix[1, 3]f64 -Matrix1x4f64 :: distinct matrix[1, 4]f64 +Matrix1x1f64 :: matrix[1, 1]f64 +Matrix1x2f64 :: matrix[1, 2]f64 +Matrix1x3f64 :: matrix[1, 3]f64 +Matrix1x4f64 :: matrix[1, 4]f64 -Matrix2x1f64 :: distinct matrix[2, 1]f64 -Matrix2x2f64 :: distinct matrix[2, 2]f64 -Matrix2x3f64 :: distinct matrix[2, 3]f64 -Matrix2x4f64 :: distinct matrix[2, 4]f64 +Matrix2x1f64 :: matrix[2, 1]f64 +Matrix2x2f64 :: matrix[2, 2]f64 +Matrix2x3f64 :: matrix[2, 3]f64 +Matrix2x4f64 :: matrix[2, 4]f64 -Matrix3x1f64 :: distinct matrix[3, 1]f64 -Matrix3x2f64 :: distinct matrix[3, 2]f64 -Matrix3x3f64 :: distinct matrix[3, 3]f64 -Matrix3x4f64 :: distinct matrix[3, 4]f64 +Matrix3x1f64 :: matrix[3, 1]f64 +Matrix3x2f64 :: matrix[3, 2]f64 +Matrix3x3f64 :: matrix[3, 3]f64 +Matrix3x4f64 :: matrix[3, 4]f64 -Matrix4x1f64 :: distinct matrix[4, 1]f64 -Matrix4x2f64 :: distinct matrix[4, 2]f64 -Matrix4x3f64 :: distinct matrix[4, 3]f64 -Matrix4x4f64 :: distinct matrix[4, 4]f64 +Matrix4x1f64 :: matrix[4, 1]f64 +Matrix4x2f64 :: matrix[4, 2]f64 +Matrix4x3f64 :: matrix[4, 3]f64 +Matrix4x4f64 :: matrix[4, 4]f64 Matrix1f64 :: Matrix1x1f64 Matrix2f64 :: Matrix2x2f64 Matrix3f64 :: Matrix3x3f64 Matrix4f64 :: Matrix4x4f64 -Quaternionf16 :: distinct quaternion64 -Quaternionf32 :: distinct quaternion128 -Quaternionf64 :: distinct quaternion256 +Quaternionf16 :: quaternion64 +Quaternionf32 :: quaternion128 +Quaternionf64 :: quaternion256 MATRIX1F16_IDENTITY :: Matrix1f16(1) MATRIX2F16_IDENTITY :: Matrix2f16(1) @@ -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) @@ -584,7 +584,7 @@ angle_axis_from_quaternion :: proc { @(require_results) -quaternion_from_forward_and_up_f16 :: proc "contextless" (forward, up: Vector3f16) -> Quaternionf16 { +quaternion_from_forward_and_up_f16 :: proc "contextless" (forward, up: Vector3f16) -> Quaternionf16 #no_bounds_check { f := normalize(forward) s := normalize(cross(f, up)) u := cross(s, f) @@ -628,7 +628,7 @@ quaternion_from_forward_and_up_f16 :: proc "contextless" (forward, up: Vector3f1 return normalize(q) } @(require_results) -quaternion_from_forward_and_up_f32 :: proc "contextless" (forward, up: Vector3f32) -> Quaternionf32 { +quaternion_from_forward_and_up_f32 :: proc "contextless" (forward, up: Vector3f32) -> Quaternionf32 #no_bounds_check { f := normalize(forward) s := normalize(cross(f, up)) u := cross(s, f) @@ -672,7 +672,7 @@ quaternion_from_forward_and_up_f32 :: proc "contextless" (forward, up: Vector3f3 return normalize(q) } @(require_results) -quaternion_from_forward_and_up_f64 :: proc "contextless" (forward, up: Vector3f64) -> Quaternionf64 { +quaternion_from_forward_and_up_f64 :: proc "contextless" (forward, up: Vector3f64) -> Quaternionf64 #no_bounds_check { f := normalize(forward) s := normalize(cross(f, up)) u := cross(s, f) @@ -886,7 +886,7 @@ quaternion_squad :: proc{ @(require_results) -quaternion_from_matrix4_f16 :: proc "contextless" (m: Matrix4f16) -> (q: Quaternionf16) { +quaternion_from_matrix4_f16 :: proc "contextless" (m: Matrix4f16) -> (q: Quaternionf16) #no_bounds_check { m3: Matrix3f16 = --- m3[0, 0], m3[1, 0], m3[2, 0] = m[0, 0], m[1, 0], m[2, 0] m3[0, 1], m3[1, 1], m3[2, 1] = m[0, 1], m[1, 1], m[2, 1] @@ -894,7 +894,7 @@ quaternion_from_matrix4_f16 :: proc "contextless" (m: Matrix4f16) -> (q: Quatern return quaternion_from_matrix3(m3) } @(require_results) -quaternion_from_matrix4_f32 :: proc "contextless" (m: Matrix4f32) -> (q: Quaternionf32) { +quaternion_from_matrix4_f32 :: proc "contextless" (m: Matrix4f32) -> (q: Quaternionf32) #no_bounds_check { m3: Matrix3f32 = --- m3[0, 0], m3[1, 0], m3[2, 0] = m[0, 0], m[1, 0], m[2, 0] m3[0, 1], m3[1, 1], m3[2, 1] = m[0, 1], m[1, 1], m[2, 1] @@ -902,7 +902,7 @@ quaternion_from_matrix4_f32 :: proc "contextless" (m: Matrix4f32) -> (q: Quatern return quaternion_from_matrix3(m3) } @(require_results) -quaternion_from_matrix4_f64 :: proc "contextless" (m: Matrix4f64) -> (q: Quaternionf64) { +quaternion_from_matrix4_f64 :: proc "contextless" (m: Matrix4f64) -> (q: Quaternionf64) #no_bounds_check { m3: Matrix3f64 = --- m3[0, 0], m3[1, 0], m3[2, 0] = m[0, 0], m[1, 0], m[2, 0] m3[0, 1], m3[1, 1], m3[2, 1] = m[0, 1], m[1, 1], m[2, 1] @@ -917,7 +917,7 @@ quaternion_from_matrix4 :: proc{ @(require_results) -quaternion_from_matrix3_f16 :: proc "contextless" (m: Matrix3f16) -> (q: Quaternionf16) { +quaternion_from_matrix3_f16 :: proc "contextless" (m: Matrix3f16) -> (q: Quaternionf16) #no_bounds_check { four_x_squared_minus_1 := m[0, 0] - m[1, 1] - m[2, 2] four_y_squared_minus_1 := m[1, 1] - m[0, 0] - m[2, 2] four_z_squared_minus_1 := m[2, 2] - m[0, 0] - m[1, 1] @@ -967,7 +967,7 @@ quaternion_from_matrix3_f16 :: proc "contextless" (m: Matrix3f16) -> (q: Quatern return } @(require_results) -quaternion_from_matrix3_f32 :: proc "contextless" (m: Matrix3f32) -> (q: Quaternionf32) { +quaternion_from_matrix3_f32 :: proc "contextless" (m: Matrix3f32) -> (q: Quaternionf32) #no_bounds_check { four_x_squared_minus_1 := m[0, 0] - m[1, 1] - m[2, 2] four_y_squared_minus_1 := m[1, 1] - m[0, 0] - m[2, 2] four_z_squared_minus_1 := m[2, 2] - m[0, 0] - m[1, 1] @@ -1017,7 +1017,7 @@ quaternion_from_matrix3_f32 :: proc "contextless" (m: Matrix3f32) -> (q: Quatern return } @(require_results) -quaternion_from_matrix3_f64 :: proc "contextless" (m: Matrix3f64) -> (q: Quaternionf64) { +quaternion_from_matrix3_f64 :: proc "contextless" (m: Matrix3f64) -> (q: Quaternionf64) #no_bounds_check { four_x_squared_minus_1 := m[0, 0] - m[1, 1] - m[2, 2] four_y_squared_minus_1 := m[1, 1] - m[0, 0] - m[2, 2] four_z_squared_minus_1 := m[2, 2] - m[0, 0] - m[1, 1] @@ -1147,7 +1147,7 @@ quaternion_between_two_vector3 :: proc{ @(require_results) -matrix2_inverse_transpose_f16 :: proc "contextless" (m: Matrix2f16) -> (c: Matrix2f16) { +matrix2_inverse_transpose_f16 :: proc "contextless" (m: Matrix2f16) -> (c: Matrix2f16) #no_bounds_check { d := m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] id := 1.0/d c[0, 0] = +m[1, 1] * id @@ -1157,7 +1157,7 @@ matrix2_inverse_transpose_f16 :: proc "contextless" (m: Matrix2f16) -> (c: Matri return c } @(require_results) -matrix2_inverse_transpose_f32 :: proc "contextless" (m: Matrix2f32) -> (c: Matrix2f32) { +matrix2_inverse_transpose_f32 :: proc "contextless" (m: Matrix2f32) -> (c: Matrix2f32) #no_bounds_check { d := m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] id := 1.0/d c[0, 0] = +m[1, 1] * id @@ -1167,7 +1167,7 @@ matrix2_inverse_transpose_f32 :: proc "contextless" (m: Matrix2f32) -> (c: Matri return c } @(require_results) -matrix2_inverse_transpose_f64 :: proc "contextless" (m: Matrix2f64) -> (c: Matrix2f64) { +matrix2_inverse_transpose_f64 :: proc "contextless" (m: Matrix2f64) -> (c: Matrix2f64) #no_bounds_check { d := m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] id := 1.0/d c[0, 0] = +m[1, 1] * id @@ -1184,15 +1184,15 @@ matrix2_inverse_transpose :: proc{ @(require_results) -matrix2_determinant_f16 :: proc "contextless" (m: Matrix2f16) -> f16 { +matrix2_determinant_f16 :: proc "contextless" (m: Matrix2f16) -> f16 #no_bounds_check { return m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] } @(require_results) -matrix2_determinant_f32 :: proc "contextless" (m: Matrix2f32) -> f32 { +matrix2_determinant_f32 :: proc "contextless" (m: Matrix2f32) -> f32 #no_bounds_check { return m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] } @(require_results) -matrix2_determinant_f64 :: proc "contextless" (m: Matrix2f64) -> f64 { +matrix2_determinant_f64 :: proc "contextless" (m: Matrix2f64) -> f64 #no_bounds_check { return m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] } matrix2_determinant :: proc{ @@ -1203,7 +1203,7 @@ matrix2_determinant :: proc{ @(require_results) -matrix2_inverse_f16 :: proc "contextless" (m: Matrix2f16) -> (c: Matrix2f16) { +matrix2_inverse_f16 :: proc "contextless" (m: Matrix2f16) -> (c: Matrix2f16) #no_bounds_check { d := m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] id := 1.0/d c[0, 0] = +m[1, 1] * id @@ -1213,7 +1213,7 @@ matrix2_inverse_f16 :: proc "contextless" (m: Matrix2f16) -> (c: Matrix2f16) { return c } @(require_results) -matrix2_inverse_f32 :: proc "contextless" (m: Matrix2f32) -> (c: Matrix2f32) { +matrix2_inverse_f32 :: proc "contextless" (m: Matrix2f32) -> (c: Matrix2f32) #no_bounds_check { d := m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] id := 1.0/d c[0, 0] = +m[1, 1] * id @@ -1223,7 +1223,7 @@ matrix2_inverse_f32 :: proc "contextless" (m: Matrix2f32) -> (c: Matrix2f32) { return c } @(require_results) -matrix2_inverse_f64 :: proc "contextless" (m: Matrix2f64) -> (c: Matrix2f64) { +matrix2_inverse_f64 :: proc "contextless" (m: Matrix2f64) -> (c: Matrix2f64) #no_bounds_check { d := m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] id := 1.0/d c[0, 0] = +m[1, 1] * id @@ -1240,7 +1240,7 @@ matrix2_inverse :: proc{ @(require_results) -matrix2_adjoint_f16 :: proc "contextless" (m: Matrix2f16) -> (c: Matrix2f16) { +matrix2_adjoint_f16 :: proc "contextless" (m: Matrix2f16) -> (c: Matrix2f16) #no_bounds_check { c[0, 0] = +m[1, 1] c[1, 0] = -m[0, 1] c[0, 1] = -m[1, 0] @@ -1248,7 +1248,7 @@ matrix2_adjoint_f16 :: proc "contextless" (m: Matrix2f16) -> (c: Matrix2f16) { return c } @(require_results) -matrix2_adjoint_f32 :: proc "contextless" (m: Matrix2f32) -> (c: Matrix2f32) { +matrix2_adjoint_f32 :: proc "contextless" (m: Matrix2f32) -> (c: Matrix2f32) #no_bounds_check { c[0, 0] = +m[1, 1] c[1, 0] = -m[0, 1] c[0, 1] = -m[1, 0] @@ -1256,7 +1256,7 @@ matrix2_adjoint_f32 :: proc "contextless" (m: Matrix2f32) -> (c: Matrix2f32) { return c } @(require_results) -matrix2_adjoint_f64 :: proc "contextless" (m: Matrix2f64) -> (c: Matrix2f64) { +matrix2_adjoint_f64 :: proc "contextless" (m: Matrix2f64) -> (c: Matrix2f64) #no_bounds_check { c[0, 0] = +m[1, 1] c[1, 0] = -m[0, 1] c[0, 1] = -m[1, 0] @@ -1271,7 +1271,44 @@ matrix2_adjoint :: proc{ @(require_results) -matrix3_from_quaternion_f16 :: proc "contextless" (q: Quaternionf16) -> (m: Matrix3f16) { +matrix2_rotate_f16 :: proc "contextless" (angle_radians: f16) -> Matrix2f16 { + c := math.cos(angle_radians) + s := math.sin(angle_radians) + + return Matrix2f16{ + c, -s, + s, c, + } +} +@(require_results) +matrix2_rotate_f32 :: proc "contextless" (angle_radians: f32) -> Matrix2f32 { + c := math.cos(angle_radians) + s := math.sin(angle_radians) + + return Matrix2f32{ + c, -s, + s, c, + } +} +@(require_results) +matrix2_rotate_f64 :: proc "contextless" (angle_radians: f64) -> Matrix2f64 { + c := math.cos(angle_radians) + s := math.sin(angle_radians) + + return Matrix2f64{ + c, -s, + s, c, + } +} +matrix2_rotate :: proc{ + matrix2_rotate_f16, + matrix2_rotate_f32, + matrix2_rotate_f64, +} + + +@(require_results) +matrix3_from_quaternion_f16 :: proc "contextless" (q: Quaternionf16) -> (m: Matrix3f16) #no_bounds_check { qxx := q.x * q.x qyy := q.y * q.y qzz := q.z * q.z @@ -1296,7 +1333,7 @@ matrix3_from_quaternion_f16 :: proc "contextless" (q: Quaternionf16) -> (m: Matr return m } @(require_results) -matrix3_from_quaternion_f32 :: proc "contextless" (q: Quaternionf32) -> (m: Matrix3f32) { +matrix3_from_quaternion_f32 :: proc "contextless" (q: Quaternionf32) -> (m: Matrix3f32) #no_bounds_check { qxx := q.x * q.x qyy := q.y * q.y qzz := q.z * q.z @@ -1321,7 +1358,7 @@ matrix3_from_quaternion_f32 :: proc "contextless" (q: Quaternionf32) -> (m: Matr return m } @(require_results) -matrix3_from_quaternion_f64 :: proc "contextless" (q: Quaternionf64) -> (m: Matrix3f64) { +matrix3_from_quaternion_f64 :: proc "contextless" (q: Quaternionf64) -> (m: Matrix3f64) #no_bounds_check { qxx := q.x * q.x qyy := q.y * q.y qzz := q.z * q.z @@ -1372,21 +1409,21 @@ matrix3_inverse :: proc{ @(require_results) -matrix3_determinant_f16 :: proc "contextless" (m: Matrix3f16) -> f16 { +matrix3_determinant_f16 :: proc "contextless" (m: Matrix3f16) -> f16 #no_bounds_check { a := +m[0, 0] * (m[1, 1] * m[2, 2] - m[1, 2] * m[2, 1]) b := -m[0, 1] * (m[1, 0] * m[2, 2] - m[1, 2] * m[2, 0]) c := +m[0, 2] * (m[1, 0] * m[2, 1] - m[1, 1] * m[2, 0]) return a + b + c } @(require_results) -matrix3_determinant_f32 :: proc "contextless" (m: Matrix3f32) -> f32 { +matrix3_determinant_f32 :: proc "contextless" (m: Matrix3f32) -> f32 #no_bounds_check { a := +m[0, 0] * (m[1, 1] * m[2, 2] - m[1, 2] * m[2, 1]) b := -m[0, 1] * (m[1, 0] * m[2, 2] - m[1, 2] * m[2, 0]) c := +m[0, 2] * (m[1, 0] * m[2, 1] - m[1, 1] * m[2, 0]) return a + b + c } @(require_results) -matrix3_determinant_f64 :: proc "contextless" (m: Matrix3f64) -> f64 { +matrix3_determinant_f64 :: proc "contextless" (m: Matrix3f64) -> f64 #no_bounds_check { a := +m[0, 0] * (m[1, 1] * m[2, 2] - m[1, 2] * m[2, 1]) b := -m[0, 1] * (m[1, 0] * m[2, 2] - m[1, 2] * m[2, 0]) c := +m[0, 2] * (m[1, 0] * m[2, 1] - m[1, 1] * m[2, 0]) @@ -1400,7 +1437,7 @@ matrix3_determinant :: proc{ @(require_results) -matrix3_adjoint_f16 :: proc "contextless" (m: Matrix3f16) -> (adjoint: Matrix3f16) { +matrix3_adjoint_f16 :: proc "contextless" (m: Matrix3f16) -> (adjoint: Matrix3f16) #no_bounds_check { adjoint[0, 0] = +(m[1, 1] * m[2, 2] - m[2, 1] * m[1, 2]) adjoint[0, 1] = -(m[1, 0] * m[2, 2] - m[2, 0] * m[1, 2]) adjoint[0, 2] = +(m[1, 0] * m[2, 1] - m[2, 0] * m[1, 1]) @@ -1413,7 +1450,7 @@ matrix3_adjoint_f16 :: proc "contextless" (m: Matrix3f16) -> (adjoint: Matrix3f1 return adjoint } @(require_results) -matrix3_adjoint_f32 :: proc "contextless" (m: Matrix3f32) -> (adjoint: Matrix3f32) { +matrix3_adjoint_f32 :: proc "contextless" (m: Matrix3f32) -> (adjoint: Matrix3f32) #no_bounds_check { adjoint[0, 0] = +(m[1, 1] * m[2, 2] - m[2, 1] * m[1, 2]) adjoint[0, 1] = -(m[1, 0] * m[2, 2] - m[2, 0] * m[1, 2]) adjoint[0, 2] = +(m[1, 0] * m[2, 1] - m[2, 0] * m[1, 1]) @@ -1426,7 +1463,7 @@ matrix3_adjoint_f32 :: proc "contextless" (m: Matrix3f32) -> (adjoint: Matrix3f3 return adjoint } @(require_results) -matrix3_adjoint_f64 :: proc "contextless" (m: Matrix3f64) -> (adjoint: Matrix3f64) { +matrix3_adjoint_f64 :: proc "contextless" (m: Matrix3f64) -> (adjoint: Matrix3f64) #no_bounds_check { adjoint[0, 0] = +(m[1, 1] * m[2, 2] - m[2, 1] * m[1, 2]) adjoint[0, 1] = -(m[1, 0] * m[2, 2] - m[2, 0] * m[1, 2]) adjoint[0, 2] = +(m[1, 0] * m[2, 1] - m[2, 0] * m[1, 1]) @@ -1447,16 +1484,16 @@ matrix3_adjoint :: proc{ @(require_results) -matrix3_inverse_transpose_f16 :: proc "contextless" (m: Matrix3f16) -> (inverse_transpose: Matrix3f16) { - return builtin.inverse_transpose(m) +matrix3_inverse_transpose_f16 :: proc "contextless" (m: Matrix3f16) -> (p: Matrix3f16) { + return inverse_transpose(m) } @(require_results) -matrix3_inverse_transpose_f32 :: proc "contextless" (m: Matrix3f32) -> (inverse_transpose: Matrix3f32) { - return builtin.inverse_transpose(m) +matrix3_inverse_transpose_f32 :: proc "contextless" (m: Matrix3f32) -> (p: Matrix3f32) { + return inverse_transpose(m) } @(require_results) -matrix3_inverse_transpose_f64 :: proc "contextless" (m: Matrix3f64) -> (inverse_transpose: Matrix3f64) { - return builtin.inverse_transpose(m) +matrix3_inverse_transpose_f64 :: proc "contextless" (m: Matrix3f64) -> (p: Matrix3f64) { + return inverse_transpose(m) } matrix3_inverse_transpose :: proc{ matrix3_inverse_transpose_f16, @@ -1466,21 +1503,21 @@ matrix3_inverse_transpose :: proc{ @(require_results) -matrix3_scale_f16 :: proc "contextless" (s: Vector3f16) -> (m: Matrix3f16) { +matrix3_scale_f16 :: proc "contextless" (s: Vector3f16) -> (m: Matrix3f16) #no_bounds_check { m[0, 0] = s[0] m[1, 1] = s[1] m[2, 2] = s[2] return m } @(require_results) -matrix3_scale_f32 :: proc "contextless" (s: Vector3f32) -> (m: Matrix3f32) { +matrix3_scale_f32 :: proc "contextless" (s: Vector3f32) -> (m: Matrix3f32) #no_bounds_check { m[0, 0] = s[0] m[1, 1] = s[1] m[2, 2] = s[2] return m } @(require_results) -matrix3_scale_f64 :: proc "contextless" (s: Vector3f64) -> (m: Matrix3f64) { +matrix3_scale_f64 :: proc "contextless" (s: Vector3f64) -> (m: Matrix3f64) #no_bounds_check { m[0, 0] = s[0] m[1, 1] = s[1] m[2, 2] = s[2] @@ -1494,7 +1531,7 @@ matrix3_scale :: proc{ @(require_results) -matrix3_rotate_f16 :: proc "contextless" (angle_radians: f16, v: Vector3f16) -> (rot: Matrix3f16) { +matrix3_rotate_f16 :: proc "contextless" (angle_radians: f16, v: Vector3f16) -> (rot: Matrix3f16) #no_bounds_check { c := math.cos(angle_radians) s := math.sin(angle_radians) @@ -1516,7 +1553,7 @@ matrix3_rotate_f16 :: proc "contextless" (angle_radians: f16, v: Vector3f16) -> return rot } @(require_results) -matrix3_rotate_f32 :: proc "contextless" (angle_radians: f32, v: Vector3f32) -> (rot: Matrix3f32) { +matrix3_rotate_f32 :: proc "contextless" (angle_radians: f32, v: Vector3f32) -> (rot: Matrix3f32) #no_bounds_check { c := math.cos(angle_radians) s := math.sin(angle_radians) @@ -1607,7 +1644,7 @@ matrix3_look_at :: proc{ @(require_results) -matrix4_from_quaternion_f16 :: proc "contextless" (q: Quaternionf16) -> (m: Matrix4f16) { +matrix4_from_quaternion_f16 :: proc "contextless" (q: Quaternionf16) -> (m: Matrix4f16) #no_bounds_check { qxx := q.x * q.x qyy := q.y * q.y qzz := q.z * q.z @@ -1635,7 +1672,7 @@ matrix4_from_quaternion_f16 :: proc "contextless" (q: Quaternionf16) -> (m: Matr return m } @(require_results) -matrix4_from_quaternion_f32 :: proc "contextless" (q: Quaternionf32) -> (m: Matrix4f32) { +matrix4_from_quaternion_f32 :: proc "contextless" (q: Quaternionf32) -> (m: Matrix4f32) #no_bounds_check { qxx := q.x * q.x qyy := q.y * q.y qzz := q.z * q.z @@ -1663,7 +1700,7 @@ matrix4_from_quaternion_f32 :: proc "contextless" (q: Quaternionf32) -> (m: Matr return m } @(require_results) -matrix4_from_quaternion_f64 :: proc "contextless" (q: Quaternionf64) -> (m: Matrix4f64) { +matrix4_from_quaternion_f64 :: proc "contextless" (q: Quaternionf64) -> (m: Matrix4f64) #no_bounds_check { qxx := q.x * q.x qyy := q.y * q.y qzz := q.z * q.z @@ -1746,7 +1783,7 @@ matrix4_inverse :: proc{ @(require_results) -matrix4_minor_f16 :: proc "contextless" (m: Matrix4f16, c, r: int) -> f16 { +matrix4_minor_f16 :: proc "contextless" (m: Matrix4f16, c, r: int) -> f16 #no_bounds_check { cut_down: Matrix3f16 for i in 0..<3 { col := i if i < c else i+1 @@ -1758,7 +1795,7 @@ matrix4_minor_f16 :: proc "contextless" (m: Matrix4f16, c, r: int) -> f16 { return matrix3_determinant(cut_down) } @(require_results) -matrix4_minor_f32 :: proc "contextless" (m: Matrix4f32, c, r: int) -> f32 { +matrix4_minor_f32 :: proc "contextless" (m: Matrix4f32, c, r: int) -> f32 #no_bounds_check { cut_down: Matrix3f32 for i in 0..<3 { col := i if i < c else i+1 @@ -1770,7 +1807,7 @@ matrix4_minor_f32 :: proc "contextless" (m: Matrix4f32, c, r: int) -> f32 { return matrix3_determinant(cut_down) } @(require_results) -matrix4_minor_f64 :: proc "contextless" (m: Matrix4f64, c, r: int) -> f64 { +matrix4_minor_f64 :: proc "contextless" (m: Matrix4f64, c, r: int) -> f64 #no_bounds_check { cut_down: Matrix3f64 for i in 0..<3 { col := i if i < c else i+1 @@ -1817,7 +1854,7 @@ matrix4_cofactor :: proc{ @(require_results) -matrix4_adjoint_f16 :: proc "contextless" (m: Matrix4f16) -> (adjoint: Matrix4f16) { +matrix4_adjoint_f16 :: proc "contextless" (m: Matrix4f16) -> (adjoint: Matrix4f16) #no_bounds_check { for i in 0..<4 { for j in 0..<4 { adjoint[i][j] = matrix4_cofactor(m, i, j) @@ -1826,7 +1863,7 @@ matrix4_adjoint_f16 :: proc "contextless" (m: Matrix4f16) -> (adjoint: Matrix4f1 return } @(require_results) -matrix4_adjoint_f32 :: proc "contextless" (m: Matrix4f32) -> (adjoint: Matrix4f32) { +matrix4_adjoint_f32 :: proc "contextless" (m: Matrix4f32) -> (adjoint: Matrix4f32) #no_bounds_check { for i in 0..<4 { for j in 0..<4 { adjoint[i][j] = matrix4_cofactor(m, i, j) @@ -1835,7 +1872,7 @@ matrix4_adjoint_f32 :: proc "contextless" (m: Matrix4f32) -> (adjoint: Matrix4f3 return } @(require_results) -matrix4_adjoint_f64 :: proc "contextless" (m: Matrix4f64) -> (adjoint: Matrix4f64) { +matrix4_adjoint_f64 :: proc "contextless" (m: Matrix4f64) -> (adjoint: Matrix4f64) #no_bounds_check { for i in 0..<4 { for j in 0..<4 { adjoint[i][j] = matrix4_cofactor(m, i, j) @@ -1851,7 +1888,7 @@ matrix4_adjoint :: proc{ @(require_results) -matrix4_determinant_f16 :: proc "contextless" (m: Matrix4f16) -> (determinant: f16) { +matrix4_determinant_f16 :: proc "contextless" (m: Matrix4f16) -> (determinant: f16) #no_bounds_check { adjoint := matrix4_adjoint(m) for i in 0..<4 { determinant += m[i][0] * adjoint[i][0] @@ -1859,7 +1896,7 @@ matrix4_determinant_f16 :: proc "contextless" (m: Matrix4f16) -> (determinant: f return } @(require_results) -matrix4_determinant_f32 :: proc "contextless" (m: Matrix4f32) -> (determinant: f32) { +matrix4_determinant_f32 :: proc "contextless" (m: Matrix4f32) -> (determinant: f32) #no_bounds_check { adjoint := matrix4_adjoint(m) for i in 0..<4 { determinant += m[i][0] * adjoint[i][0] @@ -1867,7 +1904,7 @@ matrix4_determinant_f32 :: proc "contextless" (m: Matrix4f32) -> (determinant: f return } @(require_results) -matrix4_determinant_f64 :: proc "contextless" (m: Matrix4f64) -> (determinant: f64) { +matrix4_determinant_f64 :: proc "contextless" (m: Matrix4f64) -> (determinant: f64) #no_bounds_check { adjoint := matrix4_adjoint(m) for i in 0..<4 { determinant += m[i][0] * adjoint[i][0] @@ -1882,7 +1919,7 @@ matrix4_determinant :: proc{ @(require_results) -matrix4_inverse_transpose_f16 :: proc "contextless" (m: Matrix4f16) -> (inverse_transpose: Matrix4f16) { +matrix4_inverse_transpose_f16 :: proc "contextless" (m: Matrix4f16) -> (inverse_transpose: Matrix4f16) #no_bounds_check { adjoint := matrix4_adjoint(m) determinant: f16 = 0 for i in 0..<4 { @@ -1897,7 +1934,7 @@ matrix4_inverse_transpose_f16 :: proc "contextless" (m: Matrix4f16) -> (inverse_ return } @(require_results) -matrix4_inverse_transpose_f32 :: proc "contextless" (m: Matrix4f32) -> (inverse_transpose: Matrix4f32) { +matrix4_inverse_transpose_f32 :: proc "contextless" (m: Matrix4f32) -> (inverse_transpose: Matrix4f32) #no_bounds_check { adjoint := matrix4_adjoint(m) determinant: f32 = 0 for i in 0..<4 { @@ -1912,7 +1949,7 @@ matrix4_inverse_transpose_f32 :: proc "contextless" (m: Matrix4f32) -> (inverse_ return } @(require_results) -matrix4_inverse_transpose_f64 :: proc "contextless" (m: Matrix4f64) -> (inverse_transpose: Matrix4f64) { +matrix4_inverse_transpose_f64 :: proc "contextless" (m: Matrix4f64) -> (inverse_transpose: Matrix4f64) #no_bounds_check { adjoint := matrix4_adjoint(m) determinant: f64 = 0 for i in 0..<4 { @@ -1934,7 +1971,7 @@ matrix4_inverse_transpose :: proc{ @(require_results) -matrix4_translate_f16 :: proc "contextless" (v: Vector3f16) -> Matrix4f16 { +matrix4_translate_f16 :: proc "contextless" (v: Vector3f16) -> Matrix4f16 #no_bounds_check { m := MATRIX4F16_IDENTITY m[3][0] = v[0] m[3][1] = v[1] @@ -1942,7 +1979,7 @@ matrix4_translate_f16 :: proc "contextless" (v: Vector3f16) -> Matrix4f16 { return m } @(require_results) -matrix4_translate_f32 :: proc "contextless" (v: Vector3f32) -> Matrix4f32 { +matrix4_translate_f32 :: proc "contextless" (v: Vector3f32) -> Matrix4f32 #no_bounds_check { m := MATRIX4F32_IDENTITY m[3][0] = v[0] m[3][1] = v[1] @@ -1950,7 +1987,7 @@ matrix4_translate_f32 :: proc "contextless" (v: Vector3f32) -> Matrix4f32 { return m } @(require_results) -matrix4_translate_f64 :: proc "contextless" (v: Vector3f64) -> Matrix4f64 { +matrix4_translate_f64 :: proc "contextless" (v: Vector3f64) -> Matrix4f64 #no_bounds_check { m := MATRIX4F64_IDENTITY m[3][0] = v[0] m[3][1] = v[1] @@ -1965,7 +2002,7 @@ matrix4_translate :: proc{ @(require_results) -matrix4_rotate_f16 :: proc "contextless" (angle_radians: f16, v: Vector3f16) -> Matrix4f16 { +matrix4_rotate_f16 :: proc "contextless" (angle_radians: f16, v: Vector3f16) -> Matrix4f16 #no_bounds_check { c := math.cos(angle_radians) s := math.sin(angle_radians) @@ -1992,7 +2029,7 @@ matrix4_rotate_f16 :: proc "contextless" (angle_radians: f16, v: Vector3f16) -> return rot } @(require_results) -matrix4_rotate_f32 :: proc "contextless" (angle_radians: f32, v: Vector3f32) -> Matrix4f32 { +matrix4_rotate_f32 :: proc "contextless" (angle_radians: f32, v: Vector3f32) -> Matrix4f32 #no_bounds_check { c := math.cos(angle_radians) s := math.sin(angle_radians) @@ -2019,7 +2056,7 @@ matrix4_rotate_f32 :: proc "contextless" (angle_radians: f32, v: Vector3f32) -> return rot } @(require_results) -matrix4_rotate_f64 :: proc "contextless" (angle_radians: f64, v: Vector3f64) -> Matrix4f64 { +matrix4_rotate_f64 :: proc "contextless" (angle_radians: f64, v: Vector3f64) -> Matrix4f64 #no_bounds_check { c := math.cos(angle_radians) s := math.sin(angle_radians) @@ -2053,7 +2090,7 @@ matrix4_rotate :: proc{ @(require_results) -matrix4_scale_f16 :: proc "contextless" (v: Vector3f16) -> (m: Matrix4f16) { +matrix4_scale_f16 :: proc "contextless" (v: Vector3f16) -> (m: Matrix4f16) #no_bounds_check { m[0][0] = v[0] m[1][1] = v[1] m[2][2] = v[2] @@ -2061,7 +2098,7 @@ matrix4_scale_f16 :: proc "contextless" (v: Vector3f16) -> (m: Matrix4f16) { return } @(require_results) -matrix4_scale_f32 :: proc "contextless" (v: Vector3f32) -> (m: Matrix4f32) { +matrix4_scale_f32 :: proc "contextless" (v: Vector3f32) -> (m: Matrix4f32) #no_bounds_check { m[0][0] = v[0] m[1][1] = v[1] m[2][2] = v[2] @@ -2069,7 +2106,7 @@ matrix4_scale_f32 :: proc "contextless" (v: Vector3f32) -> (m: Matrix4f32) { return } @(require_results) -matrix4_scale_f64 :: proc "contextless" (v: Vector3f64) -> (m: Matrix4f64) { +matrix4_scale_f64 :: proc "contextless" (v: Vector3f64) -> (m: Matrix4f64) #no_bounds_check { m[0][0] = v[0] m[1][1] = v[1] m[2][2] = v[2] @@ -2188,7 +2225,7 @@ matrix4_look_at_from_fru :: proc{ @(require_results) -matrix4_perspective_f16 :: proc "contextless" (fovy, aspect, near, far: f16, flip_z_axis := true) -> (m: Matrix4f16) { +matrix4_perspective_f16 :: proc "contextless" (fovy, aspect, near, far: f16, flip_z_axis := true) -> (m: Matrix4f16) #no_bounds_check { tan_half_fovy := math.tan(0.5 * fovy) m[0, 0] = 1 / (aspect*tan_half_fovy) m[1, 1] = 1 / (tan_half_fovy) @@ -2203,7 +2240,7 @@ matrix4_perspective_f16 :: proc "contextless" (fovy, aspect, near, far: f16, fli return } @(require_results) -matrix4_perspective_f32 :: proc "contextless" (fovy, aspect, near, far: f32, flip_z_axis := true) -> (m: Matrix4f32) { +matrix4_perspective_f32 :: proc "contextless" (fovy, aspect, near, far: f32, flip_z_axis := true) -> (m: Matrix4f32) #no_bounds_check { tan_half_fovy := math.tan(0.5 * fovy) m[0, 0] = 1 / (aspect*tan_half_fovy) m[1, 1] = 1 / (tan_half_fovy) @@ -2218,7 +2255,7 @@ matrix4_perspective_f32 :: proc "contextless" (fovy, aspect, near, far: f32, fli return } @(require_results) -matrix4_perspective_f64 :: proc "contextless" (fovy, aspect, near, far: f64, flip_z_axis := true) -> (m: Matrix4f64) { +matrix4_perspective_f64 :: proc "contextless" (fovy, aspect, near, far: f64, flip_z_axis := true) -> (m: Matrix4f64) #no_bounds_check { tan_half_fovy := math.tan(0.5 * fovy) m[0, 0] = 1 / (aspect*tan_half_fovy) m[1, 1] = 1 / (tan_half_fovy) @@ -2241,7 +2278,7 @@ matrix4_perspective :: proc{ @(require_results) -matrix_ortho3d_f16 :: proc "contextless" (left, right, bottom, top, near, far: f16, flip_z_axis := true) -> (m: Matrix4f16) { +matrix_ortho3d_f16 :: proc "contextless" (left, right, bottom, top, near, far: f16, flip_z_axis := true) -> (m: Matrix4f16) #no_bounds_check { m[0, 0] = +2 / (right - left) m[1, 1] = +2 / (top - bottom) m[2, 2] = +2 / (far - near) @@ -2257,7 +2294,7 @@ matrix_ortho3d_f16 :: proc "contextless" (left, right, bottom, top, near, far: f return } @(require_results) -matrix_ortho3d_f32 :: proc "contextless" (left, right, bottom, top, near, far: f32, flip_z_axis := true) -> (m: Matrix4f32) { +matrix_ortho3d_f32 :: proc "contextless" (left, right, bottom, top, near, far: f32, flip_z_axis := true) -> (m: Matrix4f32) #no_bounds_check { m[0, 0] = +2 / (right - left) m[1, 1] = +2 / (top - bottom) m[2, 2] = +2 / (far - near) @@ -2273,7 +2310,7 @@ matrix_ortho3d_f32 :: proc "contextless" (left, right, bottom, top, near, far: f return } @(require_results) -matrix_ortho3d_f64 :: proc "contextless" (left, right, bottom, top, near, far: f64, flip_z_axis := true) -> (m: Matrix4f64) { +matrix_ortho3d_f64 :: proc "contextless" (left, right, bottom, top, near, far: f64, flip_z_axis := true) -> (m: Matrix4f64) #no_bounds_check { m[0, 0] = +2 / (right - left) m[1, 1] = +2 / (top - bottom) m[2, 2] = +2 / (far - near) @@ -2297,7 +2334,7 @@ matrix_ortho3d :: proc{ @(require_results) -matrix4_infinite_perspective_f16 :: proc "contextless" (fovy, aspect, near: f16, flip_z_axis := true) -> (m: Matrix4f16) { +matrix4_infinite_perspective_f16 :: proc "contextless" (fovy, aspect, near: f16, flip_z_axis := true) -> (m: Matrix4f16) #no_bounds_check { tan_half_fovy := math.tan(0.5 * fovy) m[0, 0] = 1 / (aspect*tan_half_fovy) m[1, 1] = 1 / (tan_half_fovy) @@ -2312,7 +2349,7 @@ matrix4_infinite_perspective_f16 :: proc "contextless" (fovy, aspect, near: f16, return } @(require_results) -matrix4_infinite_perspective_f32 :: proc "contextless" (fovy, aspect, near: f32, flip_z_axis := true) -> (m: Matrix4f32) { +matrix4_infinite_perspective_f32 :: proc "contextless" (fovy, aspect, near: f32, flip_z_axis := true) -> (m: Matrix4f32) #no_bounds_check { tan_half_fovy := math.tan(0.5 * fovy) m[0, 0] = 1 / (aspect*tan_half_fovy) m[1, 1] = 1 / (tan_half_fovy) @@ -2327,7 +2364,7 @@ matrix4_infinite_perspective_f32 :: proc "contextless" (fovy, aspect, near: f32, return } @(require_results) -matrix4_infinite_perspective_f64 :: proc "contextless" (fovy, aspect, near: f64, flip_z_axis := true) -> (m: Matrix4f64) { +matrix4_infinite_perspective_f64 :: proc "contextless" (fovy, aspect, near: f64, flip_z_axis := true) -> (m: Matrix4f64) #no_bounds_check { tan_half_fovy := math.tan(0.5 * fovy) m[0, 0] = 1 / (aspect*tan_half_fovy) m[1, 1] = 1 / (tan_half_fovy) @@ -2350,19 +2387,19 @@ matrix4_infinite_perspective :: proc{ @(require_results) -matrix2_from_scalar_f16 :: proc "contextless" (f: f16) -> (m: Matrix2f16) { +matrix2_from_scalar_f16 :: proc "contextless" (f: f16) -> (m: Matrix2f16) #no_bounds_check { m[0, 0], m[1, 0] = f, 0 m[0, 1], m[1, 1] = 0, f return } @(require_results) -matrix2_from_scalar_f32 :: proc "contextless" (f: f32) -> (m: Matrix2f32) { +matrix2_from_scalar_f32 :: proc "contextless" (f: f32) -> (m: Matrix2f32) #no_bounds_check { m[0, 0], m[1, 0] = f, 0 m[0, 1], m[1, 1] = 0, f return } @(require_results) -matrix2_from_scalar_f64 :: proc "contextless" (f: f64) -> (m: Matrix2f64) { +matrix2_from_scalar_f64 :: proc "contextless" (f: f64) -> (m: Matrix2f64) #no_bounds_check { m[0, 0], m[1, 0] = f, 0 m[0, 1], m[1, 1] = 0, f return @@ -2375,21 +2412,21 @@ matrix2_from_scalar :: proc{ @(require_results) -matrix3_from_scalar_f16 :: proc "contextless" (f: f16) -> (m: Matrix3f16) { +matrix3_from_scalar_f16 :: proc "contextless" (f: f16) -> (m: Matrix3f16) #no_bounds_check { m[0, 0], m[1, 0], m[2, 0] = f, 0, 0 m[0, 1], m[1, 1], m[2, 1] = 0, f, 0 m[0, 2], m[1, 2], m[2, 2] = 0, 0, f return } @(require_results) -matrix3_from_scalar_f32 :: proc "contextless" (f: f32) -> (m: Matrix3f32) { +matrix3_from_scalar_f32 :: proc "contextless" (f: f32) -> (m: Matrix3f32) #no_bounds_check { m[0, 0], m[1, 0], m[2, 0] = f, 0, 0 m[0, 1], m[1, 1], m[2, 1] = 0, f, 0 m[0, 2], m[1, 2], m[2, 2] = 0, 0, f return } @(require_results) -matrix3_from_scalar_f64 :: proc "contextless" (f: f64) -> (m: Matrix3f64) { +matrix3_from_scalar_f64 :: proc "contextless" (f: f64) -> (m: Matrix3f64) #no_bounds_check { m[0, 0], m[1, 0], m[2, 0] = f, 0, 0 m[0, 1], m[1, 1], m[2, 1] = 0, f, 0 m[0, 2], m[1, 2], m[2, 2] = 0, 0, f @@ -2403,7 +2440,7 @@ matrix3_from_scalar :: proc{ @(require_results) -matrix4_from_scalar_f16 :: proc "contextless" (f: f16) -> (m: Matrix4f16) { +matrix4_from_scalar_f16 :: proc "contextless" (f: f16) -> (m: Matrix4f16) #no_bounds_check { m[0, 0], m[1, 0], m[2, 0], m[3, 0] = f, 0, 0, 0 m[0, 1], m[1, 1], m[2, 1], m[3, 1] = 0, f, 0, 0 m[0, 2], m[1, 2], m[2, 2], m[3, 2] = 0, 0, f, 0 @@ -2411,7 +2448,7 @@ matrix4_from_scalar_f16 :: proc "contextless" (f: f16) -> (m: Matrix4f16) { return } @(require_results) -matrix4_from_scalar_f32 :: proc "contextless" (f: f32) -> (m: Matrix4f32) { +matrix4_from_scalar_f32 :: proc "contextless" (f: f32) -> (m: Matrix4f32) #no_bounds_check { m[0, 0], m[1, 0], m[2, 0], m[3, 0] = f, 0, 0, 0 m[0, 1], m[1, 1], m[2, 1], m[3, 1] = 0, f, 0, 0 m[0, 2], m[1, 2], m[2, 2], m[3, 2] = 0, 0, f, 0 @@ -2419,7 +2456,7 @@ matrix4_from_scalar_f32 :: proc "contextless" (f: f32) -> (m: Matrix4f32) { return } @(require_results) -matrix4_from_scalar_f64 :: proc "contextless" (f: f64) -> (m: Matrix4f64) { +matrix4_from_scalar_f64 :: proc "contextless" (f: f64) -> (m: Matrix4f64) #no_bounds_check { m[0, 0], m[1, 0], m[2, 0], m[3, 0] = f, 0, 0, 0 m[0, 1], m[1, 1], m[2, 1], m[3, 1] = 0, f, 0, 0 m[0, 2], m[1, 2], m[2, 2], m[3, 2] = 0, 0, f, 0 @@ -2434,19 +2471,19 @@ matrix4_from_scalar :: proc{ @(require_results) -matrix2_from_matrix3_f16 :: proc "contextless" (m: Matrix3f16) -> (r: Matrix2f16) { +matrix2_from_matrix3_f16 :: proc "contextless" (m: Matrix3f16) -> (r: Matrix2f16) #no_bounds_check { r[0, 0], r[1, 0] = m[0, 0], m[1, 0] r[0, 1], r[1, 1] = m[0, 1], m[1, 1] return } @(require_results) -matrix2_from_matrix3_f32 :: proc "contextless" (m: Matrix3f32) -> (r: Matrix2f32) { +matrix2_from_matrix3_f32 :: proc "contextless" (m: Matrix3f32) -> (r: Matrix2f32) #no_bounds_check { r[0, 0], r[1, 0] = m[0, 0], m[1, 0] r[0, 1], r[1, 1] = m[0, 1], m[1, 1] return } @(require_results) -matrix2_from_matrix3_f64 :: proc "contextless" (m: Matrix3f64) -> (r: Matrix2f64) { +matrix2_from_matrix3_f64 :: proc "contextless" (m: Matrix3f64) -> (r: Matrix2f64) #no_bounds_check { r[0, 0], r[1, 0] = m[0, 0], m[1, 0] r[0, 1], r[1, 1] = m[0, 1], m[1, 1] return @@ -2459,19 +2496,19 @@ matrix2_from_matrix3 :: proc{ @(require_results) -matrix2_from_matrix4_f16 :: proc "contextless" (m: Matrix4f16) -> (r: Matrix2f16) { +matrix2_from_matrix4_f16 :: proc "contextless" (m: Matrix4f16) -> (r: Matrix2f16) #no_bounds_check { r[0, 0], r[1, 0] = m[0, 0], m[1, 0] r[0, 1], r[1, 1] = m[0, 1], m[1, 1] return } @(require_results) -matrix2_from_matrix4_f32 :: proc "contextless" (m: Matrix4f32) -> (r: Matrix2f32) { +matrix2_from_matrix4_f32 :: proc "contextless" (m: Matrix4f32) -> (r: Matrix2f32) #no_bounds_check { r[0, 0], r[1, 0] = m[0, 0], m[1, 0] r[0, 1], r[1, 1] = m[0, 1], m[1, 1] return } @(require_results) -matrix2_from_matrix4_f64 :: proc "contextless" (m: Matrix4f64) -> (r: Matrix2f64) { +matrix2_from_matrix4_f64 :: proc "contextless" (m: Matrix4f64) -> (r: Matrix2f64) #no_bounds_check { r[0, 0], r[1, 0] = m[0, 0], m[1, 0] r[0, 1], r[1, 1] = m[0, 1], m[1, 1] return @@ -2484,21 +2521,21 @@ matrix2_from_matrix4 :: proc{ @(require_results) -matrix3_from_matrix2_f16 :: proc "contextless" (m: Matrix2f16) -> (r: Matrix3f16) { +matrix3_from_matrix2_f16 :: proc "contextless" (m: Matrix2f16) -> (r: Matrix3f16) #no_bounds_check { r[0, 0], r[1, 0], r[2, 0] = m[0, 0], m[1, 0], 0 r[0, 1], r[1, 1], r[2, 1] = m[0, 1], m[1, 1], 0 r[0, 2], r[1, 2], r[2, 2] = 0, 0, 1 return } @(require_results) -matrix3_from_matrix2_f32 :: proc "contextless" (m: Matrix2f32) -> (r: Matrix3f32) { +matrix3_from_matrix2_f32 :: proc "contextless" (m: Matrix2f32) -> (r: Matrix3f32) #no_bounds_check { r[0, 0], r[1, 0], r[2, 0] = m[0, 0], m[1, 0], 0 r[0, 1], r[1, 1], r[2, 1] = m[0, 1], m[1, 1], 0 r[0, 2], r[1, 2], r[2, 2] = 0, 0, 1 return } @(require_results) -matrix3_from_matrix2_f64 :: proc "contextless" (m: Matrix2f64) -> (r: Matrix3f64) { +matrix3_from_matrix2_f64 :: proc "contextless" (m: Matrix2f64) -> (r: Matrix3f64) #no_bounds_check { r[0, 0], r[1, 0], r[2, 0] = m[0, 0], m[1, 0], 0 r[0, 1], r[1, 1], r[2, 1] = m[0, 1], m[1, 1], 0 r[0, 2], r[1, 2], r[2, 2] = 0, 0, 1 @@ -2512,21 +2549,21 @@ matrix3_from_matrix2 :: proc{ @(require_results) -matrix3_from_matrix4_f16 :: proc "contextless" (m: Matrix4f16) -> (r: Matrix3f16) { +matrix3_from_matrix4_f16 :: proc "contextless" (m: Matrix4f16) -> (r: Matrix3f16) #no_bounds_check { r[0, 0], r[1, 0], r[2, 0] = m[0, 0], m[1, 0], m[2, 0] r[0, 1], r[1, 1], r[2, 1] = m[0, 1], m[1, 1], m[2, 1] r[0, 2], r[1, 2], r[2, 2] = m[0, 2], m[1, 2], m[2, 2] return } @(require_results) -matrix3_from_matrix4_f32 :: proc "contextless" (m: Matrix4f32) -> (r: Matrix3f32) { +matrix3_from_matrix4_f32 :: proc "contextless" (m: Matrix4f32) -> (r: Matrix3f32) #no_bounds_check { r[0, 0], r[1, 0], r[2, 0] = m[0, 0], m[1, 0], m[2, 0] r[0, 1], r[1, 1], r[2, 1] = m[0, 1], m[1, 1], m[2, 1] r[0, 2], r[1, 2], r[2, 2] = m[0, 2], m[1, 2], m[2, 2] return } @(require_results) -matrix3_from_matrix4_f64 :: proc "contextless" (m: Matrix4f64) -> (r: Matrix3f64) { +matrix3_from_matrix4_f64 :: proc "contextless" (m: Matrix4f64) -> (r: Matrix3f64) #no_bounds_check { r[0, 0], r[1, 0], r[2, 0] = m[0, 0], m[1, 0], m[2, 0] r[0, 1], r[1, 1], r[2, 1] = m[0, 1], m[1, 1], m[2, 1] r[0, 2], r[1, 2], r[2, 2] = m[0, 2], m[1, 2], m[2, 2] @@ -2540,7 +2577,7 @@ matrix3_from_matrix4 :: proc{ @(require_results) -matrix4_from_matrix2_f16 :: proc "contextless" (m: Matrix2f16) -> (r: Matrix4f16) { +matrix4_from_matrix2_f16 :: proc "contextless" (m: Matrix2f16) -> (r: Matrix4f16) #no_bounds_check { r[0, 0], r[1, 0], r[2, 0], r[3, 0] = m[0, 0], m[1, 0], 0, 0 r[0, 1], r[1, 1], r[2, 1], r[3, 1] = m[0, 1], m[1, 1], 0, 0 r[0, 2], r[1, 2], r[2, 2], r[3, 2] = 0, 0, 1, 0 @@ -2548,7 +2585,7 @@ matrix4_from_matrix2_f16 :: proc "contextless" (m: Matrix2f16) -> (r: Matrix4f16 return } @(require_results) -matrix4_from_matrix2_f32 :: proc "contextless" (m: Matrix2f32) -> (r: Matrix4f32) { +matrix4_from_matrix2_f32 :: proc "contextless" (m: Matrix2f32) -> (r: Matrix4f32) #no_bounds_check { r[0, 0], r[1, 0], r[2, 0], r[3, 0] = m[0, 0], m[1, 0], 0, 0 r[0, 1], r[1, 1], r[2, 1], r[3, 1] = m[0, 1], m[1, 1], 0, 0 r[0, 2], r[1, 2], r[2, 2], r[3, 2] = 0, 0, 1, 0 @@ -2556,7 +2593,7 @@ matrix4_from_matrix2_f32 :: proc "contextless" (m: Matrix2f32) -> (r: Matrix4f32 return } @(require_results) -matrix4_from_matrix2_f64 :: proc "contextless" (m: Matrix2f64) -> (r: Matrix4f64) { +matrix4_from_matrix2_f64 :: proc "contextless" (m: Matrix2f64) -> (r: Matrix4f64) #no_bounds_check { r[0, 0], r[1, 0], r[2, 0], r[3, 0] = m[0, 0], m[1, 0], 0, 0 r[0, 1], r[1, 1], r[2, 1], r[3, 1] = m[0, 1], m[1, 1], 0, 0 r[0, 2], r[1, 2], r[2, 2], r[3, 2] = 0, 0, 1, 0 @@ -2571,7 +2608,7 @@ matrix4_from_matrix2 :: proc{ @(require_results) -matrix4_from_matrix3_f16 :: proc "contextless" (m: Matrix3f16) -> (r: Matrix4f16) { +matrix4_from_matrix3_f16 :: proc "contextless" (m: Matrix3f16) -> (r: Matrix4f16) #no_bounds_check { r[0, 0], r[1, 0], r[2, 0], r[3, 0] = m[0, 0], m[1, 0], m[2, 0], 0 r[0, 1], r[1, 1], r[2, 1], r[3, 1] = m[0, 1], m[1, 1], m[2, 1], 0 r[0, 2], r[1, 2], r[2, 2], r[3, 2] = m[0, 2], m[1, 2], m[2, 2], 0 @@ -2579,7 +2616,7 @@ matrix4_from_matrix3_f16 :: proc "contextless" (m: Matrix3f16) -> (r: Matrix4f16 return } @(require_results) -matrix4_from_matrix3_f32 :: proc "contextless" (m: Matrix3f32) -> (r: Matrix4f32) { +matrix4_from_matrix3_f32 :: proc "contextless" (m: Matrix3f32) -> (r: Matrix4f32) #no_bounds_check { r[0, 0], r[1, 0], r[2, 0], r[3, 0] = m[0, 0], m[1, 0], m[2, 0], 0 r[0, 1], r[1, 1], r[2, 1], r[3, 1] = m[0, 1], m[1, 1], m[2, 1], 0 r[0, 2], r[1, 2], r[2, 2], r[3, 2] = m[0, 2], m[1, 2], m[2, 2], 0 @@ -2587,7 +2624,7 @@ matrix4_from_matrix3_f32 :: proc "contextless" (m: Matrix3f32) -> (r: Matrix4f32 return } @(require_results) -matrix4_from_matrix3_f64 :: proc "contextless" (m: Matrix3f64) -> (r: Matrix4f64) { +matrix4_from_matrix3_f64 :: proc "contextless" (m: Matrix3f64) -> (r: Matrix4f64) #no_bounds_check { r[0, 0], r[1, 0], r[2, 0], r[3, 0] = m[0, 0], m[1, 0], m[2, 0], 0 r[0, 1], r[1, 1], r[2, 1], r[3, 1] = m[0, 1], m[1, 1], m[2, 1], 0 r[0, 2], r[1, 2], r[2, 2], r[3, 2] = m[0, 2], m[1, 2], m[2, 2], 0 @@ -2673,7 +2710,8 @@ to_quaternion :: proc{ @(require_results) -matrix2_orthonormalize_f16 :: proc "contextless" (m: Matrix2f16) -> (r: Matrix2f16) { +matrix2_orthonormalize_f16 :: proc "contextless" (m: Matrix2f16) -> (r: Matrix2f16) #no_bounds_check { + r = m r[0] = normalize(m[0]) d0 := dot(r[0], r[1]) @@ -2683,7 +2721,8 @@ matrix2_orthonormalize_f16 :: proc "contextless" (m: Matrix2f16) -> (r: Matrix2f return } @(require_results) -matrix2_orthonormalize_f32 :: proc "contextless" (m: Matrix2f32) -> (r: Matrix2f32) { +matrix2_orthonormalize_f32 :: proc "contextless" (m: Matrix2f32) -> (r: Matrix2f32) #no_bounds_check { + r = m r[0] = normalize(m[0]) d0 := dot(r[0], r[1]) @@ -2693,7 +2732,8 @@ matrix2_orthonormalize_f32 :: proc "contextless" (m: Matrix2f32) -> (r: Matrix2f return } @(require_results) -matrix2_orthonormalize_f64 :: proc "contextless" (m: Matrix2f64) -> (r: Matrix2f64) { +matrix2_orthonormalize_f64 :: proc "contextless" (m: Matrix2f64) -> (r: Matrix2f64) #no_bounds_check { + r = m r[0] = normalize(m[0]) d0 := dot(r[0], r[1]) @@ -2710,7 +2750,8 @@ matrix2_orthonormalize :: proc{ @(require_results) -matrix3_orthonormalize_f16 :: proc "contextless" (m: Matrix3f16) -> (r: Matrix3f16) { +matrix3_orthonormalize_f16 :: proc "contextless" (m: Matrix3f16) -> (r: Matrix3f16) #no_bounds_check { + r = m r[0] = normalize(m[0]) d0 := dot(r[0], r[1]) @@ -2725,7 +2766,8 @@ matrix3_orthonormalize_f16 :: proc "contextless" (m: Matrix3f16) -> (r: Matrix3f return } @(require_results) -matrix3_orthonormalize_f32 :: proc "contextless" (m: Matrix3f32) -> (r: Matrix3f32) { +matrix3_orthonormalize_f32 :: proc "contextless" (m: Matrix3f32) -> (r: Matrix3f32) #no_bounds_check { + r = m r[0] = normalize(m[0]) d0 := dot(r[0], r[1]) @@ -2740,7 +2782,8 @@ matrix3_orthonormalize_f32 :: proc "contextless" (m: Matrix3f32) -> (r: Matrix3f return } @(require_results) -matrix3_orthonormalize_f64 :: proc "contextless" (m: Matrix3f64) -> (r: Matrix3f64) { +matrix3_orthonormalize_f64 :: proc "contextless" (m: Matrix3f64) -> (r: Matrix3f64) #no_bounds_check { + r = m r[0] = normalize(m[0]) d0 := dot(r[0], r[1]) 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 0d8873071..934842318 100644 --- a/core/math/math.odin +++ b/core/math/math.odin @@ -1,7 +1,7 @@ package math -import "core:intrinsics" -import "core:builtin" +import "base:intrinsics" +import "base:builtin" _ :: intrinsics Float_Class :: enum { @@ -60,6 +60,7 @@ sqrt :: proc{ @(require_results) sin_f32be :: proc "contextless" (θ: f32be) -> f32be { return #force_inline f32be(sin_f32(f32(θ))) } @(require_results) sin_f64le :: proc "contextless" (θ: f64le) -> f64le { return #force_inline f64le(sin_f64(f64(θ))) } @(require_results) sin_f64be :: proc "contextless" (θ: f64be) -> f64be { return #force_inline f64be(sin_f64(f64(θ))) } +// Return the sine of θ in radians. sin :: proc{ sin_f16, sin_f16le, sin_f16be, sin_f32, sin_f32le, sin_f32be, @@ -72,6 +73,7 @@ sin :: proc{ @(require_results) cos_f32be :: proc "contextless" (θ: f32be) -> f32be { return #force_inline f32be(cos_f32(f32(θ))) } @(require_results) cos_f64le :: proc "contextless" (θ: f64le) -> f64le { return #force_inline f64le(cos_f64(f64(θ))) } @(require_results) cos_f64be :: proc "contextless" (θ: f64be) -> f64be { return #force_inline f64be(cos_f64(f64(θ))) } +// Return the cosine of θ in radians. cos :: proc{ cos_f16, cos_f16le, cos_f16be, cos_f32, cos_f32le, cos_f32be, @@ -128,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, } @@ -149,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, @@ -177,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, } @@ -203,7 +205,8 @@ pow10_f64 :: proc "contextless" (n: f64) -> f64 { return 0 } -pow2_f64 :: proc(#any_int exp: int) -> (res: f64) { +@(require_results) +pow2_f64 :: proc "contextless" (#any_int exp: int) -> (res: f64) { switch { case exp >= -1022 && exp <= 1023: // Normal return transmute(f64)(u64(exp + F64_BIAS) << F64_SHIFT) @@ -221,7 +224,8 @@ pow2_f64 :: proc(#any_int exp: int) -> (res: f64) { unreachable() } -pow2_f32 :: proc(#any_int exp: int) -> (res: f32) { +@(require_results) +pow2_f32 :: proc "contextless" (#any_int exp: int) -> (res: f32) { switch { case exp >= -126 && exp <= 127: // Normal return transmute(f32)(u32(exp + F32_BIAS) << F32_SHIFT) @@ -236,7 +240,8 @@ pow2_f32 :: proc(#any_int exp: int) -> (res: f32) { unreachable() } -pow2_f16 :: proc(#any_int exp: int) -> (res: f16) { +@(require_results) +pow2_f16 :: proc "contextless" (#any_int exp: int) -> (res: f16) { switch { case exp >= -14 && exp <= 15: // Normal return transmute(f16)(u16(exp + F16_BIAS) << F16_SHIFT) @@ -375,6 +380,7 @@ log10 :: proc{ @(require_results) tan_f64 :: proc "contextless" (θ: f64) -> f64 { return sin(θ)/cos(θ) } @(require_results) tan_f64le :: proc "contextless" (θ: f64le) -> f64le { return f64le(tan_f64(f64(θ))) } @(require_results) tan_f64be :: proc "contextless" (θ: f64be) -> f64be { return f64be(tan_f64(f64(θ))) } +// Return the tangent of θ in radians. tan :: proc{ tan_f16, tan_f16le, tan_f16be, tan_f32, tan_f32le, tan_f32be, @@ -399,6 +405,12 @@ remap :: proc "contextless" (old_value, old_min, old_max, new_min, new_max: $T) return ((old_value - old_min) / old_range) * new_range + new_min } +@(require_results) +remap_clamped :: proc "contextless" (old_value, old_min, old_max, new_min, new_max: $T) -> (x: T) where intrinsics.type_is_numeric(T), !intrinsics.type_is_array(T) { + remapped := #force_inline remap(old_value, old_min, old_max, new_min, new_max) + return clamp(remapped, new_min, new_max) +} + @(require_results) wrap :: proc "contextless" (x, y: $T) -> T where intrinsics.type_is_numeric(T), !intrinsics.type_is_array(T) { tmp := mod(x, y) @@ -432,11 +444,11 @@ bias :: proc "contextless" (t, b: $T) -> T where intrinsics.type_is_numeric(T) { return t / (((1/b) - 2) * (1 - t) + 1) } @(require_results) -gain :: proc "contextless" (t, g: $T) -> T where intrinsics.type_is_numeric(T) { +gain :: proc "contextless" (t, g: $T) -> T where intrinsics.type_is_float(T) { if t < 0.5 { - return bias(t*2, g)*0.5 + return bias(t*2, g) * 0.5 } - return bias(t*2 - 1, 1 - g)*0.5 + 0.5 + return bias(t*2 - 1, 1 - g) * 0.5 + 0.5 } @@ -641,42 +653,175 @@ trunc :: proc{ } @(require_results) -round_f16 :: proc "contextless" (x: f16) -> f16 { - return ceil(x - 0.5) if x < 0 else floor(x + 0.5) -} -@(require_results) -round_f16le :: proc "contextless" (x: f16le) -> f16le { - return ceil(x - 0.5) if x < 0 else floor(x + 0.5) -} -@(require_results) -round_f16be :: proc "contextless" (x: f16be) -> f16be { - return ceil(x - 0.5) if x < 0 else floor(x + 0.5) +round_f16 :: proc "contextless" (x: f16) -> f16 { + // origin: Go /src/math/floor.go + // + // Copyright (c) 2009 The Go Authors. 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 Google Inc. 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 + // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + mask :: F16_MASK + shift :: F16_SHIFT + bias :: F16_BIAS + + bits := transmute(u16)x + e := (bits >> shift) & mask + + if e < bias { + bits &= 0x8000 + if e == bias - 1 { + bits |= transmute(u16)f16(1) + } + } else if e < bias + shift { + half :: 1 << (shift - 1) + mantissa :: (1 << shift) - 1 + e -= bias + bits += half >> e + bits &~= mantissa >> e + } + + return transmute(f16)bits } +@(require_results) round_f16le :: proc "contextless" (x: f16le) -> f16le { return #force_inline f16le(round_f16(f16(x))) } +@(require_results) round_f16be :: proc "contextless" (x: f16be) -> f16be { return #force_inline f16be(round_f16(f16(x))) } @(require_results) -round_f32 :: proc "contextless" (x: f32) -> f32 { - return ceil(x - 0.5) if x < 0 else floor(x + 0.5) +round_f32 :: proc "contextless" (x: f32) -> f32 { + // origin: Go /src/math/floor.go + // + // Copyright (c) 2009 The Go Authors. 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 Google Inc. 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 + // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + mask :: F32_MASK + shift :: F32_SHIFT + bias :: F32_BIAS + + bits := transmute(u32)x + e := (bits >> shift) & mask + + if e < bias { + bits &= 0x8000_0000 + if e == bias - 1 { + bits |= transmute(u32)f32(1) + } + } else if e < bias + shift { + half :: 1 << (shift - 1) + mantissa :: (1 << shift) - 1 + e -= bias + bits += half >> e + bits &~= mantissa >> e + } + + return transmute(f32)bits } +@(require_results) round_f32le :: proc "contextless" (x: f32le) -> f32le { return #force_inline f32le(round_f32(f32(x))) } +@(require_results) round_f32be :: proc "contextless" (x: f32be) -> f32be { return #force_inline f32be(round_f32(f32(x))) } + @(require_results) -round_f32le :: proc "contextless" (x: f32le) -> f32le { - return ceil(x - 0.5) if x < 0 else floor(x + 0.5) -} -@(require_results) -round_f32be :: proc "contextless" (x: f32be) -> f32be { - return ceil(x - 0.5) if x < 0 else floor(x + 0.5) -} -@(require_results) -round_f64 :: proc "contextless" (x: f64) -> f64 { - return ceil(x - 0.5) if x < 0 else floor(x + 0.5) -} -@(require_results) -round_f64le :: proc "contextless" (x: f64le) -> f64le { - return ceil(x - 0.5) if x < 0 else floor(x + 0.5) -} -@(require_results) -round_f64be :: proc "contextless" (x: f64be) -> f64be { - return ceil(x - 0.5) if x < 0 else floor(x + 0.5) +round_f64 :: proc "contextless" (x: f64) -> f64 { + // origin: Go /src/math/floor.go + // + // Copyright (c) 2009 The Go Authors. 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 Google Inc. 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 + // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + mask :: F64_MASK + shift :: F64_SHIFT + bias :: F64_BIAS + + bits := transmute(u64)x + e := (bits >> shift) & mask + + if e < bias { + bits &= 0x8000_0000_0000_0000 + if e == bias - 1 { + bits |= transmute(u64)f64(1) + } + } else if e < bias + shift { + half :: 1 << (shift - 1) + mantissa :: (1 << shift) - 1 + e -= bias + bits += half >> e + bits &~= mantissa >> e + } + + return transmute(f64)bits } +@(require_results) round_f64le :: proc "contextless" (x: f64le) -> f64le { return #force_inline f64le(round_f64(f64(x))) } +@(require_results) round_f64be :: proc "contextless" (x: f64be) -> f64be { return #force_inline f64be(round_f64(f64(x))) } round :: proc{ round_f16, round_f16le, round_f16be, round_f32, round_f32le, round_f32be, @@ -1126,7 +1271,7 @@ binomial :: proc "contextless" (n, k: int) -> int { } b := n - for i in 2.. 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, @@ -1159,7 +1304,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, @@ -1616,7 +1761,28 @@ atan2_f64be :: proc "contextless" (y, x: f64be) -> f64be { // TODO(bill): Better atan2_f32 return f64be(atan2_f64(f64(y), f64(x))) } +/* + Return the arc tangent of y/x in radians. Defined on the domain [-∞, ∞] for x and y with a range of [-π, π] + Special cases: + atan2(y, NaN) = NaN + atan2(NaN, x) = NaN + atan2(+0, x>=0) = + 0 + atan2(-0, x>=0) = - 0 + atan2(+0, x<=-0) = + π + atan2(-0, x<=-0) = - π + atan2(y>0, 0) = + π/2 + atan2(y<0, 0) = - π/2 + atan2(+∞, +∞) = + π/4 + atan2(-∞, +∞) = - π/4 + atan2(+∞, -∞) = 3π/4 + atan2(-∞, -∞) = - 3π/4 + atan2(y, +∞) = 0 + atan2(y>0, -∞) = + π + atan2(y<0, -∞) = - π + atan2(+∞, x) = + π/2 + atan2(-∞, x) = - π/2 +*/ atan2 :: proc{ atan2_f64, atan2_f32, atan2_f16, atan2_f64le, atan2_f64be, @@ -1624,6 +1790,7 @@ atan2 :: proc{ atan2_f16le, atan2_f16be, } +// Return the arc tangent of x, in radians. Defined on the domain of [-∞, ∞] with a range of [-π/2, π/2] @(require_results) atan :: proc "contextless" (x: $T) -> T where intrinsics.type_is_float(T) { return atan2(x, 1) @@ -1735,6 +1902,7 @@ asin_f16le :: proc "contextless" (x: f16le) -> f16le { asin_f16be :: proc "contextless" (x: f16be) -> f16be { return f16be(asin_f64(f64(x))) } +// Return the arc sine of x, in radians. Defined on the domain of [-1, 1] with a range of [-π/2, π/2] asin :: proc{ asin_f64, asin_f32, asin_f16, asin_f64le, asin_f64be, @@ -1849,6 +2017,7 @@ acos_f16le :: proc "contextless" (x: f16le) -> f16le { acos_f16be :: proc "contextless" (x: f16be) -> f16be { return f16be(acos_f64(f64(x))) } +// Return the arc cosine of x, in radians. Defined on the domain of [-1, 1] with a range of [0, π]. acos :: proc{ acos_f64, acos_f32, acos_f16, acos_f64le, acos_f64be, @@ -2280,6 +2449,36 @@ hypot :: proc{ hypot_f64, hypot_f64le, hypot_f64be, } +@(require_results) +count_digits_of_base :: proc "contextless" (value: $T, $base: int) -> (digits: int) where intrinsics.type_is_integer(T) { + #assert(base >= 2, "base must be 2 or greater.") + + value := value + when !intrinsics.type_is_unsigned(T) { + value = abs(value) + } + + when base == 2 { + digits = max(1, 8 * size_of(T) - int(intrinsics.count_leading_zeros(value))) + } else when intrinsics.count_ones(base) == 1 { + free_bits := 8 * size_of(T) - int(intrinsics.count_leading_zeros(value)) + digits, free_bits = divmod(free_bits, intrinsics.constant_log2(base)) + if free_bits > 0 { + digits += 1 + } + digits = max(1, digits) + } else { + digits = 1 + base := cast(T)base + for value >= base { + value /= base + digits += 1 + } + } + + return +} + F16_DIG :: 3 F16_EPSILON :: 0.00097656 F16_GUARD :: 0 @@ -2309,17 +2508,17 @@ F32_NORMALIZE :: 0 F32_RADIX :: 2 F32_ROUNDS :: 1 -F64_DIG :: 15 // # of decimal digits of precision -F64_EPSILON :: 2.2204460492503131e-016 // smallest such that 1.0+F64_EPSILON != 1.0 -F64_MANT_DIG :: 53 // # of bits in mantissa -F64_MAX :: 1.7976931348623158e+308 // max value -F64_MAX_10_EXP :: 308 // max decimal exponent -F64_MAX_EXP :: 1024 // max binary exponent -F64_MIN :: 2.2250738585072014e-308 // min positive value -F64_MIN_10_EXP :: -307 // min decimal exponent -F64_MIN_EXP :: -1021 // min binary exponent -F64_RADIX :: 2 // exponent radix -F64_ROUNDS :: 1 // addition rounding: near +F64_DIG :: 15 // Number of representable decimal digits. +F64_EPSILON :: 2.2204460492503131e-016 // Smallest number such that `1.0 + F64_EPSILON != 1.0`. +F64_MANT_DIG :: 53 // Number of bits in the mantissa. +F64_MAX :: 1.7976931348623158e+308 // Maximum representable value. +F64_MAX_10_EXP :: 308 // Maximum base-10 exponent yielding normalized value. +F64_MAX_EXP :: 1024 // One greater than the maximum possible base-2 exponent yielding normalized value. +F64_MIN :: 2.2250738585072014e-308 // Minimum positive normalized value. +F64_MIN_10_EXP :: -307 // Minimum base-10 exponent yielding normalized value. +F64_MIN_EXP :: -1021 // One greater than the minimum possible base-2 exponent yielding normalized value. +F64_RADIX :: 2 // Exponent radix. +F64_ROUNDS :: 1 // Addition rounding: near. F16_MASK :: 0x1f @@ -2350,4 +2549,4 @@ INF_F64 :: f64(0h7FF0_0000_0000_0000) NEG_INF_F64 :: f64(0hFFF0_0000_0000_0000) SNAN_F64 :: f64(0h7FF0_0000_0000_0001) -QNAN_F64 :: f64(0h7FF8_0000_0000_0001) \ No newline at end of file +QNAN_F64 :: f64(0h7FF8_0000_0000_0001) diff --git a/core/math/math_basic.odin b/core/math/math_basic.odin index 95e0a93ec..2584df71f 100644 --- a/core/math/math_basic.odin +++ b/core/math/math_basic.odin @@ -1,7 +1,7 @@ -//+build !js +#+build !js package math -import "core:intrinsics" +import "base:intrinsics" @(default_calling_convention="none", private="file") foreign _ { diff --git a/core/math/math_basic_js.odin b/core/math/math_basic_js.odin index acd3c2b39..2604ebc8b 100644 --- a/core/math/math_basic_js.odin +++ b/core/math/math_basic_js.odin @@ -1,7 +1,7 @@ -//+build js +#+build js package math -import "core:intrinsics" +import "base:intrinsics" foreign import "odin_env" 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..9b4d3de40 100644 --- a/core/math/math_sincos.odin +++ b/core/math/math_sincos.odin @@ -189,12 +189,12 @@ sincos_f64 :: proc "contextless" (x: f64) -> (sin, cos: f64) #no_bounds_check { // sin coefficients @(private="file") _sin := [?]f64{ - 0h3de5d8fd1fd19ccd, // 1.58962301576546568060e-10 - 0hbe5ae5e5a9291f5d, // -2.50507477628578072866e-8 - 0h3ec71de3567d48a1, // 2.75573136213857245213e-6 - 0hbf2a01a019bfdf03, // -1.98412698295895385996e-4 - 0h3f8111111110f7d0, // 8.33333333332211858878e-3 - 0hbfc5555555555548, // -1.66666666666666307295e-1 + 0h3de5d8fd1fd19ccd, // 1.58962301576546568060e-10 + 0hbe5ae5e5a9291f5d, // -2.50507477628578072866e-8 + 0h3ec71de3567d48a1, // 2.75573136213857245213e-6 + 0hbf2a01a019bfdf03, // -1.98412698295895385996e-4 + 0h3f8111111110f7d0, // 8.33333333332211858878e-3 + 0hbfc5555555555548, // -1.66666666666666307295e-1 } // cos coefficients @@ -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..f75c0ee87 100644 --- a/core/math/noise/internal.odin +++ b/core/math/noise/internal.odin @@ -4,7 +4,7 @@ Ported from https://github.com/KdotJPG/OpenSimplex2. Copyright 2022 Yuki2 (https://github.com/NoahR02) */ -//+private +#+private package math_noise /* @@ -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/noise/opensimplex2.odin b/core/math/noise/opensimplex2.odin index d28356f2c..634c32948 100644 --- a/core/math/noise/opensimplex2.odin +++ b/core/math/noise/opensimplex2.odin @@ -1,8 +1,8 @@ /* OpenSimplex2 noise implementation. - Ported from https://github.com/KdotJPG/OpenSimplex2. - Copyright 2022 Yuki2 (https://github.com/NoahR02) + Ported from [[ https://github.com/KdotJPG/OpenSimplex2 }]. + Copyright 2022 Yuki2 [[ https://github.com/NoahR02 ]] */ package math_noise @@ -177,4 +177,4 @@ noise_4d_fallback :: proc(seed: i64, coord: Vec4) -> (value: f32) { // Get points for A4 lattice skew := f64(SKEW_4D) * (coord.x + coord.y + coord.z + coord.w) return _internal_noise_4d_unskewed_base(seed, coord + skew) -} \ No newline at end of file +} diff --git a/core/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 7e6d58ee2..72d9400d7 100644 --- a/core/math/rand/rand.odin +++ b/core/math/rand/rand.odin @@ -4,22 +4,27 @@ Package core:math/rand implements various random number generators */ package rand -import "core:intrinsics" +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 @@ -29,144 +34,38 @@ Example: import "core:fmt" set_global_seed_example :: proc() { - rand.set_global_seed(1) + rand.reset(1) fmt.println(rand.uint64()) } Possible Output: 10 - */ -set_global_seed :: proc(seed: u64) { - init(&global_rand, seed) -} - -/* -Creates a new random number generator. - -Inputs: -- seed: The seed value to create the random number generator with - -Returns: -- res: The created random number generator - -Example: - import "core:math/rand" - import "core:fmt" - - create_example :: proc() { - my_rand := rand.create(1) - fmt.println(rand.uint64(&my_rand)) - } - -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 +74,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 +84,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 +97,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 +107,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 +120,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 +130,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 +140,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 +148,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 +157,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 +171,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 +180,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 +194,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 +203,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 +221,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 +231,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 +251,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 +262,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 +272,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 +292,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 +303,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 +313,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 +333,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 +344,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 +354,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 +376,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 +385,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 +398,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 +407,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 +417,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 +426,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 +435,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 +450,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 +461,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 +470,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 +485,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 +494,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 +507,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 +529,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 +541,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 +555,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 +570,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 +588,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(0); i < n; i += 1 { - j := int63_max(n, 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 +611,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 +620,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,10 +636,79 @@ 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, gen := context.random_generator) -> T where intrinsics.type_is_enum(T) { + when size_of(T) <= 8 && len(T) == cap(T) { + when intrinsics.type_is_unsigned(intrinsics.type_core_type(T)) && + u64(max(T)) > u64(max(i64)) { + i := uint64(gen) % u64(len(T)) + i += u64(min(T)) + return T(i) + } else { + i := int63_max(i64(len(T)), gen) + i += i64(min(T)) + return T(i) + } + } else { + values := runtime.type_info_base(type_info_of(T)).variant.(runtime.Type_Info_Enum).values + return T(choice(values)) + } +} + +/* +Returns a random *set* bit from the provided `bit_set`. + +Inputs: +- set: The `bit_set` to choose a random set bit from + +Returns: +- res: The randomly selected bit, or the zero value if `ok` is `false` +- ok: Whether the bit_set was not empty and thus `res` is actually a random set bit + +Example: + import "core:math/rand" + import "core:fmt" + + choice_bit_set_example :: proc() { + Flags :: enum { + A, + B = 10, + C, + } + + fmt.println(rand.choice_bit_set(bit_set[Flags]{})) + fmt.println(rand.choice_bit_set(bit_set[Flags]{.B})) + fmt.println(rand.choice_bit_set(bit_set[Flags]{.B, .C})) + fmt.println(rand.choice_bit_set(bit_set[0..<15]{5, 1, 4})) + } + +Possible Output: + A false + B true + C true + 5 true +*/ +@(require_results) +choice_bit_set :: proc(set: $T/bit_set[$E], gen := context.random_generator) -> (res: E, ok: bool) { + total_set := card(set) + if total_set == 0 { + return {}, false + } + + core_set := transmute(intrinsics.type_bit_set_underlying_type(T))set + + for target := int_max(total_set, gen); target > 0; target -= 1 { + core_set &= core_set - 1 + } + + return E(intrinsics.count_trailing_zeros(core_set)), true } 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/alloc.odin b/core/mem/alloc.odin index 57e82a5f8..fac58daaf 100644 --- a/core/mem/alloc.odin +++ b/core/mem/alloc.odin @@ -1,112 +1,712 @@ package mem -import "core:runtime" +import "base:runtime" -// NOTE(bill, 2019-12-31): These are defined in `package runtime` as they are used in the `context`. This is to prevent an import definition cycle. +//NOTE(bill, 2019-12-31): These are defined in `package runtime` as they are used in the `context`. This is to prevent an import definition cycle. + +/* +A request to allocator procedure. + +This type represents a type of allocation request made to an allocator +procedure. There is one allocator procedure per allocator, and this value is +used to discriminate between different functions of the allocator. + +The type is defined as follows: + + Allocator_Mode :: enum byte { + Alloc, + Alloc_Non_Zeroed, + Free, + Free_All, + Resize, + Resize_Non_Zeroed, + Query_Features, + } + +Depending on which value is used, the allocator procedure will perform different +functions: + +- `Alloc`: Allocates a memory region with a given `size` and `alignment`. +- `Alloc_Non_Zeroed`: Same as `Alloc` without explicit zero-initialization of + the memory region. +- `Free`: Free a memory region located at address `ptr` with a given `size`. +- `Free_All`: Free all memory allocated using this allocator. +- `Resize`: Resize a memory region located at address `old_ptr` with size + `old_size` to be `size` bytes in length and have the specified `alignment`, + in case a re-alllocation occurs. +- `Resize_Non_Zeroed`: Same as `Resize`, without explicit zero-initialization. +*/ Allocator_Mode :: runtime.Allocator_Mode -/* -Allocator_Mode :: enum byte { - Alloc, - Free, - Free_All, - Resize, - Query_Features, -} -*/ +/* +A set of allocator features. + +This type represents values that contain a set of features an allocator has. +Currently the type is defined as follows: + + Allocator_Mode_Set :: distinct bit_set[Allocator_Mode]; +*/ Allocator_Mode_Set :: runtime.Allocator_Mode_Set -/* -Allocator_Mode_Set :: distinct bit_set[Allocator_Mode]; -*/ +/* +Allocator information. + +This type represents information about a given allocator at a specific point +in time. Currently the type is defined as follows: + + Allocator_Query_Info :: struct { + pointer: rawptr, + size: Maybe(int), + alignment: Maybe(int), + } + +- `pointer`: Pointer to a backing buffer. +- `size`: Size of the backing buffer. +- `alignment`: The allocator's alignment. + +If not applicable, any of these fields may be `nil`. +*/ Allocator_Query_Info :: runtime.Allocator_Query_Info -/* -Allocator_Query_Info :: struct { - pointer: rawptr, - size: Maybe(int), - alignment: Maybe(int), -} -*/ -Allocator_Error :: runtime.Allocator_Error /* -Allocator_Error :: enum byte { - None = 0, - Out_Of_Memory = 1, - Invalid_Pointer = 2, - Invalid_Argument = 3, - Mode_Not_Implemented = 4, -} +An allocation request error. + +This type represents error values the allocators may return upon requests. + + Allocator_Error :: enum byte { + None = 0, + Out_Of_Memory = 1, + Invalid_Pointer = 2, + Invalid_Argument = 3, + Mode_Not_Implemented = 4, + } + +The meaning of the errors is as follows: + +- `None`: No error. +- `Out_Of_Memory`: Either: + 1. The allocator has ran out of the backing buffer, or the requested + allocation size is too large to fit into a backing buffer. + 2. The operating system error during memory allocation. + 3. The backing allocator was used to allocate a new backing buffer and the + backing allocator returned Out_Of_Memory. +- `Invalid_Pointer`: The pointer referring to a memory region does not belong + to any of the allocators backing buffers or does not point to a valid start + of an allocation made in that allocator. +- `Invalid_Argument`: Can occur if one of the arguments makes it impossible to + satisfy a request (i.e. having alignment larger than the backing buffer + of the allocation). +- `Mode_Not_Implemented`: The allocator does not support the specified + operation. For example, an arena does not support freeing individual + allocations. +*/ +Allocator_Error :: runtime.Allocator_Error + +/* +The allocator procedure. + +This type represents allocation procedures. An allocation procedure is a single +procedure, implementing all allocator functions such as allocating the memory, +freeing the memory, etc. + +Currently the type is defined as follows: + + Allocator_Proc :: #type proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size: int, + alignment: int, + old_memory: rawptr, + old_size: int, + location: Source_Code_Location = #caller_location, + ) -> ([]byte, Allocator_Error); + +The function of this procedure and the meaning of parameters depends on the +value of the `mode` parameter. For any operation the following constraints +apply: + +- The `alignment` must be a power of two. +- The `size` must be a positive integer. + +## 1. `.Alloc`, `.Alloc_Non_Zeroed` + +Allocates a memory region of size `size`, aligned on a boundary specified by +`alignment`. + +**Inputs**: +- `allocator_data`: Pointer to the allocator data. +- `mode`: `.Alloc` or `.Alloc_Non_Zeroed`. +- `size`: The desired size of the memory region. +- `alignment`: The desired alignmnet of the allocation. +- `old_memory`: Unused, should be `nil`. +- `old_size`: Unused, should be 0. + +**Returns**: +1. The memory region, if allocated successfully, or `nil` otherwise. +2. An error, if allocation failed. + +**Note**: The nil allocator may return `nil`, even if no error is returned. +Always check both the error and the allocated buffer. + +**Note**: The `.Alloc` mode is required to be implemented for an allocator +and can not return a `.Mode_Not_Implemented` error. + +## 2. `Free` + +Frees a memory region located at the address specified by `old_memory`. If the +allocator does not track sizes of allocations, the size should be specified in +the `old_size` parameter. + +**Inputs**: +- `allocator_data`: Pointer to the allocator data. +- `mode`: `.Free`. +- `size`: Unused, should be 0. +- `alignment`: Unused, should be 0. +- `old_memory`: Pointer to the memory region to free. +- `old_size`: The size of the memory region to free. This parameter is optional + if the allocator keeps track of the sizes of allocations. + +**Returns**: +1. `nil` +2. Error, if freeing failed. + +## 3. `Free_All` + +Frees all allocations, associated with the allocator, making it available for +further allocations using the same backing buffers. + +**Inputs**: +- `allocator_data`: Pointer to the allocator data. +- `mode`: `.Free_All`. +- `size`: Unused, should be 0. +- `alignment`: Unused, should be 0. +- `old_memory`: Unused, should be `nil`. +- `old_size`: Unused, should be `0`. + +**Returns**: +1. `nil`. +2. Error, if freeing failed. + +## 4. `Resize`, `Resize_Non_Zeroed` + +Resizes the memory region, of the size `old_size` located at the address +specified by `old_memory` to have the new size `size`. The slice of the new +memory region is returned from the procedure. The allocator may attempt to +keep the new memory region at the same address as the previous allocation, +however no such guarantee is made. Do not assume the new memory region will +be at the same address as the old memory region. + +If `old_memory` pointer is `nil`, this function acts just like `.Alloc` or +`.Alloc_Non_Zeroed`, using `size` and `alignment` to allocate a new memory +region. + +If `new_size` is `nil`, the procedure acts just like `.Free`, freeing the +memory region `old_size` bytes in length, located at the address specified by +`old_memory`. + +If the `old_memory` pointer is not aligned to the boundary specified by +`alignment`, the procedure relocates the buffer such that the reallocated +buffer is aligned to the boundary specified by `alignment`. + +**Inputs**: +- `allocator_data`: Pointer to the allocator data. +- `mode`: `.Resize` or `.Resize_All`. +- `size`: The desired new size of the memory region. +- `alignment`: The alignment of the new memory region, if its allocated +- `old_memory`: The pointer to the memory region to resize. +- `old_size`: The size of the memory region to resize. If the allocator + keeps track of the sizes of allocations, this parameter is optional. + +**Returns**: +1. The slice of the memory region after resize operation, if successfull, + `nil` otherwise. +2. An error, if the resize failed. + +**Note**: Some allocators may return `nil`, even if no error is returned. +Always check both the error and the allocated buffer. + +**Note**: if `old_size` is `0` and `old_memory` is `nil`, this operation is a +no-op, and should not return errors. */ Allocator_Proc :: runtime.Allocator_Proc -/* -Allocator_Proc :: #type proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, location: Source_Code_Location = #caller_location) -> ([]byte, Allocator_Error); -*/ +/* +Allocator. + +This type represents generic interface for all allocators. Currently this type +is defined as follows: + + Allocator :: struct { + procedure: Allocator_Proc, + data: rawptr, + } + +- `procedure`: Pointer to the allocation procedure. +- `data`: Pointer to the allocator data. +*/ Allocator :: runtime.Allocator -/* -Allocator :: struct { - procedure: Allocator_Proc, - data: rawptr, -} -*/ +/* +Default alignment. + +This value is the default alignment for all platforms that is used, if the +alignment is not specified explicitly. +*/ DEFAULT_ALIGNMENT :: 2*align_of(rawptr) +/* +Default page size. + +This value is the default page size for the current platform. +*/ DEFAULT_PAGE_SIZE :: 64 * 1024 when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 else 16 * 1024 when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 else 4 * 1024 +/* +Allocate memory. + +This function allocates `size` bytes of memory, aligned to a boundary specified +by `alignment` using the allocator specified by `allocator`. + +If the `size` parameter is `0`, the operation is a no-op. + +**Inputs**: +- `size`: The desired size of the allocated memory region. +- `alignment`: The desired alignment of the allocated memory region. +- `allocator`: The allocator to allocate from. + +**Returns**: +1. Pointer to the allocated memory, or `nil` if allocation failed. +2. Error, if the allocation failed. + +**Errors**: +- `None`: If no error occurred. +- `Out_Of_Memory`: Occurs when the allocator runs out of space in any of its + backing buffers, the backing allocator has ran out of space, or an operating + system failure occurred. +- `Invalid_Argument`: If the supplied `size` is negative, alignment is not a + power of two. +*/ @(require_results) -alloc :: proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (rawptr, Allocator_Error) { +alloc :: proc( + size: int, + alignment: int = DEFAULT_ALIGNMENT, + allocator := context.allocator, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { data, err := runtime.mem_alloc(size, alignment, allocator, loc) return raw_data(data), err } +/* +Allocate memory. + +This function allocates `size` bytes of memory, aligned to a boundary specified +by `alignment` using the allocator specified by `allocator`. + +**Inputs**: +- `size`: The desired size of the allocated memory region. +- `alignment`: The desired alignment of the allocated memory region. +- `allocator`: The allocator to allocate from. + +**Returns**: +1. Slice of the allocated memory region, or `nil` if allocation failed. +2. Error, if the allocation failed. + +**Errors**: +- `None`: If no error occurred. +- `Out_Of_Memory`: Occurs when the allocator runs out of space in any of its + backing buffers, the backing allocator has ran out of space, or an operating + system failure occurred. +- `Invalid_Argument`: If the supplied `size` is negative, alignment is not a + power of two. +*/ @(require_results) -alloc_bytes :: proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { +alloc_bytes :: proc( + size: int, + alignment: int = DEFAULT_ALIGNMENT, + allocator := context.allocator, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { return runtime.mem_alloc(size, alignment, allocator, loc) } +/* +Allocate non-zeroed memory. + +This function allocates `size` bytes of memory, aligned to a boundary specified +by `alignment` using the allocator specified by `allocator`. This procedure +does not explicitly zero-initialize allocated memory region. + +**Inputs**: +- `size`: The desired size of the allocated memory region. +- `alignment`: The desired alignment of the allocated memory region. +- `allocator`: The allocator to allocate from. + +**Returns**: +1. Slice of the allocated memory region, or `nil` if allocation failed. +2. Error, if the allocation failed. + +**Errors**: +- `None`: If no error occurred. +- `Out_Of_Memory`: Occurs when the allocator runs out of space in any of its + backing buffers, the backing allocator has ran out of space, or an operating + system failure occurred. +- `Invalid_Argument`: If the supplied `size` is negative, alignment is not a + power of two. +*/ @(require_results) -alloc_bytes_non_zeroed :: proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { +alloc_bytes_non_zeroed :: proc( + size: int, + alignment: int = DEFAULT_ALIGNMENT, + allocator := context.allocator, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { return runtime.mem_alloc_non_zeroed(size, alignment, allocator, loc) } -free :: proc(ptr: rawptr, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { +/* +Free memory. + +This procedure frees memory region located at the address, specified by `ptr`, +allocated from the allocator specified by `allocator`. + +**Inputs**: +- `ptr`: Pointer to the memory region to free. +- `allocator`: The allocator to free to. + +**Returns**: +- The error, if freeing failed. + +**Errors**: +- `None`: When no error has occurred. +- `Invalid_Pointer`: The specified pointer is not owned by the specified allocator, + or does not point to a valid allocation. +- `Mode_Not_Implemented`: If the specified allocator does not support the `.Free` +mode. +*/ +free :: proc( + ptr: rawptr, + allocator := context.allocator, + loc := #caller_location, +) -> Allocator_Error { return runtime.mem_free(ptr, allocator, loc) } -free_with_size :: proc(ptr: rawptr, byte_count: int, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { - if ptr == nil || allocator.procedure == nil { - return nil - } - _, err := allocator.procedure(allocator.data, .Free, 0, 0, ptr, byte_count, loc) - return err +/* +Free a memory region. + +This procedure frees `size` bytes of memory region located at the address, +specified by `ptr`, allocated from the allocator specified by `allocator`. + +If the `size` parameter is `0`, this call is equivalent to `free()`. + +**Inputs**: +- `ptr`: Pointer to the memory region to free. +- `size`: The size of the memory region to free. +- `allocator`: The allocator to free to. + +**Returns**: +- The error, if freeing failed. + +**Errors**: +- `None`: When no error has occurred. +- `Invalid_Pointer`: The specified pointer is not owned by the specified allocator, + or does not point to a valid allocation. +- `Mode_Not_Implemented`: If the specified allocator does not support the `.Free` +mode. +*/ +free_with_size :: proc( + ptr: rawptr, + size: int, + allocator := context.allocator, + loc := #caller_location, +) -> Allocator_Error { + return runtime.mem_free_with_size(ptr, size, allocator, loc) } -free_bytes :: proc(bytes: []byte, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { +/* +Free a memory region. + +This procedure frees memory region, specified by `bytes`, allocated from the +allocator specified by `allocator`. + +If the length of the specified slice is zero, the `.Invalid_Argument` error +is returned. + +**Inputs**: +- `bytes`: The memory region to free. +- `allocator`: The allocator to free to. + +**Returns**: +- The error, if freeing failed. + +**Errors**: +- `None`: When no error has occurred. +- `Invalid_Pointer`: The specified pointer is not owned by the specified allocator, + or does not point to a valid allocation. +- `Mode_Not_Implemented`: If the specified allocator does not support the `.Free` +mode. +*/ +free_bytes :: proc( + bytes: []byte, + allocator := context.allocator, + loc := #caller_location, +) -> Allocator_Error { return runtime.mem_free_bytes(bytes, allocator, loc) } +/* +Free all allocations. + +This procedure frees all allocations made on the allocator specified by +`allocator` to that allocator, making it available for further allocations. + +**Inputs**: +- `allocator`: The allocator to free to. + +**Errors**: +- `None`: When no error has occurred. +- `Mode_Not_Implemented`: If the specified allocator does not support the `.Free` +mode. +*/ free_all :: proc(allocator := context.allocator, loc := #caller_location) -> Allocator_Error { return runtime.mem_free_all(allocator, loc) } +/* +Resize a memory region. + +This procedure resizes a memory region, `old_size` bytes in size, located at +the address specified by `ptr`, such that it has a new size, specified by +`new_size` and and is aligned on a boundary specified by `alignment`. + +If the `ptr` parameter is `nil`, `resize()` acts just like `alloc()`, allocating +`new_size` bytes, aligned on a boundary specified by `alignment`. + +If the `new_size` parameter is `0`, `resize()` acts just like `free()`, freeing +the memory region `old_size` bytes in length, located at the address specified +by `ptr`. + +If the `old_memory` pointer is not aligned to the boundary specified by +`alignment`, the procedure relocates the buffer such that the reallocated +buffer is aligned to the boundary specified by `alignment`. + +**Inputs**: +- `ptr`: Pointer to the memory region to resize. +- `old_size`: Size of the memory region to resize. +- `new_size`: The desired size of the resized memory region. +- `alignment`: The desired alignment of the resized memory region. +- `allocator`: The owner of the memory region to resize. + +**Returns**: +1. The pointer to the resized memory region, if successfull, `nil` otherwise. +2. Error, if resize failed. + +**Errors**: +- `None`: No error. +- `Out_Of_Memory`: When the allocator's backing buffer or it's backing + allocator does not have enough space to fit in an allocation with the new + size, or an operating system failure occurs. +- `Invalid_Pointer`: The pointer referring to a memory region does not belong + to any of the allocators backing buffers or does not point to a valid start + of an allocation made in that allocator. +- `Invalid_Argument`: When `size` is negative, alignment is not a power of two, + or the `old_size` argument is incorrect. +- `Mode_Not_Implemented`: The allocator does not support the `.Realloc` mode. + +**Note**: if `old_size` is `0` and `old_memory` is `nil`, this operation is a +no-op, and should not return errors. +*/ @(require_results) -resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> (rawptr, Allocator_Error) { +resize :: proc( + ptr: rawptr, + old_size: int, + new_size: int, + alignment: int = DEFAULT_ALIGNMENT, + allocator := context.allocator, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { data, err := runtime.mem_resize(ptr, old_size, new_size, alignment, allocator, loc) return raw_data(data), err } +/* +Resize a memory region without zero-initialization. + +This procedure resizes a memory region, `old_size` bytes in size, located at +the address specified by `ptr`, such that it has a new size, specified by +`new_size` and and is aligned on a boundary specified by `alignment`. + +If the `ptr` parameter is `nil`, `resize()` acts just like `alloc()`, allocating +`new_size` bytes, aligned on a boundary specified by `alignment`. + +If the `new_size` parameter is `0`, `resize()` acts just like `free()`, freeing +the memory region `old_size` bytes in length, located at the address specified +by `ptr`. + +If the `old_memory` pointer is not aligned to the boundary specified by +`alignment`, the procedure relocates the buffer such that the reallocated +buffer is aligned to the boundary specified by `alignment`. + +Unlike `resize()`, this procedure does not explicitly zero-initialize any new +memory. + +**Inputs**: +- `ptr`: Pointer to the memory region to resize. +- `old_size`: Size of the memory region to resize. +- `new_size`: The desired size of the resized memory region. +- `alignment`: The desired alignment of the resized memory region. +- `allocator`: The owner of the memory region to resize. + +**Returns**: +1. The pointer to the resized memory region, if successfull, `nil` otherwise. +2. Error, if resize failed. + +**Errors**: +- `None`: No error. +- `Out_Of_Memory`: When the allocator's backing buffer or it's backing + allocator does not have enough space to fit in an allocation with the new + size, or an operating system failure occurs. +- `Invalid_Pointer`: The pointer referring to a memory region does not belong + to any of the allocators backing buffers or does not point to a valid start + of an allocation made in that allocator. +- `Invalid_Argument`: When `size` is negative, alignment is not a power of two, + or the `old_size` argument is incorrect. +- `Mode_Not_Implemented`: The allocator does not support the `.Realloc` mode. + +**Note**: if `old_size` is `0` and `old_memory` is `nil`, this operation is a +no-op, and should not return errors. +*/ @(require_results) -resize_bytes :: proc(old_data: []byte, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { +resize_non_zeroed :: proc( + ptr: rawptr, + old_size: int, + new_size: int, + alignment: int = DEFAULT_ALIGNMENT, + allocator := context.allocator, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + data, err := runtime.non_zero_mem_resize(ptr, old_size, new_size, alignment, allocator, loc) + return raw_data(data), err +} + +/* +Resize a memory region. + +This procedure resizes a memory region, specified by `old_data`, such that it +has a new size, specified by `new_size` and and is aligned on a boundary +specified by `alignment`. + +If the `old_data` parameter is `nil`, `resize_bytes()` acts just like +`alloc_bytes()`, allocating `new_size` bytes, aligned on a boundary specified +by `alignment`. + +If the `new_size` parameter is `0`, `resize_bytes()` acts just like +`free_bytes()`, freeing the memory region specified by `old_data`. + +If the `old_memory` pointer is not aligned to the boundary specified by +`alignment`, the procedure relocates the buffer such that the reallocated +buffer is aligned to the boundary specified by `alignment`. + +**Inputs**: +- `old_data`: Pointer to the memory region to resize. +- `new_size`: The desired size of the resized memory region. +- `alignment`: The desired alignment of the resized memory region. +- `allocator`: The owner of the memory region to resize. + +**Returns**: +1. The resized memory region, if successfull, `nil` otherwise. +2. Error, if resize failed. + +**Errors**: +- `None`: No error. +- `Out_Of_Memory`: When the allocator's backing buffer or it's backing + allocator does not have enough space to fit in an allocation with the new + size, or an operating system failure occurs. +- `Invalid_Pointer`: The pointer referring to a memory region does not belong + to any of the allocators backing buffers or does not point to a valid start + of an allocation made in that allocator. +- `Invalid_Argument`: When `size` is negative, alignment is not a power of two, + or the `old_size` argument is incorrect. +- `Mode_Not_Implemented`: The allocator does not support the `.Realloc` mode. + +**Note**: if `old_size` is `0` and `old_memory` is `nil`, this operation is a +no-op, and should not return errors. +*/ +@(require_results) +resize_bytes :: proc( + old_data: []byte, + new_size: int, + alignment: int = DEFAULT_ALIGNMENT, + allocator := context.allocator, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { return runtime.mem_resize(raw_data(old_data), len(old_data), new_size, alignment, allocator, loc) } +/* +Resize a memory region. + +This procedure resizes a memory region, specified by `old_data`, such that it +has a new size, specified by `new_size` and and is aligned on a boundary +specified by `alignment`. + +If the `old_data` parameter is `nil`, `resize_bytes()` acts just like +`alloc_bytes()`, allocating `new_size` bytes, aligned on a boundary specified +by `alignment`. + +If the `new_size` parameter is `0`, `resize_bytes()` acts just like +`free_bytes()`, freeing the memory region specified by `old_data`. + +If the `old_memory` pointer is not aligned to the boundary specified by +`alignment`, the procedure relocates the buffer such that the reallocated +buffer is aligned to the boundary specified by `alignment`. + +Unlike `resize_bytes()`, this procedure does not explicitly zero-initialize +any new memory. + +**Inputs**: +- `old_data`: Pointer to the memory region to resize. +- `new_size`: The desired size of the resized memory region. +- `alignment`: The desired alignment of the resized memory region. +- `allocator`: The owner of the memory region to resize. + +**Returns**: +1. The resized memory region, if successfull, `nil` otherwise. +2. Error, if resize failed. + +**Errors**: +- `None`: No error. +- `Out_Of_Memory`: When the allocator's backing buffer or it's backing + allocator does not have enough space to fit in an allocation with the new + size, or an operating system failure occurs. +- `Invalid_Pointer`: The pointer referring to a memory region does not belong + to any of the allocators backing buffers or does not point to a valid start + of an allocation made in that allocator. +- `Invalid_Argument`: When `size` is negative, alignment is not a power of two, + or the `old_size` argument is incorrect. +- `Mode_Not_Implemented`: The allocator does not support the `.Realloc` mode. + +**Note**: if `old_size` is `0` and `old_memory` is `nil`, this operation is a +no-op, and should not return errors. +*/ +@(require_results) +resize_bytes_non_zeroed :: proc( + old_data: []byte, + new_size: int, + alignment: int = DEFAULT_ALIGNMENT, + allocator := context.allocator, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + return runtime.non_zero_mem_resize(raw_data(old_data), len(old_data), new_size, alignment, allocator, loc) +} + +/* +Query allocator features. +*/ @(require_results) query_features :: proc(allocator: Allocator, loc := #caller_location) -> (set: Allocator_Mode_Set) { if allocator.procedure != nil { @@ -116,8 +716,15 @@ query_features :: proc(allocator: Allocator, loc := #caller_location) -> (set: A return nil } +/* +Query allocator information. +*/ @(require_results) -query_info :: proc(pointer: rawptr, allocator: Allocator, loc := #caller_location) -> (props: Allocator_Query_Info) { +query_info :: proc( + pointer: rawptr, + allocator: Allocator, + loc := #caller_location, +) -> (props: Allocator_Query_Info) { props.pointer = pointer if allocator.procedure != nil { allocator.procedure(allocator.data, .Query_Info, 0, 0, &props, 0, loc) @@ -125,25 +732,62 @@ query_info :: proc(pointer: rawptr, allocator: Allocator, loc := #caller_locatio return } - - -delete_string :: proc(str: string, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { - return free_with_size(raw_data(str), len(str), allocator, loc) -} -delete_cstring :: proc(str: cstring, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { - return free((^byte)(str), allocator, loc) -} -delete_dynamic_array :: proc(array: $T/[dynamic]$E, loc := #caller_location) -> Allocator_Error { - return free_with_size(raw_data(array), cap(array)*size_of(E), array.allocator, loc) -} -delete_slice :: proc(array: $T/[]$E, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { - return free_with_size(raw_data(array), len(array)*size_of(E), allocator, loc) -} -delete_map :: proc(m: $T/map[$K]$V, loc := #caller_location) -> Allocator_Error { - return runtime.map_free_dynamic(transmute(Raw_Map)m, runtime.map_info(T), loc) +/* +Free a string. +*/ +delete_string :: proc( + str: string, + allocator := context.allocator, + loc := #caller_location, +) -> Allocator_Error { + return runtime.delete_string(str, allocator, loc) } +/* +Free a cstring. +*/ +delete_cstring :: proc( + str: cstring, + allocator := context.allocator, + loc := #caller_location, +) -> Allocator_Error { + return runtime.delete_cstring(str, allocator, loc) +} +/* +Free a dynamic array. +*/ +delete_dynamic_array :: proc( + array: $T/[dynamic]$E, + loc := #caller_location, +) -> Allocator_Error { + return runtime.delete_dynamic_array(array, loc) +} + +/* +Free a slice. +*/ +delete_slice :: proc( + array: $T/[]$E, + allocator := context.allocator, + loc := #caller_location, +) -> Allocator_Error { + return runtime.delete_slice(array, allocator, loc) +} + +/* +Free a map. +*/ +delete_map :: proc( + m: $T/map[$K]$V, + loc := #caller_location, +) -> Allocator_Error { + return runtime.delete_map(m, loc) +} + +/* +Free. +*/ delete :: proc{ delete_string, delete_cstring, @@ -152,80 +796,177 @@ delete :: proc{ delete_map, } +/* +Allocate a new object. +This procedure allocates a new object of type `T` using an allocator specified +by `allocator`, and returns a pointer to the allocated object, if allocated +successfully, or `nil` otherwise. +*/ @(require_results) -new :: proc($T: typeid, allocator := context.allocator, loc := #caller_location) -> (^T, Allocator_Error) { +new :: proc( + $T: typeid, + allocator := context.allocator, + loc := #caller_location, +) -> (^T, Allocator_Error) { return new_aligned(T, align_of(T), allocator, loc) } + +/* +Allocate a new object with alignment. + +This procedure allocates a new object of type `T` using an allocator specified +by `allocator`, and returns a pointer, aligned on a boundary specified by +`alignment` to the allocated object, if allocated successfully, or `nil` +otherwise. +*/ @(require_results) -new_aligned :: proc($T: typeid, alignment: int, allocator := context.allocator, loc := #caller_location) -> (t: ^T, err: Allocator_Error) { - data := alloc_bytes(size_of(T), alignment, allocator, loc) or_return - t = (^T)(raw_data(data)) - return -} -@(require_results) -new_clone :: proc(data: $T, allocator := context.allocator, loc := #caller_location) -> (t: ^T, err: Allocator_Error) { - backing := alloc_bytes(size_of(T), align_of(T), allocator, loc) or_return - t = (^T)(raw_data(backing)) - if t != nil { - t^ = data - return t, nil - } - return nil, .Out_Of_Memory +new_aligned :: proc( + $T: typeid, + alignment: int, + allocator := context.allocator, + loc := #caller_location, +) -> (t: ^T, err: Allocator_Error) { + return runtime.new_aligned(T, alignment, allocator, loc) } -@(require_results) -make_aligned :: proc($T: typeid/[]$E, #any_int len: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (slice: T, err: Allocator_Error) { - runtime.make_slice_error_loc(loc, len) - data := alloc_bytes(size_of(E)*len, alignment, allocator, loc) or_return - if data == nil && size_of(E) != 0 { - return - } - slice = transmute(T)Raw_Slice{raw_data(data), len} - return -} -@(require_results) -make_slice :: proc($T: typeid/[]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) { - return make_aligned(T, len, align_of(E), allocator, loc) -} -@(require_results) -make_dynamic_array :: proc($T: typeid/[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) { - return make_dynamic_array_len_cap(T, 0, 16, allocator, loc) -} -@(require_results) -make_dynamic_array_len :: proc($T: typeid/[dynamic]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) { - return make_dynamic_array_len_cap(T, len, len, allocator, loc) -} -@(require_results) -make_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) { - runtime.make_dynamic_array_error_loc(loc, len, cap) - data := alloc_bytes(size_of(E)*cap, align_of(E), allocator, loc) or_return - s := Raw_Dynamic_Array{raw_data(data), len, cap, allocator} - if data == nil && size_of(E) != 0 { - s.len, s.cap = 0, 0 - } - array = transmute(T)s - return -} -@(require_results) -make_map :: proc($T: typeid/map[$K]$E, #any_int cap: int = 1< (m: T, err: Allocator_Error) { - runtime.make_map_expr_error_loc(loc, cap) - context.allocator = allocator +/* +Allocate a new object and initialize it with a value. - err = reserve_map(&m, cap, loc) - return -} +This procedure allocates a new object of type `T` using an allocator specified +by `allocator`, and returns a pointer, aligned on a boundary specified by +`alignment` to the allocated object, if allocated successfully, or `nil` +otherwise. The allocated object is initialized with `data`. +*/ @(require_results) -make_multi_pointer :: proc($T: typeid/[^]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (mp: T, err: Allocator_Error) { - runtime.make_slice_error_loc(loc, len) - data := alloc_bytes(size_of(E)*len, align_of(E), allocator, loc) or_return - if data == nil && size_of(E) != 0 { - return - } - mp = cast(T)raw_data(data) - return +new_clone :: proc( + data: $T, + allocator := context.allocator, + loc := #caller_location, +) -> (t: ^T, err: Allocator_Error) { + return runtime.new_clone(data, allocator, loc) } +/* +Allocate a new slice with alignment. + +This procedure allocates a new slice of type `T` with length `len`, aligned +on a boundary specified by `alignment` from an allocator specified by +`allocator`, and returns the allocated slice. +*/ +@(require_results) +make_aligned :: proc( + $T: typeid/[]$E, + #any_int len: int, + alignment: int, + allocator := context.allocator, + loc := #caller_location, +) -> (slice: T, err: Allocator_Error) { + return runtime.make_aligned(T, len, alignment, allocator, loc) +} + +/* +Allocate a new slice. + +This procedure allocates a new slice of type `T` with length `len`, from an +allocator specified by `allocator`, and returns the allocated slice. +*/ +@(require_results) +make_slice :: proc( + $T: typeid/[]$E, + #any_int len: int, + allocator := context.allocator, + loc := #caller_location, +) -> (T, Allocator_Error) { + return runtime.make_slice(T, len, allocator, loc) +} + +/* +Allocate a dynamic array. + +This procedure creates a dynamic array of type `T`, with `allocator` as its +backing allocator, and initial length and capacity of `0`. +*/ +@(require_results) +make_dynamic_array :: proc( + $T: typeid/[dynamic]$E, + allocator := context.allocator, + loc := #caller_location, +) -> (T, Allocator_Error) { + return runtime.make_dynamic_array(T, allocator, loc) +} + +/* +Allocate a dynamic array with initial length. + +This procedure creates a dynamic array of type `T`, with `allocator` as its +backing allocator, and initial capacity of `0`, and initial length specified by +`len`. +*/ +@(require_results) +make_dynamic_array_len :: proc( + $T: typeid/[dynamic]$E, + #any_int len: int, + allocator := context.allocator, + loc := #caller_location, +) -> (T, Allocator_Error) { + return runtime.make_dynamic_array_len_cap(T, len, len, allocator, loc) +} + +/* +Allocate a dynamic array with initial length and capacity. + +This procedure creates a dynamic array of type `T`, with `allocator` as its +backing allocator, and initial capacity specified by `cap`, and initial length +specified by `len`. +*/ +@(require_results) +make_dynamic_array_len_cap :: proc( + $T: typeid/[dynamic]$E, + #any_int len: int, + #any_int cap: int, + allocator := context.allocator, + loc := #caller_location, +) -> (array: T, err: Allocator_Error) { + return runtime.make_dynamic_array_len_cap(T, len, cap, allocator, loc) +} + +/* +Allocate a map. + +This procedure creates a map of type `T` with initial capacity specified by +`cap`, that is using an allocator specified by `allocator` as its backing +allocator. +*/ +@(require_results) +make_map :: proc( + $T: typeid/map[$K]$E, + #any_int cap: int = 1< (m: T, err: Allocator_Error) { + return runtime.make_map(T, cap, allocator, loc) +} + +/* +Allocate a multi pointer. + +This procedure allocates a multipointer of type `T` pointing to `len` elements, +from an allocator specified by `allocator`. +*/ +@(require_results) +make_multi_pointer :: proc( + $T: typeid/[^]$E, + #any_int len: int, + allocator := context.allocator, + loc := #caller_location +) -> (mp: T, err: Allocator_Error) { + return runtime.make_multi_pointer(T, len, allocator, loc) +} + +/* +Allocate. +*/ make :: proc{ make_slice, make_dynamic_array, @@ -235,36 +976,138 @@ make :: proc{ make_multi_pointer, } +/* +Default resize procedure. +When allocator does not support resize operation, but supports `.Alloc` and +`.Free`, this procedure is used to implement allocator's default behavior on +resize. + +The behavior of the function is as follows: + +- If `new_size` is `0`, the function acts like `free()`, freeing the memory + region of `old_size` bytes located at `old_memory`. +- If `old_memory` is `nil`, the function acts like `alloc()`, allocating + `new_size` bytes of memory aligned on a boundary specified by `alignment`. +- Otherwise, a new memory region of size `new_size` is allocated, then the + data from the old memory region is copied and the old memory region is + freed. +*/ @(require_results) -default_resize_align :: proc(old_memory: rawptr, old_size, new_size, alignment: int, allocator := context.allocator, loc := #caller_location) -> (res: rawptr, err: Allocator_Error) { +default_resize_align :: proc( + old_memory: rawptr, + old_size: int, + new_size: int, + alignment: int, + allocator := context.allocator, + loc := #caller_location, +) -> (res: rawptr, err: Allocator_Error) { data: []byte - data, err = default_resize_bytes_align(([^]byte)(old_memory)[:old_size], new_size, alignment, allocator, loc) + data, err = default_resize_bytes_align( + ([^]byte) (old_memory)[:old_size], + new_size, + alignment, + allocator, + loc, + ) res = raw_data(data) return } + +/* +Default resize procedure. + +When allocator does not support resize operation, but supports +`.Alloc_Non_Zeroed` and `.Free`, this procedure is used to implement allocator's +default behavior on resize. + +Unlike `default_resize_align` no new memory is being explicitly +zero-initialized. + +The behavior of the function is as follows: + +- If `new_size` is `0`, the function acts like `free()`, freeing the memory + region of `old_size` bytes located at `old_memory`. +- If `old_memory` is `nil`, the function acts like `alloc()`, allocating + `new_size` bytes of memory aligned on a boundary specified by `alignment`. +- Otherwise, a new memory region of size `new_size` is allocated, then the + data from the old memory region is copied and the old memory region is + freed. +*/ @(require_results) -default_resize_bytes_align :: proc(old_data: []byte, new_size, alignment: int, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { +default_resize_bytes_align_non_zeroed :: proc( + old_data: []byte, + new_size: int, + alignment: int, + allocator := context.allocator, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + return _default_resize_bytes_align(old_data, new_size, alignment, false, allocator, loc) +} + +/* +Default resize procedure. + +When allocator does not support resize operation, but supports `.Alloc` and +`.Free`, this procedure is used to implement allocator's default behavior on +resize. + +The behavior of the function is as follows: + +- If `new_size` is `0`, the function acts like `free()`, freeing the memory + region specified by `old_data`. +- If `old_data` is `nil`, the function acts like `alloc()`, allocating + `new_size` bytes of memory aligned on a boundary specified by `alignment`. +- Otherwise, a new memory region of size `new_size` is allocated, then the + data from the old memory region is copied and the old memory region is + freed. +*/ +@(require_results) +default_resize_bytes_align :: proc( + old_data: []byte, + new_size: int, + alignment: int, + allocator := context.allocator, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + return _default_resize_bytes_align(old_data, new_size, alignment, true, allocator, loc) +} + +@(require_results) +_default_resize_bytes_align :: #force_inline proc( + old_data: []byte, + new_size: int, + alignment: int, + should_zero: bool, + allocator := context.allocator, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { old_memory := raw_data(old_data) old_size := len(old_data) if old_memory == nil { - return alloc_bytes(new_size, alignment, allocator, loc) + if should_zero { + return alloc_bytes(new_size, alignment, allocator, loc) + } else { + return alloc_bytes_non_zeroed(new_size, alignment, allocator, loc) + } } - if new_size == 0 { err := free_bytes(old_data, allocator, loc) return nil, err } - - if new_size == old_size { + if new_size == old_size && is_aligned(old_memory, alignment) { return old_data, .None } - - new_memory, err := alloc_bytes(new_size, alignment, allocator, loc) + new_memory : []byte + err : Allocator_Error + if should_zero { + new_memory, err = alloc_bytes(new_size, alignment, allocator, loc) + } else { + new_memory, err = alloc_bytes_non_zeroed(new_size, alignment, allocator, loc) + } if new_memory == nil || err != nil { return nil, err } - runtime.copy(new_memory, old_data) free_bytes(old_data, allocator, loc) return new_memory, err diff --git a/core/mem/allocators.odin b/core/mem/allocators.odin index b6a296322..028be58e3 100644 --- a/core/mem/allocators.odin +++ b/core/mem/allocators.odin @@ -1,816 +1,58 @@ package mem -import "core:intrinsics" -import "core:runtime" -import "core:sync" +import "base:intrinsics" +import "base:runtime" -nil_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { - return nil, nil -} +/* +Nil allocator. +The `nil` allocator returns `nil` on every allocation attempt. This type of +allocator can be used in scenarios where memory doesn't need to be allocated, +but an attempt to allocate memory is not an error. +*/ +@(require_results) nil_allocator :: proc() -> Allocator { return Allocator{ procedure = nil_allocator_proc, - data = nil, + data = nil, } } -// Custom allocators - -Arena :: struct { - data: []byte, - offset: int, - peak_used: int, - temp_count: int, -} - -Arena_Temp_Memory :: struct { - arena: ^Arena, - prev_offset: int, -} - - -arena_init :: proc(a: ^Arena, data: []byte) { - a.data = data - a.offset = 0 - a.peak_used = 0 - a.temp_count = 0 -} - -@(deprecated="prefer 'mem.arena_init'") -init_arena :: proc(a: ^Arena, data: []byte) { - a.data = data - a.offset = 0 - a.peak_used = 0 - a.temp_count = 0 -} - -@(require_results) -arena_allocator :: proc(arena: ^Arena) -> Allocator { - return Allocator{ - procedure = arena_allocator_proc, - data = arena, - } -} - -arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, location := #caller_location) -> ([]byte, Allocator_Error) { - arena := cast(^Arena)allocator_data - - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - #no_bounds_check end := &arena.data[arena.offset] - - ptr := align_forward(end, uintptr(alignment)) - - total_size := size + ptr_sub((^byte)(ptr), (^byte)(end)) - - if arena.offset + total_size > len(arena.data) { - return nil, .Out_Of_Memory - } - - arena.offset += total_size - arena.peak_used = max(arena.peak_used, arena.offset) - if mode != .Alloc_Non_Zeroed { - zero(ptr, size) - } - return byte_slice(ptr, size), nil - - case .Free: - return nil, .Mode_Not_Implemented - - case .Free_All: - arena.offset = 0 - - case .Resize: - return default_resize_bytes_align(byte_slice(old_memory, old_size), size, alignment, arena_allocator(arena)) - - case .Query_Features: - set := (^Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Alloc, .Alloc_Non_Zeroed, .Free_All, .Resize, .Query_Features} - } - return nil, nil - - case .Query_Info: - return nil, .Mode_Not_Implemented - } - - return nil, nil -} - -@(require_results) -begin_arena_temp_memory :: proc(a: ^Arena) -> Arena_Temp_Memory { - tmp: Arena_Temp_Memory - tmp.arena = a - tmp.prev_offset = a.offset - a.temp_count += 1 - return tmp -} - -end_arena_temp_memory :: proc(tmp: Arena_Temp_Memory) { - assert(tmp.arena.offset >= tmp.prev_offset) - assert(tmp.arena.temp_count > 0) - tmp.arena.offset = tmp.prev_offset - tmp.arena.temp_count -= 1 -} - - - -Scratch_Allocator :: struct { - data: []byte, - curr_offset: int, - prev_allocation: rawptr, - backup_allocator: Allocator, - leaked_allocations: [dynamic][]byte, -} - -scratch_allocator_init :: proc(s: ^Scratch_Allocator, size: int, backup_allocator := context.allocator) -> Allocator_Error { - s.data = make_aligned([]byte, size, 2*align_of(rawptr), backup_allocator) or_return - s.curr_offset = 0 - s.prev_allocation = nil - s.backup_allocator = backup_allocator - s.leaked_allocations.allocator = backup_allocator - return nil -} - -scratch_allocator_destroy :: proc(s: ^Scratch_Allocator) { - if s == nil { - return - } - for ptr in s.leaked_allocations { - free_bytes(ptr, s.backup_allocator) - } - delete(s.leaked_allocations) - delete(s.data, s.backup_allocator) - s^ = {} -} - -scratch_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { - - s := (^Scratch_Allocator)(allocator_data) - - if s.data == nil { - DEFAULT_BACKING_SIZE :: 4 * Megabyte - if !(context.allocator.procedure != scratch_allocator_proc && - context.allocator.data != allocator_data) { - panic("cyclic initialization of the scratch allocator with itself") - } - scratch_allocator_init(s, DEFAULT_BACKING_SIZE) - } - - size := size - - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - size = align_forward_int(size, alignment) - - switch { - case s.curr_offset+size <= len(s.data): - start := uintptr(raw_data(s.data)) - ptr := start + uintptr(s.curr_offset) - ptr = align_forward_uintptr(ptr, uintptr(alignment)) - if mode != .Alloc_Non_Zeroed { - zero(rawptr(ptr), size) - } - - s.prev_allocation = rawptr(ptr) - offset := int(ptr - start) - s.curr_offset = offset + size - return byte_slice(rawptr(ptr), size), nil - - case size <= len(s.data): - start := uintptr(raw_data(s.data)) - ptr := align_forward_uintptr(start, uintptr(alignment)) - if mode != .Alloc_Non_Zeroed { - zero(rawptr(ptr), size) - } - - s.prev_allocation = rawptr(ptr) - offset := int(ptr - start) - s.curr_offset = offset + size - return byte_slice(rawptr(ptr), size), nil - } - a := s.backup_allocator - if a.procedure == nil { - a = context.allocator - s.backup_allocator = a - } - - ptr, err := alloc_bytes(size, alignment, a, loc) - if err != nil { - return ptr, err - } - if s.leaked_allocations == nil { - s.leaked_allocations, err = make([dynamic][]byte, a) - } - append(&s.leaked_allocations, ptr) - - if logger := context.logger; logger.lowest_level <= .Warning { - if logger.procedure != nil { - logger.procedure(logger.data, .Warning, "mem.Scratch_Allocator resorted to backup_allocator" , logger.options, loc) - } - } - - return ptr, err - - case .Free: - if old_memory == nil { - return nil, nil - } - start := uintptr(raw_data(s.data)) - end := start + uintptr(len(s.data)) - old_ptr := uintptr(old_memory) - - if s.prev_allocation == old_memory { - s.curr_offset = int(uintptr(s.prev_allocation) - start) - s.prev_allocation = nil - return nil, nil - } - - if start <= old_ptr && old_ptr < end { - // NOTE(bill): Cannot free this pointer but it is valid - return nil, nil - } - - if len(s.leaked_allocations) != 0 { - for data, i in s.leaked_allocations { - ptr := raw_data(data) - if ptr == old_memory { - free_bytes(data, s.backup_allocator) - ordered_remove(&s.leaked_allocations, i) - return nil, nil - } - } - } - return nil, .Invalid_Pointer - // panic("invalid pointer passed to default_temp_allocator"); - - case .Free_All: - s.curr_offset = 0 - s.prev_allocation = nil - for ptr in s.leaked_allocations { - free_bytes(ptr, s.backup_allocator) - } - clear(&s.leaked_allocations) - - case .Resize: - begin := uintptr(raw_data(s.data)) - end := begin + uintptr(len(s.data)) - old_ptr := uintptr(old_memory) - if begin <= old_ptr && old_ptr < end && old_ptr+uintptr(size) < end { - s.curr_offset = int(old_ptr-begin)+size - return byte_slice(old_memory, size), nil - } - data, err := scratch_allocator_proc(allocator_data, .Alloc, size, alignment, old_memory, old_size, loc) - if err != nil { - return data, err - } - runtime.copy(data, byte_slice(old_memory, old_size)) - _, err = scratch_allocator_proc(allocator_data, .Free, 0, alignment, old_memory, old_size, loc) - return data, err - - case .Query_Features: - set := (^Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Query_Features} - } - return nil, nil - - case .Query_Info: - return nil, .Mode_Not_Implemented - } - - return nil, nil -} - -@(require_results) -scratch_allocator :: proc(allocator: ^Scratch_Allocator) -> Allocator { - return Allocator{ - procedure = scratch_allocator_proc, - data = allocator, - } -} - - - - - -Stack_Allocation_Header :: struct { - prev_offset: int, - padding: int, -} - -// Stack is a stack-like allocator which has a strict memory freeing order -Stack :: struct { - data: []byte, - prev_offset: int, - curr_offset: int, - peak_used: int, -} - -stack_init :: proc(s: ^Stack, data: []byte) { - s.data = data - s.prev_offset = 0 - s.curr_offset = 0 - s.peak_used = 0 -} - -@(deprecated="prefer 'mem.stack_init'") -init_stack :: proc(s: ^Stack, data: []byte) { - s.data = data - s.prev_offset = 0 - s.curr_offset = 0 - s.peak_used = 0 -} - -@(require_results) -stack_allocator :: proc(stack: ^Stack) -> Allocator { - return Allocator{ - procedure = stack_allocator_proc, - data = stack, - } -} - - -stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, location := #caller_location) -> ([]byte, Allocator_Error) { - s := cast(^Stack)allocator_data - - if s.data == nil { - return nil, .Invalid_Argument - } - - raw_alloc :: proc(s: ^Stack, size, alignment: int, zero_memory: bool) -> ([]byte, Allocator_Error) { - curr_addr := uintptr(raw_data(s.data)) + uintptr(s.curr_offset) - padding := calc_padding_with_header(curr_addr, uintptr(alignment), size_of(Stack_Allocation_Header)) - if s.curr_offset + padding + size > len(s.data) { - return nil, .Out_Of_Memory - } - s.prev_offset = s.curr_offset - s.curr_offset += padding - - next_addr := curr_addr + uintptr(padding) - header := (^Stack_Allocation_Header)(next_addr - size_of(Stack_Allocation_Header)) - header.padding = padding - header.prev_offset = s.prev_offset - - s.curr_offset += size - - s.peak_used = max(s.peak_used, s.curr_offset) - - if zero_memory { - zero(rawptr(next_addr), size) - } - return byte_slice(rawptr(next_addr), size), nil - } - - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - return raw_alloc(s, size, alignment, mode == .Alloc) - case .Free: - if old_memory == nil { - return nil, nil - } - start := uintptr(raw_data(s.data)) - end := start + uintptr(len(s.data)) - curr_addr := uintptr(old_memory) - - if !(start <= curr_addr && curr_addr < end) { - panic("Out of bounds memory address passed to stack allocator (free)") - } - - if curr_addr >= start+uintptr(s.curr_offset) { - // NOTE(bill): Allow double frees - return nil, nil - } - - header := (^Stack_Allocation_Header)(curr_addr - size_of(Stack_Allocation_Header)) - old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data))) - - if old_offset != header.prev_offset { - // panic("Out of order stack allocator free"); - return nil, .Invalid_Pointer - } - - s.curr_offset = old_offset - s.prev_offset = header.prev_offset - - case .Free_All: - s.prev_offset = 0 - s.curr_offset = 0 - - case .Resize: - if old_memory == nil { - return raw_alloc(s, size, alignment, true) - } - if size == 0 { - return nil, nil - } - - start := uintptr(raw_data(s.data)) - end := start + uintptr(len(s.data)) - curr_addr := uintptr(old_memory) - if !(start <= curr_addr && curr_addr < end) { - panic("Out of bounds memory address passed to stack allocator (resize)") - } - - if curr_addr >= start+uintptr(s.curr_offset) { - // NOTE(bill): Allow double frees - return nil, nil - } - - if old_size == size { - return byte_slice(old_memory, size), nil - } - - header := (^Stack_Allocation_Header)(curr_addr - size_of(Stack_Allocation_Header)) - old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data))) - - if old_offset != header.prev_offset { - data, err := raw_alloc(s, size, alignment, true) - if err == nil { - runtime.copy(data, byte_slice(old_memory, old_size)) - } - return data, err - } - - old_memory_size := uintptr(s.curr_offset) - (curr_addr - start) - assert(old_memory_size == uintptr(old_size)) - - diff := size - old_size - s.curr_offset += diff // works for smaller sizes too - if diff > 0 { - zero(rawptr(curr_addr + uintptr(diff)), diff) - } - - return byte_slice(old_memory, size), nil - - case .Query_Features: - set := (^Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Query_Features} - } - return nil, nil - case .Query_Info: - return nil, .Mode_Not_Implemented - } - +nil_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { return nil, nil } +/* +Panic allocator. - - - - -Small_Stack_Allocation_Header :: struct { - padding: u8, -} - -// Small_Stack is a stack-like allocator which uses the smallest possible header but at the cost of non-strict memory freeing order -Small_Stack :: struct { - data: []byte, - offset: int, - peak_used: int, -} - -small_stack_init :: proc(s: ^Small_Stack, data: []byte) { - s.data = data - s.offset = 0 - s.peak_used = 0 -} - -@(deprecated="prefer 'small_stack_init'") -init_small_stack :: proc(s: ^Small_Stack, data: []byte) { - s.data = data - s.offset = 0 - s.peak_used = 0 -} - +The panic allocator is a type of allocator that panics on any allocation +attempt. This type of allocator can be used in scenarios where memory should +not be allocated, and an attempt to allocate memory is an error. +*/ @(require_results) -small_stack_allocator :: proc(stack: ^Small_Stack) -> Allocator { +panic_allocator :: proc() -> Allocator { return Allocator{ - procedure = small_stack_allocator_proc, - data = stack, + procedure = panic_allocator_proc, + data = nil, } } -small_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, location := #caller_location) -> ([]byte, Allocator_Error) { - s := cast(^Small_Stack)allocator_data - - if s.data == nil { - return nil, .Invalid_Argument - } - - align := clamp(alignment, 1, 8*size_of(Stack_Allocation_Header{}.padding)/2) - - raw_alloc :: proc(s: ^Small_Stack, size, alignment: int, zero_memory: bool) -> ([]byte, Allocator_Error) { - curr_addr := uintptr(raw_data(s.data)) + uintptr(s.offset) - padding := calc_padding_with_header(curr_addr, uintptr(alignment), size_of(Small_Stack_Allocation_Header)) - if s.offset + padding + size > len(s.data) { - return nil, .Out_Of_Memory - } - s.offset += padding - - next_addr := curr_addr + uintptr(padding) - header := (^Small_Stack_Allocation_Header)(next_addr - size_of(Small_Stack_Allocation_Header)) - header.padding = auto_cast padding - - s.offset += size - - s.peak_used = max(s.peak_used, s.offset) - - if zero_memory { - zero(rawptr(next_addr), size) - } - return byte_slice(rawptr(next_addr), size), nil - } - - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - return raw_alloc(s, size, align, mode == .Alloc) - case .Free: - if old_memory == nil { - return nil, nil - } - start := uintptr(raw_data(s.data)) - end := start + uintptr(len(s.data)) - curr_addr := uintptr(old_memory) - - if !(start <= curr_addr && curr_addr < end) { - // panic("Out of bounds memory address passed to stack allocator (free)"); - return nil, .Invalid_Pointer - } - - if curr_addr >= start+uintptr(s.offset) { - // NOTE(bill): Allow double frees - return nil, nil - } - - header := (^Small_Stack_Allocation_Header)(curr_addr - size_of(Small_Stack_Allocation_Header)) - old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data))) - - s.offset = old_offset - - case .Free_All: - s.offset = 0 - - case .Resize: - if old_memory == nil { - return raw_alloc(s, size, align, true) - } - if size == 0 { - return nil, nil - } - - start := uintptr(raw_data(s.data)) - end := start + uintptr(len(s.data)) - curr_addr := uintptr(old_memory) - if !(start <= curr_addr && curr_addr < end) { - // panic("Out of bounds memory address passed to stack allocator (resize)"); - return nil, .Invalid_Pointer - } - - if curr_addr >= start+uintptr(s.offset) { - // NOTE(bill): Treat as a double free - return nil, nil - } - - if old_size == size { - return byte_slice(old_memory, size), nil - } - - data, err := raw_alloc(s, size, align, true) - if err == nil { - runtime.copy(data, byte_slice(old_memory, old_size)) - } - return data, err - - case .Query_Features: - set := (^Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Query_Features} - } - return nil, nil - - case .Query_Info: - return nil, .Mode_Not_Implemented - } - - return nil, nil -} - - - - - -Dynamic_Pool :: struct { - block_size: int, - out_band_size: int, - alignment: int, - - unused_blocks: [dynamic]rawptr, - used_blocks: [dynamic]rawptr, - out_band_allocations: [dynamic]rawptr, - - current_block: rawptr, - current_pos: rawptr, - bytes_left: int, - - block_allocator: Allocator, -} - - -DYNAMIC_POOL_BLOCK_SIZE_DEFAULT :: 65536 -DYNAMIC_POOL_OUT_OF_BAND_SIZE_DEFAULT :: 6554 - - - -dynamic_pool_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { - pool := (^Dynamic_Pool)(allocator_data) - - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - return dynamic_pool_alloc_bytes(pool, size) - case .Free: - return nil, .Mode_Not_Implemented - case .Free_All: - dynamic_pool_free_all(pool) - return nil, nil - case .Resize: - if old_size >= size { - return byte_slice(old_memory, size), nil - } - data, err := dynamic_pool_alloc_bytes(pool, size) - if err == nil { - runtime.copy(data, byte_slice(old_memory, old_size)) - } - return data, err - - case .Query_Features: - set := (^Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Alloc, .Alloc_Non_Zeroed, .Free_All, .Resize, .Query_Features, .Query_Info} - } - return nil, nil - - case .Query_Info: - info := (^Allocator_Query_Info)(old_memory) - if info != nil && info.pointer != nil { - info.size = pool.block_size - info.alignment = pool.alignment - return byte_slice(info, size_of(info^)), nil - } - return nil, nil - } - return nil, nil -} - - -@(require_results) -dynamic_pool_allocator :: proc(pool: ^Dynamic_Pool) -> Allocator { - return Allocator{ - procedure = dynamic_pool_allocator_proc, - data = pool, - } -} - -dynamic_pool_init :: proc(pool: ^Dynamic_Pool, - block_allocator := context.allocator, - array_allocator := context.allocator, - block_size := DYNAMIC_POOL_BLOCK_SIZE_DEFAULT, - out_band_size := DYNAMIC_POOL_OUT_OF_BAND_SIZE_DEFAULT, - alignment := 8) { - pool.block_size = block_size - pool.out_band_size = out_band_size - pool.alignment = alignment - pool.block_allocator = block_allocator - pool.out_band_allocations.allocator = array_allocator - pool. unused_blocks.allocator = array_allocator - pool. used_blocks.allocator = array_allocator -} - -dynamic_pool_destroy :: proc(pool: ^Dynamic_Pool) { - dynamic_pool_free_all(pool) - delete(pool.unused_blocks) - delete(pool.used_blocks) - delete(pool.out_band_allocations) - - zero(pool, size_of(pool^)) -} - - -@(require_results) -dynamic_pool_alloc :: proc(pool: ^Dynamic_Pool, bytes: int) -> (rawptr, Allocator_Error) { - data, err := dynamic_pool_alloc_bytes(pool, bytes) - return raw_data(data), err -} - -@(require_results) -dynamic_pool_alloc_bytes :: proc(p: ^Dynamic_Pool, bytes: int) -> ([]byte, Allocator_Error) { - cycle_new_block :: proc(p: ^Dynamic_Pool) -> (err: Allocator_Error) { - if p.block_allocator.procedure == nil { - panic("You must call pool_init on a Pool before using it") - } - - if p.current_block != nil { - append(&p.used_blocks, p.current_block) - } - - new_block: rawptr - if len(p.unused_blocks) > 0 { - new_block = pop(&p.unused_blocks) - } else { - data: []byte - data, err = p.block_allocator.procedure(p.block_allocator.data, Allocator_Mode.Alloc, - p.block_size, p.alignment, - nil, 0) - new_block = raw_data(data) - } - - p.bytes_left = p.block_size - p.current_pos = new_block - p.current_block = new_block - return - } - - n := bytes - extra := p.alignment - (n % p.alignment) - n += extra - if n > p.block_size do return nil, .Invalid_Argument - if n >= p.out_band_size { - assert(p.block_allocator.procedure != nil) - memory, err := p.block_allocator.procedure(p.block_allocator.data, Allocator_Mode.Alloc, - p.block_size, p.alignment, - nil, 0) - if memory != nil { - append(&p.out_band_allocations, raw_data(memory)) - } - return memory, err - } - - if p.bytes_left < n { - err := cycle_new_block(p) - if err != nil { - return nil, err - } - if p.current_block == nil { - return nil, .Out_Of_Memory - } - } - - memory := p.current_pos - p.current_pos = ([^]byte)(p.current_pos)[n:] - p.bytes_left -= n - return ([^]byte)(memory)[:bytes], nil -} - - -dynamic_pool_reset :: proc(p: ^Dynamic_Pool) { - if p.current_block != nil { - append(&p.unused_blocks, p.current_block) - p.current_block = nil - } - - for block in p.used_blocks { - append(&p.unused_blocks, block) - } - clear(&p.used_blocks) - - for a in p.out_band_allocations { - free(a, p.block_allocator) - } - clear(&p.out_band_allocations) - - p.bytes_left = 0 // Make new allocations call `cycle_new_block` again. -} - -dynamic_pool_free_all :: proc(p: ^Dynamic_Pool) { - dynamic_pool_reset(p) - - for block in p.unused_blocks { - free(block, p.block_allocator) - } - clear(&p.unused_blocks) -} - - -panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int,loc := #caller_location) -> ([]byte, Allocator_Error) { - +panic_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { switch mode { case .Alloc: if size > 0 { @@ -824,13 +66,16 @@ panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, if size > 0 { panic("mem: panic allocator, .Resize called", loc=loc) } + case .Resize_Non_Zeroed: + if size > 0 { + panic("mem: panic allocator, .Resize_Non_Zeroed called", loc=loc) + } case .Free: if old_memory != nil { panic("mem: panic allocator, .Free called", loc=loc) } case .Free_All: panic("mem: panic allocator, .Free_All called", loc=loc) - case .Query_Features: set := (^Allocator_Mode_Set)(old_memory) if set != nil { @@ -841,145 +86,2249 @@ panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, case .Query_Info: panic("mem: panic allocator, .Query_Info called", loc=loc) } - return nil, nil } + +/* +Arena allocator data. +*/ +Arena :: struct { + data: []byte, + offset: int, + peak_used: int, + temp_count: int, +} + +/* +Arena allocator. + +The arena allocator (also known as a linear allocator, bump allocator, +region allocator) is an allocator that uses a single backing buffer for +allocations. + +The buffer is being used contiguously, from start by end. Each subsequent +allocation occupies the next adjacent region of memory in the buffer. Since +arena allocator does not keep track of any metadata associated with the +allocations and their locations, it is impossible to free individual +allocations. + +The arena allocator can be used for temporary allocations in frame-based memory +management. Games are one example of such applications. A global arena can be +used for any temporary memory allocations, and at the end of each frame all +temporary allocations are freed. Since no temporary object is going to live +longer than a frame, no lifetimes are violated. +*/ @(require_results) -panic_allocator :: proc() -> Allocator { +arena_allocator :: proc(arena: ^Arena) -> Allocator { return Allocator{ - procedure = panic_allocator_proc, - data = nil, + procedure = arena_allocator_proc, + data = arena, } } +/* +Initialize an arena. -Tracking_Allocator_Entry :: struct { - memory: rawptr, - size: int, - alignment: int, - mode: Allocator_Mode, - err: Allocator_Error, - location: runtime.Source_Code_Location, -} -Tracking_Allocator_Bad_Free_Entry :: struct { - memory: rawptr, - location: runtime.Source_Code_Location, -} -Tracking_Allocator :: struct { - backing: Allocator, - allocation_map: map[rawptr]Tracking_Allocator_Entry, - bad_free_array: [dynamic]Tracking_Allocator_Bad_Free_Entry, - mutex: sync.Mutex, - clear_on_free_all: bool, -} - -tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) { - t.backing = backing_allocator - t.allocation_map.allocator = internals_allocator - t.bad_free_array.allocator = internals_allocator - - if .Free_All in query_features(t.backing) { - t.clear_on_free_all = true - } -} - -tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) { - delete(t.allocation_map) - delete(t.bad_free_array) -} - - -tracking_allocator_clear :: proc(t: ^Tracking_Allocator) { - sync.mutex_lock(&t.mutex) - clear(&t.allocation_map) - clear(&t.bad_free_array) - sync.mutex_unlock(&t.mutex) +This procedure initializes the arena `a` with memory region `data` as it's +backing buffer. +*/ +arena_init :: proc(a: ^Arena, data: []byte) { + a.data = data + a.offset = 0 + a.peak_used = 0 + a.temp_count = 0 } +/* +Allocate memory from an arena. +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from an arena `a`. The allocated memory is zero-initialized. +This procedure returns a pointer to the newly allocated memory region. +*/ @(require_results) -tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator { - return Allocator{ - data = data, - procedure = tracking_allocator_proc, - } +arena_alloc :: proc( + a: ^Arena, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := arena_alloc_bytes(a, size, alignment, loc) + return raw_data(bytes), err } -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) { - data := (^Tracking_Allocator)(allocator_data) +/* +Allocate memory from an arena. - sync.mutex_guard(&data.mutex) - - if mode == .Query_Info { - info := (^Allocator_Query_Info)(old_memory) - if info != nil && info.pointer != nil { - if entry, ok := data.allocation_map[info.pointer]; ok { - info.size = entry.size - info.alignment = entry.alignment - } - info.pointer = nil - } - - return +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from an arena `a`. The allocated memory is zero-initialized. +This procedure returns a slice of the newly allocated memory region. +*/ +@(require_results) +arena_alloc_bytes :: proc( + a: ^Arena, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + bytes, err := arena_alloc_bytes_non_zeroed(a, size, alignment, loc) + if bytes != nil { + zero_slice(bytes) } + return bytes, err +} - if mode == .Free && old_memory != nil && old_memory not_in data.allocation_map { - append(&data.bad_free_array, Tracking_Allocator_Bad_Free_Entry{ - memory = old_memory, - location = loc, - }) - } else { - result = data.backing.procedure(data.backing.data, mode, size, alignment, old_memory, old_size, loc) or_return +/* +Allocate non-initialized memory from an arena. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from an arena `a`. The allocated memory is not explicitly +zero-initialized. This procedure returns a pointer to the newly allocated +memory region. +*/ +@(require_results) +arena_alloc_non_zeroed :: proc( + a: ^Arena, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := arena_alloc_bytes_non_zeroed(a, size, alignment, loc) + return raw_data(bytes), err +} + +/* +Allocate non-initialized memory from an arena. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from an arena `a`. The allocated memory is not explicitly +zero-initialized. This procedure returns a slice of the newly allocated +memory region. +*/ +@(require_results) +arena_alloc_bytes_non_zeroed :: proc( + a: ^Arena, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> ([]byte, Allocator_Error) { + if a.data == nil { + panic("Arena is not initialized", loc) } - result_ptr := raw_data(result) - - if data.allocation_map.allocator.procedure == nil { - data.allocation_map.allocator = context.allocator + #no_bounds_check end := &a.data[a.offset] + ptr := align_forward(end, uintptr(alignment)) + total_size := size + ptr_sub((^byte)(ptr), (^byte)(end)) + if a.offset + total_size > len(a.data) { + return nil, .Out_Of_Memory } + a.offset += total_size + a.peak_used = max(a.peak_used, a.offset) + return byte_slice(ptr, size), nil +} +/* +Free all memory to an arena. +*/ +arena_free_all :: proc(a: ^Arena) { + a.offset = 0 +} + +arena_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size: int, + alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + arena := cast(^Arena)allocator_data switch mode { - case .Alloc, .Alloc_Non_Zeroed: - data.allocation_map[result_ptr] = Tracking_Allocator_Entry{ - memory = result_ptr, - size = size, - mode = mode, - alignment = alignment, - err = err, - location = loc, - } + case .Alloc: + return arena_alloc_bytes(arena, size, alignment, loc) + case .Alloc_Non_Zeroed: + return arena_alloc_bytes_non_zeroed(arena, size, alignment, loc) case .Free: - delete_key(&data.allocation_map, old_memory) + return nil, .Mode_Not_Implemented case .Free_All: - if data.clear_on_free_all { - clear_map(&data.allocation_map) - } + arena_free_all(arena) case .Resize: - if old_memory != result_ptr { - delete_key(&data.allocation_map, old_memory) - } - data.allocation_map[result_ptr] = Tracking_Allocator_Entry{ - memory = result_ptr, - size = size, - mode = mode, - alignment = alignment, - err = err, - location = loc, - } - + return default_resize_bytes_align(byte_slice(old_memory, old_size), size, alignment, arena_allocator(arena), loc) + case .Resize_Non_Zeroed: + return default_resize_bytes_align_non_zeroed(byte_slice(old_memory, old_size), size, alignment, arena_allocator(arena), loc) case .Query_Features: set := (^Allocator_Mode_Set)(old_memory) if set != nil { - set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Query_Features, .Query_Info} + set^ = {.Alloc, .Alloc_Non_Zeroed, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features} } return nil, nil - case .Query_Info: - unreachable() + return nil, .Mode_Not_Implemented } + return nil, nil +} +/* +Temporary memory region of arena. + +Temporary memory regions of arena act as "savepoints" for arena. When one is +created, the subsequent allocations are done inside the temporary memory +region. When `end_arena_temp_memory` is called, the arena is rolled back, and +all of the memory that was allocated from the arena will be freed. + +Multiple temporary memory regions can exist at the same time for an arena. +*/ +Arena_Temp_Memory :: struct { + arena: ^Arena, + prev_offset: int, +} + +/* +Start a temporary memory region. + +This procedure creates a temporary memory region. After a temporary memory +region is created, all allocations are said to be *inside* the temporary memory +region, until `end_arena_temp_memory` is called. +*/ +@(require_results) +begin_arena_temp_memory :: proc(a: ^Arena) -> Arena_Temp_Memory { + tmp: Arena_Temp_Memory + tmp.arena = a + tmp.prev_offset = a.offset + a.temp_count += 1 + return tmp +} + +/* +End a temporary memory region. + +This procedure ends the temporary memory region for an arena. All of the +allocations *inside* the temporary memory region will be freed to the arena. +*/ +end_arena_temp_memory :: proc(tmp: Arena_Temp_Memory) { + assert(tmp.arena.offset >= tmp.prev_offset) + assert(tmp.arena.temp_count > 0) + tmp.arena.offset = tmp.prev_offset + tmp.arena.temp_count -= 1 +} + +/* Preserved for compatibility */ +Scratch_Allocator :: Scratch +scratch_allocator_init :: scratch_init +scratch_allocator_destroy :: scratch_destroy + +/* +Scratch allocator data. +*/ +Scratch :: struct { + data: []byte, + curr_offset: int, + prev_allocation: rawptr, + backup_allocator: Allocator, + leaked_allocations: [dynamic][]byte, +} + +/* +Scratch allocator. + +The scratch allocator works in a similar way to the `Arena` allocator. The +scratch allocator has a backing buffer, that is being allocated in +contiguous regions, from start to end. + +Each subsequent allocation will be the next adjacent region of memory in the +backing buffer. If the allocation doesn't fit into the remaining space of the +backing buffer, this allocation is put at the start of the buffer, and all +previous allocations will become invalidated. If the allocation doesn't fit +into the backing buffer as a whole, it will be allocated using a backing +allocator, and pointer to the allocated memory region will be put into the +`leaked_allocations` array. + +The `leaked_allocations` array is managed by the `context` allocator. +*/ +@(require_results) +scratch_allocator :: proc(allocator: ^Scratch) -> Allocator { + return Allocator{ + procedure = scratch_allocator_proc, + data = allocator, + } +} + +/* +Initialize scratch allocator. +*/ +scratch_init :: proc(s: ^Scratch, size: int, backup_allocator := context.allocator) -> Allocator_Error { + s.data = make_aligned([]byte, size, 2*align_of(rawptr), backup_allocator) or_return + s.curr_offset = 0 + s.prev_allocation = nil + s.backup_allocator = backup_allocator + s.leaked_allocations.allocator = backup_allocator + return nil +} + +/* +Free all data associated with a scratch allocator. +*/ +scratch_destroy :: proc(s: ^Scratch) { + if s == nil { + return + } + for ptr in s.leaked_allocations { + free_bytes(ptr, s.backup_allocator) + } + delete(s.leaked_allocations) + delete(s.data, s.backup_allocator) + s^ = {} +} + +/* +Allocate memory from scratch allocator. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is zero-initialized. This procedure +returns a pointer to the allocated memory region. +*/ +@(require_results) +scratch_alloc :: proc( + s: ^Scratch, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := scratch_alloc_bytes(s, size, alignment, loc) + return raw_data(bytes), err +} + +/* +Allocate memory from scratch allocator. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is zero-initialized. This procedure +returns a slice of the allocated memory region. +*/ +@(require_results) +scratch_alloc_bytes :: proc( + s: ^Scratch, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + bytes, err := scratch_alloc_bytes_non_zeroed(s, size, alignment, loc) + if bytes != nil { + zero_slice(bytes) + } + return bytes, err +} + +/* +Allocate non-initialized memory from scratch allocator. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is not explicitly zero-initialized. +This procedure returns a pointer to the allocated memory region. +*/ +@(require_results) +scratch_alloc_non_zeroed :: proc( + s: ^Scratch, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := scratch_alloc_bytes_non_zeroed(s, size, alignment, loc) + return raw_data(bytes), err +} + +/* +Allocate non-initialized memory from scratch allocator. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is not explicitly zero-initialized. +This procedure returns a slice of the allocated memory region. +*/ +@(require_results) +scratch_alloc_bytes_non_zeroed :: proc( + s: ^Scratch, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + if s.data == nil { + DEFAULT_BACKING_SIZE :: 4 * Megabyte + if !(context.allocator.procedure != scratch_allocator_proc && context.allocator.data != s) { + panic("cyclic initialization of the scratch allocator with itself", loc) + } + scratch_init(s, DEFAULT_BACKING_SIZE) + } + size := size + size = align_forward_int(size, alignment) + if size <= len(s.data) { + offset := uintptr(0) + if s.curr_offset+size <= len(s.data) { + offset = uintptr(s.curr_offset) + } else { + offset = 0 + } + start := uintptr(raw_data(s.data)) + ptr := align_forward_uintptr(offset+start, uintptr(alignment)) + s.prev_allocation = rawptr(ptr) + s.curr_offset = int(offset) + size + return byte_slice(rawptr(ptr), size), nil + } else { + a := s.backup_allocator + if a.procedure == nil { + a = context.allocator + s.backup_allocator = a + } + ptr, err := alloc_bytes_non_zeroed(size, alignment, a, loc) + if err != nil { + return ptr, err + } + if s.leaked_allocations == nil { + s.leaked_allocations, err = make([dynamic][]byte, a) + } + append(&s.leaked_allocations, ptr) + if logger := context.logger; logger.lowest_level <= .Warning { + if logger.procedure != nil { + logger.procedure(logger.data, .Warning, "mem.Scratch resorted to backup_allocator" , logger.options, loc) + } + } + return ptr, err + } +} + +/* +Free memory to the scratch allocator. + +This procedure frees the memory region allocated at pointer `ptr`. + +If `ptr` is not the latest allocation and is not a leaked allocation, this +operation is a no-op. +*/ +scratch_free :: proc(s: ^Scratch, ptr: rawptr, loc := #caller_location) -> Allocator_Error { + if s.data == nil { + panic("Free on an uninitialized scratch allocator", loc) + } + if ptr == nil { + return nil + } + start := uintptr(raw_data(s.data)) + end := start + uintptr(len(s.data)) + old_ptr := uintptr(ptr) + if s.prev_allocation == ptr { + s.curr_offset = int(uintptr(s.prev_allocation) - start) + s.prev_allocation = nil + return nil + } + if start <= old_ptr && old_ptr < end { + // NOTE(bill): Cannot free this pointer but it is valid + return nil + } + if len(s.leaked_allocations) != 0 { + for data, i in s.leaked_allocations { + ptr := raw_data(data) + if ptr == ptr { + free_bytes(data, s.backup_allocator, loc) + ordered_remove(&s.leaked_allocations, i, loc) + return nil + } + } + } + return .Invalid_Pointer +} + +/* +Free all memory to the scratch allocator. +*/ +scratch_free_all :: proc(s: ^Scratch, loc := #caller_location) { + s.curr_offset = 0 + s.prev_allocation = nil + for ptr in s.leaked_allocations { + free_bytes(ptr, s.backup_allocator, loc) + } + clear(&s.leaked_allocations) +} + +/* +Resize an allocation. + +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `scratch_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `scratch_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +scratch_resize :: proc( + s: ^Scratch, + old_memory: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> (rawptr, Allocator_Error) { + bytes, err := scratch_resize_bytes(s, byte_slice(old_memory, old_size), size, alignment, loc) + return raw_data(bytes), err +} + +/* +Resize an allocation. + +This procedure resizes a memory region, specified by `old_data`, to have a size +`size` and alignment `alignment`. The newly allocated memory, if any is +zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `scratch_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `scratch_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +scratch_resize_bytes :: proc( + s: ^Scratch, + old_data: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> ([]byte, Allocator_Error) { + bytes, err := scratch_resize_bytes_non_zeroed(s, old_data, size, alignment, loc) + if bytes != nil && size > len(old_data) { + zero_slice(bytes[size:]) + } + return bytes, err +} + +/* +Resize an allocation without zero-initialization. + +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is not explicitly zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `scratch_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `scratch_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +scratch_resize_non_zeroed :: proc( + s: ^Scratch, + old_memory: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> (rawptr, Allocator_Error) { + bytes, err := scratch_resize_bytes_non_zeroed(s, byte_slice(old_memory, old_size), size, alignment, loc) + return raw_data(bytes), err +} + +/* +Resize an allocation. + +This procedure resizes a memory region, specified by `old_data`, to have a size +`size` and alignment `alignment`. The newly allocated memory, if any is not +explicitly zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `scratch_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `scratch_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +scratch_resize_bytes_non_zeroed :: proc( + s: ^Scratch, + old_data: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> ([]byte, Allocator_Error) { + old_memory := raw_data(old_data) + old_size := len(old_data) + if s.data == nil { + DEFAULT_BACKING_SIZE :: 4 * Megabyte + if !(context.allocator.procedure != scratch_allocator_proc && context.allocator.data != s) { + panic("cyclic initialization of the scratch allocator with itself", loc) + } + scratch_init(s, DEFAULT_BACKING_SIZE) + } + begin := uintptr(raw_data(s.data)) + end := begin + uintptr(len(s.data)) + old_ptr := uintptr(old_memory) + if begin <= old_ptr && old_ptr < end && old_ptr+uintptr(size) < end { + s.curr_offset = int(old_ptr-begin)+size + return byte_slice(old_memory, size), nil + } + data, err := scratch_alloc_bytes_non_zeroed(s, size, alignment, loc) + if err != nil { + return data, err + } + runtime.copy(data, byte_slice(old_memory, old_size)) + err = scratch_free(s, old_memory, loc) + return data, err +} + +scratch_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + s := (^Scratch)(allocator_data) + size := size + switch mode { + case .Alloc: + return scratch_alloc_bytes(s, size, alignment, loc) + case .Alloc_Non_Zeroed: + return scratch_alloc_bytes_non_zeroed(s, size, alignment, loc) + case .Free: + return nil, scratch_free(s, old_memory, loc) + case .Free_All: + scratch_free_all(s, loc) + case .Resize: + return scratch_resize_bytes(s, byte_slice(old_memory, old_size), size, alignment, loc) + case .Resize_Non_Zeroed: + return scratch_resize_bytes_non_zeroed(s, byte_slice(old_memory, old_size), size, alignment, loc) + case .Query_Features: + set := (^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 +} + + + +/* +Stack allocator data. +*/ +Stack :: struct { + data: []byte, + prev_offset: int, + curr_offset: int, + peak_used: int, +} + +/* +Header of a stack allocation. +*/ +Stack_Allocation_Header :: struct { + prev_offset: int, + padding: int, +} + +/* +Stack allocator. + +The stack allocator is an allocator that allocates data in the backing buffer +linearly, from start to end. Each subsequent allocation will get the next +adjacent memory region. + +Unlike arena allocator, the stack allocator saves allocation metadata and has +a strict freeing order. Only the last allocated element can be freed. After the +last allocated element is freed, the next previous allocated element becomes +available for freeing. + +The metadata is stored in the allocation headers, that are located before the +start of each allocated memory region. Each header points to the start of the +previous allocation header. +*/ +@(require_results) +stack_allocator :: proc(stack: ^Stack) -> Allocator { + return Allocator{ + procedure = stack_allocator_proc, + data = stack, + } +} + +/* +Initialize the stack allocator. + +This procedure initializes the stack allocator with a backing buffer specified +by `data` parameter. +*/ +stack_init :: proc(s: ^Stack, data: []byte) { + s.data = data + s.prev_offset = 0 + s.curr_offset = 0 + s.peak_used = 0 +} + +/* +Allocate memory from stack. + +This procedure allocates `size` bytes of memory, aligned to the boundary +specified by `alignment`. The allocated memory is zero-initialized. This +procedure returns the pointer to the allocated memory. +*/ +@(require_results) +stack_alloc :: proc( + s: ^Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> (rawptr, Allocator_Error) { + bytes, err := stack_alloc_bytes(s, size, alignment, loc) + return raw_data(bytes), err +} + +/* +Allocate memory from stack. + +This procedure allocates `size` bytes of memory, aligned to the boundary +specified by `alignment`. The allocated memory is zero-initialized. This +procedure returns the slice of the allocated memory. +*/ +@(require_results) +stack_alloc_bytes :: proc( + s: ^Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> ([]byte, Allocator_Error) { + bytes, err := stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + if bytes != nil { + zero_slice(bytes) + } + return bytes, err +} + +/* +Allocate memory from stack. + +This procedure allocates `size` bytes of memory, aligned to the boundary +specified by `alignment`. The allocated memory is not explicitly +zero-initialized. This procedure returns the pointer to the allocated memory. +*/ +@(require_results) +stack_alloc_non_zeroed :: proc( + s: ^Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> (rawptr, Allocator_Error) { + bytes, err := stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + return raw_data(bytes), err +} + +/* +Allocate memory from stack. + +This procedure allocates `size` bytes of memory, aligned to the boundary +specified by `alignment`. The allocated memory is not explicitly +zero-initialized. This procedure returns the slice of the allocated memory. +*/ +@(require_results) +stack_alloc_bytes_non_zeroed :: proc( + s: ^Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location +) -> ([]byte, Allocator_Error) { + if s.data == nil { + panic("Stack allocation on an uninitialized stack allocator", loc) + } + curr_addr := uintptr(raw_data(s.data)) + uintptr(s.curr_offset) + padding := calc_padding_with_header( + curr_addr, + uintptr(alignment), + size_of(Stack_Allocation_Header), + ) + if s.curr_offset + padding + size > len(s.data) { + return nil, .Out_Of_Memory + } + s.prev_offset = s.curr_offset + s.curr_offset += padding + next_addr := curr_addr + uintptr(padding) + header := (^Stack_Allocation_Header)(next_addr - size_of(Stack_Allocation_Header)) + header.padding = padding + header.prev_offset = s.prev_offset + s.curr_offset += size + s.peak_used = max(s.peak_used, s.curr_offset) + return byte_slice(rawptr(next_addr), size), nil +} + +/* +Free memory to the stack. + +This procedure frees the memory region starting at `old_memory` to the stack. +If the freeing does is an out of order freeing, the `.Invalid_Pointer` error +is returned. +*/ +stack_free :: proc( + s: ^Stack, + old_memory: rawptr, + loc := #caller_location, +) -> (Allocator_Error) { + if s.data == nil { + panic("Stack free on an uninitialized stack allocator", loc) + } + if old_memory == nil { + return nil + } + start := uintptr(raw_data(s.data)) + end := start + uintptr(len(s.data)) + curr_addr := uintptr(old_memory) + if !(start <= curr_addr && curr_addr < end) { + panic("Out of bounds memory address passed to stack allocator (free)", loc) + } + if curr_addr >= start+uintptr(s.curr_offset) { + // NOTE(bill): Allow double frees + return nil + } + header := (^Stack_Allocation_Header)(curr_addr - size_of(Stack_Allocation_Header)) + old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data))) + if old_offset != header.prev_offset { + // panic("Out of order stack allocator free"); + return .Invalid_Pointer + } + s.curr_offset = old_offset + s.prev_offset = header.prev_offset + return nil +} + +/* +Free all allocations to the stack. +*/ +stack_free_all :: proc(s: ^Stack, loc := #caller_location) { + s.prev_offset = 0 + s.curr_offset = 0 +} + +/* +Resize an allocation. + +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `stack_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +stack_resize :: proc( + s: ^Stack, + old_memory: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := stack_resize_bytes(s, byte_slice(old_memory, old_size), size, alignment) + return raw_data(bytes), err +} + +/* +Resize an allocation. + +This procedure resizes a memory region, specified by the `old_data` parameter +to have a size `size` and alignment `alignment`. The newly allocated memory, +if any is zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `stack_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +stack_resize_bytes :: proc( + s: ^Stack, + old_data: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + bytes, err := stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + if bytes != nil { + if old_data == nil { + zero_slice(bytes) + } else if size > len(old_data) { + zero_slice(bytes[len(old_data):]) + } + } + return bytes, err +} + +/* +Resize an allocation without zero-initialization. + +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is not explicitly zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `stack_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +stack_resize_non_zeroed :: proc( + s: ^Stack, + old_memory: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := stack_resize_bytes_non_zeroed(s, byte_slice(old_memory, old_size), size, alignment) + return raw_data(bytes), err +} + +/* +Resize an allocation without zero-initialization. + +This procedure resizes a memory region, specified by the `old_data` parameter +to have a size `size` and alignment `alignment`. The newly allocated memory, +if any is not explicitly zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `stack_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +stack_resize_bytes_non_zeroed :: proc( + s: ^Stack, + old_data: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + old_memory := raw_data(old_data) + old_size := len(old_data) + if s.data == nil { + panic("Stack free all on an uninitialized stack allocator", loc) + } + if old_memory == nil { + return stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + } + if size == 0 { + return nil, nil + } + start := uintptr(raw_data(s.data)) + end := start + uintptr(len(s.data)) + curr_addr := uintptr(old_memory) + if !(start <= curr_addr && curr_addr < end) { + panic("Out of bounds memory address passed to stack allocator (resize)") + } + if curr_addr >= start+uintptr(s.curr_offset) { + // NOTE(bill): Allow double frees + return nil, nil + } + if old_size == size { + return byte_slice(old_memory, size), nil + } + header := (^Stack_Allocation_Header)(curr_addr - size_of(Stack_Allocation_Header)) + old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data))) + if old_offset != header.prev_offset { + data, err := stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + if err == nil { + runtime.copy(data, byte_slice(old_memory, old_size)) + } + return data, err + } + old_memory_size := uintptr(s.curr_offset) - (curr_addr - start) + assert(old_memory_size == uintptr(old_size)) + diff := size - old_size + s.curr_offset += diff // works for smaller sizes too + if diff > 0 { + zero(rawptr(curr_addr + uintptr(diff)), diff) + } + return byte_slice(old_memory, size), nil +} + +stack_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size: int, + alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + s := cast(^Stack)allocator_data + if s.data == nil { + return nil, .Invalid_Argument + } + switch mode { + case .Alloc: + return stack_alloc_bytes(s, size, alignment, loc) + case .Alloc_Non_Zeroed: + return stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + case .Free: + return nil, stack_free(s, old_memory, loc) + case .Free_All: + stack_free_all(s, loc) + case .Resize: + return stack_resize_bytes(s, byte_slice(old_memory, old_size), size, alignment, loc) + case .Resize_Non_Zeroed: + return stack_resize_bytes_non_zeroed(s, byte_slice(old_memory, old_size), size, alignment, loc) + case .Query_Features: + set := (^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 +} + + +/* +Allocation header of the small stack allocator. +*/ +Small_Stack_Allocation_Header :: struct { + padding: u8, +} + +/* +Small stack allocator data. +*/ +Small_Stack :: struct { + data: []byte, + offset: int, + peak_used: int, +} + +/* +Initialize small stack. + +This procedure initializes the small stack allocator with `data` as its backing +buffer. +*/ +small_stack_init :: proc(s: ^Small_Stack, data: []byte) { + s.data = data + s.offset = 0 + s.peak_used = 0 +} + +/* +Small stack allocator. + +The small stack allocator is just like a stack allocator, with the only +difference being an extremely small header size. Unlike the stack allocator, +small stack allows out-of order freeing of memory. + +The memory is allocated in the backing buffer linearly, from start to end. +Each subsequent allocation will get the next adjacent memory region. + +The metadata is stored in the allocation headers, that are located before the +start of each allocated memory region. Each header contains the amount of +padding bytes between that header and end of the previous allocation. +*/ +@(require_results) +small_stack_allocator :: proc(stack: ^Small_Stack) -> Allocator { + return Allocator{ + procedure = small_stack_allocator_proc, + data = stack, + } +} + +/* +Allocate memory from small stack. + +This procedure allocates `size` bytes of memory aligned to a boundary specified +by `alignment`. The allocated memory is zero-initialized. This procedure +returns a pointer to the allocated memory region. +*/ +@(require_results) +small_stack_alloc :: proc( + s: ^Small_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := small_stack_alloc_bytes(s, size, alignment, loc) + return raw_data(bytes), err +} + +/* +Allocate memory from small stack. + +This procedure allocates `size` bytes of memory aligned to a boundary specified +by `alignment`. The allocated memory is zero-initialized. This procedure +returns a slice of the allocated memory region. +*/ +@(require_results) +small_stack_alloc_bytes :: proc( + s: ^Small_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + bytes, err := small_stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + if bytes != nil { + zero_slice(bytes) + } + return bytes, err +} + +/* +Allocate memory from small stack. + +This procedure allocates `size` bytes of memory aligned to a boundary specified +by `alignment`. The allocated memory is not explicitly zero-initialized. This +procedure returns a pointer to the allocated memory region. +*/ +@(require_results) +small_stack_alloc_non_zeroed :: proc( + s: ^Small_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := small_stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + return raw_data(bytes), err +} + +/* +Allocate memory from small stack. + +This procedure allocates `size` bytes of memory aligned to a boundary specified +by `alignment`. The allocated memory is not explicitly zero-initialized. This +procedure returns a slice of the allocated memory region. +*/ +@(require_results) +small_stack_alloc_bytes_non_zeroed :: proc( + s: ^Small_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + if s.data == nil { + panic("Small stack is not initialized", loc) + } + alignment := alignment + alignment = clamp(alignment, 1, 8*size_of(Stack_Allocation_Header{}.padding)/2) + curr_addr := uintptr(raw_data(s.data)) + uintptr(s.offset) + padding := calc_padding_with_header(curr_addr, uintptr(alignment), size_of(Small_Stack_Allocation_Header)) + if s.offset + padding + size > len(s.data) { + return nil, .Out_Of_Memory + } + s.offset += padding + next_addr := curr_addr + uintptr(padding) + header := (^Small_Stack_Allocation_Header)(next_addr - size_of(Small_Stack_Allocation_Header)) + header.padding = auto_cast padding + s.offset += size + s.peak_used = max(s.peak_used, s.offset) + return byte_slice(rawptr(next_addr), size), nil +} + +/* +Allocate memory from small stack. + +This procedure allocates `size` bytes of memory aligned to a boundary specified +by `alignment`. The allocated memory is not explicitly zero-initialized. This +procedure returns a slice of the allocated memory region. +*/ +small_stack_free :: proc( + s: ^Small_Stack, + old_memory: rawptr, + loc := #caller_location, +) -> Allocator_Error { + if s.data == nil { + panic("Small stack is not initialized", loc) + } + if old_memory == nil { + return nil + } + start := uintptr(raw_data(s.data)) + end := start + uintptr(len(s.data)) + curr_addr := uintptr(old_memory) + if !(start <= curr_addr && curr_addr < end) { + // panic("Out of bounds memory address passed to stack allocator (free)"); + return .Invalid_Pointer + } + if curr_addr >= start+uintptr(s.offset) { + // NOTE(bill): Allow double frees + return nil + } + header := (^Small_Stack_Allocation_Header)(curr_addr - size_of(Small_Stack_Allocation_Header)) + old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data))) + s.offset = old_offset + return nil +} + +/* +Free all memory to small stack. +*/ +small_stack_free_all :: proc(s: ^Small_Stack) { + s.offset = 0 +} + +/* +Resize an allocation. + +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `small_stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `small_stack_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +small_stack_resize :: proc( + s: ^Small_Stack, + old_memory: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := small_stack_resize_bytes(s, byte_slice(old_memory, old_size), size, alignment, loc) + return raw_data(bytes), err +} + +/* +Resize an allocation. + +This procedure resizes a memory region, specified by the `old_data` parameter +to have a size `size` and alignment `alignment`. The newly allocated memory, +if any is zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `small_stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `small_stack_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +small_stack_resize_bytes :: proc( + s: ^Small_Stack, + old_data: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + bytes, err := small_stack_resize_bytes_non_zeroed(s, old_data, size, alignment, loc) + if bytes != nil { + if old_data == nil { + zero_slice(bytes) + } else if size > len(old_data) { + zero_slice(bytes[len(old_data):]) + } + } + return bytes, err +} + +/* +Resize an allocation without zero-initialization. + +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is not explicitly zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `small_stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `small_stack_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +small_stack_resize_non_zeroed :: proc( + s: ^Small_Stack, + old_memory: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := small_stack_resize_bytes_non_zeroed(s, byte_slice(old_memory, old_size), size, alignment, loc) + return raw_data(bytes), err +} + +/* +Resize an allocation without zero-initialization. + +This procedure resizes a memory region, specified by the `old_data` parameter +to have a size `size` and alignment `alignment`. The newly allocated memory, +if any is not explicitly zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `small_stack_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `small_stack_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +small_stack_resize_bytes_non_zeroed :: proc( + s: ^Small_Stack, + old_data: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + if s.data == nil { + panic("Small stack is not initialized", loc) + } + old_memory := raw_data(old_data) + old_size := len(old_data) + alignment := alignment + alignment = clamp(alignment, 1, 8*size_of(Stack_Allocation_Header{}.padding)/2) + if old_memory == nil { + return small_stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + } + if size == 0 { + return nil, nil + } + start := uintptr(raw_data(s.data)) + end := start + uintptr(len(s.data)) + curr_addr := uintptr(old_memory) + if !(start <= curr_addr && curr_addr < end) { + // panic("Out of bounds memory address passed to stack allocator (resize)"); + return nil, .Invalid_Pointer + } + if curr_addr >= start+uintptr(s.offset) { + // NOTE(bill): Treat as a double free + return nil, nil + } + if old_size == size { + return byte_slice(old_memory, size), nil + } + data, err := small_stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + if err == nil { + runtime.copy(data, byte_slice(old_memory, old_size)) + } + return data, err + +} + +small_stack_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + s := cast(^Small_Stack)allocator_data + if s.data == nil { + return nil, .Invalid_Argument + } + switch mode { + case .Alloc: + return small_stack_alloc_bytes(s, size, alignment, loc) + case .Alloc_Non_Zeroed: + return small_stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + case .Free: + return nil, small_stack_free(s, old_memory, loc) + case .Free_All: + small_stack_free_all(s) + case .Resize: + return small_stack_resize_bytes(s, byte_slice(old_memory, old_size), size, alignment, loc) + case .Resize_Non_Zeroed: + return small_stack_resize_bytes_non_zeroed(s, byte_slice(old_memory, old_size), size, alignment, loc) + case .Query_Features: + set := (^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 +} + + +/* Preserved for compatibility */ +Dynamic_Pool :: Dynamic_Arena +DYNAMIC_POOL_BLOCK_SIZE_DEFAULT :: DYNAMIC_ARENA_BLOCK_SIZE_DEFAULT +DYNAMIC_POOL_OUT_OF_BAND_SIZE_DEFAULT :: DYNAMIC_ARENA_OUT_OF_BAND_SIZE_DEFAULT +dynamic_pool_allocator_proc :: dynamic_arena_allocator_proc +dynamic_pool_free_all :: dynamic_arena_free_all +dynamic_pool_reset :: dynamic_arena_reset +dynamic_pool_alloc_bytes :: dynamic_arena_alloc_bytes +dynamic_pool_alloc :: dynamic_arena_alloc +dynamic_pool_init :: dynamic_arena_init +dynamic_pool_allocator :: dynamic_arena_allocator +dynamic_pool_destroy :: dynamic_arena_destroy + +/* +Default block size for dynamic arena. +*/ +DYNAMIC_ARENA_BLOCK_SIZE_DEFAULT :: 65536 + +/* +Default out-band size of the dynamic arena. +*/ +DYNAMIC_ARENA_OUT_OF_BAND_SIZE_DEFAULT :: 6554 + +/* +Dynamic arena allocator data. +*/ +Dynamic_Arena :: struct { + block_size: int, + out_band_size: int, + alignment: int, + unused_blocks: [dynamic]rawptr, + used_blocks: [dynamic]rawptr, + out_band_allocations: [dynamic]rawptr, + current_block: rawptr, + current_pos: rawptr, + bytes_left: int, + block_allocator: Allocator, +} + +/* +Initialize a dynamic arena. + +This procedure initializes a dynamic arena. The specified `block_allocator` +will be used to allocate arena blocks, and `array_allocator` to allocate +arrays of blocks and out-band blocks. The blocks have the default size of +`block_size` and out-band threshold will be `out_band_size`. All allocations +will be aligned to a boundary specified by `alignment`. +*/ +dynamic_arena_init :: proc( + pool: ^Dynamic_Arena, + block_allocator := context.allocator, + array_allocator := context.allocator, + block_size := DYNAMIC_ARENA_BLOCK_SIZE_DEFAULT, + out_band_size := DYNAMIC_ARENA_OUT_OF_BAND_SIZE_DEFAULT, + alignment := DEFAULT_ALIGNMENT, +) { + pool.block_size = block_size + pool.out_band_size = out_band_size + pool.alignment = alignment + pool.block_allocator = block_allocator + pool.out_band_allocations.allocator = array_allocator + pool.unused_blocks.allocator = array_allocator + pool.used_blocks.allocator = array_allocator +} + +/* +Dynamic arena allocator. + +The dynamic arena allocator uses blocks of a specific size, allocated on-demand +using the block allocator. This allocator acts similarly to arena. All +allocations in a block happen contiguously, from start to end. If an allocation +does not fit into the remaining space of the block, and its size is smaller +than the specified out-band size, a new block is allocated using the +`block_allocator` and the allocation is performed from a newly-allocated block. + +If an allocation has bigger size than the specified out-band size, a new block +is allocated such that the allocation fits into this new block. This is referred +to as an *out-band allocation*. The out-band blocks are kept separately from +normal blocks. + +Just like arena, the dynamic arena does not support freeing of individual +objects. +*/ +@(require_results) +dynamic_arena_allocator :: proc(a: ^Dynamic_Arena) -> Allocator { + return Allocator{ + procedure = dynamic_arena_allocator_proc, + data = a, + } +} + +/* +Destroy a dynamic arena. + +This procedure frees all allocations, made on a dynamic arena, including the +unused blocks, as well as the arrays for storing blocks. +*/ +dynamic_arena_destroy :: proc(a: ^Dynamic_Arena) { + dynamic_arena_free_all(a) + delete(a.unused_blocks) + delete(a.used_blocks) + delete(a.out_band_allocations) + zero(a, size_of(a^)) +} + +@(private="file") +_dynamic_arena_cycle_new_block :: proc(a: ^Dynamic_Arena, loc := #caller_location) -> (err: Allocator_Error) { + if a.block_allocator.procedure == nil { + panic("You must call arena_init on a Pool before using it", loc) + } + if a.current_block != nil { + append(&a.used_blocks, a.current_block, loc=loc) + } + new_block: rawptr + if len(a.unused_blocks) > 0 { + new_block = pop(&a.unused_blocks) + } else { + data: []byte + data, err = a.block_allocator.procedure( + a.block_allocator.data, + Allocator_Mode.Alloc, + a.block_size, + a.alignment, + nil, + 0, + ) + new_block = raw_data(data) + } + a.bytes_left = a.block_size + a.current_pos = new_block + a.current_block = new_block return } +/* +Allocate memory from a dynamic arena. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from a dynamic arena `a`. The allocated memory is +zero-initialized. This procedure returns a pointer to the newly allocated memory +region. +*/ +@(private, require_results) +dynamic_arena_alloc :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> (rawptr, Allocator_Error) { + data, err := dynamic_arena_alloc_bytes(a, size, loc) + return raw_data(data), err +} + +/* +Allocate memory from a dynamic arena. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from a dynamic arena `a`. The allocated memory is +zero-initialized. This procedure returns a slice of the newly allocated memory +region. +*/ +@(require_results) +dynamic_arena_alloc_bytes :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { + bytes, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, loc) + if bytes != nil { + zero_slice(bytes) + } + return bytes, err +} + +/* +Allocate non-initialized memory from a dynamic arena. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from a dynamic arena `a`. The allocated memory is not explicitly +zero-initialized. This procedure returns a pointer to the newly allocated +memory region. +*/ +@(require_results) +dynamic_arena_alloc_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> (rawptr, Allocator_Error) { + data, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, loc) + return raw_data(data), err +} + +/* +Allocate non-initialized memory from a dynamic arena. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment` from a dynamic arena `a`. The allocated memory is not explicitly +zero-initialized. This procedure returns a slice of the newly allocated +memory region. +*/ +@(require_results) +dynamic_arena_alloc_bytes_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { + n := align_formula(size, a.alignment) + if n > a.block_size { + return nil, .Invalid_Argument + } + if n >= a.out_band_size { + assert(a.block_allocator.procedure != nil, "Backing block allocator must be initialized", loc=loc) + memory, err := alloc_bytes_non_zeroed(a.block_size, a.alignment, a.block_allocator, loc) + if memory != nil { + append(&a.out_band_allocations, raw_data(memory), loc = loc) + } + return memory, err + } + if a.bytes_left < n { + err := _dynamic_arena_cycle_new_block(a, loc) + if err != nil { + return nil, err + } + if a.current_block == nil { + return nil, .Out_Of_Memory + } + } + memory := a.current_pos + a.current_pos = ([^]byte)(a.current_pos)[n:] + a.bytes_left -= n + return ([^]byte)(memory)[:size], nil +} + +/* +Reset the dynamic arena. + +This procedure frees all the allocations, owned by the dynamic arena, excluding +the unused blocks. +*/ +dynamic_arena_reset :: proc(a: ^Dynamic_Arena, loc := #caller_location) { + if a.current_block != nil { + append(&a.unused_blocks, a.current_block, loc=loc) + a.current_block = nil + } + for block in a.used_blocks { + append(&a.unused_blocks, block, loc=loc) + } + clear(&a.used_blocks) + for allocation in a.out_band_allocations { + free(allocation, a.block_allocator, loc=loc) + } + clear(&a.out_band_allocations) + a.bytes_left = 0 // Make new allocations call `_dynamic_arena_cycle_new_block` again. +} + +/* +Free all memory from a dynamic arena. + +This procedure frees all the allocations, owned by the dynamic arena, including +the unused blocks. +*/ +dynamic_arena_free_all :: proc(a: ^Dynamic_Arena, loc := #caller_location) { + dynamic_arena_reset(a) + for block in a.unused_blocks { + free(block, a.block_allocator, loc) + } + clear(&a.unused_blocks) +} + +/* +Resize an allocation. + +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `dynamic_arena_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `dynamic_arena_free()`, freeing +the memory region located at an address specified by `old_memory`. + +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +dynamic_arena_resize :: proc( + a: ^Dynamic_Arena, + old_memory: rawptr, + old_size: int, + size: int, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := dynamic_arena_resize_bytes(a, byte_slice(old_memory, old_size), size, loc) + return raw_data(bytes), err +} + +/* +Resize an allocation. + +This procedure resizes a memory region, specified by `old_data`, to have a size +`size` and alignment `alignment`. The newly allocated memory, if any is +zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `dynamic_arena_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `dynamic_arena_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +dynamic_arena_resize_bytes :: proc( + a: ^Dynamic_Arena, + old_data: []byte, + size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + bytes, err := dynamic_arena_resize_bytes_non_zeroed(a, old_data, size, loc) + if bytes != nil { + if old_data == nil { + zero_slice(bytes) + } else if size > len(old_data) { + zero_slice(bytes[len(old_data):]) + } + } + return bytes, err +} + +/* +Resize an allocation without zero-initialization. + +This procedure resizes a memory region, defined by its location, `old_memory`, +and its size, `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any is not explicitly zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `dynamic_arena_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `dynamic_arena_free()`, freeing the +memory region located at an address specified by `old_memory`. + +This procedure returns the pointer to the resized memory region. +*/ +@(require_results) +dynamic_arena_resize_non_zeroed :: proc( + a: ^Dynamic_Arena, + old_memory: rawptr, + old_size: int, + size: int, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := dynamic_arena_resize_bytes_non_zeroed(a, byte_slice(old_memory, old_size), size, loc) + return raw_data(bytes), err +} + +/* +Resize an allocation. + +This procedure resizes a memory region, specified by `old_data`, to have a size +`size` and alignment `alignment`. The newly allocated memory, if any is not +explicitly zero-initialized. + +If `old_memory` is `nil`, this procedure acts just like `dynamic_arena_alloc()`, +allocating a memory region `size` bytes in size, aligned on a boundary specified +by `alignment`. + +If `size` is 0, this procedure acts just like `dynamic_arena_free()`, freeing +the memory region located at an address specified by `old_memory`. + +This procedure returns the slice of the resized memory region. +*/ +@(require_results) +dynamic_arena_resize_bytes_non_zeroed :: proc( + a: ^Dynamic_Arena, + old_data: []byte, + size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + old_memory := raw_data(old_data) + old_size := len(old_data) + if old_size >= size { + return byte_slice(old_memory, size), nil + } + data, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, loc) + if err == nil { + runtime.copy(data, byte_slice(old_memory, old_size)) + } + return data, err +} + +dynamic_arena_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size: int, + alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + arena := (^Dynamic_Arena)(allocator_data) + switch mode { + case .Alloc: + return dynamic_arena_alloc_bytes(arena, size, loc) + case .Alloc_Non_Zeroed: + return dynamic_arena_alloc_bytes_non_zeroed(arena, size, loc) + case .Free: + return nil, .Mode_Not_Implemented + case .Free_All: + dynamic_arena_free_all(arena, loc) + case .Resize: + return dynamic_arena_resize_bytes(arena, byte_slice(old_memory, old_size), size, loc) + case .Resize_Non_Zeroed: + return dynamic_arena_resize_bytes_non_zeroed(arena, byte_slice(old_memory, old_size), size, loc) + case .Query_Features: + set := (^Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Alloc_Non_Zeroed, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features, .Query_Info} + } + return nil, nil + case .Query_Info: + info := (^Allocator_Query_Info)(old_memory) + if info != nil && info.pointer != nil { + info.size = arena.block_size + info.alignment = arena.alignment + return byte_slice(info, size_of(info^)), nil + } + return nil, nil + } + return nil, nil +} + + +/* +Header of the buddy block. +*/ +Buddy_Block :: struct #align(align_of(uint)) { + size: uint, + is_free: bool, +} + +/* +Obtain the next buddy block. +*/ +@(require_results) +buddy_block_next :: proc(block: ^Buddy_Block) -> ^Buddy_Block { + return (^Buddy_Block)(([^]byte)(block)[block.size:]) +} + +/* +Split the block into two, by truncating the given block to a given size. +*/ +@(require_results) +buddy_block_split :: proc(block: ^Buddy_Block, size: uint) -> ^Buddy_Block { + block := block + if block != nil && size != 0 { + // Recursive Split + for size < block.size { + sz := block.size >> 1 + block.size = sz + block = buddy_block_next(block) + block.size = sz + block.is_free = true + } + if size <= block.size { + return block + } + } + // Block cannot fit the requested allocation size + return nil +} + +/* +Coalesce contiguous blocks in a range of blocks into one. +*/ +buddy_block_coalescence :: proc(head, tail: ^Buddy_Block) { + for { + // Keep looping until there are no more buddies to coalesce + block := head + buddy := buddy_block_next(block) + no_coalescence := true + for block < tail && buddy < tail { // make sure the buddies are within the range + if block.is_free && buddy.is_free && block.size == buddy.size { + // Coalesce buddies into one + block.size <<= 1 + block = buddy_block_next(block) + if block < tail { + buddy = buddy_block_next(block) + no_coalescence = false + } + } else if block.size < buddy.size { + // The buddy block is split into smaller blocks + block = buddy + buddy = buddy_block_next(buddy) + } else { + block = buddy_block_next(buddy) + if block < tail { + // Leave the buddy block for the next iteration + buddy = buddy_block_next(block) + } + } + } + if no_coalescence { + return + } + } +} + +/* +Find the best block for storing a given size in a range of blocks. +*/ +@(require_results) +buddy_block_find_best :: proc(head, tail: ^Buddy_Block, size: uint) -> ^Buddy_Block { + assert(size != 0) + best_block: ^Buddy_Block + block := head // left + buddy := buddy_block_next(block) // right + // The entire memory section between head and tail is free, + // just call 'buddy_block_split' to get the allocation + if buddy == tail && block.is_free { + return buddy_block_split(block, size) + } + // Find the block which is the 'best_block' to requested allocation sized + for block < tail && buddy < tail { // make sure the buddies are within the range + // If both buddies are free, coalesce them together + // NOTE: this is an optimization to reduce fragmentation + // this could be completely ignored + if block.is_free && buddy.is_free && block.size == buddy.size { + block.size <<= 1 + if size <= block.size && (best_block == nil || block.size <= best_block.size) { + best_block = block + } + block = buddy_block_next(buddy) + if block < tail { + // Delay the buddy block for the next iteration + buddy = buddy_block_next(block) + } + continue + } + if block.is_free && size <= block.size && + (best_block == nil || block.size <= best_block.size) { + best_block = block + } + if buddy.is_free && size <= buddy.size && + (best_block == nil || buddy.size < best_block.size) { + // If each buddy are the same size, then it makes more sense + // to pick the buddy as it "bounces around" less + best_block = buddy + } + if block.size <= buddy.size { + block = buddy_block_next(buddy) + if (block < tail) { + // Delay the buddy block for the next iteration + buddy = buddy_block_next(block) + } + } else { + // Buddy was split into smaller blocks + block = buddy + buddy = buddy_block_next(buddy) + } + } + if best_block != nil { + // This will handle the case if the 'best_block' is also the perfect fit + return buddy_block_split(best_block, size) + } + // Maybe out of memory + return nil +} + +/* +The buddy allocator data. +*/ +Buddy_Allocator :: struct { + head: ^Buddy_Block, + tail: ^Buddy_Block, + alignment: uint, +} + +/* +Buddy allocator. + +The buddy allocator is a type of allocator that splits the backing buffer into +multiple regions called buddy blocks. Initially, the allocator only has one +block with the size of the backing buffer. Upon each allocation, the allocator +finds the smallest block that can fit the size of requested memory region, and +splits the block according to the allocation size. If no block can be found, +the contiguous free blocks are coalesced and the search is performed again. +*/ +@(require_results) +buddy_allocator :: proc(b: ^Buddy_Allocator) -> Allocator { + return Allocator{ + procedure = buddy_allocator_proc, + data = b, + } +} + +/* +Initialize the buddy allocator. + +This procedure initializes the buddy allocator `b` with a backing buffer `data` +and block alignment specified by `alignment`. +*/ +buddy_allocator_init :: proc(b: ^Buddy_Allocator, data: []byte, alignment: uint, loc := #caller_location) { + assert(data != nil) + assert(is_power_of_two(uintptr(len(data))), "Size of the backing buffer must be power of two", loc) + assert(is_power_of_two(uintptr(alignment)), "Alignment must be a power of two", loc) + alignment := alignment + if alignment < size_of(Buddy_Block) { + alignment = size_of(Buddy_Block) + } + ptr := raw_data(data) + assert(uintptr(ptr) % uintptr(alignment) == 0, "data is not aligned to minimum alignment", loc) + b.head = (^Buddy_Block)(ptr) + b.head.size = len(data) + b.head.is_free = true + b.tail = buddy_block_next(b.head) + b.alignment = alignment +} + +/* +Get required block size to fit in the allocation as well as the alignment padding. +*/ +@(require_results) +buddy_block_size_required :: proc(b: ^Buddy_Allocator, size: uint) -> uint { + size := size + actual_size := b.alignment + size += size_of(Buddy_Block) + size = align_forward_uint(size, b.alignment) + for size > actual_size { + actual_size <<= 1 + } + return actual_size +} + +/* +Allocate memory from a buddy allocator. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is zero-initialized. This procedure +returns a pointer to the allocated memory region. +*/ +@(require_results) +buddy_allocator_alloc :: proc(b: ^Buddy_Allocator, size: uint) -> (rawptr, Allocator_Error) { + bytes, err := buddy_allocator_alloc_bytes(b, size) + return raw_data(bytes), err +} + +/* +Allocate memory from a buddy allocator. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is zero-initialized. This procedure +returns a slice of the allocated memory region. +*/ +@(require_results) +buddy_allocator_alloc_bytes :: proc(b: ^Buddy_Allocator, size: uint) -> ([]byte, Allocator_Error) { + bytes, err := buddy_allocator_alloc_bytes_non_zeroed(b, size) + if bytes != nil { + zero_slice(bytes) + } + return bytes, err +} + +/* +Allocate non-initialized memory from a buddy allocator. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is not explicitly zero-initialized. +This procedure returns a pointer to the allocated memory region. +*/ +@(require_results) +buddy_allocator_alloc_non_zeroed :: proc(b: ^Buddy_Allocator, size: uint) -> (rawptr, Allocator_Error) { + bytes, err := buddy_allocator_alloc_bytes_non_zeroed(b, size) + return raw_data(bytes), err +} + +/* +Allocate non-initialized memory from a buddy allocator. + +This procedure allocates `size` bytes of memory aligned on a boundary specified +by `alignment`. The allocated memory region is not explicitly zero-initialized. +This procedure returns a slice of the allocated memory region. +*/ +@(require_results) +buddy_allocator_alloc_bytes_non_zeroed :: proc(b: ^Buddy_Allocator, size: uint) -> ([]byte, Allocator_Error) { + if size != 0 { + actual_size := buddy_block_size_required(b, size) + found := buddy_block_find_best(b.head, b.tail, actual_size) + if found != nil { + // Try to coalesce all the free buddy blocks and then search again + buddy_block_coalescence(b.head, b.tail) + found = buddy_block_find_best(b.head, b.tail, actual_size) + } + if found == nil { + return nil, .Out_Of_Memory + } + found.is_free = false + data := ([^]byte)(found)[b.alignment:][:size] + return data, nil + } + return nil, nil +} + +/* +Free memory to the buddy allocator. + +This procedure frees the memory region allocated at pointer `ptr`. + +If `ptr` is not the latest allocation and is not a leaked allocation, this +operation is a no-op. +*/ +buddy_allocator_free :: proc(b: ^Buddy_Allocator, ptr: rawptr) -> Allocator_Error { + if ptr != nil { + if !(b.head <= ptr && ptr <= b.tail) { + return .Invalid_Pointer + } + block := (^Buddy_Block)(([^]byte)(ptr)[-b.alignment:]) + block.is_free = true + buddy_block_coalescence(b.head, b.tail) + } + return nil +} + +/* +Free all memory to the buddy allocator. +*/ +buddy_allocator_free_all :: proc(b: ^Buddy_Allocator) { + alignment := b.alignment + head := ([^]byte)(b.head) + tail := ([^]byte)(b.tail) + data := head[:ptr_sub(tail, head)] + buddy_allocator_init(b, data, alignment) +} + +buddy_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + b := (^Buddy_Allocator)(allocator_data) + switch mode { + case .Alloc: + return buddy_allocator_alloc_bytes(b, uint(size)) + case .Alloc_Non_Zeroed: + return buddy_allocator_alloc_bytes_non_zeroed(b, uint(size)) + case .Resize: + return default_resize_bytes_align(byte_slice(old_memory, old_size), size, alignment, buddy_allocator(b), loc) + case .Resize_Non_Zeroed: + return default_resize_bytes_align_non_zeroed(byte_slice(old_memory, old_size), size, alignment, buddy_allocator(b), loc) + case .Free: + return nil, buddy_allocator_free(b, old_memory) + case .Free_All: + buddy_allocator_free_all(b) + case .Query_Features: + set := (^Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Query_Features, .Alloc, .Alloc_Non_Zeroed, .Resize, .Resize_Non_Zeroed, .Free, .Free_All, .Query_Info} + } + return nil, nil + case .Query_Info: + info := (^Allocator_Query_Info)(old_memory) + if info != nil && info.pointer != nil { + ptr := info.pointer + if !(b.head <= ptr && ptr <= b.tail) { + return nil, .Invalid_Pointer + } + block := (^Buddy_Block)(([^]byte)(ptr)[-b.alignment:]) + info.size = int(block.size) + info.alignment = int(b.alignment) + return byte_slice(info, size_of(info^)), nil + } + return nil, nil + } + return nil, nil +} + +// An allocator that keeps track of allocation sizes and passes it along to resizes. +// This is useful if you are using a library that needs an equivalent of `realloc` but want to use +// the Odin allocator interface. +// +// You want to wrap your allocator into this one if you are trying to use any allocator that relies +// on the old size to work. +// +// The overhead of this allocator is an extra max(alignment, size_of(Header)) bytes allocated for each allocation, these bytes are +// used to store the size and original pointer. +Compat_Allocator :: struct { + parent: Allocator, +} + +compat_allocator_init :: proc(rra: ^Compat_Allocator, allocator := context.allocator) { + rra.parent = allocator +} + +compat_allocator :: proc(rra: ^Compat_Allocator) -> Allocator { + return Allocator{ + data = rra, + procedure = compat_allocator_proc, + } +} + +compat_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, + location := #caller_location) -> (data: []byte, err: Allocator_Error) { + size, old_size := size, old_size + + Header :: struct { + size: int, + ptr: rawptr, + } + + rra := (^Compat_Allocator)(allocator_data) + switch mode { + case .Alloc, .Alloc_Non_Zeroed: + a := max(alignment, size_of(Header)) + size += a + assert(size >= 0, "overflow") + + allocation := rra.parent.procedure(rra.parent.data, mode, size, alignment, old_memory, old_size, location) or_return + #no_bounds_check data = allocation[a:] + + ([^]Header)(raw_data(data))[-1] = { + size = size, + ptr = raw_data(allocation), + } + return + + case .Free: + header := ([^]Header)(old_memory)[-1] + return rra.parent.procedure(rra.parent.data, mode, size, alignment, header.ptr, header.size, location) + + case .Resize, .Resize_Non_Zeroed: + header := ([^]Header)(old_memory)[-1] + + a := max(alignment, size_of(header)) + size += a + assert(size >= 0, "overflow") + + allocation := rra.parent.procedure(rra.parent.data, mode, size, alignment, header.ptr, header.size, location) or_return + #no_bounds_check data = allocation[a:] + + ([^]Header)(raw_data(data))[-1] = { + size = size, + ptr = raw_data(allocation), + } + return + + case .Free_All, .Query_Info, .Query_Features: + return rra.parent.procedure(rra.parent.data, mode, size, alignment, old_memory, old_size, location) + + case: unreachable() + } +} diff --git a/core/mem/doc.odin b/core/mem/doc.odin index e232428c2..98755d797 100644 --- a/core/mem/doc.odin +++ b/core/mem/doc.odin @@ -1,35 +1,114 @@ /* -package mem implements various types of allocators. +The `mem` package implements various allocators and provides utility procedures +for dealing with memory, pointers and slices. +The documentation below describes basic concepts, applicable to the `mem` +package. -An example of how to use the `Tracking_Allocator` to track subsequent allocations -in your program and report leaks and bad frees: +## Pointers, multipointers, and slices -```odin -package foo +A *pointer* is an abstraction of an *address*, a numberic value representing the +location of an object in memory. That object is said to be *pointed to* by the +pointer. To obtain the address of a pointer, cast it to `uintptr`. -import "core:mem" -import "core:fmt" +A multipointer is a pointer that points to multiple objects. Unlike a pointer, +a multipointer can be indexed, but does not have a definite length. A slice is +a pointer that points to multiple objects equipped with the length, specifying +the amount of objects a slice points to. -_main :: proc() { - do stuff -} +When object's values are read through a pointer, that operation is called a +*load* operation. When memory is read through a pointer, that operation is +called a *store* operation. Both of these operations can be called a *memory +access operation*. -main :: proc() { - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - defer mem.tracking_allocator_destroy(&track) - context.allocator = mem.tracking_allocator(&track) +## Allocators - _main() +In C and C++ memory models, allocations of objects in memory are typically +treated individually with a generic allocator (The `malloc` procedure). Which in +some scenarios can lead to poor cache utilization, slowdowns on individual +objects' memory management and growing complexity of the code needing to keep +track of the pointers and their lifetimes. - for _, leak in track.allocation_map { - fmt.printf("%v leaked %m\n", leak.location, leak.size) - } - for bad_free in track.bad_free_array { - fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory) - } -} -``` +Using different kinds of *allocators* for different purposes can solve these +problems. The allocators are typically optimized for specific use-cases and +can potentially simplify the memory management code. + +For example, in the context of making a game, having an Arena allocator could +simplify allocations of any temporary memory, because the programmer doesn't +have to keep track of which objects need to be freed every time they are +allocated, because at the end of every frame the whole allocator is reset to +its initial state and all objects are freed at once. + +The allocators have different kinds of restrictions on object lifetimes, sizes, +alignment and can be a significant gain, if used properly. Odin supports +allocators on a language level. + +Operations such as `new`, `free` and `delete` by default will use +`context.allocator`, which can be overridden by the user. When an override +happens all called procedures will inherit the new context and use the same +allocator. + +We will define one concept to simplify the description of some allocator-related +procedures, which is ownership. If the memory was allocated via a specific +allocator, that allocator is said to be the *owner* of that memory region. To +note, unlike Rust, in Odin the memory ownership model is not strict. + +## Alignment + +An address is said to be *aligned to `N` bytes*, if the addresses's numeric +value is divisible by `N`. The number `N` in this case can be referred to as +the *alignment boundary*. Typically an alignment is a power of two integer +value. + +A *natural alignment* of an object is typically equal to its size. For example +a 16 bit integer has a natural alignment of 2 bytes. When an object is not +located on its natural alignment boundary, accesses to that object are +considered *unaligned*. + +Some machines issue a hardware **exception**, or experience **slowdowns** when a +memory access operation occurs from an unaligned address. Examples of such +operations are: + +- SIMD instructions on x86. These instructions require all memory accesses to be + on an address that is aligned to 16 bytes. +- On ARM unaligned loads have an extra cycle penalty. + +As such, many operations that allocate memory in this package allow to +explicitly specify the alignment of allocated pointers/slices. The default +alignment for all operations is specified in a constant `mem.DEFAULT_ALIGNMENT`. + +## Zero by default + +Whenever new memory is allocated, via an allocator, or on the stack, by default +Odin will zero-initialize that memory, even if it wasn't explicitly +initialized. This allows for some convenience in certain scenarios and ease of +debugging, which will not be described in detail here. + +However zero-initialization can be a cause of slowdowns, when allocating large +buffers. For this reason, allocators have `*_non_zeroed` modes of allocation +that allow the user to request for uninitialized memory and will avoid a +relatively expensive zero-filling of the buffer. + +## Naming conventions + +The word `size` is used to denote the **size in bytes**. The word `length` is +used to denote the count of objects. + +The allocation procedures use the following conventions: + +- If the name contains `alloc_bytes` or `resize_bytes`, then the procedure takes + in slice parameters and returns slices. +- If the procedure name contains `alloc` or `resize`, then the procedure takes + in a raw pointer and returns raw pointers. +- If the procedure name contains `free_bytes`, then the procedure takes in a + slice. +- If the procedure name contains `free`, then the procedure takes in a pointer. + +Higher-level allocation procedures follow the following naming scheme: + +- `new`: Allocates a single object +- `free`: Free a single object (opposite of `new`) +- `make`: Allocate a group of objects +- `delete`: Free a group of objects (opposite of `make`) */ package mem diff --git a/core/mem/mem.odin b/core/mem/mem.odin index dd985d5dd..b2a7158a1 100644 --- a/core/mem/mem.odin +++ b/core/mem/mem.odin @@ -1,50 +1,188 @@ package mem -import "core:runtime" -import "core:intrinsics" +import "base:runtime" +import "base:intrinsics" -Byte :: runtime.Byte +/* +The size, in bytes, of a single byte. + +This constant is equal to the value of `1`. +*/ +Byte :: runtime.Byte + +/* +The size, in bytes, of one kilobyte. + +This constant is equal to the amount of bytes in one kilobyte (also known as +kibibyte), which is equal to 1024 bytes. +*/ Kilobyte :: runtime.Kilobyte -Megabyte :: runtime.Megabyte -Gigabyte :: runtime.Gigabyte -Terabyte :: runtime.Terabyte -Petabyte :: runtime.Petabyte -Exabyte :: runtime.Exabyte +/* +The size, in bytes, of one megabyte. + +This constant is equal to the amount of bytes in one megabyte (also known as +mebibyte), which is equal to 1024 kilobyte. +*/ +Megabyte :: runtime.Megabyte + +/* +The size, in bytes, of one gigabyte. + +This constant is equal to the amount of bytes in one gigabyte (also known as +gibiibyte), which is equal to 1024 megabytes. +*/ +Gigabyte :: runtime.Gigabyte + +/* +The size, in bytes, of one terabyte. + +This constant is equal to the amount of bytes in one terabyte (also known as +tebiibyte), which is equal to 1024 gigabytes. +*/ +Terabyte :: runtime.Terabyte + +/* +The size, in bytes, of one petabyte. + +This constant is equal to the amount of bytes in one petabyte (also known as +pebiibyte), which is equal to 1024 terabytes. +*/ +Petabyte :: runtime.Petabyte + +/* +The size, in bytes, of one exabyte. + +This constant is equal to the amount of bytes in one exabyte (also known as +exbibyte), which is equal to 1024 petabytes. +*/ +Exabyte :: runtime.Exabyte + +/* +Set each byte of a memory range to a specific value. + +This procedure copies value specified by the `value` parameter into each of the +`len` bytes of a memory range, located at address `data`. + +This procedure returns the pointer to `data`. +*/ set :: proc "contextless" (data: rawptr, value: byte, len: int) -> rawptr { return runtime.memset(data, i32(value), len) } + +/* +Set each byte of a memory range to zero. + +This procedure copies the value `0` into the `len` bytes of a memory range, +starting at address `data`. + +This procedure returns the pointer to `data`. +*/ zero :: proc "contextless" (data: rawptr, len: int) -> rawptr { intrinsics.mem_zero(data, len) return data } + +/* +Set each byte of a memory range to zero. + +This procedure copies the value `0` into the `len` bytes of a memory range, +starting at address `data`. + +This procedure returns the pointer to `data`. + +Unlike the `zero()` procedure, which can be optimized away or reordered by the +compiler under certain circumstances, `zero_explicit()` procedure can not be +optimized away or reordered with other memory access operations, and the +compiler assumes volatile semantics of the memory. +*/ zero_explicit :: proc "contextless" (data: rawptr, len: int) -> rawptr { // This routine tries to avoid the compiler optimizing away the call, - // so that it is always executed. It is intended to provided + // so that it is always executed. It is intended to provide // equivalent semantics to those provided by the C11 Annex K 3.7.4.1 // memset_s call. intrinsics.mem_zero_volatile(data, len) // Use the volatile mem_zero intrinsics.atomic_thread_fence(.Seq_Cst) // Prevent reordering return data } + +/* +Zero-fill the memory of an object. + +This procedure sets each byte of the object pointed to by the pointer `item` +to zero, and returns the pointer to `item`. +*/ zero_item :: proc "contextless" (item: $P/^$T) -> P { intrinsics.mem_zero(item, size_of(T)) return item } + +/* +Zero-fill the memory of the slice. + +This procedure sets each byte of the slice pointed to by the slice `data` +to zero, and returns the slice `data`. +*/ zero_slice :: proc "contextless" (data: $T/[]$E) -> T { zero(raw_data(data), size_of(E)*len(data)) return data } +/* +Copy bytes from one memory range to another. +This procedure copies `len` bytes of data, from the memory range pointed to by +the `src` pointer into the memory range pointed to by the `dst` pointer, and +returns the `dst` pointer. +*/ copy :: proc "contextless" (dst, src: rawptr, len: int) -> rawptr { intrinsics.mem_copy(dst, src, len) return dst } + +/* +Copy bytes between two non-overlapping memory ranges. + +This procedure copies `len` bytes of data, from the memory range pointed to by +the `src` pointer into the memory range pointed to by the `dst` pointer, and +returns the `dst` pointer. + +This is a slightly more optimized version of the `copy` procedure that requires +that memory ranges specified by the parameters to this procedure are not +overlapping. If the memory ranges specified by `dst` and `src` pointers overlap, +the behavior of this function may be unpredictable. +*/ copy_non_overlapping :: proc "contextless" (dst, src: rawptr, len: int) -> rawptr { intrinsics.mem_copy_non_overlapping(dst, src, len) return dst } + +/* +Compare two memory ranges defined by slices. + +This procedure performs a byte-by-byte comparison between memory ranges +specified by slices `a` and `b`, and returns a value, specifying their relative +ordering. + +If the return value is: +- Equal to `-1`, then `a` is "smaller" than `b`. +- Equal to `+1`, then `a` is "bigger" than `b`. +- Equal to `0`, then `a` and `b` are equal. + +The comparison is performed as follows: +1. Each byte, upto `min(len(a), len(b))` bytes is compared between `a` and `b`. + - If the byte in slice `a` is smaller than a byte in slice `b`, then comparison + stops and this procedure returns `-1`. + - If the byte in slice `a` is bigger than a byte in slice `b`, then comparison + stops and this procedure returns `+1`. + - Otherwise the comparison continues until `min(len(a), len(b))` are compared. +2. If all the bytes in the range are equal, then the lengths of the slices are + compared. + - If the length of slice `a` is smaller than the length of slice `b`, then `-1` is returned. + - If the length of slice `b` is smaller than the length of slice `b`, then `+1` is returned. + - Otherwise `0` is returned. +*/ +@(require_results) compare :: proc "contextless" (a, b: []byte) -> int { res := compare_byte_ptrs(raw_data(a), raw_data(b), min(len(a), len(b))) if res == 0 && len(a) != len(b) { @@ -55,16 +193,89 @@ compare :: proc "contextless" (a, b: []byte) -> int { return res } +/* +Compare two memory ranges defined by byte pointers. + +This procedure performs a byte-by-byte comparison between memory ranges of size +`n` located at addresses `a` and `b`, and returns a value, specifying their relative +ordering. + +If the return value is: +- Equal to `-1`, then `a` is "smaller" than `b`. +- Equal to `+1`, then `a` is "bigger" than `b`. +- Equal to `0`, then `a` and `b` are equal. + +The comparison is performed as follows: +1. Each byte, upto `n` bytes is compared between `a` and `b`. + - If the byte in `a` is smaller than a byte in `b`, then comparison stops + and this procedure returns `-1`. + - If the byte in `a` is bigger than a byte in `b`, then comparison stops + and this procedure returns `+1`. + - Otherwise the comparison continues until `n` bytes are compared. +2. If all the bytes in the range are equal, this procedure returns `0`. +*/ @(require_results) compare_byte_ptrs :: proc "contextless" (a, b: ^byte, n: int) -> int #no_bounds_check { return runtime.memory_compare(a, b, n) } +/* +Compare two memory ranges defined by pointers. + +This procedure performs a byte-by-byte comparison between memory ranges of size +`n` located at addresses `a` and `b`, and returns a value, specifying their relative +ordering. + +If the return value is: +- Equal to `-1`, then `a` is "smaller" than `b`. +- Equal to `+1`, then `a` is "bigger" than `b`. +- Equal to `0`, then `a` and `b` are equal. + +The comparison is performed as follows: +1. Each byte, upto `n` bytes is compared between `a` and `b`. + - If the byte in `a` is smaller than a byte in `b`, then comparison stops + and this procedure returns `-1`. + - If the byte in `a` is bigger than a byte in `b`, then comparison stops + and this procedure returns `+1`. + - Otherwise the comparison continues until `n` bytes are compared. +2. If all the bytes in the range are equal, this procedure returns `0`. +*/ +@(require_results) +compare_ptrs :: proc "contextless" (a, b: rawptr, n: int) -> int { + return compare_byte_ptrs((^byte)(a), (^byte)(b), n) +} + +/* +Check whether two objects are equal on binary level. + +This procedure checks whether the memory ranges occupied by objects `a` and +`b` are equal. See `compare_byte_ptrs()` for how this comparison is done. +*/ +@(require_results) +simple_equal :: proc "contextless" (a, b: $T) -> bool where intrinsics.type_is_simple_compare(T) { + a, b := a, b + return compare_byte_ptrs((^byte)(&a), (^byte)(&b), size_of(T)) == 0 +} + +/* +Check if the memory range defined by a slice is zero-filled. + +This procedure checks whether every byte, pointed to by the slice, specified +by the parameter `data`, is zero. If all bytes of the slice are zero, this +procedure returns `true`. Otherwise this procedure returns `false`. +*/ @(require_results) check_zero :: proc(data: []byte) -> bool { return check_zero_ptr(raw_data(data), len(data)) } +/* +Check if the memory range defined defined by a pointer is zero-filled. + +This procedure checks whether each of the `len` bytes, starting at address +`ptr` is zero. If all bytes of this range are zero, this procedure returns +`true`. Otherwise this procedure returns `false`. +*/ @(require_results) check_zero_ptr :: proc(ptr: rawptr, len: int) -> bool { switch { @@ -79,57 +290,99 @@ check_zero_ptr :: proc(ptr: rawptr, len: int) -> bool { case 4: return intrinsics.unaligned_load((^u32)(ptr)) == 0 case 8: return intrinsics.unaligned_load((^u64)(ptr)) == 0 } - start := uintptr(ptr) start_aligned := align_forward_uintptr(start, align_of(uintptr)) end := start + uintptr(len) end_aligned := align_backward_uintptr(end, align_of(uintptr)) - for b in start.. bool where intrinsics.type_is_simple_compare(T) { - a, b := a, b - return compare_byte_ptrs((^byte)(&a), (^byte)(&b), size_of(T)) == 0 -} +/* +Offset a given pointer by a given amount. -@(require_results) -compare_ptrs :: proc "contextless" (a, b: rawptr, n: int) -> int { - return compare_byte_ptrs((^byte)(a), (^byte)(b), n) -} +This procedure offsets the pointer `ptr` to an object of type `T`, by the amount +of bytes specified by `offset*size_of(T)`, and returns the pointer `ptr`. +**Note**: Prefer to use multipointer types, if possible. +*/ ptr_offset :: intrinsics.ptr_offset + +/* +Offset a given pointer by a given amount backwards. + +This procedure offsets the pointer `ptr` to an object of type `T`, by the amount +of bytes specified by `offset*size_of(T)` in the negative direction, and +returns the pointer `ptr`. +*/ ptr_sub :: intrinsics.ptr_sub +/* +Construct a slice from pointer and length. + +This procedure creates a slice, that points to `len` amount of objects located +at an address, specified by `ptr`. +*/ @(require_results) slice_ptr :: proc "contextless" (ptr: ^$T, len: int) -> []T { return ([^]T)(ptr)[:len] } +/* +Construct a byte slice from raw pointer and length. + +This procedure creates a byte slice, that points to `len` amount of bytes +located at an address specified by `data`. +*/ @(require_results) byte_slice :: #force_inline proc "contextless" (data: rawptr, #any_int len: int) -> []byte { return ([^]u8)(data)[:max(len, 0)] } +/* +Create a byte slice from pointer and length. + +This procedure creates a byte slice, pointing to `len` objects, starting from +the address specified by `ptr`. +*/ +@(require_results) +ptr_to_bytes :: proc "contextless" (ptr: ^$T, len := 1) -> []byte { + return transmute([]byte)Raw_Slice{ptr, len*size_of(T)} +} + +/* +Obtain the slice, pointing to the contents of `any`. + +This procedure returns the slice, pointing to the contents of the specified +value of the `any` type. +*/ +@(require_results) +any_to_bytes :: proc "contextless" (val: any) -> []byte { + ti := type_info_of(val.id) + size := ti != nil ? ti.size : 0 + return transmute([]byte)Raw_Slice{val.data, size} +} + +/* +Obtain a byte slice from any slice. + +This procedure returns a slice, that points to the same bytes as the slice, +specified by `slice` and returns the resulting byte slice. +*/ @(require_results) slice_to_bytes :: proc "contextless" (slice: $E/[]$T) -> []byte { s := transmute(Raw_Slice)slice @@ -137,6 +390,15 @@ slice_to_bytes :: proc "contextless" (slice: $E/[]$T) -> []byte { return transmute([]byte)s } +/* +Transmute slice to a different type. + +This procedure performs an operation similar to transmute, returning a slice of +type `T` that points to the same bytes as the slice specified by `slice` +parameter. Unlike plain transmute operation, this procedure adjusts the length +of the resulting slice, such that the resulting slice points to the correct +amount of objects to cover the memory region pointed to by `slice`. +*/ @(require_results) slice_data_cast :: proc "contextless" ($T: typeid/[]$A, slice: $S/[]$B) -> T { when size_of(A) == 0 || size_of(B) == 0 { @@ -148,12 +410,25 @@ slice_data_cast :: proc "contextless" ($T: typeid/[]$A, slice: $S/[]$B) -> T { } } +/* +Obtain data and length of a slice. + +This procedure returns the pointer to the start of the memory region pointed to +by slice `slice` and the length of the slice. +*/ @(require_results) slice_to_components :: proc "contextless" (slice: $E/[]$T) -> (data: ^T, len: int) { s := transmute(Raw_Slice)slice return (^T)(s.data), s.len } +/* +Create a dynamic array from slice. + +This procedure creates a dynamic array, using slice `backing` as the backing +buffer for the dynamic array. The resulting dynamic array can not grow beyond +the size of the specified slice. +*/ @(require_results) buffer_from_slice :: proc "contextless" (backing: $T/[]$E) -> [dynamic]E { return transmute([dynamic]E)Raw_Dynamic_Array{ @@ -167,19 +442,12 @@ buffer_from_slice :: proc "contextless" (backing: $T/[]$E) -> [dynamic]E { } } -@(require_results) -ptr_to_bytes :: proc "contextless" (ptr: ^$T, len := 1) -> []byte { - return transmute([]byte)Raw_Slice{ptr, len*size_of(T)} -} - -@(require_results) -any_to_bytes :: proc "contextless" (val: any) -> []byte { - ti := type_info_of(val.id) - size := ti != nil ? ti.size : 0 - return transmute([]byte)Raw_Slice{val.data, size} -} - +/* +Check whether a number is a power of two. +This procedure checks whether a given pointer-sized unsigned integer contains +a power-of-two value. +*/ @(require_results) is_power_of_two :: proc "contextless" (x: uintptr) -> bool { if x <= 0 { @@ -188,66 +456,169 @@ is_power_of_two :: proc "contextless" (x: uintptr) -> bool { return (x & (x-1)) == 0 } +/* +Check if a pointer is aligned. + +This procedure checks whether a pointer `x` is aligned to a boundary specified +by `align`, and returns `true` if the pointer is aligned, and false otherwise. + +The specified alignment must be a power of 2. +*/ +is_aligned :: proc "contextless" (x: rawptr, align: int) -> bool { + p := uintptr(x) + return (p & (uintptr(align) - 1)) == 0 +} + +/* +Align uintptr forward. + +This procedure returns the next address after `ptr`, that is located on the +alignment boundary specified by `align`. If `ptr` is already aligned to `align` +bytes, `ptr` is returned. + +The specified alignment must be a power of 2. +*/ +@(require_results) +align_forward_uintptr :: proc(ptr, align: uintptr) -> uintptr { + assert(is_power_of_two(align)) + return (ptr + align-1) & ~(align-1) +} + +/* +Align pointer forward. + +This procedure returns the next address after `ptr`, that is located on the +alignment boundary specified by `align`. If `ptr` is already aligned to `align` +bytes, `ptr` is returned. + +The specified alignment must be a power of 2. +*/ @(require_results) align_forward :: proc(ptr: rawptr, align: uintptr) -> rawptr { return rawptr(align_forward_uintptr(uintptr(ptr), align)) } -@(require_results) -align_forward_uintptr :: proc(ptr, align: uintptr) -> uintptr { - assert(is_power_of_two(align)) +/* +Align int forward. - p := ptr - modulo := p & (align-1) - if modulo != 0 { - p += align - modulo - } - return p -} +This procedure returns the next address after `ptr`, that is located on the +alignment boundary specified by `align`. If `ptr` is already aligned to `align` +bytes, `ptr` is returned. +The specified alignment must be a power of 2. +*/ @(require_results) align_forward_int :: proc(ptr, align: int) -> int { return int(align_forward_uintptr(uintptr(ptr), uintptr(align))) } + +/* +Align uint forward. + +This procedure returns the next address after `ptr`, that is located on the +alignment boundary specified by `align`. If `ptr` is already aligned to `align` +bytes, `ptr` is returned. + +The specified alignment must be a power of 2. +*/ @(require_results) align_forward_uint :: proc(ptr, align: uint) -> uint { return uint(align_forward_uintptr(uintptr(ptr), uintptr(align))) } +/* +Align uintptr backwards. + +This procedure returns the previous address before `ptr`, that is located on the +alignment boundary specified by `align`. If `ptr` is already aligned to `align` +bytes, `ptr` is returned. + +The specified alignment must be a power of 2. +*/ +@(require_results) +align_backward_uintptr :: proc(ptr, align: uintptr) -> uintptr { + assert(is_power_of_two(align)) + return ptr & ~(align-1) +} + +/* +Align rawptr backwards. + +This procedure returns the previous address before `ptr`, that is located on the +alignment boundary specified by `align`. If `ptr` is already aligned to `align` +bytes, `ptr` is returned. + +The specified alignment must be a power of 2. +*/ @(require_results) align_backward :: proc(ptr: rawptr, align: uintptr) -> rawptr { return rawptr(align_backward_uintptr(uintptr(ptr), align)) } -@(require_results) -align_backward_uintptr :: proc(ptr, align: uintptr) -> uintptr { - return align_forward_uintptr(ptr - align + 1, align) -} +/* +Align int backwards. +This procedure returns the previous address before `ptr`, that is located on the +alignment boundary specified by `align`. If `ptr` is already aligned to `align` +bytes, `ptr` is returned. + +The specified alignment must be a power of 2. +*/ @(require_results) align_backward_int :: proc(ptr, align: int) -> int { return int(align_backward_uintptr(uintptr(ptr), uintptr(align))) } + +/* +Align uint backwards. + +This procedure returns the previous address before `ptr`, that is located on the +alignment boundary specified by `align`. If `ptr` is already aligned to `align` +bytes, `ptr` is returned. + +The specified alignment must be a power of 2. +*/ @(require_results) align_backward_uint :: proc(ptr, align: uint) -> uint { return uint(align_backward_uintptr(uintptr(ptr), uintptr(align))) } +/* +Create a context with a given allocator. + +This procedure returns a copy of the current context with the allocator replaced +by the allocator `a`. +*/ @(require_results) context_from_allocator :: proc(a: Allocator) -> type_of(context) { context.allocator = a return context } +/* +Copy the value from a pointer into a value. + +This procedure copies the object of type `T` pointed to by the pointer `ptr` +into a new stack-allocated value and returns that value. +*/ @(require_results) reinterpret_copy :: proc "contextless" ($T: typeid, ptr: rawptr) -> (value: T) { copy(&value, ptr, size_of(T)) return } +/* +Dynamic array with a fixed capacity buffer. +This type represents dynamic arrays with a fixed-size backing buffer. Upon +allocating memory beyond reaching the maximum capacity, allocations from fixed +byte buffers return `nil` and no error. +*/ Fixed_Byte_Buffer :: distinct [dynamic]byte +/* +Create a fixed byte buffer from a slice. +*/ @(require_results) make_fixed_byte_buffer :: proc "contextless" (backing: []byte) -> Fixed_Byte_Buffer { s := transmute(Raw_Slice)backing @@ -262,43 +633,56 @@ make_fixed_byte_buffer :: proc "contextless" (backing: []byte) -> Fixed_Byte_Buf return transmute(Fixed_Byte_Buffer)d } +/* +General-purpose align formula. - +This procedure is equivalent to `align_forward`, but it does not require the +alignment to be a power of two. +*/ @(require_results) align_formula :: proc "contextless" (size, align: int) -> int { result := size + align-1 return result - result%align } +/* +Calculate the padding for header preceding aligned data. + +This procedure returns the padding, following the specified pointer `ptr` that +will be able to fit in a header of the size `header_size`, immediately +preceding the memory region, aligned on a boundary specified by `align`. See +the following diagram for a visual representation. + + header size + |<------>| + +---+--------+------------- - - - + | HEADER | DATA... + +---+--------+------------- - - - + ^ ^ + |<---------->| + | padding | + ptr aligned ptr + +The function takes in `ptr` and `header_size`, as well as the required +alignment for `DATA`. The return value of the function is the padding between +`ptr` and `aligned_ptr` that will be able to fit the header. +*/ @(require_results) calc_padding_with_header :: proc "contextless" (ptr: uintptr, align: uintptr, header_size: int) -> int { p, a := ptr, align modulo := p & (a-1) - padding := uintptr(0) if modulo != 0 { padding = a - modulo } - needed_space := uintptr(header_size) if padding < needed_space { needed_space -= padding - if needed_space & (a-1) > 0 { padding += align * (1+(needed_space/align)) } else { padding += align * (needed_space/align) } } - return int(padding) -} - - - -@(require_results, deprecated="prefer 'slice.clone'") -clone_slice :: proc(slice: $T/[]$E, allocator := context.allocator, loc := #caller_location) -> (new_slice: T) { - new_slice, _ = make(T, len(slice), allocator, loc) - runtime.copy(new_slice, slice) - return new_slice -} +} \ No newline at end of file diff --git a/core/mem/mutex_allocator.odin b/core/mem/mutex_allocator.odin new file mode 100644 index 000000000..7361016c3 --- /dev/null +++ b/core/mem/mutex_allocator.odin @@ -0,0 +1,52 @@ +#+build !freestanding, wasm32, wasm64p32 +package mem + +import "core:sync" + +/* +The data for mutex allocator. +*/ +Mutex_Allocator :: struct { + backing: Allocator, + mutex: sync.Mutex, +} + +/* +Initialize the mutex allocator. + +This procedure initializes the mutex allocator using `backin_allocator` as the +allocator that will be used to pass all allocation requests through. +*/ +mutex_allocator_init :: proc(m: ^Mutex_Allocator, backing_allocator: Allocator) { + m.backing = backing_allocator + m.mutex = {} +} + +/* +Mutex allocator. + +The mutex allocator is a wrapper for allocators that is used to serialize all +allocator requests across multiple threads. +*/ +@(require_results) +mutex_allocator :: proc(m: ^Mutex_Allocator) -> Allocator { + return Allocator{ + procedure = mutex_allocator_proc, + data = m, + } +} + +mutex_allocator_proc :: proc( + allocator_data: rawptr, + mode: Allocator_Mode, + size: int, + alignment: int, + old_memory: rawptr, + old_size: int, + loc := #caller_location, +) -> (result: []byte, err: Allocator_Error) { + m := (^Mutex_Allocator)(allocator_data) + sync.mutex_guard(&m.mutex) + return m.backing.procedure(m.backing.data, mode, size, alignment, old_memory, old_size, loc) +} + diff --git a/core/mem/raw.odin b/core/mem/raw.odin index 9a521598e..41c91555e 100644 --- a/core/mem/raw.odin +++ b/core/mem/raw.odin @@ -1,25 +1,102 @@ package mem -import "core:builtin" -import "core:runtime" +import "base:builtin" +import "base:runtime" -Raw_Any :: runtime.Raw_Any -Raw_String :: runtime.Raw_String -Raw_Cstring :: runtime.Raw_Cstring -Raw_Slice :: runtime.Raw_Slice +/* +Memory layout of the `any` type. +*/ +Raw_Any :: runtime.Raw_Any + +/* +Memory layout of the `string` type. +*/ +Raw_String :: runtime.Raw_String + +/* +Memory layout of the `cstring` type. +*/ +Raw_Cstring :: runtime.Raw_Cstring + +/* +Memory layout of `[]T` types. +*/ +Raw_Slice :: runtime.Raw_Slice + +/* +Memory layout of `[dynamic]T` types. +*/ 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} +/* +Memory layout of `map[K]V` types. +*/ +Raw_Map :: runtime.Raw_Map +/* +Memory layout of `#soa []T` types. +*/ +Raw_Soa_Pointer :: runtime.Raw_Soa_Pointer + +/* +Memory layout of the `complex32` type. +*/ +Raw_Complex32 :: runtime.Raw_Complex32 + +/* +Memory layout of the `complex64` type. +*/ +Raw_Complex64 :: runtime.Raw_Complex64 + +/* +Memory layout of the `complex128` type. +*/ +Raw_Complex128 :: runtime.Raw_Complex128 + +/* +Memory layout of the `quaternion64` type. +*/ +Raw_Quaternion64 :: runtime.Raw_Quaternion64 + +/* +Memory layout of the `quaternion128` type. +*/ +Raw_Quaternion128 :: runtime.Raw_Quaternion128 + +/* +Memory layout of the `quaternion256` type. +*/ +Raw_Quaternion256 :: runtime.Raw_Quaternion256 + +/* +Memory layout of the `quaternion64` type. +*/ +Raw_Quaternion64_Vector_Scalar :: runtime.Raw_Quaternion64_Vector_Scalar + +/* +Memory layout of the `quaternion128` type. +*/ +Raw_Quaternion128_Vector_Scalar :: runtime.Raw_Quaternion128_Vector_Scalar + +/* +Memory layout of the `quaternion256` type. +*/ +Raw_Quaternion256_Vector_Scalar :: runtime.Raw_Quaternion256_Vector_Scalar + +/* +Create a value of the any type. + +This procedure creates a value with type `any` that points to an object with +typeid `id` located at an address specified by `data`. +*/ make_any :: proc "contextless" (data: rawptr, id: typeid) -> any { return transmute(any)Raw_Any{data, id} } +/* +Obtain pointer to the data. + +This procedure returns the pointer to the data of a slice, string, or a dynamic +array. +*/ raw_data :: builtin.raw_data diff --git a/core/mem/rollback_stack_allocator.odin b/core/mem/rollback_stack_allocator.odin new file mode 100644 index 000000000..43ef10fe9 --- /dev/null +++ b/core/mem/rollback_stack_allocator.odin @@ -0,0 +1,480 @@ +package mem + +import "base:runtime" + +/* +Rollback stack default block size. +*/ +ROLLBACK_STACK_DEFAULT_BLOCK_SIZE :: 4 * Megabyte + +/* +Rollback stack max head block size. + +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 + +/* +Allocation header of the rollback stack allocator. +*/ +Rollback_Stack_Header :: bit_field u64 { + prev_offset: uintptr | 32, + is_free: bool | 1, + prev_ptr: uintptr | 31, +} + +/* +Block header of the rollback stack allocator. +*/ +Rollback_Stack_Block :: struct { + next_block: ^Rollback_Stack_Block, + last_alloc: rawptr, + offset: uintptr, + buffer: []byte, +} + +/* +Rollback stack allocator data. +*/ +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):]) + } +} + +/* +Free memory to a rollback stack allocator. +*/ +@(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 +} + +/* +Free all memory owned by the rollback stack allocator. +*/ +@(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 +} + +/* +Allocate memory using the rollback stack allocator. +*/ +@(require_results) +rb_alloc :: proc( + stack: ^Rollback_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := rb_alloc_bytes_non_zeroed(stack, size, alignment, loc) + if bytes != nil { + zero_slice(bytes) + } + return raw_data(bytes), err +} + +/* +Allocate memory using the rollback stack allocator. +*/ +@(require_results) +rb_alloc_bytes :: proc( + stack: ^Rollback_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]byte, Allocator_Error) { + bytes, err := rb_alloc_bytes_non_zeroed(stack, size, alignment, loc) + if bytes != nil { + zero_slice(bytes) + } + return bytes, err +} + +/* +Allocate non-initialized memory using the rollback stack allocator. +*/ +@(require_results) +rb_alloc_non_zeroed :: proc( + stack: ^Rollback_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := rb_alloc_bytes_non_zeroed(stack, size, alignment, loc) + return raw_data(bytes), err +} + +/* +Allocate non-initialized memory using the rollback stack allocator. +*/ +@(require_results) +rb_alloc_bytes_non_zeroed :: proc( + stack: ^Rollback_Stack, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (result: []byte, err: Allocator_Error) { + assert(size >= 0, "Size must be positive or zero.", loc) + assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", loc) + 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 +} + +/* +Resize an allocation owned by rollback stack allocator. +*/ +@(require_results) +rb_resize :: proc( + stack: ^Rollback_Stack, + old_ptr: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := rb_resize_bytes_non_zeroed(stack, byte_slice(old_ptr, old_size), size, alignment, loc) + if bytes != nil { + if old_ptr == nil { + zero_slice(bytes) + } else if size > old_size { + zero_slice(bytes[old_size:]) + } + } + return raw_data(bytes), err +} + +/* +Resize an allocation owned by rollback stack allocator. +*/ +@(require_results) +rb_resize_bytes :: proc( + stack: ^Rollback_Stack, + old_memory: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> ([]u8, Allocator_Error) { + bytes, err := rb_resize_bytes_non_zeroed(stack, old_memory, size, alignment, loc) + if bytes != nil { + if old_memory == nil { + zero_slice(bytes) + } else if size > len(old_memory) { + zero_slice(bytes[len(old_memory):]) + } + } + return bytes, err +} + +/* +Resize an allocation owned by rollback stack allocator without explicit +zero-initialization. +*/ +@(require_results) +rb_resize_non_zeroed :: proc( + stack: ^Rollback_Stack, + old_ptr: rawptr, + old_size: int, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (rawptr, Allocator_Error) { + bytes, err := rb_resize_bytes_non_zeroed(stack, byte_slice(old_ptr, old_size), size, alignment, loc) + return raw_data(bytes), err +} + +/* +Resize an allocation owned by rollback stack allocator without explicit +zero-initialization. +*/ +@(require_results) +rb_resize_bytes_non_zeroed :: proc( + stack: ^Rollback_Stack, + old_memory: []byte, + size: int, + alignment := DEFAULT_ALIGNMENT, + loc := #caller_location, +) -> (result: []byte, err: Allocator_Error) { + old_size := len(old_memory) + ptr := raw_data(old_memory) + assert(size >= 0, "Size must be positive or zero.", loc) + assert(old_size >= 0, "Old size must be positive or zero.", loc) + assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", loc) + 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 (ptr)[:size], nil + } + } + } + result = rb_alloc_bytes_non_zeroed(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_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 +} + +/* +Initialize the rollback stack allocator using a fixed backing buffer. +*/ +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) +} + +/* +Initialize the rollback stack alocator using a backing block allocator. +*/ +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 +} + +/* +Initialize the rollback stack. +*/ +rollback_stack_init :: proc { + rollback_stack_init_buffered, + rollback_stack_init_dynamic, +} + +/* +Destroy a rollback stack. +*/ +rollback_stack_destroy :: proc(stack: ^Rollback_Stack) { + if stack.block_allocator.procedure != nil { + rb_free_all(stack) + free(stack.head, stack.block_allocator) + } + stack^ = {} +} + +/* +Rollback stack allocator. + +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. +*/ +@(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, + loc := #caller_location, +) -> (result: []byte, err: Allocator_Error) { + stack := cast(^Rollback_Stack)allocator_data + switch mode { + case .Alloc: + return rb_alloc_bytes(stack, size, alignment, loc) + case .Alloc_Non_Zeroed: + return rb_alloc_bytes_non_zeroed(stack, size, alignment, loc) + case .Free: + return nil, rb_free(stack, old_memory) + case .Free_All: + rb_free_all(stack) + return nil, nil + case .Resize: + return rb_resize_bytes(stack, byte_slice(old_memory, old_size), size, alignment, loc) + case .Resize_Non_Zeroed: + return rb_resize_bytes_non_zeroed(stack, byte_slice(old_memory, old_size), size, alignment, loc) + 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 new file mode 100644 index 000000000..cf780de3f --- /dev/null +++ b/core/mem/tracking_allocator.odin @@ -0,0 +1,258 @@ +#+build !freestanding, wasm32, wasm64p32 +package mem + +import "base:runtime" +import "core:sync" + +/* +Allocation entry for the tracking allocator. + +This structure stores the data related to an allocation. +*/ +Tracking_Allocator_Entry :: struct { + // Pointer to an allocated region. + memory: rawptr, + // Size of the allocated memory region. + size: int, + // Requested alignment. + alignment: int, + // Mode of the operation. + mode: Allocator_Mode, + // Error. + err: Allocator_Error, + // Location of the allocation. + location: runtime.Source_Code_Location, +} + +/* +Bad free entry for a tracking allocator. +*/ +Tracking_Allocator_Bad_Free_Entry :: struct { + // Pointer, on which free operation was called. + memory: rawptr, + // The source location of where the operation was called. + location: runtime.Source_Code_Location, +} + +/* +Tracking allocator data. +*/ +Tracking_Allocator :: struct { + backing: Allocator, + allocation_map: map[rawptr]Tracking_Allocator_Entry, + 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, +} + +/* +Initialize the tracking allocator. + +This procedure initializes the tracking allocator `t` with a backing allocator +specified with `backing_allocator`. The `internals_allocator` will used to +allocate the tracked data. +*/ +tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) { + t.backing = backing_allocator + t.allocation_map.allocator = internals_allocator + t.bad_free_array.allocator = internals_allocator + if .Free_All in query_features(t.backing) { + t.clear_on_free_all = true + } +} + +/* +Destroy the tracking allocator. +*/ +tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) { + delete(t.allocation_map) + delete(t.bad_free_array) +} + +/* +Clear the tracking allocator. + +This procedure clears the tracked data from a tracking allocator. + +**Note**: This procedure clears 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 the tracking allocator. + +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) +} + +/* +Tracking allocator. + +The tracking allocator is an allocator wrapper that tracks memory allocations. +This allocator stores all the allocations in a map. Whenever a pointer that's +not inside of the map is freed, the `bad_free_array` entry is added. + +An example of how to use the `Tracking_Allocator` to track subsequent allocations +in your program and report leaks and bad frees: + +Example: + + package foo + + import "core:mem" + import "core:fmt" + + main :: proc() { + track: mem.Tracking_Allocator + mem.tracking_allocator_init(&track, context.allocator) + defer mem.tracking_allocator_destroy(&track) + context.allocator = mem.tracking_allocator(&track) + + do_stuff() + + for _, leak in track.allocation_map { + fmt.printf("%v leaked %m\n", leak.location, leak.size) + } + for bad_free in track.bad_free_array { + fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory) + } + } +*/ +@(require_results) +tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator { + return Allocator{ + data = data, + procedure = tracking_allocator_proc, + } +} + +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) + + if mode == .Query_Info { + info := (^Allocator_Query_Info)(old_memory) + if info != nil && info.pointer != nil { + if entry, ok := data.allocation_map[info.pointer]; ok { + info.size = entry.size + info.alignment = entry.alignment + } + info.pointer = nil + } + + return + } + + if mode == .Free && old_memory != nil && old_memory not_in data.allocation_map { + append(&data.bad_free_array, Tracking_Allocator_Bad_Free_Entry{ + memory = old_memory, + location = loc, + }) + } else { + result = data.backing.procedure(data.backing.data, mode, size, alignment, old_memory, old_size, loc) or_return + } + result_ptr := raw_data(result) + + if data.allocation_map.allocator.procedure == nil { + data.allocation_map.allocator = context.allocator + } + + switch mode { + case .Alloc, .Alloc_Non_Zeroed: + data.allocation_map[result_ptr] = Tracking_Allocator_Entry{ + memory = result_ptr, + size = size, + mode = mode, + alignment = alignment, + 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) + } + data.allocation_map[result_ptr] = Tracking_Allocator_Entry{ + memory = result_ptr, + size = size, + mode = mode, + alignment = alignment, + err = err, + location = loc, + } + track_alloc(data, &data.allocation_map[result_ptr]) + + case .Query_Features: + set := (^Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Query_Features, .Query_Info} + } + return nil, nil + + case .Query_Info: + unreachable() + } + + return +} + diff --git a/core/mem/virtual/arena.odin b/core/mem/virtual/arena.odin index d15df46ad..4a0fff241 100644 --- a/core/mem/virtual/arena.odin +++ b/core/mem/virtual/arena.odin @@ -17,18 +17,23 @@ Arena_Kind :: enum uint { Buffer: A single `Memory_Block` created from a user provided []byte. */ Arena :: struct { - kind: Arena_Kind, - curr_block: ^Memory_Block, - total_used: uint, - total_reserved: uint, - minimum_block_size: uint, - temp_count: uint, - mutex: sync.Mutex, + kind: Arena_Kind, + curr_block: ^Memory_Block, + + total_used: uint, + total_reserved: uint, + + default_commit_size: uint, // commit size <= reservation size + minimum_block_size: uint, // block size == total reservation + + temp_count: uint, + mutex: sync.Mutex, } // 1 MiB should be enough to start with DEFAULT_ARENA_STATIC_COMMIT_SIZE :: mem.Megabyte +DEFAULT_ARENA_GROWING_COMMIT_SIZE :: 8*mem.Megabyte DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: DEFAULT_ARENA_STATIC_COMMIT_SIZE // 1 GiB on 64-bit systems, 128 MiB on 32-bit systems by default @@ -44,6 +49,10 @@ arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING arena.curr_block = memory_block_alloc(0, reserved, {}) or_return arena.total_used = 0 arena.total_reserved = arena.curr_block.reserved + + if arena.minimum_block_size == 0 { + arena.minimum_block_size = reserved + } return } @@ -51,7 +60,7 @@ arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING // Initialization of an `Arena` to be a `.Static` variant. // A static arena contains a single `Memory_Block` allocated with virtual memory. @(require_results) -arena_init_static :: proc(arena: ^Arena, reserved: uint, commit_size: uint = DEFAULT_ARENA_STATIC_COMMIT_SIZE) -> (err: Allocator_Error) { +arena_init_static :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_STATIC_RESERVE_SIZE, commit_size: uint = DEFAULT_ARENA_STATIC_COMMIT_SIZE) -> (err: Allocator_Error) { arena.kind = .Static arena.curr_block = memory_block_alloc(commit_size, reserved, {}) or_return arena.total_used = 0 @@ -98,22 +107,34 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l switch arena.kind { case .Growing: - if arena.curr_block == nil || (safe_add(arena.curr_block.used, size) or_else 0) > arena.curr_block.reserved { - size = mem.align_forward_uint(size, alignment) + needed := mem.align_forward_uint(size, alignment) + if arena.curr_block == nil || (safe_add(arena.curr_block.used, needed) or_else 0) > arena.curr_block.reserved { if arena.minimum_block_size == 0 { arena.minimum_block_size = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE + arena.minimum_block_size = mem.align_forward_uint(arena.minimum_block_size, DEFAULT_PAGE_SIZE) + } + if arena.default_commit_size == 0 { + arena.default_commit_size = min(DEFAULT_ARENA_GROWING_COMMIT_SIZE, arena.minimum_block_size) + arena.default_commit_size = mem.align_forward_uint(arena.default_commit_size, DEFAULT_PAGE_SIZE) } - block_size := max(size, arena.minimum_block_size) + if arena.default_commit_size != 0 { + arena.default_commit_size, arena.minimum_block_size = + min(arena.default_commit_size, arena.minimum_block_size), + max(arena.default_commit_size, arena.minimum_block_size) + } - new_block := memory_block_alloc(size, block_size, {}) or_return + needed = max(needed, arena.default_commit_size) + block_size := max(needed, arena.minimum_block_size) + + new_block := memory_block_alloc(needed, block_size, alignment, {}) or_return new_block.prev = arena.curr_block arena.curr_block = new_block arena.total_reserved += new_block.reserved } prev_used := arena.curr_block.used - data, err = alloc_from_memory_block(arena.curr_block, size, alignment) + data, err = alloc_from_memory_block(arena.curr_block, size, alignment, default_commit_size=arena.default_commit_size) arena.total_used += arena.curr_block.used - prev_used case .Static: if arena.curr_block == nil { @@ -122,12 +143,17 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l } arena_init_static(arena, reserved=arena.minimum_block_size, commit_size=DEFAULT_ARENA_STATIC_COMMIT_SIZE) or_return } - fallthrough + if arena.curr_block == nil { + return nil, .Out_Of_Memory + } + data, err = alloc_from_memory_block(arena.curr_block, size, alignment, default_commit_size=arena.default_commit_size) + arena.total_used = arena.curr_block.used + case .Buffer: if arena.curr_block == nil { return nil, .Out_Of_Memory } - data, err = alloc_from_memory_block(arena.curr_block, size, alignment) + data, err = alloc_from_memory_block(arena.curr_block, size, alignment, default_commit_size=0) arena.total_used = arena.curr_block.used } return @@ -143,8 +169,8 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) prev_pos := arena.curr_block.used arena.curr_block.used = clamp(pos, 0, arena.curr_block.reserved) - if prev_pos < pos { - mem.zero_slice(arena.curr_block.base[arena.curr_block.used:][:pos-prev_pos]) + if prev_pos > pos { + mem.zero_slice(arena.curr_block.base[arena.curr_block.used:][:prev_pos-pos]) } arena.total_used = arena.curr_block.used return true @@ -178,8 +204,9 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) { } // Zero the first block's memory if arena.curr_block != nil { - mem.zero(arena.curr_block.base, int(arena.curr_block.used)) + curr_block_used := int(arena.curr_block.used) arena.curr_block.used = 0 + mem.zero(arena.curr_block.base, curr_block_used) } arena.total_used = 0 case .Static, .Buffer: @@ -288,7 +315,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, err = .Mode_Not_Implemented case .Free_All: arena_free_all(arena, location) - case .Resize: + case .Resize, .Resize_Non_Zeroed: old_data := ([^]byte)(old_memory) switch { diff --git a/core/mem/virtual/arena_util.odin b/core/mem/virtual/arena_util.odin new file mode 100644 index 000000000..4e28c17d6 --- /dev/null +++ b/core/mem/virtual/arena_util.odin @@ -0,0 +1,67 @@ +package mem_virtual + +import "base:runtime" +_ :: runtime + +// The `new` procedure allocates memory for a type `T` from a `virtual.Arena`. The second argument is a type, +// not a value, and the value return is a pointer to a newly allocated value of that type using the specified allocator. +@(require_results) +new :: proc(arena: ^Arena, $T: typeid, loc := #caller_location) -> (ptr: ^T, err: Allocator_Error) { + return new_aligned(arena, T, align_of(T), loc) +} + +// The `new_aligned` procedure allocates memory for a type `T` from a `virtual.Arena` with a specified `alignment`. +// The second argument is a type, not a value, and the value return is a pointer to a newly allocated value of +// that type using the specified allocator. +@(require_results) +new_aligned :: proc(arena: ^Arena, $T: typeid, alignment: uint, loc := #caller_location) -> (ptr: ^T, err: Allocator_Error) { + data := arena_alloc(arena, size_of(T), alignment, loc) or_return + ptr = (^T)(raw_data(data)) + return +} + +// `make_slice` allocates and initializes a slice. Like `new`, the second argument is a type, not a value. +// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. +// +// Note: Prefer using the procedure group `make`. +@(require_results) +make_slice :: proc(arena: ^Arena, $T: typeid/[]$E, #any_int len: int, loc := #caller_location) -> (T, Allocator_Error) { + return make_aligned(arena, T, len, align_of(E), loc) +} + +// `make_aligned` allocates and initializes a slice. Like `new`, the second argument is a type, not a value. +// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. +// +// Note: Prefer using the procedure group `make`. +@(require_results) +make_aligned :: proc(arena: ^Arena, $T: typeid/[]$E, #any_int len: int, alignment: uint, loc := #caller_location) -> (T, Allocator_Error) { + runtime.make_slice_error_loc(loc, len) + data, err := arena_alloc(arena, size_of(E)*uint(len), alignment, loc) + if data == nil && size_of(E) != 0 { + return nil, err + } + s := ([^]E)(raw_data(data))[:len] + return T(s), err +} + + +// `make_multi_pointer` allocates and initializes a dynamic array. Like `new`, the second argument is a type, not a value. +// Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. +// +// This is "similar" to doing `raw_data(make([]E, len, allocator))`. +// +// Note: Prefer using the procedure group `make`. +@(require_results) +make_multi_pointer :: proc(arena: ^Arena, $T: typeid/[^]$E, #any_int len: int, loc := #caller_location) -> (T, Allocator_Error) { + runtime.make_slice_error_loc(loc, len) + data, err := arena_alloc(arena, size_of(E)*uint(len), align_of(E), loc) + if data == nil && size_of(E) != 0 { + return nil, err + } + return (T)(raw_data(data)), err +} + +make :: proc{ + make_slice, + make_multi_pointer, +} \ No newline at end of file diff --git a/core/mem/virtual/file.odin b/core/mem/virtual/file.odin new file mode 100644 index 000000000..2f852b40c --- /dev/null +++ b/core/mem/virtual/file.odin @@ -0,0 +1,47 @@ +package mem_virtual + +import "core:os" + +Map_File_Error :: enum { + None, + Open_Failure, + Stat_Failure, + Negative_Size, + Too_Large_Size, + Map_Failure, +} + +Map_File_Flag :: enum u32 { + Read, + Write, +} +Map_File_Flags :: distinct bit_set[Map_File_Flag; u32] + +map_file :: proc{ + map_file_from_path, + map_file_from_file_descriptor, +} + +map_file_from_path :: proc(filename: string, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { + fd, err := os.open(filename, os.O_RDWR) + if err != nil { + return nil, .Open_Failure + } + defer os.close(fd) + + return map_file_from_file_descriptor(uintptr(fd), flags) +} + +map_file_from_file_descriptor :: proc(fd: uintptr, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { + size, os_err := os.file_size(os.Handle(fd)) + if os_err != nil { + return nil, .Stat_Failure + } + if size < 0 { + return nil, .Negative_Size + } + if size != i64(int(size)) { + return nil, .Too_Large_Size + } + return _map_file(fd, size, flags) +} diff --git a/core/mem/virtual/virtual.odin b/core/mem/virtual/virtual.odin index 1624fae9d..4afc33813 100644 --- a/core/mem/virtual/virtual.odin +++ b/core/mem/virtual/virtual.odin @@ -1,12 +1,17 @@ package mem_virtual import "core:mem" -import "core:intrinsics" -import "core:runtime" +import "base:intrinsics" +import "base:runtime" _ :: runtime DEFAULT_PAGE_SIZE := uint(4096) +@(init, private) +platform_memory_init :: proc() { + _platform_memory_init() +} + Allocator_Error :: mem.Allocator_Error @(require_results) @@ -68,7 +73,7 @@ align_formula :: #force_inline proc "contextless" (size, align: uint) -> uint { } @(require_results) -memory_block_alloc :: proc(committed, reserved: uint, flags: Memory_Block_Flags) -> (block: ^Memory_Block, err: Allocator_Error) { +memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags: Memory_Block_Flags = {}) -> (block: ^Memory_Block, err: Allocator_Error) { page_size := DEFAULT_PAGE_SIZE assert(mem.is_power_of_two(uintptr(page_size))) @@ -79,8 +84,8 @@ memory_block_alloc :: proc(committed, reserved: uint, flags: Memory_Block_Flags) reserved = align_formula(reserved, page_size) committed = clamp(committed, 0, reserved) - total_size := uint(reserved + size_of(Platform_Memory_Block)) - base_offset := uintptr(size_of(Platform_Memory_Block)) + total_size := uint(reserved + max(alignment, size_of(Platform_Memory_Block))) + base_offset := uintptr(max(alignment, size_of(Platform_Memory_Block))) protect_offset := uintptr(0) do_protection := false @@ -112,7 +117,7 @@ memory_block_alloc :: proc(committed, reserved: uint, flags: Memory_Block_Flags) } @(require_results) -alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint) -> (data: []byte, err: Allocator_Error) { +alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint, default_commit_size: uint = 0) -> (data: []byte, err: Allocator_Error) { calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint { alignment_offset := uint(0) ptr := uintptr(block.base[block.used:]) @@ -123,7 +128,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint) return alignment_offset } - do_commit_if_necessary :: proc(block: ^Memory_Block, size: uint) -> (err: Allocator_Error) { + do_commit_if_necessary :: proc(block: ^Memory_Block, size: uint, default_commit_size: uint) -> (err: Allocator_Error) { if block.committed - block.used < size { pmblock := (^Platform_Memory_Block)(block) base_offset := uint(uintptr(pmblock.block.base) - uintptr(pmblock)) @@ -133,7 +138,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint) extra_size := max(size, block.committed>>1) platform_total_commit := base_offset + block.used + extra_size platform_total_commit = align_formula(platform_total_commit, DEFAULT_PAGE_SIZE) - platform_total_commit = min(platform_total_commit, pmblock.reserved) + platform_total_commit = min(max(platform_total_commit, default_commit_size), pmblock.reserved) assert(pmblock.committed <= pmblock.reserved) assert(pmblock.committed < platform_total_commit) @@ -163,7 +168,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint) return } assert(block.committed <= block.reserved) - do_commit_if_necessary(block, size) or_return + do_commit_if_necessary(block, size, default_commit_size) or_return data = block.base[block.used+alignment_offset:][:min_size] block.used += size @@ -183,4 +188,4 @@ memory_block_dealloc :: proc(block_to_free: ^Memory_Block) { safe_add :: #force_inline proc "contextless" (x, y: uint) -> (uint, bool) { z, did_overflow := intrinsics.overflow_add(x, y) return z, !did_overflow -} \ No newline at end of file +} diff --git a/core/mem/virtual/virtual_darwin.odin b/core/mem/virtual/virtual_darwin.odin deleted file mode 100644 index 5505149b0..000000000 --- a/core/mem/virtual/virtual_darwin.odin +++ /dev/null @@ -1,148 +0,0 @@ -//+build darwin -//+private -package mem_virtual - -foreign import libc "System.framework" -import "core:c" - -PROT_NONE :: 0x0 /* [MC2] no permissions */ -PROT_READ :: 0x1 /* [MC2] pages can be read */ -PROT_WRITE :: 0x2 /* [MC2] pages can be written */ -PROT_EXEC :: 0x4 /* [MC2] pages can be executed */ - -// Sharing options -MAP_SHARED :: 0x1 /* [MF|SHM] share changes */ -MAP_PRIVATE :: 0x2 /* [MF|SHM] changes are private */ - -// Other flags -MAP_FIXED :: 0x0010 /* [MF|SHM] interpret addr exactly */ -MAP_RENAME :: 0x0020 /* Sun: rename private pages to file */ -MAP_NORESERVE :: 0x0040 /* Sun: don't reserve needed swap area */ -MAP_RESERVED0080 :: 0x0080 /* previously unimplemented MAP_INHERIT */ -MAP_NOEXTEND :: 0x0100 /* for MAP_FILE, don't change file size */ -MAP_HASSEMAPHORE :: 0x0200 /* region may contain semaphores */ -MAP_NOCACHE :: 0x0400 /* don't cache pages for this mapping */ -MAP_JIT :: 0x0800 /* Allocate a region that will be used for JIT purposes */ - -// Mapping type -MAP_FILE :: 0x0000 /* map from file (default) */ -MAP_ANONYMOUS :: 0x1000 /* allocated from memory, swap space */ - - -/* - * The MAP_RESILIENT_* flags can be used when the caller wants to map some - * possibly unreliable memory and be able to access it safely, possibly - * getting the wrong contents rather than raising any exception. - * For safety reasons, such mappings have to be read-only (PROT_READ access - * only). - * - * MAP_RESILIENT_CODESIGN: - * accessing this mapping will not generate code-signing violations, - * even if the contents are tainted. - * MAP_RESILIENT_MEDIA: - * accessing this mapping will not generate an exception if the contents - * are not available (unreachable removable or remote media, access beyond - * end-of-file, ...). Missing contents will be replaced with zeroes. - */ -MAP_RESILIENT_CODESIGN :: 0x2000 /* no code-signing failures */ -MAP_RESILIENT_MEDIA :: 0x4000 /* no backing-store failures */ - -MAP_32BIT :: 0x8000 /* Return virtual addresses <4G only */ - -// Flags used to support translated processes. -MAP_TRANSLATED_ALLOW_EXECUTE :: 0x20000 /* allow execute in translated processes */ -MAP_UNIX03 :: 0x40000 /* UNIX03 compliance */ - -// Process memory locking -MCL_CURRENT :: 0x0001 /* [ML] Lock only current memory */ -MCL_FUTURE :: 0x0002 /* [ML] Lock all future memory as well */ - -MADV_NORMAL :: 0 /* [MC1] no further special treatment */ -MADV_RANDOM :: 1 /* [MC1] expect random page refs */ -MADV_SEQUENTIAL :: 2 /* [MC1] expect sequential page refs */ -MADV_WILLNEED :: 3 /* [MC1] will need these pages */ -MADV_DONTNEED :: 4 /* [MC1] dont need these pages */ -MADV_FREE :: 5 /* pages unneeded, discard contents */ -MADV_ZERO_WIRED_PAGES :: 6 /* zero the wired pages that have not been unwired before the entry is deleted */ -MADV_FREE_REUSABLE :: 7 /* pages can be reused (by anyone) */ -MADV_FREE_REUSE :: 8 /* caller wants to reuse those pages */ -MADV_CAN_REUSE :: 9 -MADV_PAGEOUT :: 10 /* page out now (internal only) */ - -// msync() flags -MS_ASYNC :: 0x0001 /* [MF|SIO] return immediately */ -MS_INVALIDATE :: 0x0002 /* [MF|SIO] invalidate all cached data */ -MS_SYNC :: 0x0010 /* [MF|SIO] msync synchronously */ -MS_KILLPAGES :: 0x0004 /* invalidate pages, leave mapped */ -MS_DEACTIVATE :: 0x0008 /* deactivate pages, leave mapped */ - -// Return bits from mincore -MINCORE_INCORE :: 0x1 /* Page is incore */ -MINCORE_REFERENCED :: 0x2 /* Page has been referenced by us */ -MINCORE_MODIFIED :: 0x4 /* Page has been modified by us */ -MINCORE_REFERENCED_OTHER :: 0x8 /* Page has been referenced */ -MINCORE_MODIFIED_OTHER :: 0x10 /* Page has been modified */ -MINCORE_PAGED_OUT :: 0x20 /* Page has been paged out */ -MINCORE_COPIED :: 0x40 /* Page has been copied */ -MINCORE_ANONYMOUS :: 0x80 /* Page belongs to an anonymous object */ - -// Allocation failure result -MAP_FAILED : rawptr = rawptr(~uintptr(0)) - -foreign libc { - @(link_name="mlockall") _mlockall :: proc(flags: c.int) -> c.int --- - @(link_name="munlockall") _munlockall :: proc() -> c.int --- - @(link_name="mlock") _mlock :: proc(addr: rawptr, len: c.size_t) -> c.int --- - @(link_name="mmap") _mmap :: proc(addr: rawptr, len: c.size_t, prot: c.int, flags: c.int, fd: c.int, offset: int) -> rawptr --- - @(link_name="mprotect") _mprotect :: proc(addr: rawptr, len: c.size_t, prot: c.int) -> c.int --- - @(link_name="msync") _msync :: proc(addr: rawptr, len: c.size_t) -> c.int --- - @(link_name="munlock") _munlock :: proc(addr: rawptr, len: c.size_t) -> c.int --- - @(link_name="munmap") _munmap :: proc(addr: rawptr, len: c.size_t) -> c.int --- - @(link_name="shm_open") _shm_open :: proc(name: cstring, oflag: c.int, #c_vararg args: ..any) -> c.int --- - @(link_name="shm_unlink") _shm_unlink :: proc(name: cstring) -> c.int --- - @(link_name="posix_madvise") _posix_madvise :: proc(addr: rawptr, len: c.size_t, advice: c.int) -> c.int --- - @(link_name="madvise") _madvise :: proc(addr: rawptr, len: c.size_t, advice: c.int) -> c.int --- - @(link_name="mincore") _mincore :: proc(addr: rawptr, len: c.size_t, vec: cstring) -> c.int --- - @(link_name="minherit") _minherit :: proc(addr: rawptr, len: c.size_t, inherit: c.int) -> c.int --- -} - - -_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { - result := _mmap(nil, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) - if result == MAP_FAILED { - return nil, .Out_Of_Memory - } - return ([^]byte)(uintptr(result))[:size], nil -} - -_commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error { - result := _mprotect(data, size, PROT_READ|PROT_WRITE) - if result != 0 { - return .Out_Of_Memory - } - return nil -} -_decommit :: proc "contextless" (data: rawptr, size: uint) { - _mprotect(data, size, PROT_NONE) - _madvise(data, size, MADV_FREE) -} -_release :: proc "contextless" (data: rawptr, size: uint) { - _munmap(data, size) -} -_protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool { - pflags: c.int - pflags = PROT_NONE - if .Read in flags { pflags |= PROT_READ } - if .Write in flags { pflags |= PROT_WRITE } - if .Execute in flags { pflags |= PROT_EXEC } - err := _mprotect(data, size, pflags) - return err != 0 -} - - -_platform_memory_init :: proc() { - DEFAULT_PAGE_SIZE = 4096 - - // is power of two - assert(DEFAULT_PAGE_SIZE != 0 && (DEFAULT_PAGE_SIZE & (DEFAULT_PAGE_SIZE-1)) == 0) -} diff --git a/core/mem/virtual/virtual_linux.odin b/core/mem/virtual/virtual_linux.odin index 7ffc7643e..3e0d7668b 100644 --- a/core/mem/virtual/virtual_linux.odin +++ b/core/mem/virtual/virtual_linux.odin @@ -1,5 +1,5 @@ -//+build linux -//+private +#+build linux +#+private package mem_virtual import "core:sys/linux" @@ -36,11 +36,11 @@ _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 + return errno == .NONE } _platform_memory_init :: proc() { @@ -48,3 +48,21 @@ _platform_memory_init :: proc() { // is power of two assert(DEFAULT_PAGE_SIZE != 0 && (DEFAULT_PAGE_SIZE & (DEFAULT_PAGE_SIZE-1)) == 0) } + + +_map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { + prot: linux.Mem_Protection + if .Read in flags { + prot += {.READ} + } + if .Write in flags { + prot += {.WRITE} + } + + flags := linux.Map_Flags{.SHARED} + addr, errno := linux.mmap(0, uint(size), prot, flags, linux.Fd(fd), offset=0) + if addr == nil || errno != nil { + return nil, .Map_Failure + } + return ([^]byte)(addr)[:size], nil +} diff --git a/core/mem/virtual/virtual_bsd.odin b/core/mem/virtual/virtual_other.odin similarity index 65% rename from core/mem/virtual/virtual_bsd.odin rename to core/mem/virtual/virtual_other.odin index 103e48074..a57856975 100644 --- a/core/mem/virtual/virtual_bsd.odin +++ b/core/mem/virtual/virtual_other.odin @@ -1,9 +1,12 @@ -//+build freebsd, openbsd -//+private +#+private +#+build !darwin +#+build !freebsd +#+build !openbsd +#+build !netbsd +#+build !linux +#+build !windows package mem_virtual - - _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { return nil, nil } @@ -11,14 +14,20 @@ _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) { + return nil, .Map_Failure } diff --git a/core/mem/virtual/virtual_platform.odin b/core/mem/virtual/virtual_platform.odin index c2b505cd2..54c42ce4b 100644 --- a/core/mem/virtual/virtual_platform.odin +++ b/core/mem/virtual/virtual_platform.odin @@ -1,4 +1,4 @@ -//+private +#+private package mem_virtual Platform_Memory_Block :: struct { diff --git a/core/mem/virtual/virtual_posix.odin b/core/mem/virtual/virtual_posix.odin new file mode 100644 index 000000000..c3d6a9095 --- /dev/null +++ b/core/mem/virtual/virtual_posix.odin @@ -0,0 +1,67 @@ +#+build darwin, netbsd, freebsd, openbsd +#+private +package mem_virtual + +import "core:sys/posix" + +// Define non-posix needed flags: +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD { + MADV_FREE :: 5 /* pages unneeded, discard contents */ +} else when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { + MADV_FREE :: 6 +} + +_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { + flags := posix.Map_Flags{ .ANONYMOUS, .PRIVATE } + result := posix.mmap(nil, size, {}, flags) + if result == posix.MAP_FAILED { + return nil, .Out_Of_Memory + } + + return ([^]byte)(uintptr(result))[:size], nil +} + +_commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error { + if posix.mprotect(data, size, { .READ, .WRITE }) != .OK { + return .Out_Of_Memory + } + + return nil +} + +_decommit :: proc "contextless" (data: rawptr, size: uint) { + posix.mprotect(data, size, {}) + posix.posix_madvise(data, size, transmute(posix.MAdvice)i32(MADV_FREE)) +} + +_release :: proc "contextless" (data: rawptr, size: uint) { + posix.munmap(data, size) +} + +_protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool { + #assert(i32(posix.Prot_Flag_Bits.READ) == i32(Protect_Flag.Read)) + #assert(i32(posix.Prot_Flag_Bits.WRITE) == i32(Protect_Flag.Write)) + #assert(i32(posix.Prot_Flag_Bits.EXEC) == i32(Protect_Flag.Execute)) + + return posix.mprotect(data, size, transmute(posix.Prot_Flags)flags) == .OK +} + +_platform_memory_init :: proc() { + // NOTE: `posix.PAGESIZE` due to legacy reasons could be wrong so we use `sysconf`. + size := posix.sysconf(._PAGESIZE) + DEFAULT_PAGE_SIZE = uint(max(size, posix.PAGESIZE)) + + // is power of two + assert(DEFAULT_PAGE_SIZE != 0 && (DEFAULT_PAGE_SIZE & (DEFAULT_PAGE_SIZE-1)) == 0) +} + +_map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { + #assert(i32(posix.Prot_Flag_Bits.READ) == i32(Map_File_Flag.Read)) + #assert(i32(posix.Prot_Flag_Bits.WRITE) == i32(Map_File_Flag.Write)) + + addr := posix.mmap(nil, uint(size), transmute(posix.Prot_Flags)flags, { .SHARED }, posix.FD(fd)) + if addr == posix.MAP_FAILED || addr == nil { + return nil, .Map_Failure + } + return ([^]byte)(addr)[:size], nil +} diff --git a/core/mem/virtual/virtual_windows.odin b/core/mem/virtual/virtual_windows.odin index bbd74a925..acd30ae33 100644 --- a/core/mem/virtual/virtual_windows.odin +++ b/core/mem/virtual/virtual_windows.odin @@ -1,5 +1,5 @@ -//+build windows -//+private +#+build windows +#+private package mem_virtual foreign import Kernel32 "system:Kernel32.lib" @@ -50,19 +50,39 @@ PAGE_WRITECOPY :: 0x08 PAGE_TARGETS_INVALID :: 0x40000000 PAGE_TARGETS_NO_UPDATE :: 0x40000000 +SECTION_MAP_WRITE :: 0x0002 +SECTION_MAP_READ :: 0x0004 +FILE_MAP_WRITE :: SECTION_MAP_WRITE +FILE_MAP_READ :: SECTION_MAP_READ + ERROR_INVALID_ADDRESS :: 487 ERROR_COMMITMENT_LIMIT :: 1455 -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign Kernel32 { GetSystemInfo :: proc(lpSystemInfo: LPSYSTEM_INFO) --- VirtualAlloc :: proc(lpAddress: rawptr, dwSize: uint, flAllocationType: u32, flProtect: u32) -> rawptr --- VirtualFree :: proc(lpAddress: rawptr, dwSize: uint, dwFreeType: u32) -> b32 --- VirtualProtect :: proc(lpAddress: rawptr, dwSize: uint, flNewProtect: u32, lpflOldProtect: ^u32) -> b32 --- GetLastError :: proc() -> u32 --- + + CreateFileMappingW :: proc( + hFile: rawptr, + lpFileMappingAttributes: rawptr, + flProtect: u32, + dwMaximumSizeHigh: u32, + dwMaximumSizeLow: u32, + lpName: [^]u16, + ) -> rawptr --- + + MapViewOfFile :: proc( + hFileMappingObject: rawptr, + dwDesiredAccess: u32, + dwFileOffsetHigh: u32, + dwFileOffsetLow: u32, + dwNumberOfBytesToMap: uint, + ) -> rawptr --- } - - _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { result := VirtualAlloc(nil, size, MEM_RESERVE, PAGE_READWRITE) if result == nil { @@ -125,3 +145,33 @@ _platform_memory_init :: proc() { // is power of two assert(DEFAULT_PAGE_SIZE != 0 && (DEFAULT_PAGE_SIZE & (DEFAULT_PAGE_SIZE-1)) == 0) } + + +_map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { + page_flags: u32 + if flags == {.Read} { + page_flags = PAGE_READONLY + } else if flags == {.Write} { + page_flags = PAGE_READWRITE + } else if flags == {.Read, .Write} { + page_flags = PAGE_READWRITE + } else { + page_flags = PAGE_NOACCESS + } + maximum_size := transmute([2]u32)size + handle := CreateFileMappingW(rawptr(fd), nil, page_flags, maximum_size[1], maximum_size[0], nil) + if handle == nil { + return nil, .Map_Failure + } + + desired_access: u32 + if .Read in flags { + desired_access |= FILE_MAP_READ + } + if .Write in flags { + desired_access |= FILE_MAP_WRITE + } + + file_data := MapViewOfFile(handle, desired_access, 0, 0, uint(size)) + return ([^]byte)(file_data)[:size], nil +} diff --git a/core/net/addr.odin b/core/net/addr.odin index f0c47e926..c47c6f55e 100644 --- a/core/net/addr.odin +++ b/core/net/addr.odin @@ -1,4 +1,4 @@ -// +build windows, linux, darwin +#+build windows, linux, darwin, freebsd package net /* @@ -10,12 +10,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ import "core:strconv" @@ -119,9 +121,9 @@ aton :: proc(address_and_maybe_port: string, family: Address_Family, allow_decim } a: [4]u8 = --- - for v, i in buf { + for v, j in buf { if v > 255 { return {}, false } - a[i] = u8(v) + a[j] = u8(v) } return IP4_Address(a), true @@ -370,7 +372,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 } /* @@ -462,7 +464,9 @@ split_port :: proc(endpoint_str: string) -> (addr_or_host: string, port: int, ok // Joins an address or hostname with a port. join_port :: proc(address_or_host: string, port: int, allocator := context.allocator) -> string { addr_or_host, _, ok := split_port(address_or_host) - if !ok do return addr_or_host + if !ok { + return addr_or_host + } b := strings.builder_make(allocator) @@ -520,10 +524,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 @@ -557,7 +560,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, ":") @@ -741,4 +744,4 @@ parse_ip_component :: proc(input: string, max_value := u64(max(u32)), bases := D get_network_interfaces :: proc() -> []Address { // TODO: Implement using `enumerate_interfaces` and returning only the addresses of active interfaces. return nil -} \ No newline at end of file +} diff --git a/core/net/common.odin b/core/net/common.odin index 70a027138..263fc770f 100644 --- a/core/net/common.odin +++ b/core/net/common.odin @@ -1,4 +1,4 @@ -// +build windows, linux, darwin +#+build windows, linux, darwin, freebsd package net /* @@ -13,15 +13,17 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ -import "core:runtime" +import "base:runtime" /* TUNEABLES - See also top of `dns.odin` for DNS configuration. @@ -70,6 +72,8 @@ Network_Error :: union #shared_nil { DNS_Error, } +#assert(size_of(Network_Error) == 8) + General_Error :: enum u32 { None = 0, Unable_To_Enumerate_Network_Interfaces = 1, @@ -78,7 +82,7 @@ General_Error :: enum u32 { // `Platform_Error` is used to wrap errors returned by the different platforms that don't fit a common error. Platform_Error :: enum u32 {} -Parse_Endpoint_Error :: enum { +Parse_Endpoint_Error :: enum u32 { None = 0, Bad_Port = 1, Bad_Address, @@ -137,8 +141,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{} @@ -413,4 +417,4 @@ DNS_Record_Header :: struct #packed { DNS_Host_Entry :: struct { name: string, addr: Address, -} \ No newline at end of file +} diff --git a/core/net/dns.odin b/core/net/dns.odin index c556450c6..ffb97fc5b 100644 --- a/core/net/dns.odin +++ b/core/net/dns.odin @@ -1,4 +1,4 @@ -// +build windows, linux, darwin +#+build windows, linux, darwin, freebsd package net /* @@ -10,12 +10,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ import "core:mem" @@ -30,7 +32,7 @@ when ODIN_OS == .Windows { resolv_conf = "", hosts_file = "%WINDIR%\\system32\\drivers\\etc\\hosts", } -} else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD { +} else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{ resolv_conf = "/etc/resolv.conf", hosts_file = "/etc/hosts", @@ -816,7 +818,6 @@ parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator dq_sz :: 4 hn_sz := skip_hostname(response, cur_idx) or_return - dns_query := mem.slice_data_cast([]u16be, response[cur_idx+hn_sz:cur_idx+hn_sz+dq_sz]) cur_idx += hn_sz + dq_sz } @@ -855,4 +856,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/dns_unix.odin b/core/net/dns_unix.odin index e9b7bd066..e4336e410 100644 --- a/core/net/dns_unix.odin +++ b/core/net/dns_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin +#+build linux, darwin, freebsd package net /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. @@ -9,12 +9,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ import "core:strings" diff --git a/core/net/dns_windows.odin b/core/net/dns_windows.odin index e54b067b6..2f3831767 100644 --- a/core/net/dns_windows.odin +++ b/core/net/dns_windows.odin @@ -1,4 +1,4 @@ -//+build windows +#+build windows package net /* @@ -10,12 +10,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ import "core:strings" @@ -85,11 +87,9 @@ _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator : append(&recs, record) case .CNAME: - - hostname := strings.clone(string(r.Data.CNAME)) record := DNS_Record_CNAME{ base = base_record, - host_name = hostname, + host_name = strings.clone(string(r.Data.CNAME)), } append(&recs, record) @@ -107,10 +107,9 @@ _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator : } case .NS: - hostname := strings.clone(string(r.Data.NS)) record := DNS_Record_NS{ base = base_record, - host_name = hostname, + host_name = strings.clone(string(r.Data.NS)), } append(&recs, record) @@ -129,33 +128,37 @@ _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator : append(&recs, record) case .SRV: - target := strings.clone(string(r.Data.SRV.pNameTarget)) // The target hostname/address that the service can be found on - priority := int(r.Data.SRV.wPriority) - weight := int(r.Data.SRV.wWeight) - port := int(r.Data.SRV.wPort) - // NOTE(tetra): Srv record name should be of the form '_servicename._protocol.hostname' // The record name is the name of the record. // Not to be confused with the _target_ of the record, which is--in combination with the port--what we're looking up // by making this request in the first place. - // NOTE(Jeroen): Service Name and Protocol Name can probably just be string slices into the record name. - // It's already cloned, after all. I wouldn't put them on the temp allocator like this. + service_name, protocol_name: string - parts := strings.split_n(base_record.record_name, ".", 3, context.temp_allocator) - if len(parts) != 3 { + s := base_record.record_name + i := strings.index_byte(s, '.') + if i > -1 { + service_name = s[:i] + s = s[len(service_name) + 1:] + } else { + continue + } + + i = strings.index_byte(s, '.') + if i > -1 { + protocol_name = s[:i] + } else { continue } - service_name, protocol_name := parts[0], parts[1] append(&recs, DNS_Record_SRV { base = base_record, - target = target, - port = port, + target = strings.clone(string(r.Data.SRV.pNameTarget)), // The target hostname/address that the service can be found on + port = int(r.Data.SRV.wPort), service_name = service_name, protocol_name = protocol_name, - priority = priority, - weight = weight, + priority = int(r.Data.SRV.wPriority), + weight = int(r.Data.SRV.wWeight), }) } diff --git a/core/net/doc.odin b/core/net/doc.odin index 0f1b33172..ed720c0ae 100644 --- a/core/net/doc.odin +++ b/core/net/doc.odin @@ -2,45 +2,45 @@ Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ /* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. +Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. +For other protocols and their features, see subdirectories of this package. - Features: - - Supports Windows, Linux and OSX. - - Opening and closing of TCP and UDP sockets. - - Sending to and receiving from these sockets. - - DNS name lookup, using either the OS or our own resolver. +Features: +- Supports Windows, Linux and OSX. +- Opening and closing of TCP and UDP sockets. +- Sending to and receiving from these sockets. +- DNS name lookup, using either the OS or our own resolver. - Planned: - - Nonblocking IO - - `Connection` struct - A "fat socket" struct that remembers how you opened it, etc, instead of just being a handle. - - IP Range structs, CIDR/class ranges, netmask calculator and associated helper procedures. - - Use `context.temp_allocator` instead of stack-based arenas? - And check it's the default temp allocator or can give us 4 MiB worth of memory - without punting to the main allocator by comparing their addresses in an @(init) procedure. - Panic if this assumption is not met. +Planned: +- Nonblocking IO +- `Connection` struct; A "fat socket" struct that remembers how you opened it, etc, instead of just being a handle. +- IP Range structs, CIDR/class ranges, netmask calculator and associated helper procedures. +- Use `context.temp_allocator` instead of stack-based arenas? +And check it's the default temp allocator or can give us 4 MiB worth of memory +without punting to the main allocator by comparing their addresses in an @(init) procedure. +Panic if this assumption is not met. +- Document assumptions about libc usage (or avoidance thereof) for each platform. - - Document assumptions about libc usage (or avoidance thereof) for each platform. +Assumptions: +For performance reasons this package relies on the `context.temp_allocator` in some places. - Assumptions: - - For performance reasons this package relies on the `context.temp_allocator` in some places. +You can replace the default `context.temp_allocator` with your own as long as it meets +this requirement: A minimum of 4 MiB of scratch space that's expected not to be freed. - You can replace the default `context.temp_allocator` with your own as long as it meets - this requirement: A minimum of 4 MiB of scratch space that's expected not to be freed. - - If this expectation is not met, the package's @(init) procedure will attempt to detect - this and panic to avoid temp allocations prematurely overwriting data and garbling results, - or worse. This means that should you replace the temp allocator with an insufficient one, - we'll do our best to loudly complain the first time you try it. +If this expectation is not met, the package's @(init) procedure will attempt to detect +this and panic to avoid temp allocations prematurely overwriting data and garbling results, +or worse. This means that should you replace the temp allocator with an insufficient one, +we'll do our best to loudly complain the first time you try it. */ -package net \ No newline at end of file +package net diff --git a/core/net/errors_darwin.odin b/core/net/errors_darwin.odin index c80d2cf56..2905b44bc 100644 --- a/core/net/errors_darwin.odin +++ b/core/net/errors_darwin.odin @@ -1,5 +1,5 @@ +#+build darwin package net -// +build darwin /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. @@ -10,12 +10,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ import "core:c" @@ -34,7 +36,7 @@ Create_Socket_Error :: enum c.int { Dial_Error :: enum c.int { None = 0, - Port_Required = -1, + Port_Required = -1, // Attempted to dial an endpointing without a port being set. Address_In_Use = c.int(os.EADDRINUSE), In_Progress = c.int(os.EINPROGRESS), @@ -54,7 +56,9 @@ Dial_Error :: enum c.int { } Bind_Error :: enum c.int { - None = 0, + None = 0, + Privileged_Port_Without_Root = -1, // Attempted to bind to a port less than 1024 without root access. + Address_In_Use = c.int(os.EADDRINUSE), // Another application is currently bound to this endpoint. Given_Nonlocal_Address = c.int(os.EADDRNOTAVAIL), // The address is not a local address on this machine. Broadcast_Disabled = c.int(os.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set. diff --git a/core/net/errors_freebsd.odin b/core/net/errors_freebsd.odin new file mode 100644 index 000000000..486732a95 --- /dev/null +++ b/core/net/errors_freebsd.odin @@ -0,0 +1,217 @@ +#+build freebsd +package net + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ + +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code +*/ + +import "core:c" +import "core:sys/freebsd" + +Create_Socket_Error :: enum c.int { + None = 0, + Access_Denied = cast(c.int)freebsd.Errno.EACCES, + Family_Not_Supported_For_This_Socket = cast(c.int)freebsd.Errno.EAFNOSUPPORT, + Full_Per_Process_Descriptor_Table = cast(c.int)freebsd.Errno.EMFILE, + Full_System_File_Table = cast(c.int)freebsd.Errno.ENFILE, + No_Buffer_Space_Available = cast(c.int)freebsd.Errno.ENOBUFS, + Insufficient_Permission = cast(c.int)freebsd.Errno.EPERM, + Protocol_Unsupported_In_Family = cast(c.int)freebsd.Errno.EPROTONOSUPPORT, + Socket_Type_Unsupported_By_Protocol = cast(c.int)freebsd.Errno.EPROTOTYPE, +} + +Dial_Error :: enum c.int { + None = 0, + Port_Required = -1, + Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, + Invalid_Namelen = cast(c.int)freebsd.Errno.EINVAL, + Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, + Address_Unavailable = cast(c.int)freebsd.Errno.EADDRNOTAVAIL, + Wrong_Family_For_Socket = cast(c.int)freebsd.Errno.EAFNOSUPPORT, + Already_Connected = cast(c.int)freebsd.Errno.EISCONN, + Timeout = cast(c.int)freebsd.Errno.ETIMEDOUT, + Refused_By_Remote_Host = cast(c.int)freebsd.Errno.ECONNREFUSED, + // `Refused` alias for `core:net` tests. + // The above default name `Refused_By_Remote_Host` is more explicit. + Refused = Refused_By_Remote_Host, + Reset_By_Remote_Host = cast(c.int)freebsd.Errno.ECONNRESET, + Network_Unreachable = cast(c.int)freebsd.Errno.ENETUNREACH, + Host_Unreachable = cast(c.int)freebsd.Errno.EHOSTUNREACH, + Address_In_Use = cast(c.int)freebsd.Errno.EADDRINUSE, + Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT, + In_Progress = cast(c.int)freebsd.Errno.EINPROGRESS, + Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR, + Previous_Attempt_Incomplete = cast(c.int)freebsd.Errno.EALREADY, + Broadcast_Unavailable = cast(c.int)freebsd.Errno.EACCES, + Auto_Port_Unavailable = cast(c.int)freebsd.Errno.EAGAIN, + + // NOTE: There are additional connect() error possibilities, but they are + // strictly for addresses in the UNIX domain. +} + +Bind_Error :: enum c.int { + None = 0, + Kernel_Resources_Unavailable = cast(c.int)freebsd.Errno.EAGAIN, + Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, + + // NOTE: bind() can also return EINVAL if the underlying `addrlen` is an + // invalid length for the address family. This shouldn't happen for the net + // package, but it's worth noting. + Already_Bound = cast(c.int)freebsd.Errno.EINVAL, + Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, + Given_Nonlocal_Address = cast(c.int)freebsd.Errno.EADDRNOTAVAIL, + Address_In_Use = cast(c.int)freebsd.Errno.EADDRINUSE, + Address_Family_Mismatch = cast(c.int)freebsd.Errno.EAFNOSUPPORT, + Protected_Address = cast(c.int)freebsd.Errno.EACCES, + Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT, + + // NOTE: There are additional bind() error possibilities, but they are + // strictly for addresses in the UNIX domain. +} + +Listen_Error :: enum c.int { + None = 0, + Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, + Socket_Not_Bound = cast(c.int)freebsd.Errno.EDESTADDRREQ, + Already_Connected = cast(c.int)freebsd.Errno.EINVAL, + Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, + Listening_Not_Supported_For_This_Socket = cast(c.int)freebsd.Errno.EOPNOTSUPP, +} + +Accept_Error :: enum c.int { + None = 0, + Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, + Interrupted = cast(c.int)freebsd.Errno.EINTR, + Full_Per_Process_Descriptor_Table = cast(c.int)freebsd.Errno.EMFILE, + Full_System_File_Table = cast(c.int)freebsd.Errno.ENFILE, + Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, + Listen_Not_Called_On_Socket_Yet = cast(c.int)freebsd.Errno.EINVAL, + Address_Not_Writable = cast(c.int)freebsd.Errno.EFAULT, + + // NOTE: This is the same as EWOULDBLOCK. + No_Connections_Available = cast(c.int)freebsd.Errno.EAGAIN, + // `Would_Block` alias for `core:net` tests. + Would_Block = cast(c.int)freebsd.Errno.EAGAIN, + + New_Connection_Aborted = cast(c.int)freebsd.Errno.ECONNABORTED, +} + +TCP_Recv_Error :: enum c.int { + None = 0, + Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, + Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET, + Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN, + Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, + + // NOTE(Feoramund): The next two errors are only relevant for recvmsg(), + // but I'm including them for completeness's sake. + Full_Table_And_Pending_Data = cast(c.int)freebsd.Errno.EMFILE, + Invalid_Message_Size = cast(c.int)freebsd.Errno.EMSGSIZE, + + Timeout = cast(c.int)freebsd.Errno.EAGAIN, + Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR, + Buffer_Pointer_Outside_Address_Space = cast(c.int)freebsd.Errno.EFAULT, +} + +UDP_Recv_Error :: enum c.int { + None = 0, + Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, + Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET, + Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN, + Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, + + // NOTE(Feoramund): The next two errors are only relevant for recvmsg(), + // but I'm including them for completeness's sake. + Full_Table_And_Data_Discarded = cast(c.int)freebsd.Errno.EMFILE, + Invalid_Message_Size = cast(c.int)freebsd.Errno.EMSGSIZE, + + Timeout = cast(c.int)freebsd.Errno.EAGAIN, + Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR, + Buffer_Pointer_Outside_Address_Space = cast(c.int)freebsd.Errno.EFAULT, +} + +TCP_Send_Error :: enum c.int { + None = 0, + Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET, + Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, + Broadcast_Status_Mismatch = cast(c.int)freebsd.Errno.EACCES, + Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN, + Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, + Argument_In_Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT, + + Message_Size_Breaks_Atomicity = cast(c.int)freebsd.Errno.EMSGSIZE, + + /* The socket is marked non-blocking, or MSG_DONTWAIT is + specified, and the requested operation would block. */ + Would_Block = cast(c.int)freebsd.Errno.EAGAIN, + + /* NOTE: This error arises for two distinct reasons: + + 1. The system was unable to allocate an internal buffer. + The operation may succeed when buffers become available. + + 2. The output queue for a network interface was full. + This generally indicates that the interface has stopped + sending, but may be caused by transient congestion. + */ + No_Buffer_Space_Available = cast(c.int)freebsd.Errno.ENOBUFS, + + Host_Unreachable = cast(c.int)freebsd.Errno.EHOSTUNREACH, + Already_Connected = cast(c.int)freebsd.Errno.EISCONN, + ICMP_Unreachable = cast(c.int)freebsd.Errno.ECONNREFUSED, + Host_Down = cast(c.int)freebsd.Errno.EHOSTDOWN, + Network_Down = cast(c.int)freebsd.Errno.ENETDOWN, + Jailed_Socket_Tried_To_Escape = cast(c.int)freebsd.Errno.EADDRNOTAVAIL, + Cannot_Send_More_Data = cast(c.int)freebsd.Errno.EPIPE, +} + +// NOTE(Feoramund): The same as TCP errors go, as far as I'm aware. +UDP_Send_Error :: distinct TCP_Send_Error + +Shutdown_Manner :: enum c.int { + Receive = cast(c.int)freebsd.Shutdown_Method.RD, + Send = cast(c.int)freebsd.Shutdown_Method.WR, + Both = cast(c.int)freebsd.Shutdown_Method.RDWR, +} + +Shutdown_Error :: enum c.int { + None = 0, + Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, + Invalid_Manner = cast(c.int)freebsd.Errno.EINVAL, + Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN, + Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, +} + +Socket_Option_Error :: enum c.int { + None = 0, + Value_Out_Of_Range = -1, + Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, + Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, + Unknown_Option_For_Level = cast(c.int)freebsd.Errno.ENOPROTOOPT, + Argument_In_Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT, + // This error can arise for many different reasons. + Invalid_Value = cast(c.int)freebsd.Errno.EINVAL, + System_Memory_Allocation_Failed = cast(c.int)freebsd.Errno.ENOMEM, + Insufficient_System_Resources = cast(c.int)freebsd.Errno.ENOBUFS, +} + +Set_Blocking_Error :: enum c.int { + None = 0, + Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, + Wrong_Descriptor = cast(c.int)freebsd.Errno.ENOTTY, +} diff --git a/core/net/errors_linux.odin b/core/net/errors_linux.odin index 2370dd0d8..3cd51e6fd 100644 --- a/core/net/errors_linux.odin +++ b/core/net/errors_linux.odin @@ -1,5 +1,5 @@ +#+build linux package net -// +build linux /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. @@ -10,6 +10,7 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: @@ -17,6 +18,7 @@ package net Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation flysand: Move dependency from core:linux.Errno to core:sys/linux + Feoramund: FreeBSD platform code */ import "core:c" diff --git a/core/net/errors_windows.odin b/core/net/errors_windows.odin index 0538c2b82..f41bcf888 100644 --- a/core/net/errors_windows.odin +++ b/core/net/errors_windows.odin @@ -1,5 +1,5 @@ +#+build windows package net -// +build windows /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. @@ -10,12 +10,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ import "core:c" @@ -135,6 +137,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.odin b/core/net/interface.odin index df7d0223e..775a812f3 100644 --- a/core/net/interface.odin +++ b/core/net/interface.odin @@ -1,4 +1,4 @@ -// +build windows, linux, darwin +#+build windows, linux, darwin, freebsd package net /* @@ -10,12 +10,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ import "core:strings" diff --git a/core/net/interface_darwin.odin b/core/net/interface_darwin.odin index 59b0e01c5..4921bc3fe 100644 --- a/core/net/interface_darwin.odin +++ b/core/net/interface_darwin.odin @@ -1,5 +1,5 @@ +#+build darwin package net -//+build darwin /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. @@ -10,13 +10,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation - + Feoramund: FreeBSD platform code */ import "core:os" @@ -59,24 +60,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 +89,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_freebsd.odin b/core/net/interface_freebsd.odin new file mode 100644 index 000000000..50e2d1a96 --- /dev/null +++ b/core/net/interface_freebsd.odin @@ -0,0 +1,177 @@ +#+build freebsd +package net + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ + +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code +*/ + +import "core:c" +import "core:strings" +import "core:sys/freebsd" + +@(private) +_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { + // This is a simplified implementation of `getifaddrs` from the FreeBSD + // libc using only Odin and syscalls. + context.allocator = allocator + + mib := [6]freebsd.MIB_Identifier { + .CTL_NET, + cast(freebsd.MIB_Identifier)freebsd.Protocol_Family.ROUTE, + freebsd.MIB_Identifier(0), + freebsd.MIB_Identifier(0), + .NET_RT_IFLISTL, + freebsd.MIB_Identifier(0), + } + + // Figure out how much space we need. + needed: c.size_t = --- + + errno := freebsd.sysctl(mib[:], nil, &needed, nil, 0) + if errno != nil { + return nil, .Unable_To_Enumerate_Network_Interfaces + } + + // Allocate and get the entries. + buf, alloc_err := make([]byte, needed) + if alloc_err != nil { + return nil, .Unable_To_Enumerate_Network_Interfaces + } + defer delete(buf) + + errno = freebsd.sysctl(mib[:], &buf[0], &needed, nil, 0) + if errno != nil { + return nil, .Unable_To_Enumerate_Network_Interfaces + } + + // Build the interfaces with each message. + if_builder: [dynamic]Network_Interface + for message_pointer: uintptr = 0; message_pointer < cast(uintptr)needed; /**/ { + rtm := cast(^freebsd.Route_Message_Header)&buf[message_pointer] + if rtm.version != freebsd.RTM_VERSION { + continue + } + + #partial switch rtm.type { + case .IFINFO: + ifm := cast(^freebsd.Interface_Message_Header_Len)&buf[message_pointer] + if .IFP not_in ifm.addrs { + // No name available. + break + } + + dl := cast(^freebsd.Socket_Address_Data_Link)&buf[message_pointer + cast(uintptr)ifm.len] + + if_data := cast(^freebsd.Interface_Data)&buf[message_pointer + cast(uintptr)ifm.data_off] + + // This is done this way so the different message types can + // dynamically build a `Network_Interface`. + resize(&if_builder, max(len(if_builder), 1 + cast(int)ifm.index)) + interface := if_builder[ifm.index] + + interface.adapter_name = strings.clone_from_bytes(dl.data[0:dl.nlen]) + interface.mtu = if_data.mtu + + switch if_data.link_state { + case .UNKNOWN: /* Do nothing; the default value is valid. */ + case .UP: interface.link.state |= { .Up } + case .DOWN: interface.link.state |= { .Down } + } + + // TODO: Uncertain if these are equivalent: + // interface.link.transmit_speed = if_data.baudrate + // interface.link.receive_speed = if_data.baudrate + + if dl.type == .LOOP { + interface.link.state |= { .Loopback } + } else { + interface.physical_address = physical_address_to_string(dl.data[dl.nlen:][:6]) + } + + if_builder[ifm.index] = interface + + case .NEWADDR: + RTA_MASKS :: freebsd.Route_Address_Flags { .IFA, .NETMASK } + ifam := cast(^freebsd.Interface_Address_Message_Header_Len)&buf[message_pointer] + if ifam.addrs & RTA_MASKS == {} { + break + } + + resize(&if_builder, max(len(if_builder), 1 + cast(int)ifam.index)) + interface := if_builder[ifam.index] + + address_pointer := message_pointer + cast(uintptr)ifam.len + + lease: Lease + address_set: bool + for address_type in ifam.addrs { + ptr := cast(^freebsd.Socket_Address_Basic)&buf[address_pointer] + + #partial switch address_type { + case .IFA: + #partial switch ptr.family { + case .INET: + real := cast(^freebsd.Socket_Address_Internet)ptr + lease.address = cast(IP4_Address)real.addr.addr8 + address_set = true + case .INET6: + real := cast(^freebsd.Socket_Address_Internet6)ptr + lease.address = cast(IP6_Address)real.addr.addr16 + address_set = true + } + case .NETMASK: + #partial switch ptr.family { + case .INET: + real := cast(^freebsd.Socket_Address_Internet)ptr + lease.netmask = cast(Netmask)cast(IP4_Address)real.addr.addr8 + case .INET6: + real := cast(^freebsd.Socket_Address_Internet6)ptr + lease.netmask = cast(Netmask)cast(IP6_Address)real.addr.addr16 + } + } + + SALIGN : u8 : size_of(c.long) - 1 + address_advance: uintptr = --- + if ptr.len > 0 { + address_advance = cast(uintptr)((ptr.len + SALIGN) & ~SALIGN) + } else { + address_advance = cast(uintptr)(SALIGN + 1) + } + + address_pointer += address_advance + } + + if address_set { + append(&interface.unicast, lease) + } + + if_builder[ifam.index] = interface + } + + message_pointer += cast(uintptr)rtm.msglen + } + + // Remove any interfaces that were allocated but had no name. + #no_bounds_check for i := len(if_builder) - 1; i >= 0; i -= 1 { + if len(if_builder[i].adapter_name) == 0 { + ordered_remove(&if_builder, i) + } + } + + return if_builder[:], nil +} diff --git a/core/net/interface_linux.odin b/core/net/interface_linux.odin index 7c99cf23b..28724735b 100644 --- a/core/net/interface_linux.odin +++ b/core/net/interface_linux.odin @@ -1,5 +1,5 @@ +#+build linux package net -//+build linux /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. @@ -10,12 +10,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code This file uses `getifaddrs` libc call to enumerate interfaces. TODO: When we have raw sockets, split off into its own file for Linux so we can use the NETLINK protocol and bypass libc. diff --git a/core/net/interface_windows.odin b/core/net/interface_windows.odin index f8bac253a..a6eb72846 100644 --- a/core/net/interface_windows.odin +++ b/core/net/interface_windows.odin @@ -1,5 +1,5 @@ +#+build windows package net -//+build windows /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. @@ -10,12 +10,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ import sys "core:sys/windows" @@ -24,42 +26,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 +73,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.odin b/core/net/socket.odin index 40fa6ab56..950c7ac11 100644 --- a/core/net/socket.odin +++ b/core/net/socket.odin @@ -1,4 +1,4 @@ -// +build windows, linux, darwin +#+build windows, linux, darwin, freebsd package net /* @@ -10,12 +10,14 @@ package net Copyright 2022-2023 Tetralux Copyright 2022-2023 Colin Davidson Copyright 2022-2023 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ any_socket_to_socket :: proc "contextless" (socket: Any_Socket) -> Socket { @@ -132,6 +134,13 @@ listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TC return _listen_tcp(interface_endpoint, backlog) } +/* + Returns the endpoint that the given socket is listening / bound on. +*/ +bound_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Network_Error) { + return _bound_endpoint(socket) +} + accept_tcp :: proc(socket: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { return _accept_tcp(socket, options) } @@ -148,7 +157,28 @@ recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_en return _recv_udp(socket, buf) } -recv :: proc{recv_tcp, recv_udp} +/* + Receive data from into a buffer from any socket. + + Note: `remote_endpoint` parameter is non-nil only if the socket type is UDP. On TCP sockets it + will always return `nil`. +*/ +recv_any :: proc(socket: Any_Socket, buf: []byte) -> ( + bytes_read: int, + remote_endpoint: Maybe(Endpoint), + err: Network_Error, +) { + switch socktype in socket { + case TCP_Socket: + bytes_read, err = recv_tcp(socktype, buf) + return + case UDP_Socket: + return recv_udp(socktype, buf) + case: panic("Not supported") + } +} + +recv :: proc{recv_tcp, recv_udp, recv_any} /* Repeatedly sends data until the entire buffer is sent. @@ -168,7 +198,20 @@ send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_writte return _send_udp(socket, buf, to) } -send :: proc{send_tcp, send_udp} +send_any :: proc(socket: Any_Socket, buf: []byte, to: Maybe(Endpoint) = nil) -> ( + bytes_written: int, + err: Network_Error, +) { + switch socktype in socket { + case TCP_Socket: + return send_tcp(socktype, buf) + case UDP_Socket: + return send_udp(socktype, buf, to.(Endpoint)) + case: panic("Not supported") + } +} + +send :: proc{send_tcp, send_udp, send_any} shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { return _shutdown(socket, manner) @@ -180,4 +223,4 @@ set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) { return _set_blocking(socket, should_block) -} \ No newline at end of file +} diff --git a/core/net/socket_darwin.odin b/core/net/socket_darwin.odin index 2585d134b..27927e973 100644 --- a/core/net/socket_darwin.odin +++ b/core/net/socket_darwin.odin @@ -1,5 +1,5 @@ +#+build darwin package net -// +build darwin /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. @@ -10,16 +10,19 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ import "core:c" import "core:os" +import "core:sys/posix" import "core:time" Socket_Option :: enum c.int { @@ -53,9 +56,9 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so unreachable() } - sock, ok := os.socket(c_family, c_type, c_protocol) - if ok != os.ERROR_NONE { - err = Create_Socket_Error(ok) + sock, sock_err := os.socket(c_family, c_type, c_protocol) + if sock_err != nil { + err = Create_Socket_Error(os.is_platform_error(sock_err) or_else -1) return } @@ -84,21 +87,28 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio sockaddr := _endpoint_to_sockaddr(endpoint) res := os.connect(os.Socket(skt), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len)) - if res != os.ERROR_NONE { - err = Dial_Error(res) - return + if res != nil { + close(skt) + return {}, Dial_Error(os.is_platform_error(res) or_else -1) } return } +// On Darwin, any port below 1024 is 'privileged' - which means that you need root access in order to use it. +MAX_PRIVILEGED_PORT :: 1023 + @(private) _bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { sockaddr := _endpoint_to_sockaddr(ep) s := any_socket_to_socket(skt) res := os.bind(os.Socket(s), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len)) - if res != os.ERROR_NONE { - err = Bind_Error(res) + if res != nil { + if res == os.EACCES && ep.port <= MAX_PRIVILEGED_PORT { + err = .Privileged_Port_Without_Root + } else { + err = Bind_Error(os.is_platform_error(res) or_else -1) + } } return } @@ -110,6 +120,7 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_ family := family_from_endpoint(interface_endpoint) sock := create_socket(family, .TCP) or_return skt = sock.(TCP_Socket) + defer if err != nil { close(skt) } // NOTE(tetra): This is so that if we crash while the socket is open, we can // bypass the cooldown period, and allow the next run of the program to @@ -121,22 +132,35 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_ bind(sock, interface_endpoint) or_return res := os.listen(os.Socket(skt), backlog) - if res != os.ERROR_NONE { - err = Listen_Error(res) + if res != nil { + err = Listen_Error(os.is_platform_error(res) or_else -1) return } return } +@(private) +_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { + addr: posix.sockaddr_storage + addr_len := posix.socklen_t(size_of(addr)) + res := posix.getsockname(posix.FD(any_socket_to_socket(sock)), (^posix.sockaddr)(&addr), &addr_len) + if res != .OK { + err = Listen_Error(posix.errno()) + return + } + ep = _sockaddr_to_endpoint((^os.SOCKADDR_STORAGE_LH)(&addr)) + return +} + @(private) _accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { sockaddr: os.SOCKADDR_STORAGE_LH sockaddrlen := c.int(size_of(sockaddr)) - client_sock, ok := os.accept(os.Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen) - if ok != os.ERROR_NONE { - err = Accept_Error(ok) + client_sock, client_sock_err := os.accept(os.Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen) + if client_sock_err != nil { + err = Accept_Error(os.is_platform_error(client_sock_err) or_else -1) return } client = TCP_Socket(client_sock) @@ -155,9 +179,9 @@ _recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Networ if len(buf) <= 0 { return } - res, ok := os.recv(os.Socket(skt), buf, 0) - if ok != os.ERROR_NONE { - err = TCP_Recv_Error(ok) + res, res_err := os.recv(os.Socket(skt), buf, 0) + if res_err != nil { + err = TCP_Recv_Error(os.is_platform_error(res_err) or_else -1) return } return int(res), nil @@ -171,9 +195,9 @@ _recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endp from: os.SOCKADDR_STORAGE_LH fromsize := c.int(size_of(from)) - res, ok := os.recvfrom(os.Socket(skt), buf, 0, cast(^os.SOCKADDR) &from, &fromsize) - if ok != os.ERROR_NONE { - err = UDP_Recv_Error(ok) + res, res_err := os.recvfrom(os.Socket(skt), buf, 0, cast(^os.SOCKADDR) &from, &fromsize) + if res_err != nil { + err = UDP_Recv_Error(os.is_platform_error(res_err) or_else -1) return } @@ -187,9 +211,13 @@ _send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Net for bytes_written < len(buf) { limit := min(int(max(i32)), len(buf) - bytes_written) remaining := buf[bytes_written:][:limit] - res, ok := os.send(os.Socket(skt), remaining, 0) - if ok != os.ERROR_NONE { - err = TCP_Send_Error(ok) + res, res_err := os.send(os.Socket(skt), remaining, os.MSG_NOSIGNAL) + if res_err == os.EPIPE { + // EPIPE arises if the socket has been closed remotely. + err = TCP_Send_Error.Connection_Closed + return + } else if res_err != nil { + err = TCP_Send_Error(os.is_platform_error(res_err) or_else -1) return } bytes_written += int(res) @@ -203,9 +231,13 @@ _send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: for bytes_written < len(buf) { limit := min(1<<31, len(buf) - bytes_written) remaining := buf[bytes_written:][:limit] - res, ok := os.sendto(os.Socket(skt), remaining, 0, cast(^os.SOCKADDR)&toaddr, i32(toaddr.len)) - if ok != os.ERROR_NONE { - err = UDP_Send_Error(ok) + res, res_err := os.sendto(os.Socket(skt), remaining, os.MSG_NOSIGNAL, cast(^os.SOCKADDR)&toaddr, i32(toaddr.len)) + if res_err == os.EPIPE { + // EPIPE arises if the socket has been closed remotely. + err = UDP_Send_Error.Not_Socket + return + } else if res_err != nil { + err = UDP_Send_Error(os.is_platform_error(res_err) or_else -1) return } bytes_written += int(res) @@ -217,8 +249,8 @@ _send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: _shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { s := any_socket_to_socket(skt) res := os.shutdown(os.Socket(s), int(manner)) - if res != os.ERROR_NONE { - return Shutdown_Error(res) + if res != nil { + return Shutdown_Error(os.is_platform_error(res) or_else -1) } return } @@ -267,8 +299,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) @@ -296,8 +327,8 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca skt := any_socket_to_socket(s) res := os.setsockopt(os.Socket(skt), int(level), int(option), ptr, len) - if res != os.ERROR_NONE { - return Socket_Option_Error(res) + if res != nil { + return Socket_Option_Error(os.is_platform_error(res) or_else -1) } return nil @@ -308,19 +339,19 @@ _set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_E socket := any_socket_to_socket(socket) flags, getfl_err := os.fcntl(int(socket), os.F_GETFL, 0) - if getfl_err != os.ERROR_NONE { - return Set_Blocking_Error(getfl_err) + if getfl_err != nil { + return Set_Blocking_Error(os.is_platform_error(getfl_err) or_else -1) } if should_block { - flags &= ~int(os.O_NONBLOCK) + flags &~= int(os.O_NONBLOCK) } else { flags |= int(os.O_NONBLOCK) } _, setfl_err := os.fcntl(int(socket), os.F_SETFL, flags) - if setfl_err != os.ERROR_NONE { - return Set_Blocking_Error(setfl_err) + if setfl_err != nil { + return Set_Blocking_Error(os.is_platform_error(setfl_err) or_else -1) } return nil diff --git a/core/net/socket_freebsd.odin b/core/net/socket_freebsd.odin new file mode 100644 index 000000000..3a3774007 --- /dev/null +++ b/core/net/socket_freebsd.odin @@ -0,0 +1,419 @@ +#+build freebsd +package net + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ + +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code +*/ + +import "core:c" +import "core:sys/freebsd" +import "core:time" + +Fd :: freebsd.Fd + +Socket_Option :: enum c.int { + // TODO: Test and implement more socket options. + // DEBUG + Reuse_Address = cast(c.int)freebsd.Socket_Option.REUSEADDR, + Keep_Alive = cast(c.int)freebsd.Socket_Option.KEEPALIVE, + // DONTROUTE + Broadcast = cast(c.int)freebsd.Socket_Option.BROADCAST, + Use_Loopback = cast(c.int)freebsd.Socket_Option.USELOOPBACK, + Linger = cast(c.int)freebsd.Socket_Option.LINGER, + Out_Of_Bounds_Data_Inline = cast(c.int)freebsd.Socket_Option.OOBINLINE, + Reuse_Port = cast(c.int)freebsd.Socket_Option.REUSEPORT, + // TIMESTAMP + No_SIGPIPE_From_EPIPE = cast(c.int)freebsd.Socket_Option.NOSIGPIPE, + // ACCEPTFILTER + // BINTIME + // NO_OFFLOAD + // NO_DDP + Reuse_Port_Load_Balancing = cast(c.int)freebsd.Socket_Option.REUSEPORT_LB, + // RERROR + + Send_Buffer_Size = cast(c.int)freebsd.Socket_Option.SNDBUF, + Receive_Buffer_Size = cast(c.int)freebsd.Socket_Option.RCVBUF, + // SNDLOWAT + // RCVLOWAT + Send_Timeout = cast(c.int)freebsd.Socket_Option.SNDTIMEO, + Receive_Timeout = cast(c.int)freebsd.Socket_Option.RCVTIMEO, +} + +@(private) +_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { + sys_family: freebsd.Protocol_Family = --- + sys_protocol: freebsd.Protocol = --- + sys_socket_type: freebsd.Socket_Type = --- + + switch family { + case .IP4: sys_family = .INET + case .IP6: sys_family = .INET6 + } + + switch protocol { + case .TCP: sys_protocol = .TCP; sys_socket_type = .STREAM + case .UDP: sys_protocol = .UDP; sys_socket_type = .DGRAM + } + + new_socket, errno := freebsd.socket(sys_family, sys_socket_type, sys_protocol) + if errno != nil { + err = cast(Create_Socket_Error)errno + return + } + + // NOTE(Feoramund): By default, FreeBSD will generate SIGPIPE if an EPIPE + // error is raised during the writing of a socket that may be closed. + // This behavior is unlikely to be expected by general users. + // + // There are two workarounds. One is to apply the .NOSIGNAL flag when using + // the `sendto` syscall. However, that would prevent users of this library + // from re-enabling the SIGPIPE-raising functionality, if they really + // wanted it. + // + // So I have disabled it here with this socket option for all sockets. + truth: b32 = true + errno = freebsd.setsockopt(new_socket, .SOCKET, .NOSIGPIPE, &truth, size_of(truth)) + if errno != nil { + err = cast(Socket_Option_Error)errno + return + } + + switch protocol { + case .TCP: return cast(TCP_Socket)new_socket, nil + case .UDP: return cast(UDP_Socket)new_socket, nil + } + + return +} + +@(private) +_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { + if endpoint.port == 0 { + return 0, .Port_Required + } + + family := family_from_endpoint(endpoint) + new_socket := create_socket(family, .TCP) or_return + socket = new_socket.(TCP_Socket) + + sockaddr := _endpoint_to_sockaddr(endpoint) + errno := freebsd.connect(cast(Fd)socket, &sockaddr, cast(freebsd.socklen_t)sockaddr.len) + if errno != nil { + close(socket) + return {}, cast(Dial_Error)errno + } + + return +} + +@(private) +_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) { + sockaddr := _endpoint_to_sockaddr(ep) + real_socket := any_socket_to_socket(socket) + errno := freebsd.bind(cast(Fd)real_socket, &sockaddr, cast(freebsd.socklen_t)sockaddr.len) + if errno != nil { + err = cast(Bind_Error)errno + } + return +} + +@(private) +_listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, err: Network_Error) { + family := family_from_endpoint(interface_endpoint) + new_socket := create_socket(family, .TCP) or_return + socket = new_socket.(TCP_Socket) + defer if err != nil { close(socket) } + + bind(socket, interface_endpoint) or_return + + errno := freebsd.listen(cast(Fd)socket, backlog) + if errno != nil { + err = cast(Listen_Error)errno + return + } + + return +} + +@(private) +_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { + sockaddr: freebsd.Socket_Address_Storage + + errno := freebsd.getsockname(cast(Fd)any_socket_to_socket(sock), &sockaddr) + if errno != nil { + err = cast(Listen_Error)errno + return + } + + ep = _sockaddr_to_endpoint(&sockaddr) + return +} + +@(private) +_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { + sockaddr: freebsd.Socket_Address_Storage + + result, errno := freebsd.accept(cast(Fd)sock, &sockaddr) + if errno != nil { + err = cast(Accept_Error)errno + return + } + + client = cast(TCP_Socket)result + source = _sockaddr_to_endpoint(&sockaddr) + return +} + +@(private) +_close :: proc(socket: Any_Socket) { + real_socket := cast(Fd)any_socket_to_socket(socket) + // TODO: This returns an error number, but the `core:net` interface does not handle it. + _ = freebsd.close(real_socket) +} + +@(private) +_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { + if len(buf) == 0 { + return + } + result, errno := freebsd.recv(cast(Fd)socket, buf, .NONE) + if errno != nil { + err = cast(TCP_Recv_Error)errno + return + } + return result, nil +} + +@(private) +_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { + if len(buf) == 0 { + return + } + from: freebsd.Socket_Address_Storage + + result, errno := freebsd.recvfrom(cast(Fd)socket, buf, .NONE, &from) + if errno != nil { + err = cast(UDP_Recv_Error)errno + return + } + return result, _sockaddr_to_endpoint(&from), nil +} + +@(private) +_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { + for bytes_written < len(buf) { + limit := min(int(max(i32)), len(buf) - bytes_written) + remaining := buf[bytes_written:][:limit] + + result, errno := freebsd.send(cast(Fd)socket, remaining, .NONE) + if errno != nil { + err = cast(TCP_Send_Error)errno + return + } + bytes_written += result + } + return +} + +@(private) +_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { + toaddr := _endpoint_to_sockaddr(to) + for bytes_written < len(buf) { + limit := min(int(max(i32)), len(buf) - bytes_written) + remaining := buf[bytes_written:][:limit] + + result, errno := freebsd.sendto(cast(Fd)socket, remaining, .NONE, &toaddr) + if errno != nil { + err = cast(UDP_Send_Error)errno + return + } + bytes_written += result + } + return +} + +@(private) +_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { + real_socket := cast(Fd)any_socket_to_socket(socket) + errno := freebsd.shutdown(real_socket, cast(freebsd.Shutdown_Method)manner) + if errno != nil { + return cast(Shutdown_Error)errno + } + return +} + +@(private) +_set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { + // NOTE(Feoramund): I found that FreeBSD, like Linux, requires at least 32 + // bits for a boolean socket option value. Nothing less will work. + bool_value: b32 + // TODO: Assuming no larger than i32, but the system may accept i64. + int_value: i32 + timeval_value: freebsd.timeval + + ptr: rawptr + len: freebsd.socklen_t + + switch option { + case + .Reuse_Address, + .Keep_Alive, + .Broadcast, + .Use_Loopback, + .Out_Of_Bounds_Data_Inline, + .Reuse_Port, + .No_SIGPIPE_From_EPIPE, + .Reuse_Port_Load_Balancing: + switch real in value { + case bool: bool_value = cast(b32)real + case b8: bool_value = cast(b32)real + case b16: bool_value = cast(b32)real + case b32: bool_value = real + case b64: bool_value = cast(b32)real + case: + panic("set_option() value must be a boolean here", loc) + } + ptr = &bool_value + len = size_of(bool_value) + case + .Linger, + .Send_Timeout, + .Receive_Timeout: + t, ok := value.(time.Duration) + if !ok { + panic("set_option() value must be a time.Duration here", loc) + } + + micros := cast(freebsd.time_t)time.duration_microseconds(t) + timeval_value.usec = cast(freebsd.suseconds_t)micros % 1e6 + timeval_value.sec = (micros - cast(freebsd.time_t)timeval_value.usec) / 1e6 + + ptr = &timeval_value + len = size_of(timeval_value) + case + .Receive_Buffer_Size, + .Send_Buffer_Size: + switch real in value { + case i8: int_value = cast(i32)real + case u8: int_value = cast(i32)real + case i16: int_value = cast(i32)real + case u16: int_value = cast(i32)real + case i32: int_value = real + case u32: + if real > u32(max(i32)) { return .Value_Out_Of_Range } + int_value = cast(i32)real + case i64: + if real > i64(max(i32)) || real < i64(min(i32)) { return .Value_Out_Of_Range } + int_value = cast(i32)real + case u64: + if real > u64(max(i32)) { return .Value_Out_Of_Range } + int_value = cast(i32)real + case i128: + if real > i128(max(i32)) || real < i128(min(i32)) { return .Value_Out_Of_Range } + int_value = cast(i32)real + case u128: + if real > u128(max(i32)) { return .Value_Out_Of_Range } + int_value = cast(i32)real + case int: + if real > int(max(i32)) || real < int(min(i32)) { return .Value_Out_Of_Range } + int_value = cast(i32)real + case uint: + if real > uint(max(i32)) { return .Value_Out_Of_Range } + int_value = cast(i32)real + case: + panic("set_option() value must be an integer here", loc) + } + ptr = &int_value + len = size_of(int_value) + case: + unimplemented("set_option() option not yet implemented", loc) + } + + real_socket := any_socket_to_socket(socket) + errno := freebsd.setsockopt(cast(Fd)real_socket, .SOCKET, cast(freebsd.Socket_Option)option, ptr, len) + if errno != nil { + return cast(Socket_Option_Error)errno + } + + return nil +} + +@(private) +_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) { + real_socket := any_socket_to_socket(socket) + + flags, errno := freebsd.fcntl_getfl(cast(freebsd.Fd)real_socket) + if errno != nil { + return cast(Set_Blocking_Error)errno + } + + if should_block { + flags &= ~{ .NONBLOCK } + } else { + flags |= { .NONBLOCK } + } + + errno = freebsd.fcntl_setfl(cast(freebsd.Fd)real_socket, flags) + if errno != nil { + return cast(Set_Blocking_Error)errno + } + + return +} + +@(private) +_endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: freebsd.Socket_Address_Storage) { + switch addr in ep.address { + case IP4_Address: + (cast(^freebsd.Socket_Address_Internet)(&sockaddr))^ = { + len = size_of(freebsd.Socket_Address_Internet), + family = .INET, + port = cast(freebsd.in_port_t)ep.port, + addr = transmute(freebsd.IP4_Address)addr, + } + case IP6_Address: + (cast(^freebsd.Socket_Address_Internet6)(&sockaddr))^ = { + len = size_of(freebsd.Socket_Address_Internet), + family = .INET6, + port = cast(freebsd.in_port_t)ep.port, + addr = transmute(freebsd.IP6_Address)addr, + } + } + return +} + +@(private) +_sockaddr_to_endpoint :: proc(native_addr: ^freebsd.Socket_Address_Storage) -> (ep: Endpoint) { + #partial switch native_addr.family { + case .INET: + addr := cast(^freebsd.Socket_Address_Internet)native_addr + ep = { + address = cast(IP4_Address)addr.addr.addr8, + port = cast(int)addr.port, + } + case .INET6: + addr := cast(^freebsd.Socket_Address_Internet6)native_addr + ep = { + address = cast(IP6_Address)addr.addr.addr16, + port = cast(int)addr.port, + } + case: + panic("native_addr is neither an IP4 or IP6 address") + } + return +} diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin index 539317141..b7816b0b6 100644 --- a/core/net/socket_linux.odin +++ b/core/net/socket_linux.odin @@ -1,5 +1,5 @@ +#+build linux package net -// +build linux /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. @@ -10,6 +10,7 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: @@ -17,6 +18,7 @@ package net Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation flysand: Move dependency from core:os to core:sys/linux + Feoramund: FreeBSD platform code */ import "core:c" @@ -31,8 +33,8 @@ Socket_Option :: enum c.int { Linger = c.int(linux.Socket_Option.LINGER), Receive_Buffer_Size = c.int(linux.Socket_Option.RCVBUF), Send_Buffer_Size = c.int(linux.Socket_Option.SNDBUF), - Receive_Timeout = c.int(linux.Socket_Option.RCVTIMEO_NEW), - Send_Timeout = c.int(linux.Socket_Option.SNDTIMEO_NEW), + Receive_Timeout = c.int(linux.Socket_Option.RCVTIMEO), + Send_Timeout = c.int(linux.Socket_Option.SNDTIMEO), } // Wrappers and unwrappers for system-native types @@ -80,14 +82,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 +119,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) } @@ -125,14 +127,14 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (An } @(private) -_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (tcp_sock: TCP_Socket, err: Network_Error) { +_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (TCP_Socket, Network_Error) { errno: linux.Errno if endpoint.port == 0 { return 0, .Port_Required } // 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) @@ -145,7 +147,8 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio addr := _unwrap_os_addr(endpoint) errno = linux.connect(linux.Fd(os_sock), &addr) if errno != .NONE { - return cast(TCP_Socket) os_sock, Dial_Error(errno) + close(cast(TCP_Socket) os_sock) + return {}, Dial_Error(errno) } // NOTE(tetra): Not vital to succeed; error ignored no_delay: b32 = cast(b32) options.no_delay @@ -164,40 +167,61 @@ _bind :: proc(sock: Any_Socket, endpoint: Endpoint) -> (Network_Error) { } @(private) -_listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (TCP_Socket, Network_Error) { +_listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, err: Network_Error) { errno: linux.Errno assert(backlog > 0 && i32(backlog) < max(i32)) + // Figure out the address family and address of the endpoint ep_family := _unwrap_os_family(family_from_endpoint(endpoint)) 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) + err = Create_Socket_Error(errno) + return } + socket = cast(TCP_Socket)os_sock + defer if err != nil { close(socket) } + // NOTE(tetra): This is so that if we crash while the socket is open, we can // bypass the cooldown period, and allow the next run of the program to // use the same address immediately. // // TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address! do_reuse_addr: b32 = true - errno = linux.setsockopt(os_sock, linux.SOL_SOCKET, linux.Socket_Option.REUSEADDR, &do_reuse_addr) - if errno != .NONE { - return cast(TCP_Socket) os_sock, Listen_Error(errno) + if errno = linux.setsockopt(os_sock, linux.SOL_SOCKET, linux.Socket_Option.REUSEADDR, &do_reuse_addr); errno != .NONE { + err = Listen_Error(errno) + return } + // Bind the socket to endpoint address - errno = linux.bind(os_sock, &ep_address) - if errno != .NONE { - return cast(TCP_Socket) os_sock, Bind_Error(errno) + if errno = linux.bind(os_sock, &ep_address); errno != .NONE { + err = Bind_Error(errno) + return } + // Listen on bound socket - errno = linux.listen(os_sock, cast(i32) backlog) - if errno != .NONE { - return cast(TCP_Socket) os_sock, Listen_Error(errno) + if errno = linux.listen(os_sock, cast(i32) backlog); errno != .NONE { + err = Listen_Error(errno) + return } - return cast(TCP_Socket) os_sock, nil + + return +} + +@(private) +_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { + addr: linux.Sock_Addr_Any + errno := linux.getsockname(_unwrap_os_socket(sock), &addr) + if errno != .NONE { + err = Listen_Error(errno) + return + } + + ep = _wrap_os_addr(addr) + return } @(private) @@ -258,8 +282,12 @@ _send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) { for total_written < len(buf) { limit := min(int(max(i32)), len(buf) - total_written) remaining := buf[total_written:][:limit] - res, errno := linux.send(linux.Fd(tcp_sock), remaining, {}) - if errno != .NONE { + res, errno := linux.send(linux.Fd(tcp_sock), remaining, {.NOSIGNAL}) + if errno == .EPIPE { + // If the peer is disconnected when we are trying to send we will get an `EPIPE` error, + // so we turn that into a clearer error + return total_written, TCP_Send_Error.Connection_Closed + } else if errno != .NONE { return total_written, TCP_Send_Error(errno) } total_written += int(res) @@ -333,7 +361,9 @@ _set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc := .Send_Timeout, .Receive_Timeout: t, ok := value.(time.Duration) - if !ok do panic("set_option() value must be a time.Duration here", loc) + if !ok { + panic("set_option() value must be a time.Duration here", loc) + } micros := cast(i64) (time.duration_microseconds(t)) timeval_value.microseconds = cast(int) (micros % 1e6) @@ -371,9 +401,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..747d5cab3 100644 --- a/core/net/socket_windows.odin +++ b/core/net/socket_windows.odin @@ -1,5 +1,5 @@ +#+build windows package net -// +build windows /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. @@ -10,12 +10,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ import "core:c" @@ -78,8 +80,8 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio sockaddr := _endpoint_to_sockaddr(endpoint) res := win.connect(win.SOCKET(socket), &sockaddr, size_of(sockaddr)) if res < 0 { - err = Dial_Error(win.WSAGetLastError()) - return + close(socket) + return {}, Dial_Error(win.WSAGetLastError()) } if options.no_delay { @@ -105,6 +107,7 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T family := family_from_endpoint(interface_endpoint) sock := create_socket(family, .TCP) or_return socket = sock.(TCP_Socket) + defer if err != nil { close(socket) } // NOTE(tetra): While I'm not 100% clear on it, my understanding is that this will // prevent hijacking of the server's endpoint by other applications. @@ -118,6 +121,19 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T return } +@(private) +_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { + sockaddr: win.SOCKADDR_STORAGE_LH + sockaddrlen := c.int(size_of(sockaddr)) + if win.getsockname(win.SOCKET(any_socket_to_socket(sock)), &sockaddr, &sockaddrlen) == win.SOCKET_ERROR { + err = Listen_Error(win.WSAGetLastError()) + return + } + + ep = _sockaddr_to_endpoint(&sockaddr) + return +} + @(private) _accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { for { @@ -263,12 +279,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 +296,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 @@ -364,4 +382,4 @@ _sockaddr_to_endpoint :: proc(native_addr: ^win.SOCKADDR_STORAGE_LH) -> (ep: End panic("native_addr is neither IP4 or IP6 address") } return -} \ No newline at end of file +} diff --git a/core/net/url.odin b/core/net/url.odin index ef43d6c9f..aadcf5e48 100644 --- a/core/net/url.odin +++ b/core/net/url.odin @@ -8,12 +8,14 @@ package net Copyright 2022 Tetralux Copyright 2022 Colin Davidson Copyright 2022 Jeroen van Rijn . + Copyright 2024 Feoramund . Made available under Odin's BSD-3 license. List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation + Feoramund: FreeBSD platform code */ import "core:strings" @@ -21,15 +23,21 @@ import "core:strconv" import "core:unicode/utf8" import "core:encoding/hex" -split_url :: proc(url: string, allocator := context.allocator) -> (scheme, host, path: string, queries: map[string]string) { +split_url :: proc(url: string, allocator := context.allocator) -> (scheme, host, path: string, queries: map[string]string, fragment: string) { s := url - i := strings.last_index(s, "://") + i := strings.index(s, "://") if i >= 0 { scheme = s[:i] s = s[i+3:] } + i = strings.index(s, "#") + if i != -1 { + fragment = s[i+1:] + s = s[:i] + } + i = strings.index(s, "?") if i != -1 { query_str := s[i+1:] @@ -62,7 +70,7 @@ split_url :: proc(url: string, allocator := context.allocator) -> (scheme, host, return } -join_url :: proc(scheme, host, path: string, queries: map[string]string, allocator := context.allocator) -> string { +join_url :: proc(scheme, host, path: string, queries: map[string]string, fragment: string, allocator := context.allocator) -> string { b := strings.builder_make(allocator) strings.builder_grow(&b, len(scheme) + 3 + len(host) + 1 + len(path)) @@ -95,6 +103,13 @@ join_url :: proc(scheme, host, path: string, queries: map[string]string, allocat i += 1 } + if fragment != "" { + if fragment[0] != '#' { + strings.write_string(&b, "#") + } + strings.write_string(&b, strings.trim_space(fragment)) + } + return strings.to_string(b) } @@ -123,7 +138,9 @@ percent_encode :: proc(s: string, allocator := context.allocator) -> string { percent_decode :: proc(encoded_string: string, allocator := context.allocator) -> (decoded_string: string, ok: bool) { b := strings.builder_make(allocator) strings.builder_grow(&b, len(encoded_string)) - defer if !ok do strings.builder_destroy(&b) + defer if !ok { + strings.builder_destroy(&b) + } s := encoded_string @@ -137,7 +154,9 @@ percent_decode :: proc(encoded_string: string, allocator := context.allocator) - strings.write_string(&b, s[:i]) s = s[i:] - if len(s) == 0 do return // percent without anything after it + if len(s) == 0 { + return // percent without anything after it + } s = s[1:] if s[0] == '%' { @@ -177,7 +196,9 @@ base64url_encode :: proc(data: []byte, allocator := context.allocator) -> string } i := len(out)-1; for ; i >= 0; i -= 1 { - if out[i] != '=' do break; + if out[i] != '=' { + break; + } } return string(out[:i+1]); } diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin index 67a26d6f2..f62feec8c 100644 --- a/core/odin/ast/ast.odin +++ b/core/odin/ast/ast.odin @@ -27,6 +27,8 @@ Proc_Calling_Convention :: union { Node_State_Flag :: enum { Bounds_Check, No_Bounds_Check, + Type_Assert, + No_Type_Assert, } Node_State_Flags :: distinct bit_set[Node_State_Flag] @@ -67,6 +69,7 @@ File :: struct { fullpath: string, src: string, + tags: [dynamic]tokenizer.Token, docs: ^Comment_Group, pkg_decl: ^Package_Decl, @@ -513,6 +516,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 +542,7 @@ Foreign_Import_Decl :: struct { import_tok: tokenizer.Token, name: ^Ident, collection_name: string, - fullpaths: []string, + fullpaths: []^Expr, comment: ^Comment_Group, } @@ -597,6 +601,8 @@ Field_Flag :: enum { Any_Int, Subtype, By_Ptr, + No_Broadcast, + No_Capture, Results, Tags, @@ -616,6 +622,8 @@ field_flag_strings := [Field_Flag]string{ .Any_Int = "#any_int", .Subtype = "#subtype", .By_Ptr = "#by_ptr", + .No_Broadcast = "#no_broadcast", + .No_Capture = "#no_capture", .Results = "results", .Tags = "field tag", @@ -624,12 +632,14 @@ field_flag_strings := [Field_Flag]string{ } field_hash_flag_strings := []struct{key: string, flag: Field_Flag}{ - {"no_alias", .No_Alias}, - {"c_vararg", .C_Vararg}, - {"const", .Const}, - {"any_int", .Any_Int}, - {"subtype", .Subtype}, - {"by_ptr", .By_Ptr}, + {"no_alias", .No_Alias}, + {"c_vararg", .C_Vararg}, + {"const", .Const}, + {"any_int", .Any_Int}, + {"subtype", .Subtype}, + {"by_ptr", .By_Ptr}, + {"no_broadcast", .No_Broadcast}, + {"no_capture", .No_Capture}, } @@ -650,6 +660,7 @@ Field_Flags_Signature :: Field_Flags{ .Const, .Any_Int, .By_Ptr, + .No_Broadcast, .Default_Parameters, } @@ -749,7 +760,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, // Unary_Expr node for [?]T array types, nil for slice types close: tokenizer.Pos, elem: ^Expr, } @@ -765,16 +776,18 @@ Dynamic_Array_Type :: struct { Struct_Type :: struct { using node: Expr, - tok_pos: tokenizer.Pos, - poly_params: ^Field_List, - align: ^Expr, - where_token: tokenizer.Token, - where_clauses: []^Expr, - is_packed: bool, - is_raw_union: bool, - is_no_copy: bool, - fields: ^Field_List, - name_count: int, + tok_pos: tokenizer.Pos, + poly_params: ^Field_List, + align: ^Expr, + min_field_align: ^Expr, + max_field_align: ^Expr, + where_token: tokenizer.Token, + where_clauses: []^Expr, + is_packed: bool, + is_raw_union: bool, + is_no_copy: bool, + fields: ^Field_List, + name_count: int, } Union_Type_Kind :: enum u8 { @@ -837,6 +850,24 @@ Matrix_Type :: struct { elem: ^Expr, } +Bit_Field_Type :: struct { + using node: Expr, + tok_pos: tokenizer.Pos, + backing_type: ^Expr, + open: tokenizer.Pos, + fields: []^Bit_Field_Field, + close: tokenizer.Pos, +} + +Bit_Field_Field :: struct { + using node: Node, + docs: ^Comment_Group, + name: ^Expr, + type: ^Expr, + bit_size: ^Expr, + tag: tokenizer.Token, + comments: ^Comment_Group, +} Any_Node :: union { ^Package, @@ -893,6 +924,7 @@ Any_Node :: union { ^Map_Type, ^Relative_Type, ^Matrix_Type, + ^Bit_Field_Type, ^Bad_Stmt, ^Empty_Stmt, @@ -923,6 +955,7 @@ Any_Node :: union { ^Attribute, ^Field, ^Field_List, + ^Bit_Field_Field, } @@ -977,6 +1010,7 @@ Any_Expr :: union { ^Map_Type, ^Relative_Type, ^Matrix_Type, + ^Bit_Field_Type, } diff --git a/core/odin/ast/clone.odin b/core/odin/ast/clone.odin index 79e7a166e..67f7ffa95 100644 --- a/core/odin/ast/clone.odin +++ b/core/odin/ast/clone.odin @@ -1,13 +1,13 @@ package odin_ast -import "core:intrinsics" +import "base:intrinsics" import "core:mem" import "core:fmt" import "core:reflect" import "core:odin/tokenizer" _ :: intrinsics -new :: proc($T: typeid, pos, end: tokenizer.Pos) -> ^T { +new_from_positions :: proc($T: typeid, pos, end: tokenizer.Pos) -> ^T { n, _ := mem.new(T) n.pos = pos n.end = end @@ -23,6 +23,15 @@ new :: proc($T: typeid, pos, end: tokenizer.Pos) -> ^T { return n } +new_from_pos_and_end_node :: proc($T: typeid, pos: tokenizer.Pos, end: ^Node) -> ^T { + return new(T, pos, end != nil ? end.end : pos) +} + +new :: proc { + new_from_positions, + new_from_pos_and_end_node, +} + clone :: proc{ clone_node, clone_expr, @@ -107,226 +116,239 @@ clone_node :: proc(node: ^Node) -> ^Node { reflect.set_union_value(ds, res_ptr_any) } - if res.derived != nil do switch r in res.derived { - case ^Package, ^File: - case ^Bad_Expr: - case ^Ident: - case ^Implicit: - case ^Undef: - case ^Basic_Lit: - case ^Basic_Directive: - case ^Comment_Group: + if res.derived != nil { + switch r in res.derived { + case ^Package, ^File: + case ^Bad_Expr: + case ^Ident: + case ^Implicit: + case ^Undef: + case ^Basic_Lit: + case ^Basic_Directive: + case ^Comment_Group: - case ^Ellipsis: - r.expr = clone(r.expr) - case ^Proc_Lit: - r.type = auto_cast clone(r.type) - r.body = clone(r.body) - case ^Comp_Lit: - r.type = clone(r.type) - r.elems = clone(r.elems) + case ^Ellipsis: + r.expr = clone(r.expr) + case ^Proc_Lit: + r.type = auto_cast clone(r.type) + r.body = clone(r.body) + case ^Comp_Lit: + r.type = clone(r.type) + r.elems = clone(r.elems) - case ^Tag_Expr: - r.expr = clone(r.expr) - case ^Unary_Expr: - r.expr = clone(r.expr) - case ^Binary_Expr: - r.left = clone(r.left) - r.right = clone(r.right) - case ^Paren_Expr: - r.expr = clone(r.expr) - case ^Selector_Expr: - r.expr = clone(r.expr) - r.field = auto_cast clone(r.field) - case ^Implicit_Selector_Expr: - r.field = auto_cast clone(r.field) - case ^Selector_Call_Expr: - r.expr = clone(r.expr) - r.call = auto_cast clone(r.call) - case ^Index_Expr: - r.expr = clone(r.expr) - r.index = clone(r.index) - case ^Matrix_Index_Expr: - r.expr = clone(r.expr) - r.row_index = clone(r.row_index) - r.column_index = clone(r.column_index) - case ^Deref_Expr: - r.expr = clone(r.expr) - case ^Slice_Expr: - r.expr = clone(r.expr) - r.low = clone(r.low) - r.high = clone(r.high) - case ^Call_Expr: - r.expr = clone(r.expr) - r.args = clone(r.args) - case ^Field_Value: - r.field = clone(r.field) - r.value = clone(r.value) - case ^Ternary_If_Expr: - r.x = clone(r.x) - r.cond = clone(r.cond) - r.y = clone(r.y) - case ^Ternary_When_Expr: - r.x = clone(r.x) - r.cond = clone(r.cond) - r.y = clone(r.y) - case ^Or_Else_Expr: - r.x = clone(r.x) - r.y = clone(r.y) - case ^Or_Return_Expr: - r.expr = clone(r.expr) - case ^Or_Branch_Expr: - r.expr = clone(r.expr) - r.label = clone(r.label) - case ^Type_Assertion: - r.expr = clone(r.expr) - r.type = clone(r.type) - case ^Type_Cast: - r.type = clone(r.type) - r.expr = clone(r.expr) - case ^Auto_Cast: - r.expr = clone(r.expr) - case ^Inline_Asm_Expr: - r.param_types = clone(r.param_types) - r.return_type = clone(r.return_type) - r.constraints_string = clone(r.constraints_string) - r.asm_string = clone(r.asm_string) + case ^Tag_Expr: + r.expr = clone(r.expr) + case ^Unary_Expr: + r.expr = clone(r.expr) + case ^Binary_Expr: + r.left = clone(r.left) + r.right = clone(r.right) + case ^Paren_Expr: + r.expr = clone(r.expr) + case ^Selector_Expr: + r.expr = clone(r.expr) + r.field = auto_cast clone(r.field) + case ^Implicit_Selector_Expr: + r.field = auto_cast clone(r.field) + case ^Selector_Call_Expr: + r.expr = clone(r.expr) + r.call = auto_cast clone(r.call) + case ^Index_Expr: + r.expr = clone(r.expr) + r.index = clone(r.index) + case ^Matrix_Index_Expr: + r.expr = clone(r.expr) + r.row_index = clone(r.row_index) + r.column_index = clone(r.column_index) + case ^Deref_Expr: + r.expr = clone(r.expr) + case ^Slice_Expr: + r.expr = clone(r.expr) + r.low = clone(r.low) + r.high = clone(r.high) + case ^Call_Expr: + r.expr = clone(r.expr) + r.args = clone(r.args) + case ^Field_Value: + r.field = clone(r.field) + r.value = clone(r.value) + case ^Ternary_If_Expr: + r.x = clone(r.x) + r.cond = clone(r.cond) + r.y = clone(r.y) + case ^Ternary_When_Expr: + r.x = clone(r.x) + r.cond = clone(r.cond) + r.y = clone(r.y) + case ^Or_Else_Expr: + r.x = clone(r.x) + r.y = clone(r.y) + case ^Or_Return_Expr: + r.expr = clone(r.expr) + case ^Or_Branch_Expr: + r.expr = clone(r.expr) + r.label = clone(r.label) + case ^Type_Assertion: + r.expr = clone(r.expr) + r.type = clone(r.type) + case ^Type_Cast: + r.type = clone(r.type) + r.expr = clone(r.expr) + case ^Auto_Cast: + r.expr = clone(r.expr) + case ^Inline_Asm_Expr: + r.param_types = clone(r.param_types) + r.return_type = clone(r.return_type) + r.constraints_string = clone(r.constraints_string) + r.asm_string = clone(r.asm_string) - case ^Bad_Stmt: - // empty - case ^Empty_Stmt: - // empty - case ^Expr_Stmt: - r.expr = clone(r.expr) - case ^Tag_Stmt: - r.stmt = clone(r.stmt) + case ^Bad_Stmt: + // empty + case ^Empty_Stmt: + // empty + case ^Expr_Stmt: + r.expr = clone(r.expr) + case ^Tag_Stmt: + r.stmt = clone(r.stmt) - case ^Assign_Stmt: - r.lhs = clone(r.lhs) - r.rhs = clone(r.rhs) - case ^Block_Stmt: - r.label = clone(r.label) - r.stmts = clone(r.stmts) - case ^If_Stmt: - r.label = clone(r.label) - r.init = clone(r.init) - r.cond = clone(r.cond) - r.body = clone(r.body) - r.else_stmt = clone(r.else_stmt) - case ^When_Stmt: - r.cond = clone(r.cond) - r.body = clone(r.body) - r.else_stmt = clone(r.else_stmt) - case ^Return_Stmt: - r.results = clone(r.results) - case ^Defer_Stmt: - r.stmt = clone(r.stmt) - case ^For_Stmt: - r.label = clone(r.label) - r.init = clone(r.init) - r.cond = clone(r.cond) - r.post = clone(r.post) - r.body = clone(r.body) - case ^Range_Stmt: - r.label = clone(r.label) - r.vals = clone(r.vals) - r.expr = clone(r.expr) - r.body = clone(r.body) - case ^Inline_Range_Stmt: - r.label = clone(r.label) - r.val0 = clone(r.val0) - r.val1 = clone(r.val1) - r.expr = clone(r.expr) - r.body = clone(r.body) - case ^Case_Clause: - r.list = clone(r.list) - r.body = clone(r.body) - case ^Switch_Stmt: - r.label = clone(r.label) - r.init = clone(r.init) - r.cond = clone(r.cond) - r.body = clone(r.body) - case ^Type_Switch_Stmt: - r.label = clone(r.label) - r.tag = clone(r.tag) - r.expr = clone(r.expr) - r.body = clone(r.body) - case ^Branch_Stmt: - r.label = auto_cast clone(r.label) - case ^Using_Stmt: - r.list = clone(r.list) - case ^Bad_Decl: - case ^Value_Decl: - r.attributes = clone(r.attributes) - r.names = clone(r.names) - r.type = clone(r.type) - r.values = clone(r.values) - case ^Package_Decl: - case ^Import_Decl: - case ^Foreign_Block_Decl: - r.attributes = clone(r.attributes) - r.foreign_library = clone(r.foreign_library) - r.body = clone(r.body) - case ^Foreign_Import_Decl: - r.name = auto_cast clone(r.name) - case ^Proc_Group: - r.args = clone(r.args) - case ^Attribute: - r.elems = clone(r.elems) - case ^Field: - r.names = clone(r.names) - r.type = clone(r.type) - r.default_value = clone(r.default_value) - case ^Field_List: - r.list = clone(r.list) - case ^Typeid_Type: - r.specialization = clone(r.specialization) - case ^Helper_Type: - r.type = clone(r.type) - case ^Distinct_Type: - r.type = clone(r.type) - case ^Poly_Type: - r.type = auto_cast clone(r.type) - r.specialization = clone(r.specialization) - case ^Proc_Type: - r.params = auto_cast clone(r.params) - r.results = auto_cast clone(r.results) - case ^Pointer_Type: - r.elem = clone(r.elem) - r.tag = clone(r.tag) - case ^Multi_Pointer_Type: - r.elem = clone(r.elem) - case ^Array_Type: - r.len = clone(r.len) - r.elem = clone(r.elem) - case ^Dynamic_Array_Type: - r.elem = clone(r.elem) - case ^Struct_Type: - r.poly_params = auto_cast clone(r.poly_params) - r.align = clone(r.align) - r.fields = auto_cast clone(r.fields) - case ^Union_Type: - r.poly_params = auto_cast clone(r.poly_params) - r.align = clone(r.align) - r.variants = clone(r.variants) - case ^Enum_Type: - r.base_type = clone(r.base_type) - r.fields = clone(r.fields) - case ^Bit_Set_Type: - r.elem = clone(r.elem) - r.underlying = clone(r.underlying) - case ^Map_Type: - r.key = clone(r.key) - r.value = clone(r.value) - case ^Matrix_Type: - r.row_count = clone(r.row_count) - r.column_count = clone(r.column_count) - r.elem = clone(r.elem) - case ^Relative_Type: - r.tag = clone(r.tag) - r.type = clone(r.type) - case: - fmt.panicf("Unhandled node kind: %v", r) + case ^Assign_Stmt: + r.lhs = clone(r.lhs) + r.rhs = clone(r.rhs) + case ^Block_Stmt: + r.label = clone(r.label) + r.stmts = clone(r.stmts) + case ^If_Stmt: + r.label = clone(r.label) + r.init = clone(r.init) + r.cond = clone(r.cond) + r.body = clone(r.body) + r.else_stmt = clone(r.else_stmt) + case ^When_Stmt: + r.cond = clone(r.cond) + r.body = clone(r.body) + r.else_stmt = clone(r.else_stmt) + case ^Return_Stmt: + r.results = clone(r.results) + case ^Defer_Stmt: + r.stmt = clone(r.stmt) + case ^For_Stmt: + r.label = clone(r.label) + r.init = clone(r.init) + r.cond = clone(r.cond) + r.post = clone(r.post) + r.body = clone(r.body) + case ^Range_Stmt: + r.label = clone(r.label) + r.vals = clone(r.vals) + r.expr = clone(r.expr) + r.body = clone(r.body) + case ^Inline_Range_Stmt: + r.label = clone(r.label) + r.val0 = clone(r.val0) + r.val1 = clone(r.val1) + r.expr = clone(r.expr) + r.body = clone(r.body) + case ^Case_Clause: + r.list = clone(r.list) + r.body = clone(r.body) + case ^Switch_Stmt: + r.label = clone(r.label) + r.init = clone(r.init) + r.cond = clone(r.cond) + r.body = clone(r.body) + case ^Type_Switch_Stmt: + r.label = clone(r.label) + r.tag = clone(r.tag) + r.expr = clone(r.expr) + r.body = clone(r.body) + case ^Branch_Stmt: + r.label = auto_cast clone(r.label) + case ^Using_Stmt: + r.list = clone(r.list) + case ^Bad_Decl: + case ^Value_Decl: + r.attributes = clone(r.attributes) + r.names = clone(r.names) + r.type = clone(r.type) + r.values = clone(r.values) + case ^Package_Decl: + case ^Import_Decl: + case ^Foreign_Block_Decl: + r.attributes = clone(r.attributes) + 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: + r.elems = clone(r.elems) + case ^Field: + r.names = clone(r.names) + r.type = clone(r.type) + r.default_value = clone(r.default_value) + case ^Field_List: + r.list = clone(r.list) + case ^Typeid_Type: + r.specialization = clone(r.specialization) + case ^Helper_Type: + r.type = clone(r.type) + case ^Distinct_Type: + r.type = clone(r.type) + case ^Poly_Type: + r.type = auto_cast clone(r.type) + r.specialization = clone(r.specialization) + case ^Proc_Type: + r.params = auto_cast clone(r.params) + r.results = auto_cast clone(r.results) + case ^Pointer_Type: + r.elem = clone(r.elem) + r.tag = clone(r.tag) + case ^Multi_Pointer_Type: + r.elem = clone(r.elem) + case ^Array_Type: + r.len = clone(r.len) + r.elem = clone(r.elem) + case ^Dynamic_Array_Type: + r.elem = clone(r.elem) + case ^Struct_Type: + r.poly_params = auto_cast clone(r.poly_params) + r.align = clone(r.align) + r.min_field_align = clone(r.min_field_align) + r.max_field_align = clone(r.max_field_align) + r.fields = auto_cast clone(r.fields) + case ^Union_Type: + r.poly_params = auto_cast clone(r.poly_params) + r.align = clone(r.align) + r.variants = clone(r.variants) + case ^Enum_Type: + r.base_type = clone(r.base_type) + r.fields = clone(r.fields) + case ^Bit_Set_Type: + r.elem = clone(r.elem) + r.underlying = clone(r.underlying) + case ^Map_Type: + r.key = clone(r.key) + r.value = clone(r.value) + case ^Matrix_Type: + r.row_count = clone(r.row_count) + r.column_count = clone(r.column_count) + r.elem = clone(r.elem) + case ^Relative_Type: + r.tag = clone(r.tag) + r.type = clone(r.type) + case ^Bit_Field_Type: + r.backing_type = clone(r.backing_type) + r.fields = auto_cast clone(r.fields) + case ^Bit_Field_Field: + r.name = clone(r.name) + r.type = clone(r.type) + r.bit_size = clone(r.bit_size) + case: + fmt.panicf("Unhandled node kind: %v", r) + } } return res diff --git a/core/odin/ast/walk.odin b/core/odin/ast/walk.odin index 966a8137e..cba040875 100644 --- a/core/odin/ast/walk.odin +++ b/core/odin/ast/walk.odin @@ -61,7 +61,7 @@ walk :: proc(v: ^Visitor, node: ^Node) { return } - switch n in &node.derived { + switch n in node.derived { case ^File: if n.docs != nil { walk(v, n.docs) @@ -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) @@ -414,7 +415,15 @@ walk :: proc(v: ^Visitor, node: ^Node) { walk(v, n.row_count) walk(v, n.column_count) walk(v, n.elem) - + case ^Bit_Field_Type: + walk(v, n.backing_type) + for f in n.fields { + walk(v, f) + } + case ^Bit_Field_Field: + walk(v, n.name) + walk(v, n.type) + walk(v, n.bit_size) case: fmt.panicf("ast.walk: unexpected node type %T", n) } diff --git a/core/odin/doc-format/doc_format.odin b/core/odin/doc-format/doc_format.odin index d22dafd27..c2d86a0ba 100644 --- a/core/odin/doc-format/doc_format.odin +++ b/core/odin/doc-format/doc_format.odin @@ -10,8 +10,8 @@ Array :: struct($T: typeid) { String :: distinct Array(byte) Version_Type_Major :: 0 -Version_Type_Minor :: 2 -Version_Type_Patch :: 4 +Version_Type_Minor :: 3 +Version_Type_Patch :: 1 Version_Type :: struct { major, minor, patch: u8, @@ -102,13 +102,17 @@ Entity_Flag :: enum u32le { Foreign = 0, Export = 1, - Param_Using = 2, // using - Param_Const = 3, // #const - Param_Auto_Cast = 4, // auto_cast - Param_Ellipsis = 5, // Variadic parameter - Param_CVararg = 6, // #c_vararg - Param_No_Alias = 7, // #no_alias - Param_Any_Int = 8, // #any_int + Param_Using = 2, // using + Param_Const = 3, // #const + Param_Auto_Cast = 4, // auto_cast + Param_Ellipsis = 5, // Variadic parameter + Param_CVararg = 6, // #c_vararg + Param_No_Alias = 7, // #no_alias + Param_Any_Int = 8, // #any_int + Param_By_Ptr = 9, // #by_ptr + Param_No_Broadcast = 10, // #no_broadcast + + Bit_Field_Field = 19, Type_Alias = 20, @@ -137,6 +141,7 @@ Entity :: struct { // May be used by (Struct fields and procedure fields): // .Variable // .Constant + // This is equal to the negative of the "bit size" it this is a `bit_field`s field field_group_index: i32le, // May used by: @@ -187,6 +192,7 @@ Type_Kind :: enum u32le { Multi_Pointer = 22, Matrix = 23, Soa_Pointer = 24, + Bit_Field = 25, } Type_Elems_Cap :: 4 @@ -243,10 +249,10 @@ Type :: struct { // .Bit_Set - <=2 types: 0=element type, 1=underlying type (Underlying_Type flag will be set) // .Simd_Vector - 1 type: 0=element // .Relative_Pointer - 2 types: 0=pointer type, 1=base integer - // .Relative_Slice - 2 types: 0=slice type, 1=base integer // .Multi_Pointer - 1 type: 0=element // .Matrix - 1 type: 0=element // .Soa_Pointer - 1 type: 0=element + // .Bit_Field - 1 type: 0=backing type types: Array(Type_Index), // Used by: 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/file_tags.odin b/core/odin/parser/file_tags.odin new file mode 100644 index 000000000..c5c6637c3 --- /dev/null +++ b/core/odin/parser/file_tags.odin @@ -0,0 +1,276 @@ +package odin_parser + +import "base:runtime" +import "core:strings" +import "core:reflect" + +import "../ast" + +Private_Flag :: enum { + Public, + Package, + File, +} + +Build_Kind :: struct { + os: runtime.Odin_OS_Types, + arch: runtime.Odin_Arch_Types, +} + +// empty build kind acts as a marker for separating multiple lines with build tags +BUILD_KIND_NEWLINE_MARKER :: Build_Kind{} + +File_Tags :: struct { + build_project_name: [][]string, + build: []Build_Kind, + private: Private_Flag, + ignore: bool, + lazy: bool, + no_instrumentation: bool, +} + +@require_results +get_build_os_from_string :: proc(str: string) -> runtime.Odin_OS_Type { + fields := reflect.enum_fields_zipped(runtime.Odin_OS_Type) + for os in fields { + if strings.equal_fold(os.name, str) { + return runtime.Odin_OS_Type(os.value) + } + } + return .Unknown +} +@require_results +get_build_arch_from_string :: proc(str: string) -> runtime.Odin_Arch_Type { + fields := reflect.enum_fields_zipped(runtime.Odin_Arch_Type) + for os in fields { + if strings.equal_fold(os.name, str) { + return runtime.Odin_Arch_Type(os.value) + } + } + return .Unknown +} + +@require_results +parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags: File_Tags) { + context.allocator = allocator + + if file.docs == nil && file.tags == nil { + return + } + + next_char :: proc(src: string, i: ^int) -> (ch: u8) { + if i^ < len(src) { + ch = src[i^] + } + i^ += 1 + return + } + skip_whitespace :: proc(src: string, i: ^int) { + for { + switch next_char(src, i) { + case ' ', '\t': + continue + case: + i^ -= 1 + return + } + } + } + scan_value :: proc(src: string, i: ^int) -> string { + start := i^ + for { + switch next_char(src, i) { + case ' ', '\t', '\n', '\r', 0, ',': + i^ -= 1 + return src[start:i^] + case: + continue + } + } + } + + build_kinds: [dynamic]Build_Kind + defer shrink(&build_kinds) + + build_project_name_strings: [dynamic]string + defer shrink(&build_project_name_strings) + + build_project_names: [dynamic][]string + defer shrink(&build_project_names) + + parse_tag :: proc(text: string, tags: ^File_Tags, build_kinds: ^[dynamic]Build_Kind, + build_project_name_strings: ^[dynamic]string, + build_project_names: ^[dynamic][]string) { + i := 0 + + skip_whitespace(text, &i) + + if next_char(text, &i) == '+' { + switch scan_value(text, &i) { + case "ignore": + tags.ignore = true + case "lazy": + tags.lazy = true + case "no-instrumentation": + tags.no_instrumentation = true + case "private": + skip_whitespace(text, &i) + switch scan_value(text, &i) { + case "file": + tags.private = .File + case "package", "": + tags.private = .Package + } + case "build-project-name": + groups_loop: for { + index_start := len(build_project_name_strings) + + defer append(build_project_names, build_project_name_strings[index_start:]) + + for { + skip_whitespace(text, &i) + name_start := i + + switch next_char(text, &i) { + case 0, '\r', '\n': + i -= 1 + break groups_loop + case ',': + continue groups_loop + case '!': + // include ! in the name + case: + i -= 1 + } + + scan_value(text, &i) + append(build_project_name_strings, text[name_start:i]) + } + + append(build_project_names, build_project_name_strings[index_start:]) + } + case "build": + + if len(build_kinds) > 0 { + append(build_kinds, BUILD_KIND_NEWLINE_MARKER) + } + + kinds_loop: for { + os_positive: runtime.Odin_OS_Types + os_negative: runtime.Odin_OS_Types + + arch_positive: runtime.Odin_Arch_Types + arch_negative: runtime.Odin_Arch_Types + + defer append(build_kinds, Build_Kind{ + os = (os_positive == {} ? runtime.ALL_ODIN_OS_TYPES : os_positive) -os_negative, + arch = (arch_positive == {} ? runtime.ALL_ODIN_ARCH_TYPES : arch_positive)-arch_negative, + }) + + for { + skip_whitespace(text, &i) + + is_notted: bool + switch next_char(text, &i) { + case 0, '\r', '\n': + i -= 1 + break kinds_loop + case ',': + continue kinds_loop + case '!': + is_notted = true + case: + i -= 1 + } + + value := scan_value(text, &i) + + if value == "ignore" { + tags.ignore = true + } else if os := get_build_os_from_string(value); os != .Unknown { + if is_notted { + os_negative += {os} + } else { + os_positive += {os} + } + } else if arch := get_build_arch_from_string(value); arch != .Unknown { + if is_notted { + arch_negative += {arch} + } else { + arch_positive += {arch} + } + } + } + } + } + } + } + + if file.docs != nil { + for comment in file.docs.list { + if len(comment.text) < 3 || comment.text[:2] != "//" { + continue + } + text := comment.text[2:] + + parse_tag(text, &tags, &build_kinds, &build_project_name_strings, &build_project_names) + } + } + + for tag in file.tags { + if len(tag.text) < 3 || tag.text[:2] != "#+" { + continue + } + // Only skip # because parse_tag skips the plus + text := tag.text[1:] + + parse_tag(text, &tags, &build_kinds, &build_project_name_strings, &build_project_names) + } + + tags.build = build_kinds[:] + tags.build_project_name = build_project_names[:] + + return +} + +Build_Target :: struct { + os: runtime.Odin_OS_Type, + arch: runtime.Odin_Arch_Type, + project_name: string, +} + +@require_results +match_build_tags :: proc(file_tags: File_Tags, target: Build_Target) -> bool { + + project_name_correct := len(target.project_name) == 0 || len(file_tags.build_project_name) == 0 + + for group in file_tags.build_project_name { + group_correct := true + for name in group { + if name[0] == '!' { + group_correct &&= target.project_name != name[1:] + } else { + group_correct &&= target.project_name == name + } + } + project_name_correct ||= group_correct + } + + os_and_arch_correct := true + + if len(file_tags.build) > 0 { + os_and_arch_correct_line := false + + for kind in file_tags.build { + if kind == BUILD_KIND_NEWLINE_MARKER { + os_and_arch_correct &&= os_and_arch_correct_line + os_and_arch_correct_line = false + } else { + os_and_arch_correct_line ||= target.os in kind.os && target.arch in kind.arch + } + } + os_and_arch_correct &&= os_and_arch_correct_line + } + + return !file_tags.ignore && project_name_correct && os_and_arch_correct +} diff --git a/core/odin/parser/parse_files.odin b/core/odin/parser/parse_files.odin index 6452faf4c..d4e532ec7 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) diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index bbfaf9114..5a7440339 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -161,11 +161,36 @@ parse_file :: proc(p: ^Parser, file: ^ast.File) -> bool { docs := p.lead_comment - p.file.pkg_token = expect_token(p, .Package) - if p.file.pkg_token.kind != .Package { - return false + invalid_pre_package_token: Maybe(tokenizer.Token) + + for p.curr_tok.kind != .Package && p.curr_tok.kind != .EOF { + if p.curr_tok.kind == .Comment { + consume_comment_groups(p, p.prev_tok) + } else if p.curr_tok.kind == .File_Tag { + append(&p.file.tags, p.curr_tok) + advance_token(p) + } else { + if invalid_pre_package_token == nil { + invalid_pre_package_token = p.curr_tok + } + + advance_token(p) + } } + if p.curr_tok.kind != .Package { + t := invalid_pre_package_token.? or_else p.curr_tok + error(p, t.pos, "Expected a package declaration at the start of the file") + return false + } + + p.file.pkg_token = expect_token(p, .Package) + + if ippt, ok := invalid_pre_package_token.?; ok { + error(p, ippt.pos, "Expected only comments or lines starting with '#+' before the package declaration") + return false + } + pkg_name := expect_token_after(p, .Ident, "package") if pkg_name.kind == .Ident { switch name := pkg_name.text; { @@ -308,7 +333,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) } @@ -416,24 +441,46 @@ end_of_line_pos :: proc(p: ^Parser, tok: tokenizer.Token) -> tokenizer.Pos { } expect_closing_brace_of_field_list :: proc(p: ^Parser) -> tokenizer.Token { + return expect_closing_token_of_field_list(p, .Close_Brace, "field list") +} + +expect_closing_token_of_field_list :: proc(p: ^Parser, closing_kind: tokenizer.Token_Kind, msg: string) -> tokenizer.Token { token := p.curr_tok - if allow_token(p, .Close_Brace) { + if allow_token(p, closing_kind) { return token } if allow_token(p, .Semicolon) && !tokenizer.is_newline(token) { str := tokenizer.token_to_string(token) error(p, end_of_line_pos(p, p.prev_tok), "expected a comma, got %s", str) } - expect_brace := expect_token(p, .Close_Brace) + expect_closing := expect_token_after(p, closing_kind, msg) - if expect_brace.kind != .Close_Brace { - for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF && !is_non_inserted_semicolon(p.curr_tok) { + if expect_closing.kind != closing_kind { + for p.curr_tok.kind != closing_kind && p.curr_tok.kind != .EOF && !is_non_inserted_semicolon(p.curr_tok) { advance_token(p) } return p.curr_tok } - return expect_brace + return expect_closing +} + +expect_closing_parentheses_of_field_list :: proc(p: ^Parser) -> tokenizer.Token { + token := p.curr_tok + if allow_token(p, .Close_Paren) { + return token + } + + if allow_token(p, .Semicolon) && !tokenizer.is_newline(token) { + str := tokenizer.token_to_string(token) + error(p, end_of_line_pos(p, p.prev_tok), "expected a comma, got %s", str) + } + + for p.curr_tok.kind != .Close_Paren && p.curr_tok.kind != .EOF && !is_non_inserted_semicolon(p.curr_tok) { + advance_token(p) + } + + return expect_token(p, .Close_Paren) } is_non_inserted_semicolon :: proc(tok: tokenizer.Token) -> bool { @@ -513,7 +560,7 @@ is_semicolon_optional_for_node :: proc(p: ^Parser, node: ^ast.Node) -> bool { return is_semicolon_optional_for_node(p, n.type) case ^ast.Pointer_Type: return is_semicolon_optional_for_node(p, n.elem) - case ^ast.Struct_Type, ^ast.Union_Type, ^ast.Enum_Type: + case ^ast.Struct_Type, ^ast.Union_Type, ^ast.Enum_Type, ^ast.Bit_Set_Type, ^ast.Bit_Field_Type: // Require semicolon within a procedure body return p.curr_proc == nil case ^ast.Proc_Lit: @@ -667,7 +714,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 { @@ -786,8 +838,11 @@ parse_if_stmt :: proc(p: ^Parser) -> ^ast.If_Stmt { else_stmt = ast.new(ast.Bad_Stmt, p.curr_tok.pos, end_pos(p.curr_tok)) } } - - end := body.end + + end: tokenizer.Pos + if body != nil { + end = body.end + } if else_stmt != nil { end = else_stmt.end } @@ -850,7 +905,7 @@ parse_for_stmt :: proc(p: ^Parser) -> ^ast.Stmt { body = parse_body(p) } - range_stmt := ast.new(ast.Range_Stmt, tok.pos, body.end) + range_stmt := ast.new(ast.Range_Stmt, tok.pos, body) range_stmt.for_pos = tok.pos range_stmt.in_pos = in_tok.pos range_stmt.expr = rhs @@ -910,7 +965,7 @@ parse_for_stmt :: proc(p: ^Parser) -> ^ast.Stmt { rhs = assign_stmt.rhs[0] } - range_stmt := ast.new(ast.Range_Stmt, tok.pos, body.end) + range_stmt := ast.new(ast.Range_Stmt, tok.pos, body) range_stmt.for_pos = tok.pos range_stmt.vals = vals range_stmt.in_pos = assign_stmt.op.pos @@ -920,7 +975,7 @@ parse_for_stmt :: proc(p: ^Parser) -> ^ast.Stmt { } cond_expr := convert_stmt_to_expr(p, cond, "boolean expression") - for_stmt := ast.new(ast.For_Stmt, tok.pos, body.end) + for_stmt := ast.new(ast.For_Stmt, tok.pos, body) for_stmt.for_pos = tok.pos for_stmt.init = init for_stmt.cond = cond_expr @@ -976,7 +1031,7 @@ parse_switch_stmt :: proc(p: ^Parser) -> ^ast.Stmt { lhs[0] = new_blank_ident(p, tok.pos) rhs[0] = parse_expr(p, true) - as := ast.new(ast.Assign_Stmt, tok.pos, rhs[0].end) + as := ast.new(ast.Assign_Stmt, tok.pos, rhs[0]) as.lhs = lhs as.op = in_tok as.rhs = rhs @@ -1010,14 +1065,14 @@ parse_switch_stmt :: proc(p: ^Parser) -> ^ast.Stmt { body.stmts = clauses[:] if is_type_switch { - ts := ast.new(ast.Type_Switch_Stmt, tok.pos, body.end) + ts := ast.new(ast.Type_Switch_Stmt, tok.pos, body) ts.tag = tag ts.body = body ts.switch_pos = tok.pos return ts } else { cond := convert_stmt_to_expr(p, tag, "switch expression") - ts := ast.new(ast.Switch_Stmt, tok.pos, body.end) + ts := ast.new(ast.Switch_Stmt, tok.pos, body) ts.init = init ts.cond = cond ts.body = body @@ -1044,7 +1099,7 @@ parse_attribute :: proc(p: ^Parser, tok: tokenizer.Token, open_kind, close_kind: if p.curr_tok.kind == .Eq { eq := expect_token(p, .Eq) value := parse_value(p) - fv := ast.new(ast.Field_Value, elem.pos, value.end) + fv := ast.new(ast.Field_Value, elem.pos, value) fv.field = elem fv.sep = eq.pos fv.value = value @@ -1078,6 +1133,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) @@ -1137,7 +1195,7 @@ parse_foreign_block :: proc(p: ^Parser, tok: tokenizer.Token) -> ^ast.Foreign_Bl body.stmts = decls[:] body.close = close.pos - decl := ast.new(ast.Foreign_Block_Decl, tok.pos, body.end) + decl := ast.new(ast.Foreign_Block_Decl, tok.pos, body) decl.docs = docs decl.tok = tok decl.foreign_library = foreign_library @@ -1165,12 +1223,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 } @@ -1178,7 +1236,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 { @@ -1248,7 +1308,7 @@ parse_unrolled_for_loop :: proc(p: ^Parser, inline_tok: tokenizer.Token) -> ^ast return ast.new(ast.Bad_Stmt, inline_tok.pos, end_pos(p.prev_tok)) } - range_stmt := ast.new(ast.Inline_Range_Stmt, inline_tok.pos, body.end) + range_stmt := ast.new(ast.Inline_Range_Stmt, inline_tok.pos, body) range_stmt.inline_pos = inline_tok.pos range_stmt.for_pos = for_tok.pos range_stmt.val0 = val0 @@ -1280,8 +1340,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 @@ -1304,7 +1364,7 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt { case ^ast.Return_Stmt: error(p, s.pos, "you cannot defer a return statement") } - ds := ast.new(ast.Defer_Stmt, tok.pos, stmt.end) + ds := ast.new(ast.Defer_Stmt, tok.pos, stmt) ds.stmt = stmt return ds @@ -1333,6 +1393,7 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt { rs := ast.new(ast.Return_Stmt, tok.pos, end) rs.results = results[:] + expect_semicolon(p, rs) return rs case .Break, .Continue, .Fallthrough: @@ -1341,8 +1402,7 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt { if tok.kind != .Fallthrough && p.curr_tok.kind == .Ident { label = parse_ident(p) } - end := label.end if label != nil else end_pos(tok) - s := ast.new(ast.Branch_Stmt, tok.pos, end) + s := ast.new(ast.Branch_Stmt, tok.pos, label) s.tok = tok s.label = label expect_semicolon(p, s) @@ -1366,7 +1426,7 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt { if p.curr_tok.kind != .Colon { end := list[len(list)-1] expect_semicolon(p, end) - us := ast.new(ast.Using_Stmt, tok.pos, end.end) + us := ast.new(ast.Using_Stmt, tok.pos, end) us.list = list return us } @@ -1403,6 +1463,15 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt { stmt.state_flags += {.No_Bounds_Check} } return stmt + case "type_assert", "no_type_assert": + stmt := parse_stmt(p) + switch name { + case "type_assert": + stmt.state_flags += {.Type_Assert} + case "no_type_assert": + stmt.state_flags += {.No_Type_Assert} + } + return stmt case "partial": stmt := parse_stmt(p) #partial switch s in stmt.derived_stmt { @@ -1416,19 +1485,19 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt { bd.tok = tok bd.name = name ce := parse_call_expr(p, bd) - es := ast.new(ast.Expr_Stmt, ce.pos, ce.end) + es := ast.new(ast.Expr_Stmt, ce.pos, ce) es.expr = ce return es case "force_inline", "force_no_inline": expr := parse_inlining_operand(p, true, tag) - es := ast.new(ast.Expr_Stmt, expr.pos, expr.end) + es := ast.new(ast.Expr_Stmt, expr.pos, expr) es.expr = expr return es 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 { @@ -1436,7 +1505,7 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt { } range.reverse = true } else { - error(p, range.pos, "#reverse can only be applied to a 'for in' statement") + error(p, stmt.pos, "#reverse can only be applied to a 'for in' statement") } return stmt case "include": @@ -1444,7 +1513,8 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt { return ast.new(ast.Bad_Stmt, tok.pos, end_pos(tag)) case: stmt := parse_stmt(p) - te := ast.new(ast.Tag_Stmt, tok.pos, stmt.pos) + end := stmt.pos if stmt != nil else end_pos(tok) + te := ast.new(ast.Tag_Stmt, tok.pos, end) te.op = tok te.name = name te.stmt = stmt @@ -1572,7 +1642,7 @@ convert_stmt_to_body :: proc(p: ^Parser, stmt: ^ast.Stmt) -> ^ast.Stmt { error(p, stmt.pos, "expected a non-empty statement") } - bs := ast.new(ast.Block_Stmt, stmt.pos, stmt.end) + bs := ast.new(ast.Block_Stmt, stmt.pos, stmt) bs.open = stmt.pos bs.stmts = make([]^ast.Stmt, 1) bs.stmts[0] = stmt @@ -1741,7 +1811,8 @@ parse_var_type :: proc(p: ^Parser, flags: ast.Field_Flags) -> ^ast.Expr { error(p, tok.pos, "variadic field missing type after '..'") type = ast.new(ast.Bad_Expr, tok.pos, end_pos(tok)) } - e := ast.new(ast.Ellipsis, type.pos, type.end) + e := ast.new(ast.Ellipsis, type.pos, type) + e.tok = tok.kind e.expr = type return e } @@ -1808,7 +1879,7 @@ parse_ident_list :: proc(p: ^Parser, allow_poly_names: bool) -> []^ast.Expr { if is_blank_ident(ident) { error(p, ident.pos, "invalid polymorphic type definition with a blank identifier") } - poly_name := ast.new(ast.Poly_Type, tok.pos, ident.end) + poly_name := ast.new(ast.Poly_Type, tok.pos, ident) poly_name.type = ident append(&list, poly_name) } else { @@ -1817,7 +1888,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) } @@ -2091,8 +2162,10 @@ 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) - expect_token(p, .Close_Paren) + p.expr_level -= 1 + expect_closing_parentheses_of_field_list(p) results, diverging := parse_results(p) is_generic := false @@ -2141,22 +2214,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.end) } - 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 { @@ -2178,10 +2254,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 { @@ -2204,7 +2280,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { case .Distinct: tok := advance_token(p) type := parse_type(p) - dt := ast.new(ast.Distinct_Type, tok.pos, type.end) + dt := ast.new(ast.Distinct_Type, tok.pos, type) dt.tok = tok.kind dt.type = type return dt @@ -2215,23 +2291,33 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { switch name.text { case "type": type := parse_type(p) - hp := ast.new(ast.Helper_Type, tok.pos, type.end) + hp := ast.new(ast.Helper_Type, tok.pos, type) hp.tok = tok.kind 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 "caller_expression": + bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name)) + bd.tok = tok + bd.name = name.text + + if peek_token_kind(p, .Open_Paren) { + return parse_call_expr(p, bd) + } + return bd + + 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 @@ -2319,7 +2405,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { tag_call := parse_call_expr(p, tag) type := parse_type(p) - rt := ast.new(ast.Relative_Type, tok.pos, type.end) + rt := ast.new(ast.Relative_Type, tok.pos, type) rt.tag = tag_call rt.type = type return rt @@ -2328,7 +2414,8 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { return parse_inlining_operand(p, lhs, name) case: expr := parse_expr(p, lhs) - te := ast.new(ast.Tag_Expr, tok.pos, expr.pos) + end := expr.pos if expr != nil else end_pos(tok) + te := ast.new(ast.Tag_Expr, tok.pos, end) te.op = tok te.name = name.text te.expr = expr @@ -2456,7 +2543,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { case .Pointer: tok := expect_token(p, .Pointer) elem := parse_type(p) - ptr := ast.new(ast.Pointer_Type, tok.pos, elem.end) + ptr := ast.new(ast.Pointer_Type, tok.pos, elem) ptr.pointer = tok.pos ptr.elem = elem return ptr @@ -2470,7 +2557,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { tok := expect_token(p, .Pointer) close := expect_token(p, .Close_Bracket) elem := parse_type(p) - t := ast.new(ast.Multi_Pointer_Type, open.pos, elem.end) + t := ast.new(ast.Multi_Pointer_Type, open.pos, elem) t.open = open.pos t.pointer = tok.pos t.close = close.pos @@ -2480,7 +2567,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { tok := expect_token(p, .Dynamic) close := expect_token(p, .Close_Bracket) elem := parse_type(p) - da := ast.new(ast.Dynamic_Array_Type, open.pos, elem.end) + da := ast.new(ast.Dynamic_Array_Type, open.pos, elem) da.open = open.pos da.dynamic_pos = tok.pos da.close = close.pos @@ -2500,7 +2587,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { } close := expect_token(p, .Close_Bracket) elem := parse_type(p) - at := ast.new(ast.Array_Type, open.pos, elem.end) + at := ast.new(ast.Array_Type, open.pos, elem) at.open = open.pos at.len = count at.close = close.pos @@ -2514,7 +2601,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { expect_token(p, .Close_Bracket) value := parse_type(p) - mt := ast.new(ast.Map_Type, tok.pos, value.end) + mt := ast.new(ast.Map_Type, tok.pos, value) mt.tok_pos = tok.pos mt.key = key mt.value = value @@ -2523,8 +2610,10 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { case .Struct: tok := expect_token(p, .Struct) - poly_params: ^ast.Field_List - align: ^ast.Expr + poly_params: ^ast.Field_List + align: ^ast.Expr + min_field_align: ^ast.Expr + max_field_align: ^ast.Expr is_packed: bool is_raw_union: bool is_no_copy: bool @@ -2556,6 +2645,22 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { error(p, tag.pos, "duplicate struct tag '#%s'", tag.text) } align = parse_expr(p, true) + case "field_align": + if min_field_align != nil { + error(p, tag.pos, "duplicate struct tag '#%s'", tag.text) + } + warn(p, tag.pos, "#field_align has been deprecated in favour of #min_field_align") + min_field_align = parse_expr(p, true) + case "min_field_align": + if min_field_align != nil { + error(p, tag.pos, "duplicate struct tag '#%s'", tag.text) + } + min_field_align = parse_expr(p, true) + case "max_field_align": + if max_field_align != nil { + error(p, tag.pos, "duplicate struct tag '#%s'", tag.text) + } + max_field_align = parse_expr(p, true) case "raw_union": if is_raw_union { error(p, tag.pos, "duplicate struct tag '#%s'", tag.text) @@ -2596,15 +2701,17 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { close := expect_closing_brace_of_field_list(p) st := ast.new(ast.Struct_Type, tok.pos, end_pos(close)) - st.poly_params = poly_params - st.align = align - st.is_packed = is_packed - st.is_raw_union = is_raw_union - st.is_no_copy = is_no_copy - st.fields = fields - st.name_count = name_count - st.where_token = where_token - st.where_clauses = where_clauses + st.poly_params = poly_params + st.align = align + st.min_field_align = min_field_align + st.max_field_align = max_field_align + st.is_packed = is_packed + st.is_raw_union = is_raw_union + st.is_no_copy = is_no_copy + st.fields = fields + st.name_count = name_count + st.where_token = where_token + st.where_clauses = where_clauses return st case .Union: @@ -2755,12 +2862,60 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { expect_token(p, .Close_Bracket) elem := parse_type(p) - mt := ast.new(ast.Matrix_Type, tok.pos, elem.end) + mt := ast.new(ast.Matrix_Type, tok.pos, elem) mt.tok_pos = tok.pos mt.row_count = row_count mt.column_count = column_count mt.elem = elem return mt + + case .Bit_Field: + tok := expect_token(p, .Bit_Field) + + backing_type := parse_type_or_ident(p) + if backing_type == nil { + token := advance_token(p) + error(p, token.pos, "Expected a backing type for a 'bit_field'") + } + + skip_possible_newline_for_literal(p) + open := expect_token_after(p, .Open_Brace, "bit_field") + + fields: [dynamic]^ast.Bit_Field_Field + for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF { + name := parse_ident(p) + expect_token(p, .Colon) + type := parse_type(p) + expect_token(p, .Or) + bit_size := parse_expr(p, true) + + tag: tokenizer.Token + if p.curr_tok.kind == .String { + tag = expect_token(p, .String) + } + + field := ast.new(ast.Bit_Field_Field, name.pos, bit_size) + + field.name = name + field.type = type + field.bit_size = bit_size + field.tag = tag + + append(&fields, field) + + allow_token(p, .Comma) or_break + } + + close := expect_closing_brace_of_field_list(p) + + bf := ast.new(ast.Bit_Field_Type, tok.pos, end_pos(close)) + + bf.tok_pos = tok.pos + bf.backing_type = backing_type + bf.open = open.pos + bf.fields = fields[:] + bf.close = close.pos + return bf case .Asm: tok := expect_token(p, .Asm) @@ -2868,7 +3023,8 @@ is_literal_type :: proc(expr: ^ast.Expr) -> bool { ^ast.Map_Type, ^ast.Bit_Set_Type, ^ast.Matrix_Type, - ^ast.Call_Expr: + ^ast.Call_Expr, + ^ast.Bit_Field_Type: return true } return false @@ -2893,7 +3049,7 @@ parse_elem_list :: proc(p: ^Parser) -> []^ast.Expr { eq := expect_token(p, .Eq) value := parse_value(p) - fv := ast.new(ast.Field_Value, elem.pos, value.end) + fv := ast.new(ast.Field_Value, elem.pos, value) fv.field = elem fv.sep = eq.pos fv.value = value @@ -2918,7 +3074,8 @@ parse_literal_value :: proc(p: ^Parser, type: ^ast.Expr) -> ^ast.Comp_Lit { } p.expr_level -= 1 - close := expect_token_after(p, .Close_Brace, "compound literal") + skip_possible_newline(p) + close := expect_closing_brace_of_field_list(p) pos := type.pos if type != nil else open.pos lit := ast.new(ast.Comp_Lit, pos, end_pos(close)) @@ -2962,7 +3119,7 @@ parse_call_expr :: proc(p: ^Parser, operand: ^ast.Expr) -> ^ast.Expr { } value := parse_value(p) - fv := ast.new(ast.Field_Value, arg.pos, value.end) + fv := ast.new(ast.Field_Value, arg.pos, value) fv.field = arg fv.sep = eq.pos fv.value = value @@ -2981,7 +3138,7 @@ parse_call_expr :: proc(p: ^Parser, operand: ^ast.Expr) -> ^ast.Expr { allow_token(p, .Comma) or_break } - close := expect_token_after(p, .Close_Paren, "argument list") + close := expect_closing_token_of_field_list(p, .Close_Paren, "argument list") p.expr_level -= 1 ce := ast.new(ast.Call_Expr, operand.pos, end_pos(close)) @@ -2993,7 +3150,7 @@ parse_call_expr :: proc(p: ^Parser, operand: ^ast.Expr) -> ^ast.Expr { o := ast.unparen_expr(operand) if se, ok := o.derived.(^ast.Selector_Expr); ok && se.op.kind == .Arrow_Right { - sce := ast.new(ast.Selector_Call_Expr, ce.pos, ce.end) + sce := ast.new(ast.Selector_Call_Expr, ce.pos, ce) sce.expr = o sce.call = ce return sce @@ -3101,7 +3258,7 @@ parse_atom_expr :: proc(p: ^Parser, value: ^ast.Expr, lhs: bool) -> (operand: ^a case .Ident: field := parse_ident(p) - sel := ast.new(ast.Selector_Expr, operand.pos, field.end) + sel := ast.new(ast.Selector_Expr, operand.pos, field) sel.expr = operand sel.op = tok sel.field = field @@ -3127,7 +3284,7 @@ parse_atom_expr :: proc(p: ^Parser, value: ^ast.Expr, lhs: bool) -> (operand: ^a type.op = question type.expr = nil - ta := ast.new(ast.Type_Assertion, operand.pos, type.end) + ta := ast.new(ast.Type_Assertion, operand.pos, type) ta.expr = operand ta.type = type @@ -3145,7 +3302,7 @@ parse_atom_expr :: proc(p: ^Parser, value: ^ast.Expr, lhs: bool) -> (operand: ^a case .Ident: field := parse_ident(p) - sel := ast.new(ast.Selector_Expr, operand.pos, field.end) + sel := ast.new(ast.Selector_Expr, operand.pos, field) sel.expr = operand sel.op = tok sel.field = field @@ -3225,7 +3382,7 @@ parse_unary_expr :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { close := expect_token(p, .Close_Paren) expr := parse_unary_expr(p, lhs) - tc := ast.new(ast.Type_Cast, tok.pos, expr.end) + tc := ast.new(ast.Type_Cast, tok.pos, expr) tc.tok = tok tc.open = open.pos tc.type = type @@ -3237,7 +3394,7 @@ parse_unary_expr :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { op := advance_token(p) expr := parse_unary_expr(p, lhs) - ac := ast.new(ast.Auto_Cast, op.pos, expr.end) + ac := ast.new(ast.Auto_Cast, op.pos, expr) ac.op = op ac.expr = expr return ac @@ -3247,8 +3404,8 @@ parse_unary_expr :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { .And: op := advance_token(p) expr := parse_unary_expr(p, lhs) - - ue := ast.new(ast.Unary_Expr, op.pos, expr.end) + + ue := ast.new(ast.Unary_Expr, op.pos, expr) ue.op = op ue.expr = expr return ue @@ -3258,7 +3415,7 @@ parse_unary_expr :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { error(p, op.pos, "unary '%s' operator is not supported", op.text) expr := parse_unary_expr(p, lhs) - ue := ast.new(ast.Unary_Expr, op.pos, expr.end) + ue := ast.new(ast.Unary_Expr, op.pos, expr) ue.op = op ue.expr = expr return ue @@ -3266,7 +3423,7 @@ parse_unary_expr :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { case .Period: op := advance_token(p) field := parse_ident(p) - ise := ast.new(ast.Implicit_Selector_Expr, op.pos, field.end) + ise := ast.new(ast.Implicit_Selector_Expr, op.pos, field) ise.field = field return ise @@ -3407,7 +3564,7 @@ parse_simple_stmt :: proc(p: ^Parser, flags: Stmt_Allow_Flags) -> ^ast.Stmt { error(p, p.curr_tok.pos, "no right-hand side in assignment statement") return ast.new(ast.Bad_Stmt, start_tok.pos, end_pos(p.curr_tok)) } - stmt := ast.new(ast.Assign_Stmt, lhs[0].pos, rhs[len(rhs)-1].end) + stmt := ast.new(ast.Assign_Stmt, lhs[0].pos, rhs[len(rhs)-1]) stmt.lhs = lhs stmt.op = op stmt.rhs = rhs @@ -3424,7 +3581,7 @@ parse_simple_stmt :: proc(p: ^Parser, flags: Stmt_Allow_Flags) -> ^ast.Stmt { rhs := make([]^ast.Expr, 1) rhs[0] = expr - stmt := ast.new(ast.Assign_Stmt, lhs[0].pos, rhs[len(rhs)-1].end) + stmt := ast.new(ast.Assign_Stmt, lhs[0].pos, rhs[len(rhs)-1]) stmt.lhs = lhs stmt.op = op stmt.rhs = rhs @@ -3433,6 +3590,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] @@ -3447,6 +3623,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 @@ -3466,7 +3658,7 @@ parse_simple_stmt :: proc(p: ^Parser, flags: Stmt_Allow_Flags) -> ^ast.Stmt { error(p, op.pos, "postfix '%s' statement is not supported", op.text) } - es := ast.new(ast.Expr_Stmt, lhs[0].pos, lhs[0].end) + es := ast.new(ast.Expr_Stmt, lhs[0].pos, lhs[0]) es.expr = lhs[0] return es } @@ -3504,6 +3696,8 @@ parse_value_decl :: proc(p: ^Parser, names: []^ast.Expr, docs: ^ast.Comment_Grou } } + end := p.prev_tok + if p.expr_level >= 0 { end: ^ast.Expr if !is_mutable && len(values) > 0 { @@ -3523,7 +3717,7 @@ parse_value_decl :: proc(p: ^Parser, names: []^ast.Expr, docs: ^ast.Comment_Grou } } - decl := ast.new(ast.Value_Decl, names[0].pos, end_pos(p.prev_tok)) + decl := ast.new(ast.Value_Decl, names[0].pos, end_pos(end)) decl.docs = docs decl.names = names decl.type = type 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 63a3b543d..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, line_index 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, line_index 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, i 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 1aefcf967..000000000 --- a/core/odin/printer/visit.odin +++ /dev/null @@ -1,1579 +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: - 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_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: - 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 _, ok := binary.left.derived.(^ast.Implicit_Selector_Expr); ok { - either_implicit_selector = true - } else if _, ok := binary.right.derived.(^ast.Implicit_Selector_Expr); ok { - 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/odin/tokenizer/token.odin b/core/odin/tokenizer/token.odin index 23808cf44..48d08f127 100644 --- a/core/odin/tokenizer/token.odin +++ b/core/odin/tokenizer/token.odin @@ -32,6 +32,7 @@ Token_Kind :: enum u32 { Invalid, EOF, Comment, + File_Tag, B_Literal_Begin, Ident, // main @@ -137,6 +138,7 @@ Token_Kind :: enum u32 { Union, // union Enum, // enum Bit_Set, // bit_set + Bit_Field, // bit_field Map, // map Dynamic, // dynamic Auto_Cast, // auto_cast @@ -165,6 +167,7 @@ tokens := [Token_Kind.COUNT]string { "Invalid", "EOF", "Comment", + "FileTag", "", "identifier", @@ -270,6 +273,7 @@ tokens := [Token_Kind.COUNT]string { "union", "enum", "bit_set", + "bit_field", "map", "dynamic", "auto_cast", diff --git a/core/odin/tokenizer/tokenizer.odin b/core/odin/tokenizer/tokenizer.odin index 41de3ac8b..d4da82c56 100644 --- a/core/odin/tokenizer/tokenizer.odin +++ b/core/odin/tokenizer/tokenizer.odin @@ -39,6 +39,7 @@ init :: proc(t: ^Tokenizer, src: string, path: string, err: Error_Handler = defa t.read_offset = 0 t.line_offset = 0 t.line_count = len(src) > 0 ? 1 : 0 + t.insert_semicolon = false t.error_count = 0 t.path = path @@ -205,6 +206,23 @@ scan_comment :: proc(t: ^Tokenizer) -> string { return string(lit) } +scan_file_tag :: proc(t: ^Tokenizer) -> string { + offset := t.offset - 1 + + for t.ch != '\n' { + if t.ch == '/' { + next := peek_byte(t, 0) + + if next == '/' || next == '*' { + break + } + } + advance_rune(t) + } + + return string(t.src[offset : t.offset]) +} + scan_identifier :: proc(t: ^Tokenizer) -> string { offset := t.offset @@ -313,7 +331,7 @@ scan_escape :: proc(t: ^Tokenizer) -> bool { n -= 1 } - if x > max || 0xd800 <= x && x <= 0xe000 { + if x > max || 0xd800 <= x && x <= 0xdfff { error(t, offset, "escape sequence is an invalid Unicode code point") return false } @@ -635,6 +653,9 @@ scan :: proc(t: ^Tokenizer) -> Token { if t.ch == '!' { kind = .Comment lit = scan_comment(t) + } else if t.ch == '+' { + kind = .File_Tag + lit = scan_file_tag(t) } case '/': kind = .Quo diff --git a/core/os/dir_darwin.odin b/core/os/dir_darwin.odin deleted file mode 100644 index 7d0f2936d..000000000 --- a/core/os/dir_darwin.odin +++ /dev/null @@ -1,69 +0,0 @@ -package os - -import "core:strings" -import "core:mem" - -read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) { - dirp: Dir - dirp, err = _fdopendir(fd) - if err != ERROR_NONE { - return - } - - defer _closedir(dirp) - - dirpath: string - dirpath, err = absolute_path_from_handle(fd) - if err != ERROR_NONE { - return - } - - defer delete(dirpath) - - n := n - size := n - if n <= 0 { - n = -1 - size = 100 - } - - dfi := make([dynamic]File_Info, 0, size, allocator) - - for { - entry: Dirent - end_of_stream: bool - entry, err, end_of_stream = _readdir(dirp) - if err != ERROR_NONE { - for fi_ in dfi { - file_info_delete(fi_, allocator) - } - delete(dfi) - return - } else if end_of_stream { - break - } - - fi_: File_Info - filename := cast(string)(transmute(cstring)mem.Raw_Cstring{ data = &entry.name[0] }) - - if filename == "." || filename == ".." { - continue - } - - fullpath := strings.join( []string{ dirpath, filename }, "/", context.temp_allocator) - defer delete(fullpath, context.temp_allocator) - - fi_, err = stat(fullpath, allocator) - if err != ERROR_NONE { - for fi__ in dfi { - file_info_delete(fi__, allocator) - } - delete(dfi) - return - } - - append(&dfi, fi_) - } - - return dfi[:], ERROR_NONE -} diff --git a/core/os/dir_freebsd.odin b/core/os/dir_freebsd.odin deleted file mode 100644 index 2965182cd..000000000 --- a/core/os/dir_freebsd.odin +++ /dev/null @@ -1,72 +0,0 @@ -package os - -import "core:mem" - -read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) { - dirp: Dir - dirp, err = _fdopendir(fd) - if err != ERROR_NONE { - return - } - - defer _closedir(dirp) - - dirpath: string - dirpath, err = absolute_path_from_handle(fd) - - if err != ERROR_NONE { - return - } - - defer delete(dirpath) - - n := n - size := n - if n <= 0 { - n = -1 - size = 100 - } - - dfi := make([dynamic]File_Info, 0, size, allocator) - - for { - entry: Dirent - end_of_stream: bool - entry, err, end_of_stream = _readdir(dirp) - if err != ERROR_NONE { - for fi_ in dfi { - file_info_delete(fi_, allocator) - } - delete(dfi) - return - } else if end_of_stream { - break - } - - fi_: File_Info - filename := cast(string)(transmute(cstring)mem.Raw_Cstring{ data = &entry.name[0] }) - - if filename == "." || filename == ".." { - continue - } - - fullpath := make([]byte, len(dirpath)+1+len(filename), context.temp_allocator) - copy(fullpath, dirpath) - copy(fullpath[len(dirpath):], "/") - copy(fullpath[len(dirpath)+1:], filename) - defer delete(fullpath, context.temp_allocator) - - fi_, err = stat(string(fullpath), allocator) - if err != ERROR_NONE { - for fi__ in dfi { - file_info_delete(fi__, allocator) - } - delete(dfi) - return - } - - append(&dfi, fi_) - } - - return dfi[:], ERROR_NONE -} diff --git a/core/os/dir_linux.odin b/core/os/dir_linux.odin deleted file mode 100644 index 4971fa9d5..000000000 --- a/core/os/dir_linux.odin +++ /dev/null @@ -1,72 +0,0 @@ -package os - -import "core:strings" -import "core:mem" -import "core:runtime" - -read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) { - dirp: Dir - dirp, err = _fdopendir(fd) - if err != ERROR_NONE { - return - } - - defer _closedir(dirp) - - dirpath: string - dirpath, err = absolute_path_from_handle(fd) - - if err != ERROR_NONE { - return - } - - defer delete(dirpath) - - n := n - size := n - if n <= 0 { - n = -1 - size = 100 - } - - dfi := make([dynamic]File_Info, 0, size, allocator) - - for { - entry: Dirent - end_of_stream: bool - entry, err, end_of_stream = _readdir(dirp) - if err != ERROR_NONE { - for fi_ in dfi { - file_info_delete(fi_, allocator) - } - delete(dfi) - return - } else if end_of_stream { - break - } - - fi_: File_Info - filename := cast(string)(transmute(cstring)mem.Raw_Cstring{ data = &entry.name[0] }) - - if filename == "." || filename == ".." { - continue - } - - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - fullpath := strings.join( []string{ dirpath, filename }, "/", context.temp_allocator) - defer delete(fullpath, context.temp_allocator) - - fi_, err = stat(fullpath, allocator) - if err != ERROR_NONE { - for fi__ in dfi { - file_info_delete(fi__, allocator) - } - delete(dfi) - return - } - - append(&dfi, fi_) - } - - return dfi[:], ERROR_NONE -} diff --git a/core/os/dir_openbsd.odin b/core/os/dir_openbsd.odin deleted file mode 100644 index 465fd35ae..000000000 --- a/core/os/dir_openbsd.odin +++ /dev/null @@ -1,71 +0,0 @@ -package os - -import "core:strings" -import "core:mem" - -read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) { - dirp: Dir - dirp, err = _fdopendir(fd) - if err != ERROR_NONE { - return - } - - defer _closedir(dirp) - - // XXX OpenBSD - dirpath: string - dirpath, err = absolute_path_from_handle(fd) - - if err != ERROR_NONE { - return - } - - defer delete(dirpath) - - n := n - size := n - if n <= 0 { - n = -1 - size = 100 - } - - dfi := make([dynamic]File_Info, 0, size, allocator) - - for { - entry: Dirent - end_of_stream: bool - entry, err, end_of_stream = _readdir(dirp) - if err != ERROR_NONE { - for fi_ in dfi { - file_info_delete(fi_, allocator) - } - delete(dfi) - return - } else if end_of_stream { - break - } - - fi_: File_Info - filename := cast(string)(transmute(cstring)mem.Raw_Cstring{ data = &entry.name[0] }) - - if filename == "." || filename == ".." { - continue - } - - fullpath := strings.join( []string{ dirpath, filename }, "/", context.temp_allocator) - defer delete(fullpath, context.temp_allocator) - - fi_, err = stat(fullpath, allocator) - if err != ERROR_NONE { - for fi__ in dfi { - file_info_delete(fi__, allocator) - } - delete(dfi) - return - } - - append(&dfi, fi_) - } - - return dfi[:], ERROR_NONE -} diff --git a/core/os/dir_unix.odin b/core/os/dir_unix.odin new file mode 100644 index 000000000..26e865204 --- /dev/null +++ b/core/os/dir_unix.odin @@ -0,0 +1,64 @@ +#+build darwin, linux, netbsd, freebsd, openbsd +package os + +import "core:strings" + +@(require_results) +read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Error) { + dupfd := _dup(fd) or_return + + dirp := _fdopendir(dupfd) or_return + defer _closedir(dirp) + + dirpath := absolute_path_from_handle(dupfd) or_return + defer delete(dirpath) + + n := n + size := n + if n <= 0 { + n = -1 + size = 100 + } + + dfi := make([dynamic]File_Info, 0, size, allocator) or_return + defer if err != nil { + for fi_ in dfi { + file_info_delete(fi_, allocator) + } + delete(dfi) + } + + for { + entry: Dirent + end_of_stream: bool + entry, err, end_of_stream = _readdir(dirp) + if err != nil { + return + } else if end_of_stream { + break + } + + fi_: File_Info + filename := string(cstring(&entry.name[0])) + + if filename == "." || filename == ".." { + continue + } + + fullpath := strings.join({ dirpath, filename }, "/", allocator) + + s: OS_Stat + s, err = _lstat(fullpath) + if err != nil { + delete(fullpath, allocator) + return + } + _fill_file_info_from_stat(&fi_, s) + fi_.fullpath = fullpath + fi_.name = path_base(fi_.fullpath) + + append(&dfi, fi_) + } + + return dfi[:], nil +} diff --git a/core/os/dir_windows.odin b/core/os/dir_windows.odin index 531a5cd82..ae3e6922c 100644 --- a/core/os/dir_windows.odin +++ b/core/os/dir_windows.odin @@ -2,9 +2,11 @@ package os import win32 "core:sys/windows" import "core:strings" -import "core:runtime" +import "base:runtime" -read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) { +@(require_results) +read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Error) { + @(require_results) find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW) -> (fi: File_Info) { // Ignore "." and ".." if d.cFileName[0] == '.' && d.cFileName[1] == 0 { @@ -57,7 +59,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F dir_fi, _ := file_info_from_get_file_information_by_handle("", h) if !dir_fi.is_dir { - return nil, ERROR_FILE_IS_NOT_DIR + return nil, .Not_Dir } n := n @@ -68,15 +70,14 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F } runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - wpath: []u16 - wpath, err = cleanpath_from_handle_u16(fd, context.temp_allocator) - if len(wpath) == 0 || err != ERROR_NONE { + wpath := cleanpath_from_handle_u16(fd, context.temp_allocator) or_return + if len(wpath) == 0 { return } - dfi := make([dynamic]File_Info, 0, size) + dfi := make([dynamic]File_Info, 0, size) or_return - wpath_search := make([]u16, len(wpath)+3, context.temp_allocator) + wpath_search := make([]u16, len(wpath)+3, context.temp_allocator) or_return copy(wpath_search, wpath) wpath_search[len(wpath)+0] = '\\' wpath_search[len(wpath)+1] = '*' @@ -87,8 +88,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 = get_last_error() + 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 != "" { @@ -97,7 +102,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F } if !win32.FindNextFileW(find_handle, find_data) { - e := Errno(win32.GetLastError()) + e := get_last_error() if e == ERROR_NO_MORE_FILES { break } @@ -105,5 +110,5 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F } } - return dfi[:], ERROR_NONE + return dfi[:], nil } diff --git a/core/os/env_windows.odin b/core/os/env_windows.odin index ff20f126a..efd002342 100644 --- a/core/os/env_windows.odin +++ b/core/os/env_windows.odin @@ -1,33 +1,28 @@ package os import win32 "core:sys/windows" -import "core:runtime" +import "base:runtime" // lookup_env gets the value of the environment variable named by the key // If the variable is found in the environment the value (which can be empty) is returned and the boolean is true // Otherwise the returned value will be empty and the boolean will be false // NOTE: the value will be allocated with the supplied allocator +@(require_results) lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { if key == "" { return } wkey := win32.utf8_to_wstring(key) n := win32.GetEnvironmentVariableW(wkey, nil, 0) - if n == 0 { - err := win32.GetLastError() - if err == u32(ERROR_ENVVAR_NOT_FOUND) { - return "", false - } + if n == 0 && get_last_error() == ERROR_ENVVAR_NOT_FOUND { + return "", false } runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - b := make([dynamic]u16, n, context.temp_allocator) + b, _ := make([dynamic]u16, n, context.temp_allocator) n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b))) - if n == 0 { - err := win32.GetLastError() - if err == u32(ERROR_ENVVAR_NOT_FOUND) { - return "", false - } + if n == 0 && get_last_error() == ERROR_ENVVAR_NOT_FOUND { + return "", false } value, _ = win32.utf16_to_utf8(b[:n], allocator) found = true @@ -39,41 +34,46 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin // It returns the value, which will be empty if the variable is not present // To distinguish between an empty value and an unset value, use lookup_env // NOTE: the value will be allocated with the supplied allocator +@(require_results) get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } // set_env sets the value of the environment variable named by the key -set_env :: proc(key, value: string) -> Errno { +set_env :: proc(key, value: string) -> Error { k := win32.utf8_to_wstring(key) v := win32.utf8_to_wstring(value) if !win32.SetEnvironmentVariableW(k, v) { - return Errno(win32.GetLastError()) + return get_last_error() } - return 0 + return nil } // unset_env unsets a single environment variable -unset_env :: proc(key: string) -> Errno { +unset_env :: proc(key: string) -> Error { k := win32.utf8_to_wstring(key) if !win32.SetEnvironmentVariableW(k, nil) { - return Errno(win32.GetLastError()) + return get_last_error() } - return 0 + return nil } // environ returns a copy of strings representing the environment, in the form "key=value" // NOTE: the slice of strings and the strings with be allocated using the supplied allocator +@(require_results) environ :: proc(allocator := context.allocator) -> []string { - envs := cast([^]win32.WCHAR)(win32.GetEnvironmentStringsW()) + envs := ([^]win32.WCHAR)(win32.GetEnvironmentStringsW()) if envs == nil { return nil } defer win32.FreeEnvironmentStringsW(envs) - r := make([dynamic]string, 0, 50, allocator) + r, err := make([dynamic]string, 0, 50, allocator) + if err != nil { + return nil + } for from, i := 0, 0; true; i += 1 { if c := envs[i]; c == 0 { if i <= from { diff --git a/core/os/errors.odin b/core/os/errors.odin new file mode 100644 index 000000000..691397f4b --- /dev/null +++ b/core/os/errors.odin @@ -0,0 +1,322 @@ +package os + +import "base:intrinsics" +import "base:runtime" +import "core:io" + +Platform_Error :: _Platform_Error +#assert(size_of(Platform_Error) <= 4) +#assert(intrinsics.type_has_nil(Platform_Error)) + +General_Error :: enum u32 { + None, + + Permission_Denied, + Exist, + Not_Exist, + Closed, + + Timeout, + + Broken_Pipe, + + // Indicates that an attempt to retrieve a file's size was made, but the + // file doesn't have a size. + No_Size, + + Invalid_File, + Invalid_Dir, + Invalid_Path, + Invalid_Callback, + + Pattern_Has_Separator, + + Unsupported, + + File_Is_Pipe, + Not_Dir, +} + + +Errno :: Error // alias for legacy use + +Error :: union #shared_nil { + General_Error, + io.Error, + runtime.Allocator_Error, + Platform_Error, +} +#assert(size_of(Error) == 8) + +ERROR_NONE :: Error{} + +ERROR_EOF :: io.Error.EOF + +@(require_results) +is_platform_error :: proc "contextless" (ferr: Error) -> (err: i32, ok: bool) { + v := ferr.(Platform_Error) or_else {} + return i32(v), i32(v) != 0 +} + +@(require_results) +error_string :: proc "contextless" (ferr: Error) -> string { + if ferr == nil { + return "" + } + switch e in ferr { + case General_Error: + switch e { + case .None: return "" + case .Permission_Denied: return "permission denied" + case .Exist: return "file already exists" + case .Not_Exist: return "file does not exist" + case .Closed: return "file already closed" + case .Timeout: return "i/o timeout" + case .Broken_Pipe: return "Broken pipe" + case .No_Size: return "file has no definite size" + case .Invalid_File: return "invalid file" + case .Invalid_Dir: return "invalid directory" + case .Invalid_Path: return "invalid path" + case .Invalid_Callback: return "invalid callback" + case .Unsupported: return "unsupported" + case .Pattern_Has_Separator: return "pattern has separator" + case .File_Is_Pipe: return "file is pipe" + case .Not_Dir: return "file is not directory" + } + case io.Error: + switch e { + case .None: return "" + case .EOF: return "eof" + case .Unexpected_EOF: return "unexpected eof" + case .Short_Write: return "short write" + case .Invalid_Write: return "invalid write result" + case .Short_Buffer: return "short buffer" + case .No_Progress: return "multiple read calls return no data or error" + case .Invalid_Whence: return "invalid whence" + case .Invalid_Offset: return "invalid offset" + case .Invalid_Unread: return "invalid unread" + case .Negative_Read: return "negative read" + case .Negative_Write: return "negative write" + case .Negative_Count: return "negative count" + case .Buffer_Full: return "buffer full" + case .Unknown, .Empty: // + } + case runtime.Allocator_Error: + switch e { + case .None: return "" + case .Out_Of_Memory: return "out of memory" + case .Invalid_Pointer: return "invalid allocator pointer" + case .Invalid_Argument: return "invalid allocator argument" + case .Mode_Not_Implemented: return "allocator mode not implemented" + } + case Platform_Error: + return _error_string(e) + } + + return "unknown error" +} + +print_error :: proc(f: Handle, ferr: Error, msg: string) -> (n: int, err: Error) { + err_str := error_string(ferr) + + // msg + ": " + err_str + '\n' + length := len(msg) + 2 + len(err_str) + 1 + buf_ := intrinsics.alloca(length, 1) + buf := buf_[:length] + + copy(buf, msg) + buf[len(msg)] = ':' + buf[len(msg) + 1] = ' ' + copy(buf[len(msg) + 2:], err_str) + buf[length - 1] = '\n' + return write(f, buf) +} + + +@(require_results, private) +_error_string :: proc "contextless" (e: Platform_Error) -> string where intrinsics.type_is_enum(Platform_Error) { + if e == nil { + return "" + } + + when ODIN_OS == .Darwin { + if s := string(_darwin_string_error(i32(e))); s != "" { + return s + } + } + + when ODIN_OS != .Linux { + @(require_results) + binary_search :: proc "contextless" (array: $A/[]$T, key: T) -> (index: int, found: bool) #no_bounds_check { + n := len(array) + left, right := 0, n + for left < right { + mid := int(uint(left+right) >> 1) + if array[mid] < key { + left = mid+1 + } else { + // equal or greater + right = mid + } + } + return left, left < n && array[left] == key + } + + err := runtime.Type_Info_Enum_Value(e) + + ti := &runtime.type_info_base(type_info_of(Platform_Error)).variant.(runtime.Type_Info_Enum) + if idx, ok := binary_search(ti.values, err); ok { + return ti.names[idx] + } + } else { + @(rodata, static) + pe_strings := [Platform_Error]string{ + .NONE = "", + .EPERM = "Operation not permitted", + .ENOENT = "No such file or directory", + .ESRCH = "No such process", + .EINTR = "Interrupted system call", + .EIO = "Input/output error", + .ENXIO = "No such device or address", + .E2BIG = "Argument list too long", + .ENOEXEC = "Exec format error", + .EBADF = "Bad file descriptor", + .ECHILD = "No child processes", + .EAGAIN = "Resource temporarily unavailable", + .ENOMEM = "Cannot allocate memory", + .EACCES = "Permission denied", + .EFAULT = "Bad address", + .ENOTBLK = "Block device required", + .EBUSY = "Device or resource busy", + .EEXIST = "File exists", + .EXDEV = "Invalid cross-device link", + .ENODEV = "No such device", + .ENOTDIR = "Not a directory", + .EISDIR = "Is a directory", + .EINVAL = "Invalid argument", + .ENFILE = "Too many open files in system", + .EMFILE = "Too many open files", + .ENOTTY = "Inappropriate ioctl for device", + .ETXTBSY = "Text file busy", + .EFBIG = "File too large", + .ENOSPC = "No space left on device", + .ESPIPE = "Illegal seek", + .EROFS = "Read-only file system", + .EMLINK = "Too many links", + .EPIPE = "Broken pipe", + .EDOM = "Numerical argument out of domain", + .ERANGE = "Numerical result out of range", + .EDEADLK = "Resource deadlock avoided", + .ENAMETOOLONG = "File name too long", + .ENOLCK = "No locks available", + .ENOSYS = "Function not implemented", + .ENOTEMPTY = "Directory not empty", + .ELOOP = "Too many levels of symbolic links", + .EUNKNOWN_41 = "Unknown Error (41)", + .ENOMSG = "No message of desired type", + .EIDRM = "Identifier removed", + .ECHRNG = "Channel number out of range", + .EL2NSYNC = "Level 2 not synchronized", + .EL3HLT = "Level 3 halted", + .EL3RST = "Level 3 reset", + .ELNRNG = "Link number out of range", + .EUNATCH = "Protocol driver not attached", + .ENOCSI = "No CSI structure available", + .EL2HLT = "Level 2 halted", + .EBADE = "Invalid exchange", + .EBADR = "Invalid request descriptor", + .EXFULL = "Exchange full", + .ENOANO = "No anode", + .EBADRQC = "Invalid request code", + .EBADSLT = "Invalid slot", + .EUNKNOWN_58 = "Unknown Error (58)", + .EBFONT = "Bad font file format", + .ENOSTR = "Device not a stream", + .ENODATA = "No data available", + .ETIME = "Timer expired", + .ENOSR = "Out of streams resources", + .ENONET = "Machine is not on the network", + .ENOPKG = "Package not installed", + .EREMOTE = "Object is remote", + .ENOLINK = "Link has been severed", + .EADV = "Advertise error", + .ESRMNT = "Srmount error", + .ECOMM = "Communication error on send", + .EPROTO = "Protocol error", + .EMULTIHOP = "Multihop attempted", + .EDOTDOT = "RFS specific error", + .EBADMSG = "Bad message", + .EOVERFLOW = "Value too large for defined data type", + .ENOTUNIQ = "Name not unique on network", + .EBADFD = "File descriptor in bad state", + .EREMCHG = "Remote address changed", + .ELIBACC = "Can not access a needed shared library", + .ELIBBAD = "Accessing a corrupted shared library", + .ELIBSCN = ".lib section in a.out corrupted", + .ELIBMAX = "Attempting to link in too many shared libraries", + .ELIBEXEC = "Cannot exec a shared library directly", + .EILSEQ = "Invalid or incomplete multibyte or wide character", + .ERESTART = "Interrupted system call should be restarted", + .ESTRPIPE = "Streams pipe error", + .EUSERS = "Too many users", + .ENOTSOCK = "Socket operation on non-socket", + .EDESTADDRREQ = "Destination address required", + .EMSGSIZE = "Message too long", + .EPROTOTYPE = "Protocol wrong type for socket", + .ENOPROTOOPT = "Protocol not available", + .EPROTONOSUPPORT = "Protocol not supported", + .ESOCKTNOSUPPORT = "Socket type not supported", + .EOPNOTSUPP = "Operation not supported", + .EPFNOSUPPORT = "Protocol family not supported", + .EAFNOSUPPORT = "Address family not supported by protocol", + .EADDRINUSE = "Address already in use", + .EADDRNOTAVAIL = "Cannot assign requested address", + .ENETDOWN = "Network is down", + .ENETUNREACH = "Network is unreachable", + .ENETRESET = "Network dropped connection on reset", + .ECONNABORTED = "Software caused connection abort", + .ECONNRESET = "Connection reset by peer", + .ENOBUFS = "No buffer space available", + .EISCONN = "Transport endpoint is already connected", + .ENOTCONN = "Transport endpoint is not connected", + .ESHUTDOWN = "Cannot send after transport endpoint shutdown", + .ETOOMANYREFS = "Too many references: cannot splice", + .ETIMEDOUT = "Connection timed out", + .ECONNREFUSED = "Connection refused", + .EHOSTDOWN = "Host is down", + .EHOSTUNREACH = "No route to host", + .EALREADY = "Operation already in progress", + .EINPROGRESS = "Operation now in progress", + .ESTALE = "Stale file handle", + .EUCLEAN = "Structure needs cleaning", + .ENOTNAM = "Not a XENIX named type file", + .ENAVAIL = "No XENIX semaphores available", + .EISNAM = "Is a named type file", + .EREMOTEIO = "Remote I/O error", + .EDQUOT = "Disk quota exceeded", + .ENOMEDIUM = "No medium found", + .EMEDIUMTYPE = "Wrong medium type", + .ECANCELED = "Operation canceled", + .ENOKEY = "Required key not available", + .EKEYEXPIRED = "Key has expired", + .EKEYREVOKED = "Key has been revoked", + .EKEYREJECTED = "Key was rejected by service", + .EOWNERDEAD = "Owner died", + .ENOTRECOVERABLE = "State not recoverable", + .ERFKILL = "Operation not possible due to RF-kill", + .EHWPOISON = "Memory page has hardware error", + } + if Platform_Error.NONE <= e && e <= max(Platform_Error) { + return pe_strings[e] + } + } + return "" +} + +@(private, require_results) +error_to_io_error :: proc(ferr: Error) -> io.Error { + if ferr == nil { + return .None + } + return ferr.(io.Error) or_else .Unknown +} diff --git a/core/os/file_windows.odin b/core/os/file_windows.odin deleted file mode 100644 index 9d62014af..000000000 --- a/core/os/file_windows.odin +++ /dev/null @@ -1,614 +0,0 @@ -package os - -import win32 "core:sys/windows" -import "core:intrinsics" -import "core:runtime" -import "core:unicode/utf16" - -is_path_separator :: proc(c: byte) -> bool { - return c == '/' || c == '\\' -} - -open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errno) { - if len(path) == 0 { - return INVALID_HANDLE, ERROR_FILE_NOT_FOUND - } - - access: u32 - switch mode & (O_RDONLY|O_WRONLY|O_RDWR) { - case O_RDONLY: access = win32.FILE_GENERIC_READ - case O_WRONLY: access = win32.FILE_GENERIC_WRITE - case O_RDWR: access = win32.FILE_GENERIC_READ | win32.FILE_GENERIC_WRITE - } - - if mode&O_CREATE != 0 { - access |= win32.FILE_GENERIC_WRITE - } - if mode&O_APPEND != 0 { - access &~= win32.FILE_GENERIC_WRITE - access |= win32.FILE_APPEND_DATA - } - - share_mode := win32.FILE_SHARE_READ|win32.FILE_SHARE_WRITE - sa: ^win32.SECURITY_ATTRIBUTES = nil - sa_inherit := win32.SECURITY_ATTRIBUTES{nLength = size_of(win32.SECURITY_ATTRIBUTES), bInheritHandle = true} - if mode&O_CLOEXEC == 0 { - sa = &sa_inherit - } - - create_mode: u32 - switch { - case mode&(O_CREATE|O_EXCL) == (O_CREATE | O_EXCL): - create_mode = win32.CREATE_NEW - case mode&(O_CREATE|O_TRUNC) == (O_CREATE | O_TRUNC): - create_mode = win32.CREATE_ALWAYS - case mode&O_CREATE == O_CREATE: - create_mode = win32.OPEN_ALWAYS - case mode&O_TRUNC == O_TRUNC: - create_mode = win32.TRUNCATE_EXISTING - case: - create_mode = win32.OPEN_EXISTING - } - wide_path := win32.utf8_to_wstring(path) - handle := Handle(win32.CreateFileW(wide_path, access, share_mode, sa, create_mode, win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS, nil)) - if handle != INVALID_HANDLE { - return handle, ERROR_NONE - } - - err := Errno(win32.GetLastError()) - return INVALID_HANDLE, err -} - -close :: proc(fd: Handle) -> Errno { - if !win32.CloseHandle(win32.HANDLE(fd)) { - return Errno(win32.GetLastError()) - } - return ERROR_NONE -} - -flush :: proc(fd: Handle) -> (err: Errno) { - if !win32.FlushFileBuffers(win32.HANDLE(fd)) { - err = Errno(win32.GetLastError()) - } - return -} - - - -write :: proc(fd: Handle, data: []byte) -> (int, Errno) { - if len(data) == 0 { - return 0, ERROR_NONE - } - - single_write_length: win32.DWORD - total_write: i64 - length := i64(len(data)) - - for total_write < length { - remaining := length - total_write - to_write := win32.DWORD(min(i32(remaining), MAX_RW)) - - e := win32.WriteFile(win32.HANDLE(fd), &data[total_write], to_write, &single_write_length, nil) - if single_write_length <= 0 || !e { - err := Errno(win32.GetLastError()) - return int(total_write), err - } - total_write += i64(single_write_length) - } - return int(total_write), ERROR_NONE -} - -@(private="file") -read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Errno) { - if len(b) == 0 { - return 0, 0 - } - - BUF_SIZE :: 386 - buf16: [BUF_SIZE]u16 - buf8: [4*BUF_SIZE]u8 - - for n < len(b) && err == 0 { - min_read := max(len(b)/4, 1 if len(b) > 0 else 0) - max_read := u32(min(BUF_SIZE, min_read)) - if max_read == 0 { - break - } - - single_read_length: u32 - ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil) - if !ok { - err = Errno(win32.GetLastError()) - } - - buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length]) - src := buf8[:buf8_len] - - ctrl_z := false - for i := 0; i < len(src) && n+i < len(b); i += 1 { - x := src[i] - if x == 0x1a { // ctrl-z - ctrl_z = true - break - } - b[n] = x - n += 1 - } - if ctrl_z || single_read_length < max_read { - break - } - - // NOTE(bill): if the last two values were a newline, then it is expected that - // this is the end of the input - if n >= 2 && single_read_length == max_read && string(b[n-2:n]) == "\r\n" { - break - } - - } - - return -} - -read :: proc(fd: Handle, data: []byte) -> (int, Errno) { - if len(data) == 0 { - return 0, ERROR_NONE - } - - handle := win32.HANDLE(fd) - - m: u32 - is_console := win32.GetConsoleMode(handle, &m) - - single_read_length: win32.DWORD - total_read: int - length := len(data) - - // NOTE(Jeroen): `length` can't be casted to win32.DWORD here because it'll overflow if > 4 GiB and return 0 if exactly that. - to_read := min(i64(length), MAX_RW) - - e: win32.BOOL - if is_console { - n, err := read_console(handle, data[total_read:][:to_read]) - total_read += n - if err != 0 { - return int(total_read), err - } - } else { - // NOTE(Jeroen): So we cast it here *after* we've ensured that `to_read` is at most MAX_RW (1 GiB) - e = win32.ReadFile(handle, &data[total_read], win32.DWORD(to_read), &single_read_length, nil) - } - if single_read_length <= 0 || !e { - err := Errno(win32.GetLastError()) - return int(total_read), err - } - total_read += int(single_read_length) - - return int(total_read), ERROR_NONE -} - -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { - w: u32 - switch whence { - case 0: w = win32.FILE_BEGIN - case 1: w = win32.FILE_CURRENT - case 2: w = win32.FILE_END - } - hi := i32(offset>>32) - lo := i32(offset) - ft := win32.GetFileType(win32.HANDLE(fd)) - if ft == win32.FILE_TYPE_PIPE { - return 0, ERROR_FILE_IS_PIPE - } - - dw_ptr := win32.SetFilePointer(win32.HANDLE(fd), lo, &hi, w) - if dw_ptr == win32.INVALID_SET_FILE_POINTER { - err := Errno(win32.GetLastError()) - return 0, err - } - return i64(hi)<<32 + i64(dw_ptr), ERROR_NONE -} - -file_size :: proc(fd: Handle) -> (i64, Errno) { - length: win32.LARGE_INTEGER - err: Errno - if !win32.GetFileSizeEx(win32.HANDLE(fd), &length) { - err = Errno(win32.GetLastError()) - } - return i64(length), err -} - - -@(private) -MAX_RW :: 1<<30 -ERROR_EOF :: 38 - -@(private) -pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { - buf := data - if len(buf) > MAX_RW { - buf = buf[:MAX_RW] - - } - - o := win32.OVERLAPPED{ - OffsetHigh = u32(offset>>32), - Offset = u32(offset), - } - - // TODO(bill): Determine the correct behaviour for consoles - - h := win32.HANDLE(fd) - done: win32.DWORD - e: Errno - if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) { - e = Errno(win32.GetLastError()) - done = 0 - } - return int(done), e -} -@(private) -pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { - buf := data - if len(buf) > MAX_RW { - buf = buf[:MAX_RW] - - } - - o := win32.OVERLAPPED{ - OffsetHigh = u32(offset>>32), - Offset = u32(offset), - } - - h := win32.HANDLE(fd) - done: win32.DWORD - e: Errno - if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) { - e = Errno(win32.GetLastError()) - done = 0 - } - return int(done), e -} - -/* -read_at returns n: 0, err: 0 on EOF -on Windows, read_at changes the position of the file cursor, on *nix, it does not. - - bytes: [8]u8{} - read_at(fd, bytes, 0) - read(fd, bytes) - -will read from the location twice on *nix, and from two different locations on Windows -*/ -read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) { - if offset < 0 { - return 0, ERROR_NEGATIVE_OFFSET - } - - b, offset := data, offset - for len(b) > 0 { - m, e := pread(fd, b, offset) - if e == ERROR_EOF { - err = 0 - break - } - if e != 0 { - err = e - break - } - n += m - b = b[m:] - offset += i64(m) - } - return -} - -/* -on Windows, write_at changes the position of the file cursor, on *nix, it does not. - - bytes: [8]u8{} - write_at(fd, bytes, 0) - write(fd, bytes) - -will write to the location twice on *nix, and to two different locations on Windows -*/ -write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) { - if offset < 0 { - return 0, ERROR_NEGATIVE_OFFSET - } - - b, offset := data, offset - for len(b) > 0 { - m, e := pwrite(fd, b, offset) - if e != 0 { - err = e - break - } - n += m - b = b[m:] - offset += i64(m) - } - return -} - - - -// 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)) - - -get_std_handle :: proc "contextless" (h: uint) -> Handle { - fd := win32.GetStdHandle(win32.DWORD(h)) - return Handle(fd) -} - - -exists :: proc(path: string) -> bool { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - wpath := win32.utf8_to_wstring(path, context.temp_allocator) - attribs := win32.GetFileAttributesW(wpath) - - return i32(attribs) != win32.INVALID_FILE_ATTRIBUTES -} - -is_file :: proc(path: string) -> bool { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - wpath := win32.utf8_to_wstring(path, context.temp_allocator) - attribs := win32.GetFileAttributesW(wpath) - - if i32(attribs) != win32.INVALID_FILE_ATTRIBUTES { - return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == 0 - } - return false -} - -is_dir :: proc(path: string) -> bool { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - wpath := win32.utf8_to_wstring(path, context.temp_allocator) - attribs := win32.GetFileAttributesW(wpath) - - if i32(attribs) != win32.INVALID_FILE_ATTRIBUTES { - return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != 0 - } - return false -} - -// NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName -@private cwd_lock := win32.SRWLOCK{} // zero is initialized - -get_current_directory :: proc(allocator := context.allocator) -> string { - win32.AcquireSRWLockExclusive(&cwd_lock) - - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - - sz_utf16 := win32.GetCurrentDirectoryW(0, nil) - dir_buf_wstr := make([]u16, sz_utf16, context.temp_allocator) // the first time, it _includes_ the NUL. - - sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr)) - assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL. - - win32.ReleaseSRWLockExclusive(&cwd_lock) - - return win32.utf16_to_utf8(dir_buf_wstr, allocator) or_else "" -} - -set_current_directory :: proc(path: string) -> (err: Errno) { - wstr := win32.utf8_to_wstring(path) - - win32.AcquireSRWLockExclusive(&cwd_lock) - - if !win32.SetCurrentDirectoryW(wstr) { - err = Errno(win32.GetLastError()) - } - - win32.ReleaseSRWLockExclusive(&cwd_lock) - - return -} - - - -change_directory :: proc(path: string) -> (err: Errno) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - wpath := win32.utf8_to_wstring(path, context.temp_allocator) - - if !win32.SetCurrentDirectoryW(wpath) { - err = Errno(win32.GetLastError()) - } - return -} - -make_directory :: proc(path: string, mode: u32 = 0) -> (err: Errno) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - // Mode is unused on Windows, but is needed on *nix - wpath := win32.utf8_to_wstring(path, context.temp_allocator) - - if !win32.CreateDirectoryW(wpath, nil) { - err = Errno(win32.GetLastError()) - } - return -} - - -remove_directory :: proc(path: string) -> (err: Errno) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - wpath := win32.utf8_to_wstring(path, context.temp_allocator) - - if !win32.RemoveDirectoryW(wpath) { - err = Errno(win32.GetLastError()) - } - return -} - - - -@(private) -is_abs :: proc(path: string) -> bool { - if len(path) > 0 && path[0] == '/' { - return true - } - when ODIN_OS == .Windows { - if len(path) > 2 { - switch path[0] { - case 'A'..='Z', 'a'..='z': - return path[1] == ':' && is_path_separator(path[2]) - } - } - } - return false -} - -@(private) -fix_long_path :: proc(path: string) -> string { - if len(path) < 248 { - return path - } - - if len(path) >= 2 && path[:2] == `\\` { - return path - } - if !is_abs(path) { - return path - } - - prefix :: `\\?` - - path_buf := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator) - copy(path_buf, prefix) - n := len(path) - r, w := 0, len(prefix) - for r < n { - switch { - case is_path_separator(path[r]): - r += 1 - case path[r] == '.' && (r+1 == n || is_path_separator(path[r+1])): - r += 1 - case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_path_separator(path[r+2])): - return path - case: - path_buf[w] = '\\' - w += 1 - for ; r < n && !is_path_separator(path[r]); r += 1 { - path_buf[w] = path[r] - w += 1 - } - } - } - - if w == len(`\\?\c:`) { - path_buf[w] = '\\' - w += 1 - } - return string(path_buf[:w]) -} - - -link :: proc(old_name, new_name: string) -> (err: Errno) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - n := win32.utf8_to_wstring(fix_long_path(new_name)) - o := win32.utf8_to_wstring(fix_long_path(old_name)) - return Errno(win32.CreateHardLinkW(n, o, nil)) -} - -unlink :: proc(path: string) -> (err: Errno) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - wpath := win32.utf8_to_wstring(path, context.temp_allocator) - - if !win32.DeleteFileW(wpath) { - err = Errno(win32.GetLastError()) - } - return -} - - - -rename :: proc(old_path, new_path: string) -> (err: Errno) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - from := win32.utf8_to_wstring(old_path, context.temp_allocator) - to := win32.utf8_to_wstring(new_path, context.temp_allocator) - - if !win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) { - err = Errno(win32.GetLastError()) - } - return -} - - -ftruncate :: proc(fd: Handle, length: i64) -> (err: Errno) { - curr_off, e := seek(fd, 0, 1) - if e != 0 { - return e - } - defer seek(fd, curr_off, 0) - _, e = seek(fd, length, 0) - if e != 0 { - return e - } - ok := win32.SetEndOfFile(win32.HANDLE(fd)) - if !ok { - return Errno(win32.GetLastError()) - } - return ERROR_NONE -} - -truncate :: proc(path: string, length: i64) -> (err: Errno) { - fd: Handle - fd, err = open(path, O_WRONLY|O_CREATE, 0o666) - if err != 0 { - return - } - defer close(fd) - err = ftruncate(fd, length) - return -} - - -remove :: proc(name: string) -> Errno { - p := win32.utf8_to_wstring(fix_long_path(name)) - err, err1: win32.DWORD - if !win32.DeleteFileW(p) { - err = win32.GetLastError() - } - if err == 0 { - return 0 - } - if !win32.RemoveDirectoryW(p) { - err1 = win32.GetLastError() - } - if err1 == 0 { - return 0 - } - - if err != err1 { - a := win32.GetFileAttributesW(p) - if a == ~u32(0) { - err = win32.GetLastError() - } else { - if a & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { - err = err1 - } else if a & win32.FILE_ATTRIBUTE_READONLY != 0 { - if win32.SetFileAttributesW(p, a &~ win32.FILE_ATTRIBUTE_READONLY) { - err = 0 - if !win32.DeleteFileW(p) { - err = win32.GetLastError() - } - } - } - } - } - - return Errno(err) -} - - -pipe :: proc() -> (r, w: Handle, err: Errno) { - sa: win32.SECURITY_ATTRIBUTES - sa.nLength = size_of(win32.SECURITY_ATTRIBUTES) - sa.bInheritHandle = true - if !win32.CreatePipe((^win32.HANDLE)(&r), (^win32.HANDLE)(&w), &sa, 0) { - err = Errno(win32.GetLastError()) - } - return -} - diff --git a/core/os/os.odin b/core/os/os.odin index 15864e47e..30b86d4cd 100644 --- a/core/os/os.odin +++ b/core/os/os.odin @@ -1,6 +1,8 @@ package os -import "core:mem" +import "base:intrinsics" +import "base:runtime" +import "core:io" import "core:strconv" import "core:unicode/utf8" @@ -13,15 +15,15 @@ SEEK_SET :: 0 SEEK_CUR :: 1 SEEK_END :: 2 -write_string :: proc(fd: Handle, str: string) -> (int, Errno) { +write_string :: proc(fd: Handle, str: string) -> (int, Error) { return write(fd, transmute([]byte)str) } -write_byte :: proc(fd: Handle, b: byte) -> (int, Errno) { +write_byte :: proc(fd: Handle, b: byte) -> (int, Error) { return write(fd, []byte{b}) } -write_rune :: proc(fd: Handle, r: rune) -> (int, Errno) { +write_rune :: proc(fd: Handle, r: rune) -> (int, Error) { if r < utf8.RUNE_SELF { return write_byte(fd, byte(r)) } @@ -30,105 +32,94 @@ write_rune :: proc(fd: Handle, r: rune) -> (int, Errno) { return write(fd, b[:n]) } -write_encoded_rune :: proc(fd: Handle, r: rune) { - write_byte(fd, '\'') +write_encoded_rune :: proc(f: Handle, r: rune) -> (n: int, err: Error) { + wrap :: proc(m: int, merr: Error, n: ^int, err: ^Error) -> bool { + n^ += m + if merr != nil { + err^ = merr + return true + } + return false + } + + if wrap(write_byte(f, '\''), &n, &err) { return } switch r { - case '\a': write_string(fd, "\\a") - case '\b': write_string(fd, "\\b") - case '\e': write_string(fd, "\\e") - case '\f': write_string(fd, "\\f") - case '\n': write_string(fd, "\\n") - case '\r': write_string(fd, "\\r") - case '\t': write_string(fd, "\\t") - case '\v': write_string(fd, "\\v") + case '\a': if wrap(write_string(f, "\\a"), &n, &err) { return } + case '\b': if wrap(write_string(f, "\\b"), &n, &err) { return } + case '\e': if wrap(write_string(f, "\\e"), &n, &err) { return } + case '\f': if wrap(write_string(f, "\\f"), &n, &err) { return } + case '\n': if wrap(write_string(f, "\\n"), &n, &err) { return } + case '\r': if wrap(write_string(f, "\\r"), &n, &err) { return } + case '\t': if wrap(write_string(f, "\\t"), &n, &err) { return } + case '\v': if wrap(write_string(f, "\\v"), &n, &err) { return } case: if r < 32 { - write_string(fd, "\\x") + if wrap(write_string(f, "\\x"), &n, &err) { return } b: [2]byte s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil) switch len(s) { - case 0: write_string(fd, "00") - case 1: write_rune(fd, '0') - case 2: write_string(fd, s) + case 0: if wrap(write_string(f, "00"), &n, &err) { return } + case 1: if wrap(write_rune(f, '0'), &n, &err) { return } + case 2: if wrap(write_string(f, s), &n, &err) { return } } } else { - write_rune(fd, r) + if wrap(write_rune(f, r), &n, &err) { return } } } - write_byte(fd, '\'') + _ = wrap(write_byte(f, '\''), &n, &err) + return } -read_at_least :: proc(fd: Handle, buf: []byte, min: int) -> (n: int, err: Errno) { +read_at_least :: proc(fd: Handle, buf: []byte, min: int) -> (n: int, err: Error) { if len(buf) < min { - return 0, -1 + return 0, io.Error.Short_Buffer } nn := max(int) - for nn > 0 && n < min && err == 0 { + for nn > 0 && n < min && err == nil { nn, err = read(fd, buf[n:]) n += nn } if n >= min { - err = 0 + err = nil } return } -read_full :: proc(fd: Handle, buf: []byte) -> (n: int, err: Errno) { +read_full :: proc(fd: Handle, buf: []byte) -> (n: int, err: Error) { return read_at_least(fd, buf, len(buf)) } +@(require_results) file_size_from_path :: proc(path: string) -> i64 { fd, err := open(path, O_RDONLY, 0) - if err != 0 { + if err != nil { return -1 } defer close(fd) length: i64 - if length, err = file_size(fd); err != 0 { + if length, err = file_size(fd); err != nil { return -1 } return length } +@(require_results) read_entire_file_from_filename :: proc(name: string, allocator := context.allocator, loc := #caller_location) -> (data: []byte, success: bool) { - context.allocator = allocator - - fd, err := open(name, O_RDONLY, 0) - if err != 0 { - return nil, false - } - defer close(fd) - - return read_entire_file_from_handle(fd, allocator, loc) + err: Error + data, err = read_entire_file_from_filename_or_err(name, allocator, loc) + success = err == nil + return } +@(require_results) read_entire_file_from_handle :: proc(fd: Handle, allocator := context.allocator, loc := #caller_location) -> (data: []byte, success: bool) { - context.allocator = allocator - - length: i64 - err: Errno - if length, err = file_size(fd); err != 0 { - return nil, false - } - - if length <= 0 { - return nil, true - } - - data = make([]byte, int(length), allocator, loc) - if data == nil { - return nil, false - } - - bytes_read, read_err := read_full(fd, data) - if read_err != ERROR_NONE { - delete(data) - return nil, false - } - return data[:bytes_read], true + err: Error + data, err = read_entire_file_from_handle_or_err(fd, allocator, loc) + success = err == nil + return } read_entire_file :: proc { @@ -136,7 +127,50 @@ read_entire_file :: proc { read_entire_file_from_handle, } +@(require_results) +read_entire_file_from_filename_or_err :: proc(name: string, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Error) { + context.allocator = allocator + + fd := open(name, O_RDONLY, 0) or_return + defer close(fd) + + return read_entire_file_from_handle_or_err(fd, allocator, loc) +} + +@(require_results) +read_entire_file_from_handle_or_err :: proc(fd: Handle, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Error) { + context.allocator = allocator + + length := file_size(fd) or_return + if length <= 0 { + return nil, nil + } + + data = make([]byte, int(length), allocator, loc) or_return + if data == nil { + return nil, nil + } + defer if err != nil { + delete(data, allocator) + } + + bytes_read := read_full(fd, data) or_return + data = data[:bytes_read] + return +} + +read_entire_file_or_err :: proc { + read_entire_file_from_filename_or_err, + read_entire_file_from_handle_or_err, +} + + write_entire_file :: proc(name: string, data: []byte, truncate := true) -> (success: bool) { + return write_entire_file_or_err(name, data, truncate) == nil +} + +@(require_results) +write_entire_file_or_err :: proc(name: string, data: []byte, truncate := true) -> Error { flags: int = O_WRONLY|O_CREATE if truncate { flags |= O_TRUNC @@ -148,120 +182,31 @@ write_entire_file :: proc(name: string, data: []byte, truncate := true) -> (succ mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH } - fd, err := open(name, flags, mode) - if err != 0 { - return false - } + fd := open(name, flags, mode) or_return defer close(fd) - _, write_err := write(fd, data) - return write_err == 0 + for n := 0; n < len(data); { + n += write(fd, data[n:]) or_return + } + return nil } -write_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Errno) { - s := transmute([]byte)mem.Raw_Slice{data, len} - return write(fd, s) +write_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Error) { + return write(fd, ([^]byte)(data)[:len]) } -read_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Errno) { - s := transmute([]byte)mem.Raw_Slice{data, len} - return read(fd, s) +read_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (int, Error) { + return read(fd, ([^]byte)(data)[:len]) } -heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, mem.Allocator_Error) { - // - // NOTE(tetra, 2020-01-14): The heap doesn't respect alignment. - // Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert - // padding. We also store the original pointer returned by heap_alloc right before - // the pointer we return to the user. - // +heap_allocator_proc :: runtime.heap_allocator_proc +heap_allocator :: runtime.heap_allocator - aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr = nil, zero_memory := true) -> ([]byte, mem.Allocator_Error) { - a := max(alignment, align_of(rawptr)) - space := size + a - 1 - - allocated_mem: rawptr - if old_ptr != nil { - original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^ - allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr)) - } else { - allocated_mem = heap_alloc(space+size_of(rawptr), zero_memory) - } - aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr))) - - ptr := uintptr(aligned_mem) - aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a) - diff := int(aligned_ptr - ptr) - if (size + diff) > space || allocated_mem == nil { - return nil, .Out_Of_Memory - } - - aligned_mem = rawptr(aligned_ptr) - mem.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem - - return mem.byte_slice(aligned_mem, size), nil - } - - aligned_free :: proc(p: rawptr) { - if p != nil { - heap_free(mem.ptr_offset((^rawptr)(p), -1)^) - } - } - - aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int) -> (new_memory: []byte, err: mem.Allocator_Error) { - if p == nil { - return nil, nil - } - - new_memory = aligned_alloc(new_size, new_alignment, p) or_return - - // NOTE: heap_resize does not zero the new memory, so we do it - if new_size > old_size { - new_region := mem.raw_data(new_memory[old_size:]) - mem.zero(new_region, new_size - old_size) - } - return - } - - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - return aligned_alloc(size, alignment, nil, mode == .Alloc) - - case .Free: - aligned_free(old_memory) - - case .Free_All: - return nil, .Mode_Not_Implemented - - case .Resize: - if old_memory == nil { - return aligned_alloc(size, alignment) - } - return aligned_resize(old_memory, old_size, size, alignment) - - case .Query_Features: - set := (^mem.Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Resize, .Query_Features} - } - return nil, nil - - case .Query_Info: - return nil, .Mode_Not_Implemented - } - - return nil, nil -} - -heap_allocator :: proc() -> mem.Allocator { - return mem.Allocator{ - procedure = heap_allocator_proc, - data = nil, - } -} +heap_alloc :: runtime.heap_alloc +heap_resize :: runtime.heap_resize +heap_free :: runtime.heap_free +@(require_results) processor_core_count :: proc() -> int { return _processor_core_count() } diff --git a/core/os/os2/allocators.odin b/core/os/os2/allocators.odin new file mode 100644 index 000000000..864532850 --- /dev/null +++ b/core/os/os2/allocators.odin @@ -0,0 +1,73 @@ +#+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") +MAX_TEMP_ARENA_COUNT :: 2 + +@(private="file", thread_local) +global_default_temp_allocator_arenas: [MAX_TEMP_ARENA_COUNT]runtime.Arena + +@(private="file", thread_local) +global_default_temp_allocator_index: uint + + +@(require_results) +temp_allocator :: proc() -> runtime.Allocator { + arena := &global_default_temp_allocator_arenas[global_default_temp_allocator_index] + if arena.backing_allocator.procedure == nil { + arena.backing_allocator = heap_allocator() + } + + return runtime.Allocator{ + procedure = temp_allocator_proc, + data = arena, + } +} + + + +@(require_results) +temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: runtime.Arena_Temp) { + temp = runtime.arena_temp_begin(&global_default_temp_allocator_arenas[global_default_temp_allocator_index], 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() { + for &arena in global_default_temp_allocator_arenas { + runtime.arena_destroy(&arena) + } + global_default_temp_allocator_arenas = {} +} + +TEMP_ALLOCATOR_GUARD_END :: proc(temp: runtime.Arena_Temp, loc := #caller_location) { + runtime.arena_temp_end(temp, loc) + if temp.arena != nil { + global_default_temp_allocator_index = (global_default_temp_allocator_index-1)%MAX_TEMP_ARENA_COUNT + } +} + +@(deferred_out=TEMP_ALLOCATOR_GUARD_END) +TEMP_ALLOCATOR_GUARD :: #force_inline proc(loc := #caller_location) -> (runtime.Arena_Temp, runtime.Source_Code_Location) { + global_default_temp_allocator_index = (global_default_temp_allocator_index+1)%MAX_TEMP_ARENA_COUNT + tmp := temp_allocator_temp_begin(loc) + return tmp, loc +} + +@(init, private) +init_thread_local_cleaner :: proc() { + runtime.add_thread_local_cleaner(temp_allocator_fini) +} diff --git a/core/os/os2/dir.odin b/core/os/os2/dir.odin new file mode 100644 index 000000000..a41ef68f9 --- /dev/null +++ b/core/os/os2/dir.odin @@ -0,0 +1,82 @@ +package os2 + +import "base:runtime" +import "core:slice" + +read_dir :: read_directory + +@(require_results) +read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files: []File_Info, err: Error) { + if f == nil { + return nil, .Invalid_File + } + + n := n + size := n + if n <= 0 { + n = -1 + size = 100 + } + + TEMP_ALLOCATOR_GUARD() + + it := read_directory_iterator_create(f) or_return + defer _read_directory_iterator_destroy(&it) + + dfi := make([dynamic]File_Info, 0, size, temp_allocator()) + defer if err != nil { + for fi in dfi { + file_info_delete(fi, allocator) + } + } + + for fi, index in read_directory_iterator(&it) { + if n > 0 && index == n { + break + } + append(&dfi, file_info_clone(fi, allocator) or_return) + } + + return slice.clone(dfi[:], allocator) +} + + +@(require_results) +read_all_directory :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) { + return read_directory(f, -1, allocator) +} + +@(require_results) +read_directory_by_path :: proc(path: string, n: int, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) { + f := open(path) or_return + defer close(f) + return read_directory(f, n, allocator) +} + +@(require_results) +read_all_directory_by_path :: proc(path: string, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) { + return read_directory_by_path(path, -1, allocator) +} + + + +Read_Directory_Iterator :: struct { + f: ^File, + impl: Read_Directory_Iterator_Impl, +} + + +@(require_results) +read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) { + return _read_directory_iterator_create(f) +} + +read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { + _read_directory_iterator_destroy(it) +} + +// NOTE(bill): `File_Info` does not need to deleted on each iteration. Any copies must be manually copied with `file_info_clone` +@(require_results) +read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { + return _read_directory_iterator(it) +} diff --git a/core/os/os2/dir_linux.odin b/core/os/os2/dir_linux.odin new file mode 100644 index 000000000..f26b4fc79 --- /dev/null +++ b/core/os/os2/dir_linux.odin @@ -0,0 +1,20 @@ +#+private +package os2 + +Read_Directory_Iterator_Impl :: struct { + +} + + +@(require_results) +_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { + return +} + +@(require_results) +_read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) { + return {}, .Unsupported +} + +_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { +} diff --git a/core/os/os2/dir_posix.odin b/core/os/os2/dir_posix.odin new file mode 100644 index 000000000..14fddde50 --- /dev/null +++ b/core/os/os2/dir_posix.odin @@ -0,0 +1,92 @@ +#+private +#+build darwin, netbsd, freebsd, openbsd +package os2 + +import "core:sys/posix" + +Read_Directory_Iterator_Impl :: struct { + dir: posix.DIR, + idx: int, + fullpath: [dynamic]byte, +} + +@(require_results) +_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { + fimpl := (^File_Impl)(it.f.impl) + + index = it.impl.idx + it.impl.idx += 1 + + for { + entry := posix.readdir(it.impl.dir) + if entry == nil { + // NOTE(laytan): would be good to have an `error` field on the `Read_Directory_Iterator` + // There isn't a way to now know if it failed or if we are at the end. + return + } + + cname := cstring(raw_data(entry.d_name[:])) + if cname == "." || cname == ".." { + continue + } + sname := string(cname) + + stat: posix.stat_t + if posix.fstatat(posix.dirfd(it.impl.dir), cname, &stat, { .SYMLINK_NOFOLLOW }) != .OK { + // NOTE(laytan): would be good to have an `error` field on the `Read_Directory_Iterator` + // There isn't a way to now know if it failed or if we are at the end. + return + } + + n := len(fimpl.name)+1 + non_zero_resize(&it.impl.fullpath, n+len(sname)) + n += copy(it.impl.fullpath[n:], sname) + + fi = internal_stat(stat, string(it.impl.fullpath[:])) + ok = true + return + } +} + +@(require_results) +_read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Iterator, err: Error) { + if f == nil || f.impl == nil { + err = .Invalid_File + return + } + + impl := (^File_Impl)(f.impl) + + iter.f = f + iter.impl.idx = 0 + + iter.impl.fullpath.allocator = file_allocator() + append(&iter.impl.fullpath, impl.name) + append(&iter.impl.fullpath, "/") + defer if err != nil { delete(iter.impl.fullpath) } + + // `fdopendir` consumes the file descriptor so we need to `dup` it. + dupfd := posix.dup(impl.fd) + if dupfd == -1 { + err = _get_platform_error() + return + } + defer if err != nil { posix.close(dupfd) } + + iter.impl.dir = posix.fdopendir(dupfd) + if iter.impl.dir == nil { + err = _get_platform_error() + return + } + + return +} + +_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { + if it == nil || it.impl.dir == nil { + return + } + + posix.closedir(it.impl.dir) + delete(it.impl.fullpath) +} diff --git a/core/os/os2/dir_windows.odin b/core/os/os2/dir_windows.odin new file mode 100644 index 000000000..f71e7e763 --- /dev/null +++ b/core/os/os2/dir_windows.odin @@ -0,0 +1,139 @@ +#+private +package os2 + +import "base:runtime" +import "core:time" +import win32 "core:sys/windows" + +@(private="file") +find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + // Ignore "." and ".." + if d.cFileName[0] == '.' && d.cFileName[1] == 0 { + return + } + if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 { + return + } + path := concatenate({base_path, `\`, win32_utf16_to_utf8(d.cFileName[:], temp_allocator()) or_else ""}, allocator) or_return + + handle := win32.HANDLE(_open_internal(path, {.Read}, 0o666) or_else 0) + defer win32.CloseHandle(handle) + + fi.fullpath = path + fi.name = basename(path) + fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) + + fi.type, fi.mode = _file_type_mode_from_file_attributes(d.dwFileAttributes, handle, d.dwReserved0) + + fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) + fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) + fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) + + if file_id_info: win32.FILE_ID_INFO; handle != nil && win32.GetFileInformationByHandleEx(handle, .FileIdInfo, &file_id_info, size_of(file_id_info)) { + #assert(size_of(fi.inode) == size_of(file_id_info.FileId)) + #assert(size_of(fi.inode) == 16) + runtime.mem_copy_non_overlapping(&fi.inode, &file_id_info.FileId, 16) + } + + return +} + +Read_Directory_Iterator_Impl :: struct { + find_data: win32.WIN32_FIND_DATAW, + find_handle: win32.HANDLE, + path: string, + prev_fi: File_Info, + no_more_files: bool, + index: int, +} + + +@(require_results) +_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { + if it.f == nil { + return + } + + TEMP_ALLOCATOR_GUARD() + + for !it.impl.no_more_files { + err: Error + file_info_delete(it.impl.prev_fi, file_allocator()) + it.impl.prev_fi = {} + + fi, err = find_data_to_file_info(it.impl.path, &it.impl.find_data, file_allocator()) + if err != nil { + return + } + if fi.name != "" { + it.impl.prev_fi = fi + ok = true + index = it.impl.index + it.impl.index += 1 + } + + if !win32.FindNextFileW(it.impl.find_handle, &it.impl.find_data) { + e := _get_platform_error() + if pe, _ := is_platform_error(e); pe == i32(win32.ERROR_NO_MORE_FILES) { + it.impl.no_more_files = true + } + it.impl.no_more_files = true + } + if ok { + return + } + } + return +} + +@(require_results) +_read_directory_iterator_create :: proc(f: ^File) -> (it: Read_Directory_Iterator, err: Error) { + if f == nil { + return + } + it.f = f + impl := (^File_Impl)(f.impl) + + if !is_directory(impl.name) { + err = .Invalid_Dir + return + } + + wpath: []u16 + { + i := 0 + for impl.wname[i] != 0 { + i += 1 + } + wpath = impl.wname[:i] + } + + TEMP_ALLOCATOR_GUARD() + + wpath_search := make([]u16, len(wpath)+3, temp_allocator()) + copy(wpath_search, wpath) + wpath_search[len(wpath)+0] = '\\' + wpath_search[len(wpath)+1] = '*' + wpath_search[len(wpath)+2] = 0 + + it.impl.find_handle = win32.FindFirstFileW(raw_data(wpath_search), &it.impl.find_data) + if it.impl.find_handle == win32.INVALID_HANDLE_VALUE { + err = _get_platform_error() + return + } + defer if err != nil { + win32.FindClose(it.impl.find_handle) + } + + it.impl.path = _cleanpath_from_buf(wpath, file_allocator()) or_return + return +} + +_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { + if it.f == nil { + return + } + file_info_delete(it.impl.prev_fi, file_allocator()) + delete(it.impl.path, file_allocator()) + win32.FindClose(it.impl.find_handle) +} diff --git a/core/os/os2/env.odin b/core/os/os2/env.odin index 54c26981b..c8d39b270 100644 --- a/core/os/os2/env.odin +++ b/core/os/os2/env.odin @@ -1,11 +1,12 @@ package os2 -import "core:runtime" +import "base:runtime" // get_env retrieves the value of the environment variable named by the key // 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 e7165b583..c248a323c 100644 --- a/core/os/os2/env_linux.odin +++ b/core/os/os2/env_linux.odin @@ -1,30 +1,231 @@ -//+private +#+private package os2 -import "core:runtime" +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_posix.odin b/core/os/os2/env_posix.odin new file mode 100644 index 000000000..e2080485d --- /dev/null +++ b/core/os/os2/env_posix.odin @@ -0,0 +1,77 @@ +#+private +#+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" + +import "core:strings" +import "core:sys/posix" + +_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { + if key == "" { + return + } + + TEMP_ALLOCATOR_GUARD() + + ckey := strings.clone_to_cstring(key, temp_allocator()) + cval := posix.getenv(ckey) + if cval == nil { + return + } + + found = true + value = strings.clone(string(cval), allocator) // NOTE(laytan): what if allocation fails? + + return +} + +_set_env :: proc(key, value: string) -> (ok: bool) { + TEMP_ALLOCATOR_GUARD() + + ckey := strings.clone_to_cstring(key, temp_allocator()) + cval := strings.clone_to_cstring(key, temp_allocator()) + + ok = posix.setenv(ckey, cval, true) == .OK + return +} + +_unset_env :: proc(key: string) -> (ok: bool) { + TEMP_ALLOCATOR_GUARD() + + ckey := strings.clone_to_cstring(key, temp_allocator()) + + ok = posix.unsetenv(ckey) == .OK + return +} + +// NOTE(laytan): clearing the env is weird, why would you ever do that? + +_clear_env :: proc() { + for i, entry := 0, posix.environ[0]; entry != nil; i, entry = i+1, posix.environ[i] { + key := strings.truncate_to_byte(string(entry), '=') + _unset_env(key) + } +} + +_environ :: proc(allocator: runtime.Allocator) -> (environ: []string) { + n := 0 + for entry := posix.environ[0]; entry != nil; n, entry = n+1, posix.environ[n] {} + + err: runtime.Allocator_Error + if environ, err = make([]string, n, allocator); err != nil { + // NOTE(laytan): is the environment empty or did allocation fail, how does the user know? + return + } + + for i, entry := 0, posix.environ[0]; entry != nil; i, entry = i+1, posix.environ[i] { + if environ[i], err = strings.clone(string(entry), allocator); err != nil { + // NOTE(laytan): is the entire environment returned or did allocation fail, how does the user know? + return + } + } + + return +} + + diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin index 105063343..a1e8c969d 100644 --- a/core/os/os2/env_windows.odin +++ b/core/os/os2/env_windows.odin @@ -1,14 +1,15 @@ -//+private +#+private package os2 import win32 "core:sys/windows" -import "core:runtime" +import "base:runtime" _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { if key == "" { return } - wkey := win32.utf8_to_wstring(key) + TEMP_ALLOCATOR_GUARD() + wkey, _ := win32_utf8_to_wstring(key, temp_allocator()) n := win32.GetEnvironmentVariableW(wkey, nil, 0) if n == 0 { @@ -18,7 +19,8 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string } return "", true } - 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 { @@ -29,25 +31,28 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string return "", false } - value = win32.utf16_to_utf8(b[:n], allocator) or_else "" + value = win32_utf16_to_utf8(b[:n], allocator) or_else "" found = true return } _set_env :: proc(key, value: string) -> bool { - k := win32.utf8_to_wstring(key) - v := win32.utf8_to_wstring(value) + TEMP_ALLOCATOR_GUARD() + k, _ := win32_utf8_to_wstring(key, temp_allocator()) + v, _ := win32_utf8_to_wstring(value, temp_allocator()) return bool(win32.SetEnvironmentVariableW(k, v)) } _unset_env :: proc(key: string) -> bool { - k := win32.utf8_to_wstring(key) + TEMP_ALLOCATOR_GUARD() + k, _ := win32_utf8_to_wstring(key, temp_allocator()) return bool(win32.SetEnvironmentVariableW(k, nil)) } _clear_env :: proc() { - envs := environ(_temp_allocator()) + TEMP_ALLOCATOR_GUARD() + envs := environ(temp_allocator()) for env in envs { for j in 1.. (err: i32, ok: bool) { v := ferr.(Platform_Error) or_else {} return i32(v), i32(v) != 0 } +@(require_results) error_string :: proc(ferr: Error) -> string { if ferr == nil { return "" @@ -51,10 +64,15 @@ error_string :: proc(ferr: Error) -> string { case .Not_Exist: return "file does not exist" case .Closed: return "file already closed" case .Timeout: return "i/o timeout" + case .Broken_Pipe: return "Broken pipe" + case .No_Size: return "file has no definite size" case .Invalid_File: return "invalid file" case .Invalid_Dir: return "invalid directory" case .Invalid_Path: return "invalid path" + case .Invalid_Callback: return "invalid callback" + case .Invalid_Command: return "invalid command" case .Unsupported: return "unsupported" + case .Pattern_Has_Separator: return "pattern has separator" } case io.Error: switch e { @@ -88,3 +106,19 @@ error_string :: proc(ferr: Error) -> string { return "unknown error" } + +print_error :: proc(f: ^File, ferr: Error, msg: string) { + TEMP_ALLOCATOR_GUARD() + err_str := error_string(ferr) + + // msg + ": " + err_str + '\n' + length := len(msg) + 2 + len(err_str) + 1 + buf := make([]u8, length, temp_allocator()) + + copy(buf, msg) + buf[len(msg)] = ':' + buf[len(msg) + 1] = ' ' + copy(buf[len(msg) + 2:], err_str) + buf[length - 1] = '\n' + write(f, buf) +} diff --git a/core/os/os2/errors_linux.odin b/core/os/os2/errors_linux.odin index 053256fbd..a7556c306 100644 --- a/core/os/os2/errors_linux.odin +++ b/core/os/os2/errors_linux.odin @@ -1,145 +1,177 @@ -//+private +#+private package os2 -import "core:sys/unix" +import "core:sys/linux" -EPERM :: 1 -ENOENT :: 2 -ESRCH :: 3 -EINTR :: 4 -EIO :: 5 -ENXIO :: 6 -EBADF :: 9 -EAGAIN :: 11 -ENOMEM :: 12 -EACCES :: 13 -EFAULT :: 14 -EEXIST :: 17 -ENODEV :: 19 -ENOTDIR :: 20 -EISDIR :: 21 -EINVAL :: 22 -ENFILE :: 23 -EMFILE :: 24 -ETXTBSY :: 26 -EFBIG :: 27 -ENOSPC :: 28 -ESPIPE :: 29 -EROFS :: 30 -EPIPE :: 32 -ERANGE :: 34 /* Result too large */ -EDEADLK :: 35 /* Resource deadlock would occur */ -ENAMETOOLONG :: 36 /* File name too long */ -ENOLCK :: 37 /* No record locks available */ -ENOSYS :: 38 /* Invalid system call number */ -ENOTEMPTY :: 39 /* Directory not empty */ -ELOOP :: 40 /* Too many symbolic links encountered */ -EWOULDBLOCK :: EAGAIN /* Operation would block */ -ENOMSG :: 42 /* No message of desired type */ -EIDRM :: 43 /* Identifier removed */ -ECHRNG :: 44 /* Channel number out of range */ -EL2NSYNC :: 45 /* Level 2 not synchronized */ -EL3HLT :: 46 /* Level 3 halted */ -EL3RST :: 47 /* Level 3 reset */ -ELNRNG :: 48 /* Link number out of range */ -EUNATCH :: 49 /* Protocol driver not attached */ -ENOCSI :: 50 /* No CSI structure available */ -EL2HLT :: 51 /* Level 2 halted */ -EBADE :: 52 /* Invalid exchange */ -EBADR :: 53 /* Invalid request descriptor */ -EXFULL :: 54 /* Exchange full */ -ENOANO :: 55 /* No anode */ -EBADRQC :: 56 /* Invalid request code */ -EBADSLT :: 57 /* Invalid slot */ -EDEADLOCK :: EDEADLK -EBFONT :: 59 /* Bad font file format */ -ENOSTR :: 60 /* Device not a stream */ -ENODATA :: 61 /* No data available */ -ETIME :: 62 /* Timer expired */ -ENOSR :: 63 /* Out of streams resources */ -ENONET :: 64 /* Machine is not on the network */ -ENOPKG :: 65 /* Package not installed */ -EREMOTE :: 66 /* Object is remote */ -ENOLINK :: 67 /* Link has been severed */ -EADV :: 68 /* Advertise error */ -ESRMNT :: 69 /* Srmount error */ -ECOMM :: 70 /* Communication error on send */ -EPROTO :: 71 /* Protocol error */ -EMULTIHOP :: 72 /* Multihop attempted */ -EDOTDOT :: 73 /* RFS specific error */ -EBADMSG :: 74 /* Not a data message */ -EOVERFLOW :: 75 /* Value too large for defined data type */ -ENOTUNIQ :: 76 /* Name not unique on network */ -EBADFD :: 77 /* File descriptor in bad state */ -EREMCHG :: 78 /* Remote address changed */ -ELIBACC :: 79 /* Can not access a needed shared library */ -ELIBBAD :: 80 /* Accessing a corrupted shared library */ -ELIBSCN :: 81 /* .lib section in a.out corrupted */ -ELIBMAX :: 82 /* Attempting to link in too many shared libraries */ -ELIBEXEC :: 83 /* Cannot exec a shared library directly */ -EILSEQ :: 84 /* Illegal byte sequence */ -ERESTART :: 85 /* Interrupted system call should be restarted */ -ESTRPIPE :: 86 /* Streams pipe error */ -EUSERS :: 87 /* Too many users */ -ENOTSOCK :: 88 /* Socket operation on non-socket */ -EDESTADDRREQ :: 89 /* Destination address required */ -EMSGSIZE :: 90 /* Message too long */ -EPROTOTYPE :: 91 /* Protocol wrong type for socket */ -ENOPROTOOPT :: 92 /* Protocol not available */ -EPROTONOSUPPORT:: 93 /* Protocol not supported */ -ESOCKTNOSUPPORT:: 94 /* Socket type not supported */ -EOPNOTSUPP :: 95 /* Operation not supported on transport endpoint */ -EPFNOSUPPORT :: 96 /* Protocol family not supported */ -EAFNOSUPPORT :: 97 /* Address family not supported by protocol */ -EADDRINUSE :: 98 /* Address already in use */ -EADDRNOTAVAIL :: 99 /* Cannot assign requested address */ -ENETDOWN :: 100 /* Network is down */ -ENETUNREACH :: 101 /* Network is unreachable */ -ENETRESET :: 102 /* Network dropped connection because of reset */ -ECONNABORTED :: 103 /* Software caused connection abort */ -ECONNRESET :: 104 /* Connection reset by peer */ -ENOBUFS :: 105 /* No buffer space available */ -EISCONN :: 106 /* Transport endpoint is already connected */ -ENOTCONN :: 107 /* Transport endpoint is not connected */ -ESHUTDOWN :: 108 /* Cannot send after transport endpoint shutdown */ -ETOOMANYREFS :: 109 /* Too many references: cannot splice */ -ETIMEDOUT :: 110 /* Connection timed out */ -ECONNREFUSED :: 111 /* Connection refused */ -EHOSTDOWN :: 112 /* Host is down */ -EHOSTUNREACH :: 113 /* No route to host */ -EALREADY :: 114 /* Operation already in progress */ -EINPROGRESS :: 115 /* Operation now in progress */ -ESTALE :: 116 /* Stale file handle */ -EUCLEAN :: 117 /* Structure needs cleaning */ -ENOTNAM :: 118 /* Not a XENIX named type file */ -ENAVAIL :: 119 /* No XENIX semaphores available */ -EISNAM :: 120 /* Is a named type file */ -EREMOTEIO :: 121 /* Remote I/O error */ -EDQUOT :: 122 /* Quota exceeded */ -ENOMEDIUM :: 123 /* No medium found */ -EMEDIUMTYPE :: 124 /* Wrong medium type */ -ECANCELED :: 125 /* Operation Canceled */ -ENOKEY :: 126 /* Required key not available */ -EKEYEXPIRED :: 127 /* Key has expired */ -EKEYREVOKED :: 128 /* Key has been revoked */ -EKEYREJECTED :: 129 /* Key was rejected by service */ -EOWNERDEAD :: 130 /* Owner died */ -ENOTRECOVERABLE:: 131 /* State not recoverable */ -ERFKILL :: 132 /* Operation not possible due to RF-kill */ -EHWPOISON :: 133 /* Memory page has hardware error */ +_Platform_Error :: linux.Errno + +@(rodata) +_errno_strings := [linux.Errno]string{ + .NONE = "", + .EPERM = "Operation not permitted", + .ENOENT = "No such file or directory", + .ESRCH = "No such process", + .EINTR = "Interrupted system call", + .EIO = "Input/output error", + .ENXIO = "No such device or address", + .E2BIG = "Argument list too long", + .ENOEXEC = "Exec format error", + .EBADF = "Bad file descriptor", + .ECHILD = "No child processes", + .EAGAIN = "Resource temporarily unavailable", + .ENOMEM = "Cannot allocate memory", + .EACCES = "Permission denied", + .EFAULT = "Bad address", + .ENOTBLK = "Block device required", + .EBUSY = "Device or resource busy", + .EEXIST = "File exists", + .EXDEV = "Invalid cross-device link", + .ENODEV = "No such device", + .ENOTDIR = "Not a directory", + .EISDIR = "Is a directory", + .EINVAL = "Invalid argument", + .ENFILE = "Too many open files in system", + .EMFILE = "Too many open files", + .ENOTTY = "Inappropriate ioctl for device", + .ETXTBSY = "Text file busy", + .EFBIG = "File too large", + .ENOSPC = "No space left on device", + .ESPIPE = "Illegal seek", + .EROFS = "Read-only file system", + .EMLINK = "Too many links", + .EPIPE = "Broken pipe", + .EDOM = "Numerical argument out of domain", + .ERANGE = "Numerical result out of range", + .EDEADLK = "Resource deadlock avoided", + .ENAMETOOLONG = "File name too long", + .ENOLCK = "No locks available", + .ENOSYS = "Function not implemented", + .ENOTEMPTY = "Directory not empty", + .ELOOP = "Too many levels of symbolic links", + .EUNKNOWN_41 = "Unknown Error (41)", + .ENOMSG = "No message of desired type", + .EIDRM = "Identifier removed", + .ECHRNG = "Channel number out of range", + .EL2NSYNC = "Level 2 not synchronized", + .EL3HLT = "Level 3 halted", + .EL3RST = "Level 3 reset", + .ELNRNG = "Link number out of range", + .EUNATCH = "Protocol driver not attached", + .ENOCSI = "No CSI structure available", + .EL2HLT = "Level 2 halted", + .EBADE = "Invalid exchange", + .EBADR = "Invalid request descriptor", + .EXFULL = "Exchange full", + .ENOANO = "No anode", + .EBADRQC = "Invalid request code", + .EBADSLT = "Invalid slot", + .EUNKNOWN_58 = "Unknown Error (58)", + .EBFONT = "Bad font file format", + .ENOSTR = "Device not a stream", + .ENODATA = "No data available", + .ETIME = "Timer expired", + .ENOSR = "Out of streams resources", + .ENONET = "Machine is not on the network", + .ENOPKG = "Package not installed", + .EREMOTE = "Object is remote", + .ENOLINK = "Link has been severed", + .EADV = "Advertise error", + .ESRMNT = "Srmount error", + .ECOMM = "Communication error on send", + .EPROTO = "Protocol error", + .EMULTIHOP = "Multihop attempted", + .EDOTDOT = "RFS specific error", + .EBADMSG = "Bad message", + .EOVERFLOW = "Value too large for defined data type", + .ENOTUNIQ = "Name not unique on network", + .EBADFD = "File descriptor in bad state", + .EREMCHG = "Remote address changed", + .ELIBACC = "Can not access a needed shared library", + .ELIBBAD = "Accessing a corrupted shared library", + .ELIBSCN = ".lib section in a.out corrupted", + .ELIBMAX = "Attempting to link in too many shared libraries", + .ELIBEXEC = "Cannot exec a shared library directly", + .EILSEQ = "Invalid or incomplete multibyte or wide character", + .ERESTART = "Interrupted system call should be restarted", + .ESTRPIPE = "Streams pipe error", + .EUSERS = "Too many users", + .ENOTSOCK = "Socket operation on non-socket", + .EDESTADDRREQ = "Destination address required", + .EMSGSIZE = "Message too long", + .EPROTOTYPE = "Protocol wrong type for socket", + .ENOPROTOOPT = "Protocol not available", + .EPROTONOSUPPORT = "Protocol not supported", + .ESOCKTNOSUPPORT = "Socket type not supported", + .EOPNOTSUPP = "Operation not supported", + .EPFNOSUPPORT = "Protocol family not supported", + .EAFNOSUPPORT = "Address family not supported by protocol", + .EADDRINUSE = "Address already in use", + .EADDRNOTAVAIL = "Cannot assign requested address", + .ENETDOWN = "Network is down", + .ENETUNREACH = "Network is unreachable", + .ENETRESET = "Network dropped connection on reset", + .ECONNABORTED = "Software caused connection abort", + .ECONNRESET = "Connection reset by peer", + .ENOBUFS = "No buffer space available", + .EISCONN = "Transport endpoint is already connected", + .ENOTCONN = "Transport endpoint is not connected", + .ESHUTDOWN = "Cannot send after transport endpoint shutdown", + .ETOOMANYREFS = "Too many references: cannot splice", + .ETIMEDOUT = "Connection timed out", + .ECONNREFUSED = "Connection refused", + .EHOSTDOWN = "Host is down", + .EHOSTUNREACH = "No route to host", + .EALREADY = "Operation already in progress", + .EINPROGRESS = "Operation now in progress", + .ESTALE = "Stale file handle", + .EUCLEAN = "Structure needs cleaning", + .ENOTNAM = "Not a XENIX named type file", + .ENAVAIL = "No XENIX semaphores available", + .EISNAM = "Is a named type file", + .EREMOTEIO = "Remote I/O error", + .EDQUOT = "Disk quota exceeded", + .ENOMEDIUM = "No medium found", + .EMEDIUMTYPE = "Wrong medium type", + .ECANCELED = "Operation canceled", + .ENOKEY = "Required key not available", + .EKEYEXPIRED = "Key has expired", + .EKEYREVOKED = "Key has been revoked", + .EKEYREJECTED = "Key was rejected by service", + .EOWNERDEAD = "Owner died", + .ENOTRECOVERABLE = "State not recoverable", + .ERFKILL = "Operation not possible due to RF-kill", + .EHWPOISON = "Memory page has hardware error", +} + + +_get_platform_error :: proc(errno: linux.Errno) -> Error { + #partial switch errno { + case .NONE: + return nil + case .EPERM: + return .Permission_Denied + case .EEXIST: + return .Exist + case .ENOENT: + return .Not_Exist + case .ETIMEDOUT: + return .Timeout + case .EPIPE: + return .Broken_Pipe + case .EBADF: + return .Invalid_File + case .ENOMEM: + return .Out_Of_Memory + case .ENOSYS: + return .Unsupported + } -_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_posix.odin b/core/os/os2/errors_posix.odin new file mode 100644 index 000000000..0b5876c0b --- /dev/null +++ b/core/os/os2/errors_posix.odin @@ -0,0 +1,34 @@ +#+private +#+build darwin, netbsd, freebsd, openbsd +package os2 + +import "core:sys/posix" + +_Platform_Error :: posix.Errno + +_error_string :: proc(errno: i32) -> string { + return string(posix.strerror(posix.Errno(errno))) +} + +_get_platform_error :: proc() -> Error { + #partial switch errno := posix.errno(); errno { + case .EPERM: + return .Permission_Denied + case .EEXIST: + return .Exist + case .ENOENT: + return .Not_Exist + case .ETIMEDOUT: + return .Timeout + case .EPIPE: + return .Broken_Pipe + case .EBADF: + return .Invalid_File + case .ENOMEM: + return .Out_Of_Memory + case .ENOSYS: + return .Unsupported + case: + return Platform_Error(errno) + } +} diff --git a/core/os/os2/errors_windows.odin b/core/os/os2/errors_windows.odin index 6500e7ccc..404560f98 100644 --- a/core/os/os2/errors_windows.odin +++ b/core/os/os2/errors_windows.odin @@ -1,16 +1,25 @@ -//+private +#+private package os2 +import "base:runtime" +import "core:slice" import win32 "core:sys/windows" +_Platform_Error :: win32.System_Error + _error_string :: proc(errno: i32) -> string { e := win32.DWORD(errno) if e == 0 { return "" } - // TODO(bill): _error_string for windows - // FormatMessageW - return "" + + err := runtime.Type_Info_Enum_Value(e) + + ti := &runtime.type_info_base(type_info_of(win32.System_Error)).variant.(runtime.Type_Info_Enum) + if idx, ok := slice.binary_search(ti.values, err); ok { + return ti.names[idx] + } + return "" } _get_platform_error :: proc() -> Error { @@ -43,13 +52,18 @@ _get_platform_error :: proc() -> Error { case win32.ERROR_INVALID_HANDLE: return .Invalid_File + case win32.ERROR_NEGATIVE_SEEK: + return .Invalid_Offset + + case win32.ERROR_BROKEN_PIPE: + return .Broken_Pipe + case win32.ERROR_BAD_ARGUMENTS, win32.ERROR_INVALID_PARAMETER, win32.ERROR_NOT_ENOUGH_MEMORY, win32.ERROR_NO_MORE_FILES, win32.ERROR_LOCK_VIOLATION, - win32.ERROR_BROKEN_PIPE, win32.ERROR_CALL_NOT_IMPLEMENTED, win32.ERROR_INSUFFICIENT_BUFFER, win32.ERROR_INVALID_NAME, @@ -61,4 +75,4 @@ _get_platform_error :: proc() -> Error { // fallthrough } return Platform_Error(err) -} \ No newline at end of file +} diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index da822374a..1a25472a1 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -2,20 +2,59 @@ package os2 import "core:io" import "core:time" -import "core:runtime" +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 { @@ -27,7 +66,7 @@ File_Flag :: enum { Sync, Trunc, Sparse, - Close_On_Exec, + Inheritable, Unbuffered_IO, } @@ -41,87 +80,116 @@ 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) +// open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm := 0o777) -> (^File, Error) { +// if buffer_size == 0 { +// return _open(name, flags, perm) +// } +// return _open_buffered(name, buffer_size, flags, perm) +// } + + +@(require_results) new_file :: proc(handle: uintptr, name: string) -> ^File { - return _new_file(handle, name) + file, err := _new_file(handle, name, file_allocator()) + if err != nil { + panic(error_string(err)) + } + return file } +@(require_results) fd :: proc(f: ^File) -> uintptr { return _fd(f) } +@(require_results) name :: proc(f: ^File) -> string { return _name(f) } +/* + Close a file and its stream. + + Any further use of the file or its stream should be considered to be in the + same class of bugs as a use-after-free. +*/ close :: proc(f: ^File) -> Error { if f != nil { - return io.close(f.impl.stream) + return io.close(f.stream) } return nil } seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { if f != nil { - return io.seek(f.impl.stream, offset, whence) + return io.seek(f.stream, offset, whence) } return 0, .Invalid_File } read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { if f != nil { - return io.read(f.impl.stream, p) + return io.read(f.stream, p) } return 0, .Invalid_File } read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { if f != nil { - return io.read_at(f.impl.stream, p, offset) + return io.read_at(f.stream, p, offset) } return 0, .Invalid_File } write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { if f != nil { - return io.write(f.impl.stream, p) + return io.write(f.stream, p) } return 0, .Invalid_File } write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { if f != nil { - return io.write_at(f.impl.stream, p, offset) + return io.write_at(f.stream, p, offset) } return 0, .Invalid_File } file_size :: proc(f: ^File) -> (n: i64, err: Error) { if f != nil { - return io.size(f.impl.stream) + return io.size(f.stream) } return 0, .Invalid_File } flush :: proc(f: ^File) -> Error { if f != nil { - return io.flush(f.impl.stream) + return io.flush(f.stream) } return nil } @@ -156,54 +224,86 @@ read_link :: proc(name: string, allocator: runtime.Allocator) -> (string, Error) } -chdir :: proc(name: string) -> Error { +chdir :: change_directory + +change_directory :: proc(name: string) -> Error { return _chdir(name) } -chmod :: proc(name: string, mode: File_Mode) -> Error { +chmod :: change_mode + +change_mode :: proc(name: string, mode: int) -> Error { return _chmod(name, mode) } -chown :: proc(name: string, uid, gid: int) -> Error { +chown :: change_owner + +change_owner :: proc(name: string, uid, gid: int) -> Error { return _chown(name, uid, gid) } -fchdir :: proc(f: ^File) -> Error { +fchdir :: fchange_directory + +fchange_directory :: proc(f: ^File) -> Error { return _fchdir(f) } -fchmod :: proc(f: ^File, mode: File_Mode) -> Error { +fchmod :: fchange_mode + +fchange_mode :: proc(f: ^File, mode: int) -> Error { return _fchmod(f, mode) } -fchown :: proc(f: ^File, uid, gid: int) -> Error { +fchown :: fchange_owner + +fchange_owner :: proc(f: ^File, uid, gid: int) -> Error { return _fchown(f, uid, gid) } +lchown :: change_owner_do_not_follow_links -lchown :: proc(name: string, uid, gid: int) -> Error { +change_owner_do_not_follow_links :: proc(name: string, uid, gid: int) -> Error { return _lchown(name, uid, gid) } +chtimes :: change_times -chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { +change_times :: proc(name: string, atime, mtime: time.Time) -> Error { return _chtimes(name, atime, mtime) } -fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { + +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) + TEMP_ALLOCATOR_GUARD() + fi, err := stat(path, temp_allocator()) + if err != nil { + return false + } + return fi.type == .Regular } -is_dir :: proc(path: string) -> bool { - return _is_dir(path) +is_dir :: is_directory + +@(require_results) +is_directory :: proc(path: string) -> bool { + TEMP_ALLOCATOR_GUARD() + fi, err := stat(path, temp_allocator()) + if err != nil { + return false + } + return fi.type == .Directory } @@ -211,15 +311,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_dir { + 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 ddd827bce..f8e4026da 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -1,374 +1,489 @@ -//+private +#+private package os2 +import "base:runtime" import "core:io" import "core:time" -import "core:strings" -import "core:runtime" -import "core:sys/unix" +import "core:sync" +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, - stream: io.Stream, + buffer: []byte, + rw_mutex: sync.RW_Mutex, // read write calls + p_mutex: sync.Mutex, // pread pwrite calls } -_file_allocator :: proc() -> runtime.Allocator { - return heap_allocator() +_stdin := File{ + stream = { + procedure = _file_stream_proc, + }, + fstat = _fstat, +} +_stdout := File{ + stream = { + procedure = _file_stream_proc, + }, + fstat = _fstat, +} +_stderr := File{ + stream = { + procedure = _file_stream_proc, + }, + fstat = _fstat, } -_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) +@init +_standard_stream_init :: proc() { + new_std :: proc(impl: ^File_Impl, fd: linux.Fd, name: string) -> ^File { + impl.file.impl = impl + impl.fd = linux.Fd(fd) + impl.allocator = runtime.nil_allocator() + impl.name = name + impl.file.stream = { + data = impl, + procedure = _file_stream_proc, + } + impl.file.fstat = _fstat + return &impl.file + } + + @(static) files: [3]File_Impl + stdin = new_std(&files[0], 0, "/proc/self/fd/0") + stdout = new_std(&files[1], 1, "/proc/self/fd/1") + stderr = new_std(&files[2], 2, "/proc/self/fd/2") +} + +_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 - 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 + sys_flags: linux.Open_Flags = {.NOCTTY, .CLOEXEC} + when size_of(rawptr) == 4 { + sys_flags += {.LARGEFILE} + } + switch flags & (O_RDONLY|O_WRONLY|O_RDWR) { + case O_RDONLY: + case O_WRONLY: sys_flags += {.WRONLY} + case O_RDWR: sys_flags += {.RDWR} + } + if .Append in flags { sys_flags += {.APPEND} } + if .Create in flags { sys_flags += {.CREAT} } + if .Excl in flags { sys_flags += {.EXCL} } + if .Sync in flags { sys_flags += {.DSYNC} } + if .Trunc in flags { sys_flags += {.TRUNC} } + if .Inheritable in flags { sys_flags -= {.CLOEXEC} } + + fd, errno := linux.open(name_cstr, sys_flags, transmute(linux.Mode)u32(perm)) + if errno != .NONE { + return nil, _get_platform_error(errno) } - 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) - } - - return _new_file(uintptr(fd), name), nil + return _new_file(uintptr(fd), name, file_allocator()) } -_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.impl.stream = { - data = file, +_new_file :: proc(fd: uintptr, _: string, allocator: runtime.Allocator) -> (f: ^File, err: Error) { + impl := new(File_Impl, allocator) or_return + defer if err != nil { + free(impl, allocator) + } + impl.file.impl = impl + impl.fd = linux.Fd(fd) + impl.allocator = allocator + impl.name = _get_full_path(impl.fd, impl.allocator) or_return + impl.file.stream = { + data = impl, procedure = _file_stream_proc, } - return file + impl.file.fstat = _fstat + return &impl.file, nil } -_destroy :: proc(f: ^File) -> Error { + +@(require_results) +_open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm := 0o777) -> (f: ^File, err: Error) { + assert(buffer_size > 0) + f, err = _open(name, flags, perm) + if f != nil && err == nil { + impl := (^File_Impl)(f.impl) + impl.buffer = make([]byte, buffer_size, file_allocator()) + f.stream.procedure = _file_stream_buffered_proc + } + return +} + +_destroy :: proc(f: ^File_Impl) -> Error { if f == nil { return nil } - delete(f.impl.name, f.impl.allocator) - free(f, f.impl.allocator) + a := f.allocator + err0 := delete(f.name, a) + err1 := delete(f.buffer, a) + err2 := free(f, a) + err0 or_return + err1 or_return + err2 or_return return nil } -_close :: proc(f: ^File) -> Error { - 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) { + // We have to handle this here, because Linux returns EINVAL for both + // invalid offsets and invalid whences. + switch whence { + case .Start, .Current, .End: + break + case: + return 0, .Invalid_Whence + } + n, errno := linux.lseek(f.fd, offset, linux.Seek_Whence(whence)) + #partial switch errno { + case .EINVAL: + return 0, .Invalid_Offset + case .NONE: + return n, nil + case: + return -1, _get_platform_error(errno) } - return res, 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), io.Error.EOF if n == 0 else nil +} + +_read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { + if len(p) == 0 { + return 0, nil + } + if offset < 0 { + return 0, .Invalid_Offset + } + 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 len(p) == 0 { + return 0, nil + } 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) { + // TODO: Identify 0-sized "pseudo" files and return No_Size. This would + // eliminate the need for the _read_entire_pseudo_file procs. + s: linux.Stat = --- + errno := linux.fstat(f.fd, &s) + if errno != .NONE { + return -1, _get_platform_error(errno) } - return s.size, nil + + if s.mode & linux.S_IFMT == linux.S_IFREG { + return i64(s.size), nil + } + return 0, .No_Size } _sync :: proc(f: ^File) -> Error { - 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) + if fd, errno := linux.open(name_cstr, _OPENDIR_FLAGS + {.NOFOLLOW}); errno == .NONE { + linux.close(fd) + return _get_platform_error(linux.rmdir(name_cstr)) } - 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) + return linux.access(name_cstr, linux.F_OK) == .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 { - return false +/* For reading Linux system files that stat to size 0 */ +_read_entire_pseudo_file :: proc { _read_entire_pseudo_file_string, _read_entire_pseudo_file_cstring } + +_read_entire_pseudo_file_string :: proc(name: string, allocator: runtime.Allocator) -> (b: []u8, e: Error) { + TEMP_ALLOCATOR_GUARD() + name_cstr := clone_to_cstring(name, temp_allocator()) or_return + 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) } - return S_ISREG(s.mode) -} + defer linux.close(fd) -_is_file_fd :: proc(fd: int) -> bool { - s: _Stat - res := unix.sys_fstat(fd, &s) - if res < 0 { // error - return false + 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 } - return 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 { - return false - } - return S_ISDIR(s.mode) + resize(&contents, i + n) + return contents[:], nil } -_is_dir_fd :: proc(fd: int) -> bool { - s: _Stat - res := unix.sys_fstat(fd, &s) - if res < 0 { // error - return false - } - return 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) -} - - @(private="package") _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { - f := (^File)(stream_data) + f := (^File_Impl)(stream_data) + ferr: Error + switch mode { + case .Read: + n, ferr = _read(f, p) + err = error_to_io_error(ferr) + return + case .Read_At: + n, ferr = _read_at(f, p, offset) + err = error_to_io_error(ferr) + return + case .Write: + n, ferr = _write(f, p) + err = error_to_io_error(ferr) + return + case .Write_At: + n, ferr = _write_at(f, p, offset) + err = error_to_io_error(ferr) + return + case .Seek: + n, ferr = _seek(f, offset, whence) + err = error_to_io_error(ferr) + return + case .Size: + n, ferr = _file_size(f) + err = error_to_io_error(ferr) + return + case .Flush: + ferr = _flush(f) + err = error_to_io_error(ferr) + return + case .Close, .Destroy: + ferr = _close(f) + err = error_to_io_error(ferr) + return + case .Query: + return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Destroy, .Query}) + } + return 0, .Empty +} + + +@(private="package") +_file_stream_buffered_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { + f := (^File_Impl)(stream_data) ferr: Error - i: int switch mode { case .Read: n, ferr = _read(f, p) diff --git a/core/os/os2/file_posix.odin b/core/os/os2/file_posix.odin new file mode 100644 index 000000000..184c89368 --- /dev/null +++ b/core/os/os2/file_posix.odin @@ -0,0 +1,476 @@ +#+private +#+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" + +import "core:io" +import "core:c" +import "core:time" +import "core:sys/posix" + +// Most implementations will EINVAL at some point when doing big writes. +// In practice a read/write call would probably never read/write these big buffers all at once, +// which is why the number of bytes is returned and why there are procs that will call this in a +// loop for you. +// We set a max of 1GB to keep alignment and to be safe. +MAX_RW :: 1 << 30 + +File_Impl :: struct { + file: File, + name: string, + cname: cstring, + fd: posix.FD, + allocator: runtime.Allocator, +} + +@(init) +init_std_files :: proc() { + new_std :: proc(impl: ^File_Impl, fd: posix.FD, name: cstring) -> ^File { + impl.file.impl = impl + impl.fd = fd + impl.allocator = runtime.nil_allocator() + impl.cname = name + impl.name = string(name) + impl.file.stream = { + data = impl, + procedure = _file_stream_proc, + } + impl.file.fstat = _fstat + return &impl.file + } + + @(static) files: [3]File_Impl + stdin = new_std(&files[0], posix.STDIN_FILENO, "/dev/stdin") + stdout = new_std(&files[1], posix.STDOUT_FILENO, "/dev/stdout") + stderr = new_std(&files[2], posix.STDERR_FILENO, "/dev/stderr") +} + +_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + sys_flags := posix.O_Flags{.NOCTTY, .CLOEXEC} + + if .Write in flags { + if .Read in flags { + sys_flags += {.RDWR} + } else { + sys_flags += {.WRONLY} + } + } + + if .Append in flags { sys_flags += {.APPEND} } + if .Create in flags { sys_flags += {.CREAT} } + if .Excl in flags { sys_flags += {.EXCL} } + if .Sync in flags { sys_flags += {.DSYNC} } + if .Trunc in flags { sys_flags += {.TRUNC} } + if .Inheritable in flags { sys_flags -= {.CLOEXEC} } + + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + + fd := posix.open(cname, sys_flags, transmute(posix.mode_t)posix._mode_t(perm)) + if fd < 0 { + err = _get_platform_error() + return + } + + return _new_file(uintptr(fd), name, file_allocator()) +} + +_new_file :: proc(handle: uintptr, name: string, allocator: runtime.Allocator) -> (f: ^File, err: Error) { + if name == "" { + err = .Invalid_Path + return + } else if handle == ~uintptr(0) { + err = .Invalid_File + return + } + + crname := _posix_absolute_path(posix.FD(handle), name, allocator) or_return + rname := string(crname) + + f = __new_file(posix.FD(handle), allocator) + impl := (^File_Impl)(f.impl) + impl.name = rname + impl.cname = crname + + return f, nil +} + +__new_file :: proc(handle: posix.FD, allocator: runtime.Allocator) -> ^File { + impl := new(File_Impl, allocator) + impl.file.impl = impl + impl.fd = posix.FD(handle) + impl.allocator = allocator + impl.file.stream = { + data = impl, + procedure = _file_stream_proc, + } + impl.file.fstat = _fstat + return &impl.file +} + +_close :: proc(f: ^File_Impl) -> (err: Error) { + if f == nil { return nil } + + if posix.close(f.fd) != .OK { + err = _get_platform_error() + } + + allocator := f.allocator + + delete(f.cname, allocator) + free(f, allocator) + return +} + +_fd :: proc(f: ^File) -> uintptr { + return uintptr(__fd(f)) +} + +__fd :: proc(f: ^File) -> posix.FD { + if f != nil && f.impl != nil { + return (^File_Impl)(f.impl).fd + } + return -1 +} + +_name :: proc(f: ^File) -> string { + if f != nil && f.impl != nil { + return (^File_Impl)(f.impl).name + } + return "" +} + +_sync :: proc(f: ^File) -> Error { + if posix.fsync(__fd(f)) != .OK { + return _get_platform_error() + } + return nil +} + +_truncate :: proc(f: ^File, size: i64) -> Error { + if posix.ftruncate(__fd(f), posix.off_t(size)) != .OK { + return _get_platform_error() + } + return nil +} + +_remove :: proc(name: string) -> Error { + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + if posix.remove(cname) != 0 { + return _get_platform_error() + } + return nil +} + +_rename :: proc(old_path, new_path: string) -> Error { + TEMP_ALLOCATOR_GUARD() + cold := temp_cstring(old_path) + cnew := temp_cstring(new_path) + if posix.rename(cold, cnew) != 0 { + return _get_platform_error() + } + return nil +} + +_link :: proc(old_name, new_name: string) -> Error { + TEMP_ALLOCATOR_GUARD() + cold := temp_cstring(old_name) + cnew := temp_cstring(new_name) + if posix.link(cold, cnew) != .OK { + return _get_platform_error() + } + return nil +} + +_symlink :: proc(old_name, new_name: string) -> Error { + TEMP_ALLOCATOR_GUARD() + cold := temp_cstring(old_name) + cnew := temp_cstring(new_name) + if posix.symlink(cold, cnew) != .OK { + return _get_platform_error() + } + return nil +} + +_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + + buf: [dynamic]byte + buf.allocator = allocator + defer if err != nil { delete(buf) } + + // Loop this because the file might've grown between lstat() and readlink(). + for { + stat: posix.stat_t + if posix.lstat(cname, &stat) != .OK { + err = _get_platform_error() + return + } + + bufsiz := int(stat.st_size + 1 if stat.st_size > 0 else posix.PATH_MAX) + + if bufsiz == len(buf) { + bufsiz *= 2 + } + + // Overflow. + if bufsiz <= 0 { + err = Platform_Error(posix.Errno.E2BIG) + return + } + + resize(&buf, bufsiz) or_return + + size := posix.readlink(cname, raw_data(buf), uint(bufsiz)) + if size < 0 { + err = _get_platform_error() + return + } + + // File has probably grown between lstat() and readlink(). + if size == bufsiz { + continue + } + + s = string(buf[:size]) + return + } +} + +_chdir :: proc(name: string) -> Error { + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + if posix.chdir(cname) != .OK { + return _get_platform_error() + } + return nil +} + +_fchdir :: proc(f: ^File) -> Error { + if posix.fchdir(__fd(f)) != .OK { + return _get_platform_error() + } + return nil +} + +_fchmod :: proc(f: ^File, mode: int) -> Error { + if posix.fchmod(__fd(f), transmute(posix.mode_t)posix._mode_t(mode)) != .OK { + return _get_platform_error() + } + return nil +} + +_chmod :: proc(name: string, mode: int) -> Error { + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + if posix.chmod(cname, transmute(posix.mode_t)posix._mode_t(mode)) != .OK { + return _get_platform_error() + } + return nil +} + +_fchown :: proc(f: ^File, uid, gid: int) -> Error { + if posix.fchown(__fd(f), posix.uid_t(uid), posix.gid_t(gid)) != .OK { + return _get_platform_error() + } + return nil +} + +_chown :: proc(name: string, uid, gid: int) -> Error { + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + if posix.chown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK { + return _get_platform_error() + } + return nil +} + +_lchown :: proc(name: string, uid, gid: int) -> Error { + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + if posix.lchown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK { + return _get_platform_error() + } + return nil +} + +_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { + times := [2]posix.timeval{ + { + tv_sec = posix.time_t(atime._nsec/1e9), /* seconds */ + tv_usec = posix.suseconds_t(atime._nsec%1e9/1000), /* microseconds */ + }, + { + tv_sec = posix.time_t(mtime._nsec/1e9), /* seconds */ + tv_usec = posix.suseconds_t(mtime._nsec%1e9/1000), /* microseconds */ + }, + } + + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + + if posix.utimes(cname, ×) != .OK { + return _get_platform_error() + } + return nil +} + +_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { + times := [2]posix.timespec{ + { + tv_sec = posix.time_t(atime._nsec/1e9), /* seconds */ + tv_nsec = c.long(atime._nsec%1e9), /* nanoseconds */ + }, + { + tv_sec = posix.time_t(mtime._nsec/1e9), /* seconds */ + tv_nsec = c.long(mtime._nsec%1e9), /* nanoseconds */ + }, + } + + if posix.futimens(__fd(f), ×) != .OK { + return _get_platform_error() + } + return nil +} + +_exists :: proc(path: string) -> bool { + TEMP_ALLOCATOR_GUARD() + cpath := temp_cstring(path) + return posix.access(cpath) == .OK +} + +_file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { + f := (^File_Impl)(stream_data) + fd := f.fd + + switch mode { + case .Read: + if len(p) <= 0 { + return + } + + to_read := uint(min(len(p), MAX_RW)) + n = i64(posix.read(fd, raw_data(p), to_read)) + switch { + case n == 0: + err = .EOF + case n < 0: + err = .Unknown + } + return + + case .Read_At: + if len(p) <= 0 { + return + } + + if offset < 0 { + err = .Invalid_Offset + return + } + + to_read := uint(min(len(p), MAX_RW)) + n = i64(posix.pread(fd, raw_data(p), to_read, posix.off_t(offset))) + switch { + case n == 0: + err = .EOF + case n < 0: + err = .Unknown + } + return + + case .Write: + p := p + for len(p) > 0 { + to_write := uint(min(len(p), MAX_RW)) + if _n := i64(posix.write(fd, raw_data(p), to_write)); _n <= 0 { + err = .Unknown + return + } else { + p = p[_n:] + n += _n + } + } + return + + case .Write_At: + p := p + offset := offset + + if offset < 0 { + err = .Invalid_Offset + return + } + + for len(p) > 0 { + to_write := uint(min(len(p), MAX_RW)) + if _n := i64(posix.pwrite(fd, raw_data(p), to_write, posix.off_t(offset))); _n <= 0 { + err = .Unknown + return + } else { + p = p[_n:] + n += _n + offset += _n + } + } + return + + case .Seek: + #assert(int(posix.Whence.SET) == int(io.Seek_From.Start)) + #assert(int(posix.Whence.CUR) == int(io.Seek_From.Current)) + #assert(int(posix.Whence.END) == int(io.Seek_From.End)) + + switch whence { + case .Start, .Current, .End: + break + case: + err = .Invalid_Whence + return + } + + n = i64(posix.lseek(fd, posix.off_t(offset), posix.Whence(whence))) + if n < 0 { + #partial switch posix.get_errno() { + case .EINVAL: + err = .Invalid_Offset + case: + err = .Unknown + } + } + return + + case .Size: + stat: posix.stat_t + if posix.fstat(fd, &stat) != .OK { + err = .Unknown + return + } + + n = i64(stat.st_size) + return + + case .Flush: + ferr := _sync(&f.file) + err = error_to_io_error(ferr) + return + + case .Close, .Destroy: + ferr := _close(f) + err = error_to_io_error(ferr) + return + + case .Query: + return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Destroy, .Query}) + + case: + return 0, .Empty + } +} diff --git a/core/os/os2/file_posix_darwin.odin b/core/os/os2/file_posix_darwin.odin new file mode 100644 index 000000000..920a63a71 --- /dev/null +++ b/core/os/os2/file_posix_darwin.odin @@ -0,0 +1,18 @@ +#+private +package os2 + +import "base:runtime" + +import "core:sys/posix" + +_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { + F_GETPATH :: 50 + + buf: [posix.PATH_MAX]byte + if posix.fcntl(fd, posix.FCNTL_Cmd(F_GETPATH), &buf) != 0 { + err = _get_platform_error() + return + } + + return clone_to_cstring(string(cstring(&buf[0])), allocator) +} diff --git a/core/os/os2/file_posix_freebsd.odin b/core/os/os2/file_posix_freebsd.odin new file mode 100644 index 000000000..05d031930 --- /dev/null +++ b/core/os/os2/file_posix_freebsd.odin @@ -0,0 +1,47 @@ +#+private +package os2 + +import "base:runtime" + +import "core:c" +import "core:sys/posix" + +_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { + // NOTE(Feoramund): The situation isn't ideal, but this was the best way I + // could find to implement this. There are a couple outstanding bug reports + // regarding the desire to retrieve an absolute path from a handle, but to + // my knowledge, there hasn't been any work done on it. + // + // https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=198570 + // + // This may be unreliable, according to a comment from 2023. + + KInfo_File :: struct { + structsize: c.int, + type: c.int, + fd: c.int, + ref_count: c.int, + flags: c.int, + pad0: c.int, + offset: i64, + + // NOTE(Feoramund): This field represents a complicated union that I am + // avoiding implementing for now. I only need the path data below. + _union: [336]byte, + + path: [posix.PATH_MAX]c.char, + } + + F_KINFO :: 22 + + kinfo: KInfo_File + kinfo.structsize = size_of(KInfo_File) + + res := posix.fcntl(fd, posix.FCNTL_Cmd(F_KINFO), &kinfo) + if res == -1 { + err = _get_platform_error() + return + } + + return clone_to_cstring(string(cstring(&kinfo.path[0])), allocator) +} diff --git a/core/os/os2/file_posix_netbsd.odin b/core/os/os2/file_posix_netbsd.odin new file mode 100644 index 000000000..f96c227ba --- /dev/null +++ b/core/os/os2/file_posix_netbsd.odin @@ -0,0 +1,18 @@ +#+private +package os2 + +import "base:runtime" + +import "core:sys/posix" + +_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { + F_GETPATH :: 15 + + buf: [posix.PATH_MAX]byte + if posix.fcntl(fd, posix.FCNTL_Cmd(F_GETPATH), &buf) != 0 { + err = _get_platform_error() + return + } + + return clone_to_cstring(string(cstring(&buf[0])), allocator) +} diff --git a/core/os/os2/file_posix_other.odin b/core/os/os2/file_posix_other.odin new file mode 100644 index 000000000..74b6374ec --- /dev/null +++ b/core/os/os2/file_posix_other.odin @@ -0,0 +1,21 @@ +#+private +#+build openbsd +package os2 + +import "base:runtime" + +import "core:sys/posix" + +_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + + buf: [posix.PATH_MAX]byte + path = posix.realpath(cname, raw_data(buf[:])) + if path == nil { + err = _get_platform_error() + return + } + + return clone_to_cstring(string(path), allocator) +} diff --git a/core/os/os2/file_stream.odin b/core/os/os2/file_stream.odin index da1e3344f..84176928d 100644 --- a/core/os/os2/file_stream.odin +++ b/core/os/os2/file_stream.odin @@ -4,8 +4,8 @@ import "core:io" to_stream :: proc(f: ^File) -> (s: io.Stream) { if f != nil { - assert(f.impl.stream.procedure != nil) - s = f.impl.stream + assert(f.stream.procedure != nil) + s = f.stream } return } diff --git a/core/os/os2/file_util.odin b/core/os/os2/file_util.odin index 60c3efe44..8af46fab3 100644 --- a/core/os/os2/file_util.odin +++ b/core/os/os2/file_util.odin @@ -1,7 +1,6 @@ package os2 -import "core:mem" -import "core:runtime" +import "base:runtime" import "core:strconv" import "core:unicode/utf8" @@ -9,6 +8,18 @@ write_string :: proc(f: ^File, s: string) -> (n: int, err: Error) { return write(f, transmute([]byte)s) } +write_strings :: proc(f: ^File, strings: ..string) -> (n: int, err: Error) { + for s in strings { + m: int + m, err = write_string(f, s) + n += m + if err != nil { + return + } + } + return +} + write_byte :: proc(f: ^File, b: byte) -> (n: int, err: Error) { return write(f, []byte{b}) } @@ -62,61 +73,104 @@ write_encoded_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) { return } +read_at_least :: proc(f: ^File, buf: []byte, min: int) -> (n: int, err: Error) { + if len(buf) < min { + return 0, .Short_Buffer + } + nn := max(int) + for nn > 0 && n < min && err == nil { + nn, err = read(f, buf[n:]) + n += nn + } + if n >= min { + err = nil + } + return +} + +read_full :: proc(f: ^File, buf: []byte) -> (n: int, err: Error) { + return read_at_least(f, buf, len(buf)) +} write_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) { - s := transmute([]byte)mem.Raw_Slice{data, len} - return write(f, s) + return write(f, ([^]byte)(data)[:len]) } read_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) { - s := transmute([]byte)mem.Raw_Slice{data, len} - return read(f, s) + return read(f, ([^]byte)(data)[:len]) } +read_entire_file :: proc{ + read_entire_file_from_path, + read_entire_file_from_file, +} -read_entire_file :: proc(name: string, allocator: runtime.Allocator) -> (data: []byte, err: Error) { +@(require_results) +read_entire_file_from_path :: proc(name: string, allocator: runtime.Allocator) -> (data: []byte, err: Error) { f, ferr := open(name) if ferr != nil { return nil, ferr } defer close(f) + 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 - if size64, err := file_size(f); err == nil { - if i64(int(size64)) != size64 { + has_size := false + if size64, serr := file_size(f); serr == nil { + if i64(int(size64)) == size64 { + has_size = true size = int(size64) } } - size += 1 // for EOF - // TODO(bill): Is this correct logic? - total: int - data = make([]byte, size, allocator) or_return - for { - n: int - n, err = read(f, data[total:]) - total += n - if err != nil { - if err == .EOF { - err = nil + if has_size && size > 0 { + total: int + data = make([]byte, size, allocator) or_return + for total < len(data) { + n: int + n, err = read(f, data[total:]) + total += n + if err != nil { + if err == .EOF { + err = nil + } + data = data[:total] + break + } + } + return + } else { + buffer: [1024]u8 + out_buffer := make([dynamic]u8, 0, 0, allocator) + total := 0 + for { + n: int + n, err = read(f, buffer[:]) + total += n + append_elems(&out_buffer, ..buffer[:n]) + if err != nil { + if err == .EOF || err == .Broken_Pipe { + err = nil + } + data = out_buffer[:total] + return } - data = data[:total] - return } } } -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 = 0o644, truncate := true) -> Error { flags := O_WRONLY|O_CREATE if truncate { flags |= O_TRUNC } - f, err := open(name, flags, perm) - if err != nil { - return err - } - _, err = write(f, data) + f := open(name, flags, perm) or_return + _, err := write(f, data) if cerr := close(f); cerr != nil && err == nil { err = cerr } diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 600ecde21..f594cc72f 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -1,11 +1,11 @@ -//+private +#+private package os2 +import "base:runtime" + import "core:io" import "core:mem" import "core:sync" -import "core:runtime" -import "core:strings" import "core:time" import "core:unicode/utf16" import win32 "core:sys/windows" @@ -16,47 +16,79 @@ S_IWRITE :: 0o200 _ERROR_BAD_NETPATH :: 53 MAX_RW :: 1<<30 -_file_allocator :: proc() -> runtime.Allocator { - return heap_allocator() -} -_temp_allocator :: proc() -> runtime.Allocator { - // TODO(bill): make this not depend on the context allocator - return context.temp_allocator -} - - -_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, - - stream: io.Stream, + kind: File_Impl_Kind, allocator: runtime.Allocator, + r_buf: []byte, + w_buf: []byte, + w_n: int, + max_consecutive_empty_writes: int, + rw_mutex: sync.RW_Mutex, // read write calls p_mutex: sync.Mutex, // pread pwrite calls } +@(init) +init_std_files :: proc() { + new_std :: proc(impl: ^File_Impl, code: u32, name: string) -> ^File { + impl.file.impl = impl + + impl.allocator = runtime.nil_allocator() + impl.fd = win32.GetStdHandle(code) + impl.name = name + impl.wname = nil + + 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 + } + impl.kind = kind + + impl.file.stream = { + data = impl, + procedure = _file_stream_proc, + } + impl.file.fstat = _fstat + + return &impl.file + } + + @(static) files: [3]File_Impl + stdin = new_std(&files[0], win32.STD_INPUT_HANDLE, "") + stdout = new_std(&files[1], win32.STD_OUTPUT_HANDLE, "") + stderr = new_std(&files[2], win32.STD_ERROR_HANDLE, "") +} + _handle :: proc(f: ^File) -> win32.HANDLE { return win32.HANDLE(_fd(f)) } -_open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (handle: uintptr, err: Error) { +_open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: uintptr, err: Error) { if len(name) == 0 { err = .Not_Exist return } + TEMP_ALLOCATOR_GUARD() - path := _fix_long_path(name) + path := _fix_long_path(name, temp_allocator()) or_return access: u32 switch flags & {.Read, .Write} { case {.Read}: access = win32.FILE_GENERIC_READ @@ -72,11 +104,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 @@ -91,14 +121,14 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han create_mode = win32.TRUNCATE_EXISTING } - attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL + attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS if perm & S_IWRITE == 0 { attrs = win32.FILE_ATTRIBUTE_READONLY if create_mode == win32.CREATE_ALWAYS { // NOTE(bill): Open has just asked to create a file in read-only mode. // If the file already exists, to make it akin to a *nix open call, // the call preserves the existing permissions. - h := win32.CreateFileW(path, access, share_mode, sa, win32.TRUNCATE_EXISTING, win32.FILE_ATTRIBUTE_NORMAL, nil) + h := win32.CreateFileW(path, access, share_mode, &sa, win32.TRUNCATE_EXISTING, win32.FILE_ATTRIBUTE_NORMAL, nil) if h == win32.INVALID_HANDLE { switch e := win32.GetLastError(); e { case win32.ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, win32.ERROR_PATH_NOT_FOUND: @@ -106,12 +136,13 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han case 0: return uintptr(h), nil case: - return 0, Platform_Error(e) + return 0, _get_platform_error() } } } } - h := win32.CreateFileW(path, access, share_mode, sa, create_mode, attrs, nil) + + h := win32.CreateFileW(path, access, share_mode, &sa, create_mode, attrs, nil) if h == win32.INVALID_HANDLE { return 0, _get_platform_error() } @@ -119,91 +150,127 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han } -_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, err: Error) { +_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) { flags := flags if flags != nil else {.Read} - handle := _open_internal(name, flags + {.Close_On_Exec}, perm) or_return - return _new_file(handle, name), nil + handle := _open_internal(name, flags, perm) or_return + return _new_file(handle, name, file_allocator()) } -_new_file :: proc(handle: uintptr, name: string) -> ^File { +_new_file :: proc(handle: uintptr, name: string, allocator: runtime.Allocator) -> (f: ^File, err: Error) { if handle == INVALID_HANDLE { - return nil + return + } + impl := new(File_Impl, allocator) or_return + defer if err != nil { + free(impl, allocator) } - f := new(File, _file_allocator()) - 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.file.impl = impl - handle := _handle(f) - kind := _File_Kind.File + impl.allocator = allocator + impl.fd = rawptr(handle) + impl.name = clone_string(name, impl.allocator) or_return + impl.wname = win32_utf8_to_wstring(name, impl.allocator) or_return + + handle := _handle(&impl.file) + kind := File_Impl_Kind.File if m: u32; win32.GetConsoleMode(handle, &m) { kind = .Console } if win32.GetFileType(handle) == win32.FILE_TYPE_PIPE { kind = .Pipe } - f.impl.kind = kind + impl.kind = kind - f.impl.stream = { - data = f, + impl.file.stream = { + data = impl, procedure = _file_stream_proc, } + impl.file.fstat = _fstat - return f + return &impl.file, nil } + +@(require_results) +_open_buffered :: proc(name: string, buffer_size: uint, flags := File_Flags{.Read}, perm := 0o777) -> (f: ^File, err: Error) { + assert(buffer_size > 0) + flags := flags if flags != nil else {.Read} + handle := _open_internal(name, flags, perm) or_return + return _new_file_buffered(handle, name, buffer_size) +} + +_new_file_buffered :: proc(handle: uintptr, name: string, buffer_size: uint) -> (f: ^File, err: Error) { + f, err = _new_file(handle, name, file_allocator()) + if f != nil && err == nil { + impl := (^File_Impl)(f.impl) + impl.r_buf = make([]byte, buffer_size, file_allocator()) + impl.w_buf = make([]byte, buffer_size, file_allocator()) + } + return +} + + _fd :: proc(f: ^File) -> uintptr { - if f == nil { + if f == nil || f.impl == nil { return INVALID_HANDLE } - return uintptr(f.impl.fd) + return uintptr((^File_Impl)(f.impl).fd) } -_destroy :: proc(f: ^File) -> Error { +_destroy :: proc(f: ^File_Impl) -> Error { if f == nil { return nil } - a := f.impl.allocator - free(f.impl.wname, a) - delete(f.impl.name, a) - free(f, a) + a := f.allocator + err0 := free(f.wname, a) + err1 := delete(f.name, a) + err2 := free(f, a) + err3 := delete(f.r_buf, a) + err4 := delete(f.w_buf, a) + err0 or_return + err1 or_return + err2 or_return + err3 or_return + err4 or_return return nil } -_close :: proc(f: ^File) -> Error { - if f == nil { +_close :: proc(f: ^File_Impl) -> Error { + if f == nil { return nil } - if !win32.CloseHandle(win32.HANDLE(f.impl.fd)) { + if !win32.CloseHandle(win32.HANDLE(f.fd)) { return .Closed } return _destroy(f) } _name :: proc(f: ^File) -> string { - return f.impl.name if f != nil else "" + return (^File_Impl)(f.impl).name if f != nil && f.impl != nil else "" } -_seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { - handle := _handle(f) +_seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { + handle := _handle(&f.file) if handle == win32.INVALID_HANDLE { return 0, .Invalid_File } - if f.impl.kind == .Pipe { + + if f.kind == .Pipe { return 0, .Invalid_File } - sync.guard(&f.impl.rw_mutex) + sync.guard(&f.rw_mutex) w: u32 switch whence { case .Start: w = win32.FILE_BEGIN case .Current: w = win32.FILE_CURRENT case .End: w = win32.FILE_END + case: + return 0, .Invalid_Whence } hi := i32(offset>>32) lo := i32(offset) @@ -215,13 +282,22 @@ _seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Er return i64(hi)<<32 + i64(dw_ptr), nil } -_read :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { +_read :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { + return _read_internal(f, p) +} + +_read_internal :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { + length := len(p) + if length == 0 { + return + } + read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) { if len(b) == 0 { return 0, nil } - // TODO(bill): should this be moved to `_File` instead? + // TODO(bill): should this be moved to `File_Impl` instead? BUF_SIZE :: 386 buf16: [BUF_SIZE]u16 buf8: [4*BUF_SIZE]u8 @@ -266,22 +342,21 @@ _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) @@ -289,6 +364,10 @@ _read :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { if single_read_length > 0 && ok { total_read += int(single_read_length) + } else if single_read_length == 0 && ok { + // ok and 0 bytes means EOF: + // https://learn.microsoft.com/en-us/windows/win32/fileio/testing-for-the-end-of-a-file + err = .EOF } else { err = _get_platform_error() } @@ -297,15 +376,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, 0, .Current) or_return + defer _seek(f, curr_offset, .Start) o := win32.OVERLAPPED{ OffsetHigh = u32(offset>>32), @@ -314,7 +393,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() @@ -324,7 +403,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 { @@ -336,7 +415,10 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { return } -_write :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { +_write :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { + return _write_internal(f, p) +} +_write_internal :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { if len(p) == 0 { return } @@ -345,9 +427,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)) @@ -363,22 +445,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, 0, .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() @@ -388,7 +470,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 @@ -399,9 +481,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 - handle := _handle(f) + if f.kind == .Pipe { + return 0, .No_Size + } + handle := _handle(&f.file) if !win32.GetFileSizeEx(handle, &length) { err = _get_platform_error() } @@ -411,11 +496,17 @@ _file_size :: proc(f: ^File) -> (n: i64, err: Error) { _sync :: proc(f: ^File) -> Error { - return _flush(f) + if f != nil && f.impl != nil { + return _flush_internal((^File_Impl)(f.impl)) + } + return nil } -_flush :: proc(f: ^File) -> Error { - handle := _handle(f) +_flush :: proc(f: ^File_Impl) -> Error { + return _flush_internal(f) +} +_flush_internal :: proc(f: ^File_Impl) -> Error { + handle := _handle(&f.file) if !win32.FlushFileBuffers(handle) { return _get_platform_error() } @@ -423,7 +514,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 @@ -437,7 +528,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error { } _remove :: proc(name: string) -> Error { - p := _fix_long_path(name) + TEMP_ALLOCATOR_GUARD() + p := _fix_long_path(name, temp_allocator()) or_return err, err1: Error if !win32.DeleteFileW(p) { err = _get_platform_error() @@ -454,7 +546,7 @@ _remove :: proc(name: string) -> Error { if err != err1 { a := win32.GetFileAttributesW(p) - if a == ~u32(0) { + if a == win32.INVALID_FILE_ATTRIBUTES { err = _get_platform_error() } else { if a & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { @@ -474,8 +566,9 @@ _remove :: proc(name: string) -> Error { } _rename :: proc(old_path, new_path: string) -> Error { - from := _fix_long_path(old_path) - to := _fix_long_path(new_path) + TEMP_ALLOCATOR_GUARD() + from := _fix_long_path(old_path, temp_allocator()) or_return + to := _fix_long_path(new_path, temp_allocator()) or_return if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) { return nil } @@ -483,10 +576,10 @@ _rename :: proc(old_path, new_path: string) -> Error { } - _link :: proc(old_name, new_name: string) -> Error { - o := _fix_long_path(old_name) - n := _fix_long_path(new_name) + TEMP_ALLOCATOR_GUARD() + o := _fix_long_path(old_name, temp_allocator()) or_return + n := _fix_long_path(new_name, temp_allocator()) or_return if win32.CreateHardLinkW(n, o, nil) { return nil } @@ -526,16 +619,16 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st } if !has_unc_prefix(p) { - return win32.utf16_to_utf8(p, allocator) + return win32_utf16_to_utf8(p, allocator) } ws := p[4:] switch { case len(ws) >= 2 && ws[1] == ':': - return win32.utf16_to_utf8(ws, allocator) + return win32_utf16_to_utf8(ws, allocator) case has_prefix(ws, `UNC\`): ws[3] = '\\' // override data in buffer - return win32.utf16_to_utf8(ws[3:], allocator) + return win32_utf16_to_utf8(ws[3:], allocator) } @@ -546,7 +639,10 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st if n == 0 { return "", _get_platform_error() } - buf := make([]u16, n+1, _temp_allocator()) + + TEMP_ALLOCATOR_GUARD() + + 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() @@ -557,9 +653,9 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st ws = ws[4:] if len(ws) > 3 && has_prefix(ws, `UNC`) { ws[2] = '\\' - return win32.utf16_to_utf8(ws[2:], allocator) + return win32_utf16_to_utf8(ws[2:], allocator) } - return win32.utf16_to_utf8(ws, allocator) + return win32_utf16_to_utf8(ws, allocator) } return "", .Invalid_Path } @@ -570,7 +666,9 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er @thread_local rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte - p := _fix_long_path(name) + TEMP_ALLOCATOR_GUARD() + + p := _fix_long_path(name, temp_allocator()) or_return handle := _open_sym_link(p) or_return defer win32.CloseHandle(handle) @@ -590,7 +688,7 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er pb[rb.SubstituteNameOffset+rb.SubstituteNameLength] = 0 p := pb[rb.SubstituteNameOffset:][:rb.SubstituteNameLength] if rb.Flags & win32.SYMLINK_FLAG_RELATIVE != 0 { - return win32.utf16_to_utf8(p, allocator) + return win32_utf16_to_utf8(p, allocator) } return _normalize_link_path(p, allocator) @@ -607,17 +705,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 @@ -644,14 +743,15 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error { } _chdir :: proc(name: string) -> Error { - p := _fix_long_path(name) + TEMP_ALLOCATOR_GUARD() + p := _fix_long_path(name, temp_allocator()) or_return if !win32.SetCurrentDirectoryW(p) { return _get_platform_error() } return nil } -_chmod :: proc(name: string, mode: File_Mode) -> Error { +_chmod :: proc(name: string, mode: int) -> Error { f := open(name, {.Write}) or_return defer close(f) return _fchmod(f, mode) @@ -672,7 +772,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 @@ -699,38 +799,17 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { return nil } - - _exists :: proc(path: string) -> bool { - wpath := _fix_long_path(path) + TEMP_ALLOCATOR_GUARD() + wpath, _ := _fix_long_path(path, temp_allocator()) attribs := win32.GetFileAttributesW(wpath) - return i32(attribs) != win32.INVALID_FILE_ATTRIBUTES + return attribs != win32.INVALID_FILE_ATTRIBUTES } -_is_file :: proc(path: string) -> bool { - wpath := _fix_long_path(path) - attribs := win32.GetFileAttributesW(wpath) - if i32(attribs) != win32.INVALID_FILE_ATTRIBUTES { - return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == 0 - } - return false -} - -_is_dir :: proc(path: string) -> bool { - wpath := _fix_long_path(path) - attribs := win32.GetFileAttributesW(wpath) - if i32(attribs) != win32.INVALID_FILE_ATTRIBUTES { - return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != 0 - } - return false -} - - @(private="package") _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { - f := (^File)(stream_data) + f := (^File_Impl)(stream_data) ferr: Error - i: int switch mode { case .Read: n, ferr = _read(f, p) @@ -760,15 +839,96 @@ _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 io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Destroy, .Query}) } return 0, .Empty } + + + +@(private="package", require_results) +win32_utf8_to_wstring :: proc(s: string, allocator: runtime.Allocator) -> (ws: [^]u16, err: runtime.Allocator_Error) { + ws = raw_data(win32_utf8_to_utf16(s, allocator) or_return) + return +} + +@(private="package", require_results) +win32_utf8_to_utf16 :: proc(s: string, allocator: runtime.Allocator) -> (ws: []u16, err: runtime.Allocator_Error) { + if len(s) < 1 { + return + } + + b := transmute([]byte)s + cstr := raw_data(b) + n := win32.MultiByteToWideChar(win32.CP_UTF8, win32.MB_ERR_INVALID_CHARS, cstr, i32(len(s)), nil, 0) + if n == 0 { + return nil, nil + } + + text := make([]u16, n+1, allocator) or_return + + n1 := win32.MultiByteToWideChar(win32.CP_UTF8, win32.MB_ERR_INVALID_CHARS, cstr, i32(len(s)), raw_data(text), n) + if n1 == 0 { + delete(text, allocator) + return + } + + text[n] = 0 + for n >= 1 && text[n-1] == 0 { + n -= 1 + } + ws = text[:n] + return +} + +@(private="package", require_results) +win32_wstring_to_utf8 :: proc(s: [^]u16, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) { + if s == nil || s[0] == 0 { + return "", nil + } + n := 0 + for s[n] != 0 { + n += 1 + } + return win32_utf16_to_utf8(s[:n], allocator) +} + +@(private="package", require_results) +win32_utf16_to_utf8 :: proc(s: []u16, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) { + if len(s) == 0 { + return + } + + n := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, raw_data(s), i32(len(s)), nil, 0, nil, nil) + if n == 0 { + return + } + + // If N < 0 the call to WideCharToMultiByte assume the wide string is null terminated + // and will scan it to find the first null terminated character. The resulting string will + // also be null terminated. + // If N > 0 it assumes the wide string is not null terminated and the resulting string + // will not be null terminated. + text := make([]byte, n, allocator) or_return + + n1 := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, raw_data(s), i32(len(s)), raw_data(text), n, nil, nil) + if n1 == 0 { + delete(text, allocator) + return + } + + for i in 0.. runtime.Allocator { return runtime.Allocator{ procedure = heap_allocator_proc, @@ -10,12 +11,9 @@ 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) { return _heap_allocator_proc(allocator_data, mode, size, alignment, old_memory, old_size, loc) } - - -@(private) -error_allocator := heap_allocator diff --git a/core/os/os2/heap_linux.odin b/core/os/os2/heap_linux.odin index 74528f242..8819dfac7 100644 --- a/core/os/os2/heap_linux.odin +++ b/core/os/os2/heap_linux.odin @@ -1,7 +1,7 @@ -//+private +#+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 @@ -200,7 +199,7 @@ _heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, case .Free_All: return nil, .Mode_Not_Implemented - case .Resize: + case .Resize, .Resize_Non_Zeroed: if old_memory == nil { return aligned_alloc(size, alignment) } @@ -281,7 +280,8 @@ heap_alloc :: proc(size: int) -> rawptr { _local_region, back_idx = _region_retrieve_with_space(blocks_needed, local_region_idx, back_idx) } user_ptr, used := _region_get_block(_local_region, idx, blocks_needed) - _local_region.hdr.free_blocks -= (used + 1) + + sync.atomic_sub_explicit(&_local_region.hdr.free_blocks, used + 1, .Release) // If this memory was ever used before, it now needs to be zero'd. if idx < _local_region.hdr.last_used { @@ -308,7 +308,7 @@ heap_resize :: proc(old_memory: rawptr, new_size: int) -> rawptr #no_bounds_chec heap_free :: proc(memory: rawptr) { alloc := _get_allocation_header(memory) - if alloc.requested & IS_DIRECT_MMAP == IS_DIRECT_MMAP { + if sync.atomic_load(&alloc.requested) & IS_DIRECT_MMAP == IS_DIRECT_MMAP { _direct_mmap_free(alloc) return } @@ -324,11 +324,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 @@ -415,7 +415,7 @@ _region_resize :: proc(alloc: ^Allocation_Header, new_size: int, alloc_is_free_l back_idx := -1 idx: u16 infinite: for { - for i := 0; i < len(region_iter.hdr.free_list); i += 1 { + for i := 0; i < int(region_iter.hdr.free_list_len); i += 1 { idx = region_iter.hdr.free_list[i] if _get_block_count(region_iter.memory[idx]) >= new_block_count { break infinite @@ -463,25 +463,31 @@ _region_local_free :: proc(alloc: ^Allocation_Header) #no_bounds_check { alloc := alloc add_to_free_list := true - _local_region.hdr.free_blocks += _get_block_count(alloc^) + 1 + idx := sync.atomic_load(&alloc.idx) + prev := sync.atomic_load(&alloc.prev) + next := sync.atomic_load(&alloc.next) + block_count := next - idx - 1 + free_blocks := sync.atomic_load(&_local_region.hdr.free_blocks) + block_count + 1 + sync.atomic_store_explicit(&_local_region.hdr.free_blocks, free_blocks, .Release) // try to merge with prev - if alloc.idx > 0 && _local_region.memory[alloc.prev].free_idx != NOT_FREE { - _local_region.memory[alloc.prev].next = alloc.next - _local_region.memory[alloc.next].prev = alloc.prev - alloc = &_local_region.memory[alloc.prev] + if idx > 0 && sync.atomic_load(&_local_region.memory[prev].free_idx) != NOT_FREE { + sync.atomic_store_explicit(&_local_region.memory[prev].next, next, .Release) + _local_region.memory[next].prev = prev + alloc = &_local_region.memory[prev] add_to_free_list = false } // try to merge with next - if alloc.next < BLOCKS_PER_REGION - 1 && _local_region.memory[alloc.next].free_idx != NOT_FREE { - old_next := alloc.next - alloc.next = _local_region.memory[old_next].next - _local_region.memory[alloc.next].prev = alloc.idx + if next < BLOCKS_PER_REGION - 1 && sync.atomic_load(&_local_region.memory[next].free_idx) != NOT_FREE { + old_next := next + sync.atomic_store_explicit(&alloc.next, sync.atomic_load(&_local_region.memory[old_next].next), .Release) + + sync.atomic_store_explicit(&_local_region.memory[next].prev, idx, .Release) if add_to_free_list { - _local_region.hdr.free_list[_local_region.memory[old_next].free_idx] = alloc.idx - alloc.free_idx = _local_region.memory[old_next].free_idx + sync.atomic_store_explicit(&_local_region.hdr.free_list[_local_region.memory[old_next].free_idx], idx, .Release) + sync.atomic_store_explicit(&alloc.free_idx, _local_region.memory[old_next].free_idx, .Release) } else { // NOTE: We have aleady merged with prev, and now merged with next. // Now, we are actually going to remove from the free_list. @@ -493,10 +499,11 @@ _region_local_free :: proc(alloc: ^Allocation_Header) #no_bounds_check { // This is the only place where anything is appended to the free list. if add_to_free_list { fl := _local_region.hdr.free_list - alloc.free_idx = _local_region.hdr.free_list_len - fl[alloc.free_idx] = alloc.idx - _local_region.hdr.free_list_len += 1 - if int(_local_region.hdr.free_list_len) == len(fl) { + fl_len := sync.atomic_load(&_local_region.hdr.free_list_len) + sync.atomic_store_explicit(&alloc.free_idx, fl_len, .Release) + fl[alloc.free_idx] = idx + sync.atomic_store_explicit(&_local_region.hdr.free_list_len, fl_len + 1, .Release) + if int(fl_len + 1) == len(fl) { free_alloc := _get_allocation_header(mem.raw_data(_local_region.hdr.free_list)) _region_resize(free_alloc, len(fl) * 2 * size_of(fl[0]), true) } @@ -513,8 +520,8 @@ _region_assign_free_list :: proc(region: ^Region, memory: rawptr, blocks: u16) { _region_retrieve_with_space :: proc(blocks: u16, local_idx: int = -1, back_idx: int = -1) -> (^Region, int) { r: ^Region idx: int - for r = global_regions; r != nil; r = r.hdr.next_region { - if idx == local_idx || idx < back_idx || r.hdr.free_blocks < blocks { + for r = sync.atomic_load(&global_regions); r != nil; r = r.hdr.next_region { + if idx == local_idx || idx < back_idx || sync.atomic_load(&r.hdr.free_blocks) < blocks { idx += 1 continue } @@ -582,7 +589,7 @@ _region_segment :: proc(region: ^Region, alloc: ^Allocation_Header, blocks, new_ _region_get_local_idx :: proc() -> int { idx: int - for r := global_regions; r != nil; r = r.hdr.next_region { + for r := sync.atomic_load(&global_regions); r != nil; r = r.hdr.next_region { if r == _local_region { return idx } @@ -598,9 +605,10 @@ _region_find_and_assign_local :: proc(alloc: ^Allocation_Header) { _local_region = _region_retrieve_from_addr(alloc) } - // At this point, _local_region is set correctly. Spin until acquired - res: ^^Region - for res != &_local_region { + // At this point, _local_region is set correctly. Spin until acquire + res := CURRENTLY_ACTIVE + + for res == CURRENTLY_ACTIVE { res = sync.atomic_compare_exchange_strong_explicit( &_local_region.hdr.local_addr, &_local_region, @@ -622,9 +630,9 @@ _region_contains_mem :: proc(r: ^Region, memory: rawptr) -> bool #no_bounds_chec _region_free_list_remove :: proc(region: ^Region, free_idx: u16) #no_bounds_check { // pop, swap and update allocation hdr if n := region.hdr.free_list_len - 1; free_idx != n { - region.hdr.free_list[free_idx] = region.hdr.free_list[n] + region.hdr.free_list[free_idx] = sync.atomic_load(®ion.hdr.free_list[n]) alloc_idx := region.hdr.free_list[free_idx] - region.memory[alloc_idx].free_idx = free_idx + sync.atomic_store_explicit(®ion.memory[alloc_idx].free_idx, free_idx, .Release) } region.hdr.free_list_len -= 1 } @@ -634,8 +642,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 +663,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 +705,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)) } // @@ -720,3 +723,4 @@ _get_allocation_header :: #force_inline proc(raw_mem: rawptr) -> ^Allocation_Hea _round_up_to_nearest :: #force_inline proc(size, round: int) -> int { return (size-1) + round - (size-1) % round } + diff --git a/core/os/os2/heap_posix.odin b/core/os/os2/heap_posix.odin new file mode 100644 index 000000000..1b52aed75 --- /dev/null +++ b/core/os/os2/heap_posix.odin @@ -0,0 +1,7 @@ +#+private +#+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" + +_heap_allocator_proc :: runtime.heap_allocator_proc diff --git a/core/os/os2/heap_windows.odin b/core/os/os2/heap_windows.odin index eba403c1d..7fd4529a0 100644 --- a/core/os/os2/heap_windows.odin +++ b/core/os/os2/heap_windows.odin @@ -1,4 +1,4 @@ -//+private +#+private package os2 import "core:mem" @@ -85,7 +85,7 @@ _heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, case .Free_All: return nil, .Mode_Not_Implemented - case .Resize: + case .Resize, .Resize_Non_Zeroed: if old_memory == nil { return aligned_alloc(size, alignment, true) } diff --git a/core/os/os2/internal_util.odin b/core/os/os2/internal_util.odin new file mode 100644 index 000000000..164e1e1be --- /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) #optional_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, allocator) 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 c27015862..254950d68 100644 --- a/core/os/os2/path.odin +++ b/core/os/os2/path.odin @@ -1,19 +1,25 @@ package os2 -import "core:runtime" +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 :: proc(name: string, perm: File_Mode) -> Error { +mkdir :: make_directory + +make_directory :: proc(name: string, perm: int = 0o755) -> Error { return _mkdir(name, perm) } -mkdir_all :: proc(path: string, perm: File_Mode) -> Error { +mkdir_all :: make_directory_all + +make_directory_all :: proc(path: string, perm: int = 0o755) -> Error { return _mkdir_all(path, perm) } @@ -21,11 +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 _get_working_directory(allocator) +} -getwd :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - return _getwd(allocator) -} -setwd :: proc(dir: string) -> (err: Error) { - return _setwd(dir) +setwd :: set_working_directory + +set_working_directory :: proc(dir: string) -> (err: Error) { + return _set_working_directory(dir) } diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index 2a0ef29d8..bfdb645ef 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -1,197 +1,152 @@ -//+private +#+private package os2 import "core:strings" import "core:strconv" -import "core:runtime" -import "core:sys/unix" +import "base:runtime" +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) - case: - return _get_platform_error(new_dfd) + return mkdirat(new_dfd, path[i:], perm, has_created) } - unreachable() + return _get_platform_error(errno) } - - 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 - if has_created { - return nil - } - return .Exist - //return has_created ? nil : .Exist -} - -dirent64 :: struct { - d_ino: u64, - d_off: u64, - d_reclen: u16, - d_type: u8, - d_name: [1]u8, + mkdirat(dfd, path_bytes, perm, &has_created) or_return + return nil if has_created else .Exist } _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 + offset: int + for d in linux.dirent_iterate_buf(buf[:buflen], &offset) { + d_name_str := linux.dirent_name(d) + d_name_cstr := strings.unsafe_string_to_cstring(d_name_str) - for i := 0; i < getdents_res; i += int(d.d_reclen) { - d = (^dirent64)(rawptr(&buf[i])) - d_name_cstr := cstring(&d.d_name[0]) - - buf_len := uintptr(d.d_reclen) - offset_of(d.d_name) - - /* check for current directory (.) */ - #no_bounds_check if buf_len > 1 && d.d_name[0] == '.' && d.d_name[1] == 0 { + /* check for current or parent directory (. or ..) */ + if d_name_str == "." || d_name_str == ".." { continue } - /* check for parent directory (..) */ - #no_bounds_check if buf_len > 2 && d.d_name[0] == '.' && d.d_name[1] == '.' && d.d_name[2] == 0 { - continue - } - - 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) + #partial switch d.type { + case .DIR: + 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) { +_get_working_directory :: proc(allocator: runtime.Allocator) -> (string, Error) { // NOTE(tetra): I would use PATH_MAX here, but I was not able to find // an authoritative value for it across all systems. // The largest value I could find was 4096, so might as well use the page size. @@ -199,37 +154,34 @@ _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) } unreachable() } -_setwd :: proc(dir: string) -> Error { - dir_cstr := strings.clone_to_cstring(dir, context.temp_allocator) - return _ok_or_error(unix.sys_chdir(dir_cstr)) +_set_working_directory :: proc(dir: string) -> Error { + dir_cstr := temp_cstring(dir) or_return + return _get_platform_error(linux.chdir(dir_cstr)) } -_get_full_path :: proc(fd: int, allocator: runtime.Allocator) -> string { +_get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath: string, err: Error) { PROC_FD_PATH :: "/proc/self/fd/" buf: [32]u8 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 if fullpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || fullpath[0] != '/' { - return "" + delete(fullpath, allocator) + fullpath = "" } - return fullpath + return } - diff --git a/core/os/os2/path_posix.odin b/core/os/os2/path_posix.odin new file mode 100644 index 000000000..5ffdac28e --- /dev/null +++ b/core/os/os2/path_posix.odin @@ -0,0 +1,126 @@ +#+private +#+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" +import "core:path/filepath" + +import "core:sys/posix" + +_Path_Separator :: '/' +_Path_Separator_String :: "/" +_Path_List_Separator :: ':' + +_is_path_separator :: proc(c: byte) -> bool { + return c == _Path_Separator +} + +_mkdir :: proc(name: string, perm: int) -> Error { + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) + if posix.mkdir(cname, transmute(posix.mode_t)posix._mode_t(perm)) != .OK { + return _get_platform_error() + } + return nil +} + +_mkdir_all :: proc(path: string, perm: int) -> Error { + if path == "" { + return .Invalid_Path + } + + TEMP_ALLOCATOR_GUARD() + + if exists(path) { + return .Exist + } + + clean_path := filepath.clean(path, temp_allocator()) + return internal_mkdir_all(clean_path, perm) + + internal_mkdir_all :: proc(path: string, perm: int) -> Error { + dir, file := filepath.split(path) + if file != path && dir != "/" { + if len(dir) > 1 && dir[len(dir) - 1] == '/' { + dir = dir[:len(dir) - 1] + } + internal_mkdir_all(dir, perm) or_return + } + + err := _mkdir(path, perm) + if err == .Exist { err = nil } + return err + } +} + +_remove_all :: proc(path: string) -> Error { + TEMP_ALLOCATOR_GUARD() + cpath := temp_cstring(path) + + dir := posix.opendir(cpath) + if dir == nil { + return _get_platform_error() + } + defer posix.closedir(dir) + + for { + posix.set_errno(.NONE) + entry := posix.readdir(dir) + if entry == nil { + if errno := posix.errno(); errno != .NONE { + return _get_platform_error() + } else { + break + } + } + + cname := cstring(raw_data(entry.d_name[:])) + if cname == "." || cname == ".." { + continue + } + + fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator()) + if entry.d_type == .DIR { + _remove_all(fullpath[:len(fullpath)-1]) + } else { + if posix.unlink(cstring(raw_data(fullpath))) != .OK { + return _get_platform_error() + } + } + } + + if posix.rmdir(cpath) != .OK { + return _get_platform_error() + } + return nil +} + +_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + + buf: [dynamic]byte + buf.allocator = temp_allocator() + size := uint(posix.PATH_MAX) + + cwd: cstring + for ; cwd == nil; size *= 2 { + resize(&buf, size) + + cwd = posix.getcwd(raw_data(buf), len(buf)) + if cwd == nil && posix.errno() != .ERANGE { + err = _get_platform_error() + return + } + } + + return clone_string(string(cwd), allocator) +} + +_set_working_directory :: proc(dir: string) -> (err: Error) { + TEMP_ALLOCATOR_GUARD() + cdir := temp_cstring(dir) + if posix.chdir(cdir) != .OK { + err = _get_platform_error() + } + return +} diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index a2306784e..3e92cb6f3 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -1,29 +1,30 @@ -//+private +#+private package os2 import win32 "core:sys/windows" -import "core:runtime" -import "core:strings" +import "base:runtime" -_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 { - if !win32.CreateDirectoryW(_fix_long_path(name), nil) { +_mkdir :: proc(name: string, perm: int) -> Error { + TEMP_ALLOCATOR_GUARD() + if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator()) or_return, nil) { return _get_platform_error() } return nil } -_mkdir_all :: proc(path: string, perm: File_Mode) -> Error { +_mkdir_all :: proc(path: string, perm: int) -> Error { fix_root_directory :: proc(p: string) -> (s: string, allocated: bool, err: runtime.Allocator_Error) { if len(p) == len(`\\?\c:`) { if is_path_separator(p[0]) && is_path_separator(p[1]) && p[2] == '?' && is_path_separator(p[3]) && p[5] == ':' { - s = strings.concatenate({p, `\`}, _file_allocator()) or_return + s = concatenate({p, `\`}, file_allocator()) or_return allocated = true return } @@ -31,9 +32,11 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { return p, false, nil } - dir, err := stat(path, _temp_allocator()) + TEMP_ALLOCATOR_GUARD() + + dir_stat, err := stat(path, temp_allocator()) if err == nil { - if dir.is_dir { + if dir_stat.type == .Directory { return nil } return .Exist @@ -52,15 +55,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_dir { + new_dir_stat, err1 := lstat(path, temp_allocator()) + if err1 == nil && new_dir_stat.type == .Directory { return nil } return err @@ -69,41 +72,114 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { } _remove_all :: proc(path: string) -> Error { - // TODO(bill): _remove_all for windows + if path == "" { + return nil + } + + err := remove(path) + if err == nil || err == .Not_Exist { + return nil + } + + TEMP_ALLOCATOR_GUARD() + dir := win32_utf8_to_wstring(path, temp_allocator()) or_return + + empty: [1]u16 + + file_op := win32.SHFILEOPSTRUCTW { + nil, + win32.FO_DELETE, + dir, + &empty[0], + win32.FOF_NOCONFIRMATION | win32.FOF_NOERRORUI | win32.FOF_SILENT, + false, + nil, + &empty[0], + } + res := win32.SHFileOperationW(&file_op) + if res != 0 { + return _get_platform_error() + } return nil } -_getwd :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - // TODO(bill) - return "", nil +@private cwd_lock: win32.SRWLOCK // zero is initialized + +_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + win32.AcquireSRWLockExclusive(&cwd_lock) + + TEMP_ALLOCATOR_GUARD() + + sz_utf16 := win32.GetCurrentDirectoryW(0, nil) + dir_buf_wstr := make([]u16, sz_utf16, temp_allocator()) or_return + + sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr)) + assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL. + + win32.ReleaseSRWLockExclusive(&cwd_lock) + + return win32_utf16_to_utf8(dir_buf_wstr, allocator) } -_setwd :: proc(dir: string) -> (err: Error) { - // TODO(bill) - return nil -} +_set_working_directory :: proc(dir: string) -> (err: Error) { + TEMP_ALLOCATOR_GUARD() + wstr := win32_utf8_to_wstring(dir, temp_allocator()) or_return + win32.AcquireSRWLockExclusive(&cwd_lock) + + if !win32.SetCurrentDirectoryW(wstr) { + err = _get_platform_error() + } + + win32.ReleaseSRWLockExclusive(&cwd_lock) + + return +} can_use_long_paths: bool @(init) init_long_path_support :: proc() { - // TODO(bill): init_long_path_support - // ADD THIS SHIT - // registry_path := win32.L(`Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled`) can_use_long_paths = false + + key: win32.HKEY + res := win32.RegOpenKeyExW(win32.HKEY_LOCAL_MACHINE, win32.L(`SYSTEM\CurrentControlSet\Control\FileSystem`), 0, win32.KEY_READ, &key) + defer win32.RegCloseKey(key) + if res != 0 { + return + } + + value: u32 + size := u32(size_of(value)) + res = win32.RegGetValueW( + key, + nil, + win32.L("LongPathsEnabled"), + win32.RRF_RT_ANY, + nil, + &value, + &size, + ) + if res != 0 { + return + } + if value == 1 { + can_use_long_paths = true + } + } - -_fix_long_path_slice :: proc(path: string) -> []u16 { - return win32.utf8_to_utf16(_fix_long_path_internal(path)) +@(require_results) +_fix_long_path_slice :: proc(path: string, allocator: runtime.Allocator) -> ([]u16, runtime.Allocator_Error) { + return win32_utf8_to_utf16(_fix_long_path_internal(path), allocator) } -_fix_long_path :: proc(path: string) -> win32.wstring { - return win32.utf8_to_wstring(_fix_long_path_internal(path)) +@(require_results) +_fix_long_path :: proc(path: string, allocator: runtime.Allocator) -> (win32.wstring, runtime.Allocator_Error) { + return win32_utf8_to_wstring(_fix_long_path_internal(path), allocator) } - +@(require_results) _fix_long_path_internal :: proc(path: string) -> string { if can_use_long_paths { return path @@ -125,8 +201,10 @@ _fix_long_path_internal :: proc(path: string) -> string { return path } + 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) @@ -158,5 +236,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..5d3e8368e 100644 --- a/core/os/os2/pipe.odin +++ b/core/os/os2/pipe.odin @@ -1,5 +1,43 @@ package os2 +/* +Create an anonymous pipe. + +This procedure creates an anonymous pipe, returning two ends of the pipe, `r` +and `w`. The file `r` is the readable end of the pipe. The file `w` is a +writeable end of the pipe. + +Pipes are used as an inter-process communication mechanism, to communicate +between a parent and a child process. The child uses one end of the pipe to +write data, and the parent uses the other end to read from the pipe +(or vice-versa). When a parent passes one of the ends of the pipe to the child +process, that end of the pipe needs to be closed by the parent, before any data +is attempted to be read. + +Although pipes look like files and is compatible with most file APIs in package +os2, the way it's meant to be read is different. Due to asynchronous nature of +the communication channel, the data may not be present at the time of a read +request. The other scenario is when a pipe has no data because the other end +of the pipe was closed by the child process. +*/ +@(require_results) pipe :: proc() -> (r, w: ^File, err: Error) { return _pipe() } + +/* +Check if the pipe has any data. + +This procedure checks whether a read-end of the pipe has data that can be +read, and returns `true`, if the pipe has readable data, and `false` if the +pipe is empty. This procedure does not block the execution of the current +thread. + +**Note**: If the other end of the pipe was closed by the child process, the +`.Broken_Pipe` +can be returned by this procedure. Handle these errors accordingly. +*/ +@(require_results) +pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) { + return _pipe_has_data(r) +} diff --git a/core/os/os2/pipe_linux.odin b/core/os/os2/pipe_linux.odin index b66ff9663..bb4456e1c 100644 --- a/core/os/os2/pipe_linux.odin +++ b/core/os/os2/pipe_linux.odin @@ -1,7 +1,43 @@ -//+private +#+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, {.CLOEXEC}) + if errno != .NONE { + return nil, nil,_get_platform_error(errno) + } + + r = _new_file(uintptr(fds[0]), "", file_allocator()) or_return + w = _new_file(uintptr(fds[1]), "", file_allocator()) or_return + + return } +@(require_results) +_pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) { + if r == nil || r.impl == nil { + return false, nil + } + fd := linux.Fd((^File_Impl)(r.impl).fd) + poll_fds := []linux.Poll_Fd { + linux.Poll_Fd { + fd = fd, + events = {.IN, .HUP}, + }, + } + n, errno := linux.poll(poll_fds, 0) + if n != 1 || errno != nil { + return false, _get_platform_error(errno) + } + pipe_events := poll_fds[0].revents + if pipe_events >= {.IN} { + return true, nil + } + if pipe_events >= {.HUP} { + return false, .Broken_Pipe + } + return false, nil +} \ No newline at end of file diff --git a/core/os/os2/pipe_posix.odin b/core/os/os2/pipe_posix.odin new file mode 100644 index 000000000..edead2ab3 --- /dev/null +++ b/core/os/os2/pipe_posix.odin @@ -0,0 +1,73 @@ +#+private +#+build darwin, netbsd, freebsd, openbsd +package os2 + +import "core:sys/posix" +import "core:strings" + +_pipe :: proc() -> (r, w: ^File, err: Error) { + fds: [2]posix.FD + if posix.pipe(&fds) != .OK { + err = _get_platform_error() + return + } + + if posix.fcntl(fds[0], .SETFD, i32(posix.FD_CLOEXEC)) == -1 { + err = _get_platform_error() + return + } + if posix.fcntl(fds[1], .SETFD, i32(posix.FD_CLOEXEC)) == -1 { + err = _get_platform_error() + return + } + + r = __new_file(fds[0], file_allocator()) + ri := (^File_Impl)(r.impl) + + rname := strings.builder_make(file_allocator()) + // TODO(laytan): is this on all the posix targets? + strings.write_string(&rname, "/dev/fd/") + strings.write_int(&rname, int(fds[0])) + ri.name = strings.to_string(rname) + ri.cname = strings.to_cstring(&rname) + + w = __new_file(fds[1], file_allocator()) + wi := (^File_Impl)(w.impl) + + wname := strings.builder_make(file_allocator()) + // TODO(laytan): is this on all the posix targets? + strings.write_string(&wname, "/dev/fd/") + strings.write_int(&wname, int(fds[1])) + wi.name = strings.to_string(wname) + wi.cname = strings.to_cstring(&wname) + + return +} + +@(require_results) +_pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) { + if r == nil || r.impl == nil { + return false, nil + } + fd := __fd(r) + poll_fds := []posix.pollfd { + posix.pollfd { + fd = fd, + events = {.IN, .HUP}, + }, + } + n := posix.poll(raw_data(poll_fds), u32(len(poll_fds)), 0) + if n < 0 { + return false, _get_platform_error() + } else if n != 1 { + return false, nil + } + pipe_events := poll_fds[0].revents + if pipe_events >= {.IN} { + return true, nil + } + if pipe_events >= {.HUP} { + return false, .Broken_Pipe + } + return false, nil +} diff --git a/core/os/os2/pipe_windows.odin b/core/os/os2/pipe_windows.odin index bab8b44f5..d6dc47c9c 100644 --- a/core/os/os2/pipe_windows.odin +++ b/core/os/os2/pipe_windows.odin @@ -1,13 +1,29 @@ -//+private +#+private package os2 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 } +@(require_results) +_pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) { + if r == nil || r.impl == nil { + return false, nil + } + handle := win32.HANDLE((^File_Impl)(r.impl).fd) + bytes_available: u32 + if !win32.PeekNamedPipe(handle, nil, 0, nil, &bytes_available, nil) { + return false, _get_platform_error() + } + return bytes_available > 0, nil +} \ No newline at end of file diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index db47e2f5b..c90e3add2 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -1,102 +1,530 @@ package os2 -import "core:sync" +import "base:runtime" + import "core:time" -import "core:runtime" -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") +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, +} + +ALL_INFO :: Process_Info_Fields{.Executable_Path, .PPid, .Priority, .Command_Line, .Command_Args, .Environment, .Username, .Working_Dir} + +/* +Contains information about the process as obtained by the `process_info()` +procedure. +*/ +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. The +`free_process_info` procedure needs to be called, even if this procedure +returned an error, as some of the fields may have been allocated. + +**Note**: The resulting information may or may contain the fields specified +by the `selection` parameter. Always check whether the returned +`Process_Info` struct has the required fields before checking the error code +returned by this procedure. +*/ +@(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. The +`free_process_info` procedure needs to be called, even if this procedure +returned an error, as some of the fields may have been allocated. + +**Note**: The resulting information may or may contain the fields specified +by the `selection` parameter. Always check whether the returned +`Process_Info` struct has the required fields before checking the error code +returned by this procedure. +*/ +@(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 procedure. The +`free_process_info` procedure needs to be called, even if this procedure +returned an error, as some of the fields may have been allocated. + +**Note**: The resulting information may or may contain the fields specified +by the `selection` parameter. Always check whether the returned +`Process_Info` struct has the required fields before checking the error code +returned by this procedure. +*/ +@(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) + for a in pi.command_args { + delete(a, allocator) + } + delete(pi.command_args, allocator) + for s in pi.environment { + delete(s, allocator) + } + delete(pi.environment, allocator) + delete(pi.working_dir, allocator) + delete(pi.username, allocator) +} + +/* +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, +} + +/* +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) } +/* +OS-specific process attributes. +*/ Process_Attributes :: struct { - dir: string, + sys_attr: _Sys_Process_Attributes, +} + +/* + The description of how a process should be created. +*/ +Process_Desc :: struct { + // OS-specific attributes. + sys_attr: 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. + // NOTE(laytan): maybe should be `Maybe([]string)` so you can do `nil` == current env, empty == empty/no env. 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) } +/* +Execute the process and capture stdout and stderr streams. + +This procedure creates a new process, with a given command and environment +strings as parameters, and waits until the process finishes execution. While +the process is running, this procedure accumulates the output of its stdout +and stderr streams and returns byte slices containing the captured data from +the streams. + +This procedure expects that `stdout` and `stderr` fields of the `desc` parameter +are left at default, i.e. a `nil` value. You can not capture stdout/stderr and +redirect it to a file at the same time. + +This procedure does not free `stdout` and `stderr` slices before an error is +returned. Make sure to call `delete` on these slices. +*/ +@(require_results) +process_exec :: proc( + desc: Process_Desc, + allocator: runtime.Allocator, + loc := #caller_location, +) -> ( + state: Process_State, + stdout: []byte, + stderr: []byte, + err: Error, +) { + assert(desc.stdout == nil, "Cannot redirect stdout when it's being captured", loc) + assert(desc.stderr == nil, "Cannot redirect stderr when it's being captured", loc) + + stdout_r, stdout_w := pipe() or_return + defer close(stdout_r) + stderr_r, stderr_w := pipe() or_return + defer close(stderr_r) + + process: Process + { + // NOTE(flysand): Make sure the write-ends are closed, regardless + // of the outcome. This makes read-ends readable on our side. + defer close(stdout_w) + defer close(stderr_w) + desc := desc + desc.stdout = stdout_w + desc.stderr = stderr_w + process = process_start(desc) or_return + } + + { + stdout_b: [dynamic]byte + stdout_b.allocator = allocator + defer stdout = stdout_b[:] + + stderr_b: [dynamic]byte + stderr_b.allocator = allocator + defer stderr = stderr_b[:] + + buf: [1024]u8 = --- + + stdout_done, stderr_done, has_data: bool + for err == nil && (!stdout_done || !stderr_done) { + n := 0 + + if !stdout_done { + has_data, err = pipe_has_data(stdout_r) + if has_data { + n, err = read(stdout_r, buf[:]) + } + + switch err { + case nil: + _, err = append(&stdout_b, ..buf[:n]) + case .EOF, .Broken_Pipe: + stdout_done = true + err = nil + } + } + + if err == nil && !stderr_done { + n = 0 + has_data, err = pipe_has_data(stderr_r) + if has_data { + n, err = read(stderr_r, buf[:]) + } + + switch err { + case nil: + _, err = append(&stderr_b, ..buf[:n]) + case .EOF, .Broken_Pipe: + stderr_done = true + err = nil + } + } + } + } + + if err != nil { + state, _ = process_wait(process, timeout=0) + if !state.exited { + _ = process_kill(process) + state, _ = process_wait(process) + } + return + } + + state, err = process_wait(process) + return +} + +/* + The state of the process after it has finished execution. +*/ Process_State :: struct { - pid: int, - exit_code: int, - exited: bool, - success: bool, + // The ID of the process. + pid: int, + // Specifies whether the process has terminated or is still running. + exited: bool, + // The exit code of the process, if it has exited. + // Will also store the number of the exception or signal that has crashed the + // process. + exit_code: int, + // Specifies whether the termination of the process was successfull or not, + // i.e. whether it has crashed or not. + // **Note(windows)**: On windows `true` is always returned, as there is no + // reliable way to obtain information about whether the process has crashed. + success: bool, + // The time the process has spend executing in kernel time. system_time: time.Duration, - user_time: time.Duration, - sys: rawptr, + // The time the process has spend executing in userspace. + user_time: time.Duration, } -Signal :: #type proc() +/* +Wait for a process event. -Kill: Signal = nil -Interrupt: Signal = nil +This procedure blocks the execution until the process has exited or the +timeout (if specified) has reached zero. If the timeout is `TIMEOUT_INFINITE`, +no timeout restriction is imposed and the procedure can block indefinately. +If the timeout has expired, the `General_Error.Timeout` is returned as +the error. -find_process :: proc(pid: int) -> (^Process, Process_Error) { - return nil, .None +If an error is returned for any other reason, other than timeout, the +process state is considered undetermined. +*/ +@(require_results) +process_wait :: proc(process: Process, timeout := TIMEOUT_INFINITE) -> (Process_State, Error) { + return _process_wait(process, timeout) } +/* +Close the handle to a process. -process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes) -> (^Process, Process_Error) { - return nil, .None +This procedure closes the handle associated with a process. It **does not** +terminate a process, in case it was running. In case a termination is +desired, kill the process first, wait for the process to finish, +then close the handle. +*/ +@(require_results) +process_close :: proc(process: Process) -> (Error) { + return _process_close(process) } -process_release :: proc(p: ^Process) -> Process_Error { - return .None +/* +Terminate a process. + +This procedure terminates a process, specified by it's handle, `process`. +*/ +@(require_results) +process_kill :: proc(process: Process) -> (Error) { + return _process_kill(process) } - -process_kill :: proc(p: ^Process) -> Process_Error { - return .None -} - -process_signal :: proc(p: ^Process, sig: Signal) -> Process_Error { - return .None -} - -process_wait :: proc(p: ^Process) -> (Process_State, Process_Error) { - return {}, .None -} - - - - diff --git a/core/os/os2/process_linux.odin b/core/os/os2/process_linux.odin new file mode 100644 index 000000000..936fbfc40 --- /dev/null +++ b/core/os/os2/process_linux.odin @@ -0,0 +1,841 @@ +#+build linux +#+private file +package os2 + +import "base:runtime" +import "base:intrinsics" + +import "core:time" +import "core:slice" +import "core:strings" +import "core:strconv" +import "core:sys/linux" +import "core:path/filepath" + +PIDFD_UNASSIGNED :: ~uintptr(0) + +@(private="package") +_exit :: proc "contextless" (code: int) -> ! { + linux.exit_group(i32(code)) +} + +@(private="package") +_get_uid :: proc() -> int { + return int(linux.getuid()) +} + +@(private="package") +_get_euid :: proc() -> int { + return int(linux.geteuid()) +} + +@(private="package") +_get_gid :: proc() -> int { + return int(linux.getgid()) +} + +@(private="package") +_get_egid :: proc() -> int { + return int(linux.getegid()) +} + +@(private="package") +_get_pid :: proc() -> int { + return int(linux.getpid()) +} + +@(private="package") +_get_ppid :: proc() -> int { + return int(linux.getppid()) +} + +@(private="package") +_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { + TEMP_ALLOCATOR_GUARD() + + dir_fd, errno := linux.open("/proc/", _OPENDIR_FLAGS) + #partial switch errno { + case .NONE: + // okay + case .ENOTDIR: + err = .Invalid_Dir + return + case .ENOENT: + err = .Not_Exist + return + case: + err = _get_platform_error(errno) + return + } + defer linux.close(dir_fd) + + dynamic_list := make([dynamic]int, temp_allocator()) or_return + + buf := make([dynamic]u8, 128, 128, temp_allocator()) or_return + loop: for { + buflen: int + buflen, errno = linux.getdents(dir_fd, buf[:]) + #partial switch errno { + case .EINVAL: + resize(&buf, len(buf) * 2) + continue loop + case .NONE: + if buflen == 0 { break loop } + case: + return {}, _get_platform_error(errno) + } + + offset: int + for d in linux.dirent_iterate_buf(buf[:buflen], &offset) { + d_name_str := linux.dirent_name(d) + + if pid, ok := strconv.parse_int(d_name_str); ok { + append(&dynamic_list, pid) + } + } + } + + list, err = slice.clone(dynamic_list[:], allocator) + return +} + +@(private="package") +_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + TEMP_ALLOCATOR_GUARD() + + info.pid = pid + + // Use this to make cstrings without copying. + path_backing: [48]u8 + path_builder := strings.builder_from_bytes(path_backing[:]) + + strings.write_string(&path_builder, "/proc/") + strings.write_int(&path_builder, pid) + proc_fd, errno := linux.open(strings.to_cstring(&path_builder), _OPENDIR_FLAGS) + if errno != .NONE { + err = _get_platform_error(errno) + return + } + defer linux.close(proc_fd) + + username_if: if .Username in selection { + s: linux.Stat + if errno = linux.fstat(proc_fd, &s); errno != .NONE { + err = _get_platform_error(errno) + break username_if + } + + passwd_bytes: []u8 + passwd_err: Error + passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator()) + if passwd_err != nil { + err = passwd_err + break username_if + } + + passwd := string(passwd_bytes) + for len(passwd) > 0 { + n := strings.index_byte(passwd, ':') + if n < 0 { + break + } + username := passwd[:n] + passwd = passwd[n+1:] + + // skip password field + passwd = passwd[strings.index_byte(passwd, ':') + 1:] + + n = strings.index_byte(passwd, ':') + if uid, ok := strconv.parse_int(passwd[:n]); ok && uid == int(s.uid) { + info.username = strings.clone(username, allocator) or_return + info.fields += {.Username} + break + } else if !ok { + err = .Invalid_File + break username_if + } + + eol := strings.index_byte(passwd, '\n') + if eol < 0 { + break + } + passwd = passwd[eol + 1:] + } + } + + cmdline_if: if selection & {.Working_Dir, .Command_Line, .Command_Args, .Executable_Path} != {} { + strings.builder_reset(&path_builder) + strings.write_string(&path_builder, "/proc/") + strings.write_int(&path_builder, pid) + strings.write_string(&path_builder, "/cmdline") + + cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder), temp_allocator()) + if cmdline_err != nil || len(cmdline_bytes) == 0 { + err = cmdline_err + break cmdline_if + } + cmdline := string(cmdline_bytes) + + terminator := strings.index_byte(cmdline, 0) + assert(terminator > 0) + + command_line_exec := cmdline[:terminator] + + // Still need cwd if the execution on the command line is relative. + cwd: string + cwd_err: Error + if .Working_Dir in selection || (.Executable_Path in selection && command_line_exec[0] != '/') { + strings.builder_reset(&path_builder) + strings.write_string(&path_builder, "/proc/") + strings.write_int(&path_builder, pid) + strings.write_string(&path_builder, "/cwd") + + cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder), temp_allocator()) // allowed to fail + if cwd_err == nil && .Working_Dir in selection { + info.working_dir = strings.clone(cwd, allocator) or_return + info.fields += {.Working_Dir} + } else if cwd_err != nil { + err = cwd_err + break cmdline_if + } + } + + if .Executable_Path in selection { + if cmdline[0] == '/' { + info.executable_path = strings.clone(cmdline[:terminator], allocator) or_return + info.fields += {.Executable_Path} + } else if cwd_err == nil { + info.executable_path = filepath.join({ cwd, cmdline[:terminator] }, allocator) or_return + info.fields += {.Executable_Path} + } else { + break cmdline_if + } + } + + if selection & {.Command_Line, .Command_Args} != {} { + // skip to first arg + //cmdline = cmdline[terminator + 1:] + command_line_builder: strings.Builder + command_args_list: [dynamic]string + + if .Command_Line in selection { + command_line_builder = strings.builder_make(allocator) or_return + info.fields += {.Command_Line} + } + + for i := 0; len(cmdline) > 0; i += 1 { + if terminator = strings.index_byte(cmdline, 0); terminator < 0 { + break + } + + if .Command_Line in selection { + if i > 0 { + strings.write_byte(&command_line_builder, ' ') + } + strings.write_string(&command_line_builder, cmdline[:terminator]) + } + if .Command_Args in selection { + if i == 1 { + command_args_list = make([dynamic]string, allocator) or_return + info.fields += {.Command_Args} + } + if i > 0 { + arg := strings.clone(cmdline[:terminator], allocator) or_return + append(&command_args_list, arg) or_return + } + } + + cmdline = cmdline[terminator + 1:] + } + info.command_line = strings.to_string(command_line_builder) + info.command_args = command_args_list[:] + } + } + + stat_if: if selection & {.PPid, .Priority} != {} { + strings.builder_reset(&path_builder) + strings.write_string(&path_builder, "/proc/") + strings.write_int(&path_builder, pid) + strings.write_string(&path_builder, "/stat") + + proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder), temp_allocator()) + if stat_err != nil { + err = stat_err + break stat_if + } + if len(proc_stat_bytes) <= 0 { + break stat_if + } + + // Skip to the first field after the executable name + stats: string + if start := strings.last_index_byte(string(proc_stat_bytes), ')'); start != -1 { + stats = string(proc_stat_bytes[start + 2:]) + } else { + break stat_if + } + + // NOTE: index 0 corresponds to field 3 (state) from `man 5 proc_pid_stat` + // because we skipped passed the executable name above. + Fields :: enum { + State, + PPid, + PGrp, + Session, + Tty_Nr, + TpGid, + Flags, + MinFlt, + CMinFlt, + MajFlt, + CMajFlt, + UTime, + STime, + CUTime, + CSTime, + Priority, + Nice, + //... etc, + } + stat_fields := strings.split(stats, " ", temp_allocator()) or_return + + if len(stat_fields) <= int(Fields.Nice) { + break stat_if + } + + if .PPid in selection { + if ppid, ok := strconv.parse_int(stat_fields[Fields.PPid]); ok { + info.ppid = ppid + info.fields += {.PPid} + } else { + err = .Invalid_File + break stat_if + } + } + + if .Priority in selection { + if nice, ok := strconv.parse_int(stat_fields[Fields.Nice]); ok { + info.priority = nice + info.fields += {.Priority} + } else { + err = .Invalid_File + break stat_if + } + } + } + + if .Environment in selection { + strings.builder_reset(&path_builder) + strings.write_string(&path_builder, "/proc/") + strings.write_int(&path_builder, pid) + strings.write_string(&path_builder, "/environ") + + if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder), temp_allocator()); env_err == nil { + env := string(env_bytes) + + env_list := make([dynamic]string, allocator) or_return + for len(env) > 0 { + terminator := strings.index_byte(env, 0) + if terminator <= 0 { + break + } + e := strings.clone(env[:terminator], allocator) or_return + append(&env_list, e) or_return + env = env[terminator + 1:] + } + info.environment = env_list[:] + info.fields += {.Environment} + } else if err == nil { + err = env_err + } + } + + return +} + +@(private="package") +_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + return _process_info_by_pid(process.pid, selection, allocator) +} + +@(private="package") +_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + return _process_info_by_pid(get_pid(), selection, allocator) +} + +@(private="package") +_process_open :: proc(pid: int, _: Process_Open_Flags) -> (process: Process, err: Error) { + process.pid = pid + process.handle = PIDFD_UNASSIGNED + + pidfd, errno := linux.pidfd_open(linux.Pid(pid), {}) + if errno == .ENOSYS { + return process, .Unsupported + } + if errno != .NONE { + return process, _get_platform_error(errno) + } + process.handle = uintptr(pidfd) + return +} + +@(private="package") +_Sys_Process_Attributes :: struct {} + +@(private="package") +_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { + TEMP_ALLOCATOR_GUARD() + + if len(desc.command) == 0 { + return process, .Invalid_Command + } + + dir_fd := linux.AT_FDCWD + errno: linux.Errno + if desc.working_dir != "" { + dir_cstr := temp_cstring(desc.working_dir) or_return + if dir_fd, errno = linux.open(dir_cstr, _OPENDIR_FLAGS); errno != .NONE { + return process, _get_platform_error(errno) + } + } + defer if desc.working_dir != "" { + linux.close(dir_fd) + } + + // search PATH if just a plain name is provided + exe_path: cstring + executable_name := desc.command[0] + if strings.index_byte(executable_name, '/') < 0 { + path_env := get_env("PATH", temp_allocator()) + path_dirs := filepath.split_list(path_env, temp_allocator()) or_return + + exe_builder := strings.builder_make(temp_allocator()) or_return + + found: bool + for dir in path_dirs { + strings.builder_reset(&exe_builder) + strings.write_string(&exe_builder, dir) + strings.write_byte(&exe_builder, '/') + strings.write_string(&exe_builder, executable_name) + + exe_path = strings.to_cstring(&exe_builder) + if linux.access(exe_path, linux.X_OK) == .NONE { + found = true + break + } + } + if !found { + // check in cwd to match windows behavior + strings.builder_reset(&exe_builder) + strings.write_string(&exe_builder, "./") + strings.write_string(&exe_builder, executable_name) + + exe_path = strings.to_cstring(&exe_builder) + if linux.access(exe_path, linux.X_OK) != .NONE { + return process, .Not_Exist + } + } + } else { + exe_path = temp_cstring(executable_name) or_return + if linux.access(exe_path, linux.X_OK) != .NONE { + return process, .Not_Exist + } + } + + // args and environment need to be a list of cstrings + // that are terminated by a nil pointer. + cargs := make([]cstring, len(desc.command) + 1, temp_allocator()) or_return + for command, i in desc.command { + cargs[i] = temp_cstring(command) or_return + } + + // Use current process' environment if description didn't provide it. + env: [^]cstring + if desc.env == nil { + // take this process's current environment + env = raw_data(export_cstring_environment(temp_allocator())) + } else { + cenv := make([]cstring, len(desc.env) + 1, temp_allocator()) or_return + for env, i in desc.env { + cenv[i] = temp_cstring(env) or_return + } + env = &cenv[0] + } + + child_pipe_fds: [2]linux.Fd + if errno = linux.pipe2(&child_pipe_fds, {.CLOEXEC}); errno != .NONE { + return process, _get_platform_error(errno) + } + defer linux.close(child_pipe_fds[READ]) + + // TODO: This is the traditional textbook implementation with fork. + // A more efficient implementation with vfork: + // + // 1. retrieve signal handlers + // 2. block all signals + // 3. allocate some stack space + // 4. vfork (waits for child exit or execve); In child: + // a. set child signal handlers + // b. set up any necessary pipes + // c. execve + // 5. restore signal handlers + // + pid: linux.Pid + if pid, errno = linux.fork(); errno != .NONE { + linux.close(child_pipe_fds[WRITE]) + return process, _get_platform_error(errno) + } + + STDIN :: linux.Fd(0) + STDOUT :: linux.Fd(1) + STDERR :: linux.Fd(2) + + READ :: 0 + WRITE :: 1 + + if pid == 0 { + // in child process now + write_errno_to_parent_and_abort :: proc(parent_fd: linux.Fd, errno: linux.Errno) -> ! { + error_byte: [1]u8 = { u8(errno) } + linux.write(parent_fd, error_byte[:]) + linux.exit(126) + } + + stdin_fd: linux.Fd + stdout_fd: linux.Fd + stderr_fd: linux.Fd + + if desc.stdin != nil { + stdin_fd = linux.Fd(fd(desc.stdin)) + } else { + stdin_fd, errno = linux.open("/dev/null", {}) + if errno != .NONE { + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } + } + + write_devnull: linux.Fd = -1 + + if desc.stdout != nil { + stdout_fd = linux.Fd(fd(desc.stdout)) + } else { + write_devnull, errno = linux.open("/dev/null", {.WRONLY}) + if errno != .NONE { + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } + stdout_fd = write_devnull + } + + if desc.stderr != nil { + stderr_fd = linux.Fd(fd(desc.stderr)) + } else { + if write_devnull < 0 { + write_devnull, errno = linux.open("/dev/null", {.WRONLY}) + if errno != .NONE { + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } + } + stderr_fd = write_devnull + } + + if _, errno = linux.dup2(stdin_fd, STDIN); errno != .NONE { + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } + if _, errno = linux.dup2(stdout_fd, STDOUT); errno != .NONE { + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } + if _, errno = linux.dup2(stderr_fd, STDERR); errno != .NONE { + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } + + errno = linux.execveat(dir_fd, exe_path, &cargs[0], env) + assert(errno != nil) + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } + + linux.close(child_pipe_fds[WRITE]) + + process.pid = int(pid) + + child_byte: [1]u8 + errno = .EINTR + for errno == .EINTR { + _, errno = linux.read(child_pipe_fds[READ], child_byte[:]) + } + + // If the read failed, something weird happened. Do not return the read + // error so the user knows to wait on it. + if errno == .NONE { + child_errno := linux.Errno(child_byte[0]) + if child_errno != .NONE { + // We can assume it trapped here. + _reap_terminated(process) + process.pid = 0 + return process, _get_platform_error(child_errno) + } + } + + process, _ = process_open(int(pid)) + return +} + +_process_state_update_times :: proc(state: ^Process_State) -> (err: Error) { + TEMP_ALLOCATOR_GUARD() + + stat_path_buf: [48]u8 + path_builder := strings.builder_from_bytes(stat_path_buf[:]) + strings.write_string(&path_builder, "/proc/") + strings.write_int(&path_builder, int(state.pid)) + strings.write_string(&path_builder, "/stat") + + stat_buf: []u8 + stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder), temp_allocator()) + if err != nil { + return + } + + // ')' will be the end of the executable name (item 2) + idx := strings.last_index_byte(string(stat_buf), ')') + stats := string(stat_buf[idx + 2:]) + + // utime and stime are the 14 and 15th items, respectively, and we are + // currently on item 3. Skip 11 items here. + for _ in 0..<11 { + stats = stats[strings.index_byte(stats, ' ') + 1:] + } + + idx = strings.index_byte(stats, ' ') + utime_str := stats[:idx] + + stats = stats[idx + 1:] + stime_str := stats[:strings.index_byte(stats, ' ')] + + utime, stime: int + ok: bool + if utime, ok = strconv.parse_int(utime_str, 10); !ok { + return .Invalid_File + } + if stime, ok = strconv.parse_int(stime_str, 10); !ok { + return .Invalid_File + } + + // NOTE: Assuming HZ of 100, 1 jiffy == 10 ms + state.user_time = time.Duration(utime) * 10 * time.Millisecond + state.system_time = time.Duration(stime) * 10 * time.Millisecond + + return +} + +_reap_terminated :: proc(process: Process) -> (state: Process_State, err: Error) { + state.pid = process.pid + _process_state_update_times(&state) + + info: linux.Sig_Info + errno := linux.Errno.EINTR + for errno == .EINTR { + errno = linux.waitid(.PID, linux.Id(process.pid), &info, {.WEXITED}, nil) + } + err = _get_platform_error(errno) + + switch linux.Sig_Child_Code(info.code) { + case .NONE, .CONTINUED, .STOPPED: + unreachable() + case .EXITED: + state.exited = true + state.exit_code = int(info.status) + state.success = state.exit_code == 0 + case .KILLED, .DUMPED, .TRAPPED: + state.exited = true + state.exit_code = int(info.status) + state.success = false + } + return +} + +_timed_wait_on_handle :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { + timeout := timeout + + process_state.pid = process.pid + pidfd := linux.Fd(process.handle) + pollfd: [1]linux.Poll_Fd = { + { + fd = pidfd, + events = {.IN}, + }, + } + + start_tick := time.tick_now() + + mask: bit_set[0..<64; u64] + mask += { int(linux.Signal.SIGCHLD) - 1 } + sigchld_set := transmute(linux.Sig_Set)(mask) + + info: linux.Sig_Info + for { + if timeout <= 0 { + _process_state_update_times(&process_state) + err = .Timeout + return + } + + ts: linux.Time_Spec = { + time_sec = uint(timeout / time.Second), + time_nsec = uint(timeout % time.Second), + } + + n, errno := linux.ppoll(pollfd[:], &ts, &sigchld_set) + if errno != .NONE { + if errno == .EINTR { + timeout -= time.tick_since(start_tick) + start_tick = time.tick_now() + continue + } + return process_state, _get_platform_error(errno) + } + + if n == 0 { // timeout with no events + _process_state_update_times(&process_state) + err = .Timeout + return + } + + if errno = linux.waitid(.PIDFD, linux.Id(process.handle), &info, {.WEXITED, .WNOHANG, .WNOWAIT}, nil); errno != .NONE { + return process_state, _get_platform_error(errno) + } + + if info.signo == .SIGCHLD { + break + } + + timeout -= time.tick_since(start_tick) + start_tick = time.tick_now() + } + + // _reap_terminated for pidfd + { + _process_state_update_times(&process_state) + + errno := linux.Errno.EINTR + for errno == .EINTR { + errno = linux.waitid(.PIDFD, linux.Id(process.handle), &info, {.WEXITED}, nil) + } + err = _get_platform_error(errno) + + switch linux.Sig_Child_Code(info.code) { + case .NONE, .CONTINUED, .STOPPED: + unreachable() + case .EXITED: + process_state.exited = true + process_state.exit_code = int(info.status) + process_state.success = process_state.exit_code == 0 + case .KILLED, .DUMPED, .TRAPPED: + process_state.exited = true + process_state.exit_code = int(info.status) + process_state.success = false + } + } + return +} + +_timed_wait_on_pid :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { + timeout := timeout + process_state.pid = process.pid + + mask: bit_set[0..<64; u64] + mask += { int(linux.Signal.SIGCHLD) - 1 } + sigchld_set := transmute(linux.Sig_Set)(mask) + + start_tick := time.tick_now() + + org_sigset: linux.Sig_Set + errno := linux.rt_sigprocmask(.SIG_BLOCK, &sigchld_set, &org_sigset) + if errno != .NONE { + return process_state, _get_platform_error(errno) + } + defer linux.rt_sigprocmask(.SIG_SETMASK, &org_sigset, nil) + + // In case there was a signal handler on SIGCHLD, avoid race + // condition by checking wait first. + info: linux.Sig_Info + errno = linux.waitid(.PID, linux.Id(process.pid), &info, {.WNOWAIT, .WEXITED, .WNOHANG}, nil) + + for errno != .NONE || info.code == 0 || info.pid != linux.Pid(process.pid) { + if timeout <= 0 { + _process_state_update_times(&process_state) + err = .Timeout + return + } + + ts: linux.Time_Spec = { + time_sec = uint(timeout / time.Second), + time_nsec = uint(timeout % time.Second), + } + + _, errno = linux.rt_sigtimedwait(&sigchld_set, &info, &ts) + #partial switch errno { + case .EAGAIN: // timeout + _process_state_update_times(&process_state) + err = .Timeout + return + case .EINTR: + timeout -= time.tick_since(start_tick) + start_tick = time.tick_now() + case .EINVAL: + return process_state, _get_platform_error(errno) + } + } + + return _reap_terminated(process) +} + +@(private="package") +_process_wait :: proc(process: Process, timeout: time.Duration) -> (Process_State, Error) { + if timeout > 0 { + if process.handle == PIDFD_UNASSIGNED { + return _timed_wait_on_pid(process, timeout) + } else { + return _timed_wait_on_handle(process, timeout) + } + } + + process_state: Process_State = { + pid = process.pid, + } + + errno: linux.Errno + options: linux.Wait_Options = {.WEXITED} + if timeout == 0 { + options += {.WNOHANG} + } + + info: linux.Sig_Info + + errno = .EINTR + for errno == .EINTR { + errno = linux.waitid(.PID, linux.Id(process.pid), &info, options + {.WNOWAIT}, nil) + } + if errno == .EAGAIN || (errno == .NONE && info.signo != .SIGCHLD) { + _process_state_update_times(&process_state) + return process_state, .Timeout + } + if errno != .NONE { + return process_state, _get_platform_error(errno) + } + + return _reap_terminated(process) +} + +@(private="package") +_process_close :: proc(process: Process) -> Error { + if process.handle == 0 || process.handle == PIDFD_UNASSIGNED { + return nil + } + pidfd := linux.Fd(process.handle) + return _get_platform_error(linux.close(pidfd)) +} + +@(private="package") +_process_kill :: proc(process: Process) -> Error { + return _get_platform_error(linux.kill(linux.Pid(process.pid), .SIGKILL)) +} + diff --git a/core/os/os2/process_posix.odin b/core/os/os2/process_posix.odin new file mode 100644 index 000000000..b54374cec --- /dev/null +++ b/core/os/os2/process_posix.odin @@ -0,0 +1,351 @@ +#+private +#+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" + +import "core:time" +import "core:strings" +import "core:path/filepath" + +import kq "core:sys/kqueue" +import "core:sys/posix" + +_exit :: proc "contextless" (code: int) -> ! { + posix.exit(i32(code)) +} + +_get_uid :: proc() -> int { + return int(posix.getuid()) +} + +_get_euid :: proc() -> int { + return int(posix.geteuid()) +} + +_get_gid :: proc() -> int { + return int(posix.getgid()) +} + +_get_egid :: proc() -> int { + return int(posix.getegid()) +} + +_get_pid :: proc() -> int { + return int(posix.getpid()) +} + +_get_ppid :: proc() -> int { + return int(posix.getppid()) +} + +_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + return _process_info_by_pid(process.pid, selection, allocator) +} + +_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + return _process_info_by_pid(_get_pid(), selection, allocator) +} + +_Sys_Process_Attributes :: struct {} + +_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { + if len(desc.command) == 0 { + err = .Invalid_Path + return + } + + TEMP_ALLOCATOR_GUARD() + + // search PATH if just a plain name is provided. + exe_builder := strings.builder_make(temp_allocator()) + exe_name := desc.command[0] + if strings.index_byte(exe_name, '/') < 0 { + path_env := get_env("PATH", temp_allocator()) + path_dirs := filepath.split_list(path_env, temp_allocator()) + + found: bool + for dir in path_dirs { + strings.builder_reset(&exe_builder) + strings.write_string(&exe_builder, dir) + strings.write_byte(&exe_builder, '/') + strings.write_string(&exe_builder, exe_name) + + if exe_fd := posix.open(strings.to_cstring(&exe_builder), {.CLOEXEC, .EXEC}); exe_fd == -1 { + continue + } else { + posix.close(exe_fd) + found = true + break + } + } + if !found { + // check in cwd to match windows behavior + strings.builder_reset(&exe_builder) + strings.write_string(&exe_builder, desc.working_dir) + if len(desc.working_dir) > 0 && desc.working_dir[len(desc.working_dir)-1] != '/' { + strings.write_byte(&exe_builder, '/') + } + strings.write_string(&exe_builder, "./") + strings.write_string(&exe_builder, exe_name) + + // "hello/./world" is fine right? + + if exe_fd := posix.open(strings.to_cstring(&exe_builder), {.CLOEXEC, .EXEC}); exe_fd == -1 { + err = .Not_Exist + return + } else { + posix.close(exe_fd) + } + } + } else { + strings.builder_reset(&exe_builder) + strings.write_string(&exe_builder, exe_name) + + if exe_fd := posix.open(strings.to_cstring(&exe_builder), {.CLOEXEC, .EXEC}); exe_fd == -1 { + err = .Not_Exist + return + } else { + posix.close(exe_fd) + } + } + + cwd: cstring; if desc.working_dir != "" { + cwd = temp_cstring(desc.working_dir) + } + + cmd := make([]cstring, len(desc.command) + 1, temp_allocator()) + for part, i in desc.command { + cmd[i] = temp_cstring(part) + } + + env: [^]cstring + if desc.env == nil { + // take this process's current environment + env = posix.environ + } else { + cenv := make([]cstring, len(desc.env) + 1, temp_allocator()) + for env, i in desc.env { + cenv[i] = temp_cstring(env) + } + env = raw_data(cenv) + } + + READ :: 0 + WRITE :: 1 + + pipe: [2]posix.FD + if posix.pipe(&pipe) != .OK { + err = _get_platform_error() + return + } + defer posix.close(pipe[READ]) + + if posix.fcntl(pipe[READ], .SETFD, i32(posix.FD_CLOEXEC)) == -1 { + posix.close(pipe[WRITE]) + err = _get_platform_error() + return + } + if posix.fcntl(pipe[WRITE], .SETFD, i32(posix.FD_CLOEXEC)) == -1 { + posix.close(pipe[WRITE]) + err = _get_platform_error() + return + } + + switch pid := posix.fork(); pid { + case -1: + posix.close(pipe[WRITE]) + err = _get_platform_error() + return + + case 0: + abort :: proc(parent_fd: posix.FD) -> ! { + #assert(len(posix.Errno) < max(u8)) + errno := u8(posix.errno()) + posix.write(parent_fd, &errno, 1) + posix.exit(126) + } + + null := posix.open("/dev/null", {.RDWR}) + if null == -1 { abort(pipe[WRITE]) } + + stderr := (^File_Impl)(desc.stderr.impl).fd if desc.stderr != nil else null + stdout := (^File_Impl)(desc.stdout.impl).fd if desc.stdout != nil else null + stdin := (^File_Impl)(desc.stdin.impl).fd if desc.stdin != nil else null + + if posix.dup2(stderr, posix.STDERR_FILENO) == -1 { abort(pipe[WRITE]) } + if posix.dup2(stdout, posix.STDOUT_FILENO) == -1 { abort(pipe[WRITE]) } + if posix.dup2(stdin, posix.STDIN_FILENO ) == -1 { abort(pipe[WRITE]) } + + if cwd != nil { + if posix.chdir(cwd) != .OK { abort(pipe[WRITE]) } + } + + res := posix.execve(strings.to_cstring(&exe_builder), raw_data(cmd), env) + assert(res == -1) + abort(pipe[WRITE]) + + case: + posix.close(pipe[WRITE]) + + errno: posix.Errno + for { + errno_byte: u8 + switch posix.read(pipe[READ], &errno_byte, 1) { + case 1: + errno = posix.Errno(errno_byte) + case -1: + errno = posix.errno() + if errno == .EINTR { + continue + } else { + // If the read failed, something weird happened. Do not return the read + // error so the user knows to wait on it. + errno = nil + } + } + break + } + + if errno != nil { + // We can assume it trapped here. + + for { + info: posix.siginfo_t + wpid := posix.waitid(.P_PID, posix.id_t(process.pid), &info, {.EXITED}) + if wpid == -1 && posix.errno() == .EINTR { + continue + } + break + } + + err = errno + return + } + + process, _ = _process_open(int(pid), {}) + return + } +} + +_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { + process_state.pid = process.pid + + _process_handle_still_valid(process) or_return + + // timeout > 0 = use kqueue to wait (with a timeout) on process exit + // timeout == 0 = use waitid with WNOHANG so it returns immediately + // timeout > 0 = use waitid without WNOHANG so it waits indefinitely + // + // at the end use waitid to actually reap the process and get it's status + + if timeout > 0 { + timeout := timeout + + queue := kq.kqueue() or_return + defer posix.close(queue) + + changelist, eventlist: [1]kq.KEvent + + changelist[0] = { + ident = uintptr(process.pid), + filter = .Proc, + flags = { .Add }, + fflags = { + fproc = { .Exit }, + }, + } + + for { + start := time.tick_now() + n, kerr := kq.kevent(queue, changelist[:], eventlist[:], &{ + tv_sec = posix.time_t(timeout / time.Second), + tv_nsec = i64(timeout % time.Second), + }) + if kerr == .EINTR { + timeout -= time.tick_since(start) + continue + } else if kerr != nil { + err = kerr + return + } else if n == 0 { + err = .Timeout + _process_state_update_times(process, &process_state) + return + } else { + _process_state_update_times(process, &process_state) + break + } + } + } else { + flags := posix.Wait_Flags{.EXITED, .NOWAIT} + if timeout == 0 { + flags += {.NOHANG} + } + + info: posix.siginfo_t + for { + wpid := posix.waitid(.P_PID, posix.id_t(process.pid), &info, flags) + if wpid == -1 { + if errno := posix.errno(); errno == .EINTR { + continue + } else { + err = _get_platform_error() + return + } + } + break + } + + _process_state_update_times(process, &process_state) + + if info.si_signo == nil { + assert(timeout == 0) + err = .Timeout + return + } + } + + info: posix.siginfo_t + for { + wpid := posix.waitid(.P_PID, posix.id_t(process.pid), &info, {.EXITED}) + if wpid == -1 { + if errno := posix.errno(); errno == .EINTR { + continue + } else { + err = _get_platform_error() + return + } + } + break + } + + switch info.si_code.chld { + case: unreachable() + case .CONTINUED, .STOPPED: unreachable() + case .EXITED: + process_state.exited = true + process_state.exit_code = int(info.si_status) + process_state.success = process_state.exit_code == 0 + case .KILLED, .DUMPED, .TRAPPED: + process_state.exited = true + process_state.exit_code = int(info.si_status) + process_state.success = false + } + + return +} + +_process_close :: proc(process: Process) -> Error { + return nil +} + +_process_kill :: proc(process: Process) -> (err: Error) { + _process_handle_still_valid(process) or_return + + if posix.kill(posix.pid_t(process.pid), .SIGKILL) != .OK { + err = _get_platform_error() + } + + return +} diff --git a/core/os/os2/process_posix_darwin.odin b/core/os/os2/process_posix_darwin.odin new file mode 100644 index 000000000..0ea1f643c --- /dev/null +++ b/core/os/os2/process_posix_darwin.odin @@ -0,0 +1,304 @@ +#+private +package os2 + +import "base:runtime" +import "base:intrinsics" + +import "core:bytes" +import "core:sys/darwin" +import "core:sys/posix" +import "core:sys/unix" +import "core:time" + +foreign import lib "system:System.framework" + +foreign lib { + sysctl :: proc( + name: [^]i32, namelen: u32, + oldp: rawptr, oldlenp: ^uint, + newp: rawptr, newlen: uint, + ) -> posix.result --- +} + +_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + get_pidinfo :: proc(pid: int, selection: Process_Info_Fields) -> (ppid: u32, prio: Maybe(i32), uid: posix.uid_t, ok: bool) { + // Short info is enough and requires less permissions if the priority isn't requested. + if .Priority in selection { + info: darwin.proc_taskallinfo + ret := darwin.proc_pidinfo(posix.pid_t(pid), .TASKALLINFO, 0, &info, size_of(info)) + if ret > 0 { + assert(ret == size_of(info)) + ppid = info.pbsd.pbi_ppid + prio = info.ptinfo.pti_priority + uid = info.pbsd.pbi_uid + ok = true + return + } + } + + // Try short info, requires less permissions, but doesn't give a `nice`. + psinfo: darwin.proc_bsdshortinfo + ret := darwin.proc_pidinfo(posix.pid_t(pid), .SHORTBSDINFO, 0, &psinfo, size_of(psinfo)) + if ret > 0 { + assert(ret == size_of(psinfo)) + ppid = psinfo.pbsi_ppid + uid = psinfo.pbsi_uid + ok = true + } + + return + } + + + info.pid = pid + + // Thought on errors is: allocation failures return immediately (also why the non-allocation stuff is done first), + // other errors usually mean other parts of the info could be retrieved though, so in those cases we keep trying to get the other information. + + pidinfo: { + if selection & {.PPid, .Priority, .Username } != {} { + ppid, mprio, uid, ok := get_pidinfo(pid, selection) + if !ok { + if err == nil { + err = _get_platform_error() + } + break pidinfo + } + + if .PPid in selection { + info.ppid = int(ppid) + info.fields += {.PPid} + } + + if prio, has_prio := mprio.?; has_prio && .Priority in selection { + info.priority = int(prio) + info.fields += {.Priority} + } + + if .Username in selection { + pw := posix.getpwuid(uid) + if pw == nil { + if err == nil { + err = _get_platform_error() + } + break pidinfo + } + + info.username = clone_string(string(pw.pw_name), allocator) or_return + info.fields += {.Username} + } + } + } + + if .Working_Dir in selection { + pinfo: darwin.proc_vnodepathinfo + ret := darwin.proc_pidinfo(posix.pid_t(pid), .VNODEPATHINFO, 0, &pinfo, size_of(pinfo)) + if ret > 0 { + assert(ret == size_of(pinfo)) + info.working_dir = clone_string(string(cstring(raw_data(pinfo.pvi_cdir.vip_path[:]))), allocator) or_return + info.fields += {.Working_Dir} + } else if err == nil { + err = _get_platform_error() + } + } + + if .Executable_Path in selection { + buffer: [darwin.PIDPATHINFO_MAXSIZE]byte = --- + ret := darwin.proc_pidpath(posix.pid_t(pid), raw_data(buffer[:]), len(buffer)) + if ret > 0 { + info.executable_path = clone_string(string(buffer[:ret]), allocator) or_return + info.fields += {.Executable_Path} + } else if err == nil { + err = _get_platform_error() + } + } + + args: if selection & { .Command_Line, .Command_Args, .Environment } != {} { + mib := []i32{ + unix.CTL_KERN, + unix.KERN_PROCARGS2, + i32(pid), + } + length: uint + if sysctl(raw_data(mib), 3, nil, &length, nil, 0) != .OK { + if err == nil { + err = _get_platform_error() + } + break args + } + + buf := runtime.make_aligned([]byte, length, 4, temp_allocator()) + if sysctl(raw_data(mib), 3, raw_data(buf), &length, nil, 0) != .OK { + if err == nil { + err = _get_platform_error() + + // Looks like EINVAL is returned here if you don't have permission. + if err == Platform_Error(posix.Errno.EINVAL) { + err = .Permission_Denied + } + } + break args + } + + buf = buf[:length] + + if len(buf) < 4 { + break args + } + + // Layout isn't really documented anywhere, I deduced it to be: + // i32 - argc + // cstring - command name (skipped) + // [^]byte - couple of 0 bytes (skipped) + // [^]cstring - argv (up to argc entries) + // [^]cstring - key=value env entries until the end (many intermittent 0 bytes and entries without `=` we skip here too) + + argc := (^i32)(raw_data(buf))^ + buf = buf[size_of(i32):] + + { + command_line: [dynamic]byte + command_line.allocator = allocator + + argv: [dynamic]string + argv.allocator = allocator + + defer if err != nil { + for arg in argv { delete(arg, allocator) } + delete(argv) + delete(command_line) + } + + _, _ = bytes.split_iterator(&buf, {0}) + buf = bytes.trim_left(buf, {0}) + + first_arg := true + for arg in bytes.split_iterator(&buf, {0}) { + if .Command_Line in selection { + if !first_arg { + append(&command_line, ' ') or_return + } + append(&command_line, ..arg) or_return + } + + if .Command_Args in selection { + sarg := clone_string(string(arg), allocator) or_return + append(&argv, sarg) or_return + } + + first_arg = false + argc -= 1 + if argc == 0 { + break + } + } + + if .Command_Line in selection { + info.command_line = string(command_line[:]) + info.fields += {.Command_Line} + } + if .Command_Args in selection { + info.command_args = argv[:] + info.fields += {.Command_Args} + } + } + + if .Environment in selection { + environment: [dynamic]string + environment.allocator = allocator + + defer if err != nil { + for entry in environment { delete(entry, allocator) } + delete(environment) + } + + for entry in bytes.split_iterator(&buf, {0}) { + if bytes.index_byte(entry, '=') > -1 { + sentry := clone_string(string(entry), allocator) or_return + append(&environment, sentry) or_return + } + } + + info.environment = environment[:] + info.fields += {.Environment} + } + } + + // Fields were requested that we didn't add. + if err == nil && selection - info.fields != {} { + err = .Unsupported + } + + return +} + +_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { + ret := darwin.proc_listallpids(nil, 0) + if ret < 0 { + err = _get_platform_error() + return + } + + TEMP_ALLOCATOR_GUARD() + + buffer := make([]i32, ret, temp_allocator()) + ret = darwin.proc_listallpids(raw_data(buffer), ret*size_of(i32)) + if ret < 0 { + err = _get_platform_error() + return + } + + list = make([]int, ret, allocator) or_return + #no_bounds_check for &entry, i in list { + entry = int(buffer[i]) + } + + return +} + +_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { + rusage: darwin.rusage_info_v0 + if ret := darwin.proc_pid_rusage(posix.pid_t(pid), .V0, &rusage); ret != 0 { + err = _get_platform_error() + return + } + + // Using the start time as the handle, there is no pidfd or anything on Darwin. + // There is a uuid, but once a process becomes a zombie it changes... + process.handle = uintptr(rusage.ri_proc_start_abstime) + process.pid = int(pid) + return +} + +_process_handle_still_valid :: proc(p: Process) -> Error { + rusage: darwin.rusage_info_v0 + if ret := darwin.proc_pid_rusage(posix.pid_t(p.pid), .V0, &rusage); ret != 0 { + return _get_platform_error() + } + + handle := uintptr(rusage.ri_proc_start_abstime) + if p.handle != handle { + return posix.Errno.ESRCH + } + + return nil +} + +_process_state_update_times :: proc(p: Process, state: ^Process_State) { + rusage: darwin.rusage_info_v0 + if ret := darwin.proc_pid_rusage(posix.pid_t(p.pid), .V0, &rusage); ret != 0 { + return + } + + // NOTE(laytan): I have no clue if this is correct, the output seems correct comparing it with `time`'s output. + HZ :: 20000000 + + state.user_time = ( + (time.Duration(rusage.ri_user_time) / HZ * time.Second) + + time.Duration(rusage.ri_user_time % HZ)) + state.system_time = ( + (time.Duration(rusage.ri_system_time) / HZ * time.Second) + + time.Duration(rusage.ri_system_time % HZ)) + + return +} diff --git a/core/os/os2/process_posix_other.odin b/core/os/os2/process_posix_other.odin new file mode 100644 index 000000000..65da3e9e2 --- /dev/null +++ b/core/os/os2/process_posix_other.odin @@ -0,0 +1,29 @@ +#+private +#+build netbsd, openbsd, freebsd +package os2 + +import "base:runtime" + +_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + err = .Unsupported + return +} + +_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { + err = .Unsupported + return +} + +_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { + process.pid = pid + err = .Unsupported + return +} + +_process_handle_still_valid :: proc(p: Process) -> Error { + return nil +} + +_process_state_update_times :: proc(p: Process, state: ^Process_State) { + return +} diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin new file mode 100644 index 000000000..1984cbfdf --- /dev/null +++ b/core/os/os2/process_windows.odin @@ -0,0 +1,743 @@ +#+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 + // Note(flysand): Open the process handle right away to prevent some race + // conditions. Once the handle is open, the process will be kept alive by + // the OS. + ph := win32.INVALID_HANDLE_VALUE + if selection >= {.Command_Line, .Environment, .Working_Dir, .Username} { + 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) + } + snapshot_process: if selection >= {.PPid, .Priority} { + entry, entry_err := _process_entry_by_pid(info.pid) + if entry_err != nil { + err = entry_err + if entry_err == General_Error.Not_Exist { + return + } else { + break snapshot_process + } + } + if .PPid in selection { + info.fields += {.PPid} + info.ppid = int(entry.th32ParentProcessID) + } + if .Priority in selection { + info.fields += {.Priority} + info.priority = int(entry.pcPriClassBase) + } + } + snapshot_modules: if .Executable_Path in selection { + exe_path: string + exe_path, err = _process_exe_by_pid(pid, allocator) + if _, ok := err.(runtime.Allocator_Error); ok { + return + } else if err != nil { + break snapshot_modules + } + info.executable_path = exe_path + info.fields += {.Executable_Path} + } + read_peb: if selection >= {.Command_Line, .Environment, .Working_Dir} { + process_info_size: u32 + process_info: win32.PROCESS_BASIC_INFORMATION + status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size) + if status != 0 { + // TODO(flysand): There's probably a mismatch between NTSTATUS and + // windows userland error codes, I haven't checked. + err = Platform_Error(status) + break read_peb + } + assert(process_info.PebBaseAddress != nil) + process_peb: win32.PEB + _, err = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) + if err != nil { + break read_peb + } + process_params: win32.RTL_USER_PROCESS_PARAMETERS + _, err = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) + if err != nil { + break read_peb + } + if selection >= {.Command_Line, .Command_Args} { + TEMP_ALLOCATOR_GUARD() + cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return + _, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) + if err != nil { + break read_peb + } + if .Command_Line in selection { + info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return + info.fields += {.Command_Line} + } + if .Command_Args in selection { + info.command_args = _parse_command_line(raw_data(cmdline_w), allocator) or_return + info.fields += {.Command_Args} + } + } + if .Environment in selection { + TEMP_ALLOCATOR_GUARD() + env_len := process_params.EnvironmentSize / 2 + envs_w := make([]u16, env_len, temp_allocator()) or_return + _, err = read_memory_as_slice(ph, process_params.Environment, envs_w) + if err != nil { + break read_peb + } + info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return + info.fields += {.Environment} + } + if .Working_Dir in selection { + TEMP_ALLOCATOR_GUARD() + cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return + _, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) + if err != nil { + break read_peb + } + info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return + info.fields += {.Working_Dir} + } + } + read_username: if .Username in selection { + username: string + username, err = _get_process_user(ph, allocator) + if _, ok := err.(runtime.Allocator_Error); ok { + return + } else if err != nil { + break read_username + } + info.username = username + info.fields += {.Username} + } + err = nil + return +} + +@(private="package") +_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + pid := process.pid + info.pid = pid + // Data obtained from process snapshots + snapshot_process: if selection >= {.PPid, .Priority} { + entry, entry_err := _process_entry_by_pid(info.pid) + if entry_err != nil { + err = entry_err + if entry_err == General_Error.Not_Exist { + return + } else { + break snapshot_process + } + } + if .PPid in selection { + info.fields += {.PPid} + info.ppid = int(entry.th32ParentProcessID) + } + if .Priority in selection { + info.fields += {.Priority} + info.priority = int(entry.pcPriClassBase) + } + } + snapshot_module: if .Executable_Path in selection { + exe_path: string + exe_path, err = _process_exe_by_pid(pid, allocator) + if _, ok := err.(runtime.Allocator_Error); ok { + return + } else if err != nil { + break snapshot_module + } + info.executable_path = exe_path + info.fields += {.Executable_Path} + } + ph := win32.HANDLE(process.handle) + read_peb: if selection >= {.Command_Line, .Environment, .Working_Dir} { + process_info_size: u32 + process_info: win32.PROCESS_BASIC_INFORMATION + status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size) + if status != 0 { + // TODO(flysand): There's probably a mismatch between NTSTATUS and + // windows userland error codes, I haven't checked. + err = Platform_Error(status) + return + } + assert(process_info.PebBaseAddress != nil) + process_peb: win32.PEB + _, err = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) + if err != nil { + break read_peb + } + process_params: win32.RTL_USER_PROCESS_PARAMETERS + _, err = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) + if err != nil { + break read_peb + } + if selection >= {.Command_Line, .Command_Args} { + TEMP_ALLOCATOR_GUARD() + cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return + _, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) + if err != nil { + break read_peb + } + if .Command_Line in selection { + info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return + info.fields += {.Command_Line} + } + if .Command_Args in selection { + info.command_args = _parse_command_line(raw_data(cmdline_w), allocator) or_return + info.fields += {.Command_Args} + } + } + if .Environment in selection { + TEMP_ALLOCATOR_GUARD() + env_len := process_params.EnvironmentSize / 2 + envs_w := make([]u16, env_len, temp_allocator()) or_return + _, err = read_memory_as_slice(ph, process_params.Environment, envs_w) + if err != nil { + break read_peb + } + info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return + info.fields += {.Environment} + } + if .Working_Dir in selection { + TEMP_ALLOCATOR_GUARD() + cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return + _, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) + if err != nil { + break read_peb + } + info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return + info.fields += {.Working_Dir} + } + } + read_username: if .Username in selection { + username: string + username, err = _get_process_user(ph, allocator) + if _, ok := err.(runtime.Allocator_Error); ok { + return + } else if err != nil { + break read_username + } + info.username = username + info.fields += {.Username} + } + err = nil + return +} + +@(private="package") +_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + info.pid = get_pid() + snapshot_process: if selection >= {.PPid, .Priority} { + entry, entry_err := _process_entry_by_pid(info.pid) + if entry_err != nil { + err = entry_err + if entry_err == General_Error.Not_Exist { + return + } else { + break snapshot_process + } + } + if .PPid in selection { + info.fields += {.PPid} + info.ppid = int(entry.th32ProcessID) + } + if .Priority in selection { + info.fields += {.Priority} + info.priority = int(entry.pcPriClassBase) + } + } + module_filename: if .Executable_Path in selection { + exe_filename_w: [256]u16 + path_len := win32.GetModuleFileNameW(nil, raw_data(exe_filename_w[:]), len(exe_filename_w)) + assert(path_len > 0) + info.executable_path = win32_utf16_to_utf8(exe_filename_w[:path_len], allocator) or_return + info.fields += {.Executable_Path} + } + command_line: if selection >= {.Command_Line, .Command_Args} { + command_line_w := win32.GetCommandLineW() + assert(command_line_w != nil) + if .Command_Line in selection { + info.command_line = win32_wstring_to_utf8(command_line_w, allocator) or_return + info.fields += {.Command_Line} + } + if .Command_Args in selection { + info.command_args = _parse_command_line(command_line_w, allocator) or_return + info.fields += {.Command_Args} + } + } + read_environment: if .Environment in selection { + env_block := win32.GetEnvironmentStringsW() + assert(env_block != nil) + info.environment = _parse_environment_block(env_block, allocator) or_return + info.fields += {.Environment} + } + read_username: if .Username in selection { + process_handle := win32.GetCurrentProcess() + username: string + username, err = _get_process_user(process_handle, allocator) + if _, ok := err.(runtime.Allocator_Error); ok { + return + } else if err != nil { + break read_username + } + info.username = username + info.fields += {.Username} + } + if .Working_Dir in selection { + // TODO(flysand): Implement this by reading PEB + err = .Mode_Not_Implemented + return + } + err = nil + return +} + +@(private="package") +_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { + // Note(flysand): The handle will be used for querying information so we + // take the necessary permissions right away. + dwDesiredAccess := win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.SYNCHRONIZE + if .Mem_Read in flags { + dwDesiredAccess |= win32.PROCESS_VM_READ + } + if .Mem_Write in flags { + dwDesiredAccess |= win32.PROCESS_VM_WRITE + } + handle := win32.OpenProcess( + dwDesiredAccess, + false, + u32(pid), + ) + if handle == win32.INVALID_HANDLE_VALUE { + err = _get_platform_error() + } else { + process = {pid = pid, handle = uintptr(handle)} + } + return +} + +@(private="package") +_Sys_Process_Attributes :: struct {} + +@(private="package") +_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { + TEMP_ALLOCATOR_GUARD() + command_line := _build_command_line(desc.command, temp_allocator()) + command_line_w := win32_utf8_to_wstring(command_line, temp_allocator()) or_return + environment := desc.env + if desc.env == nil { + environment = environ(temp_allocator()) + } + environment_block := _build_environment_block(environment, temp_allocator()) + environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator()) or_return + stderr_handle := win32.GetStdHandle(win32.STD_ERROR_HANDLE) + stdout_handle := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE) + stdin_handle := win32.GetStdHandle(win32.STD_INPUT_HANDLE) + + if desc.stdout != nil { + stdout_handle = win32.HANDLE((^File_Impl)(desc.stdout.impl).fd) + } + if desc.stderr != nil { + stderr_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd) + } + if desc.stdin != nil { + stdin_handle = win32.HANDLE((^File_Impl)(desc.stdin.impl).fd) + } + + working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator()) or_else nil) if len(desc.working_dir) > 0 else nil + process_info: win32.PROCESS_INFORMATION + ok := win32.CreateProcessW( + nil, + command_line_w, + nil, + nil, + true, + win32.CREATE_UNICODE_ENVIRONMENT|win32.NORMAL_PRIORITY_CLASS, + raw_data(environment_block_w), + working_dir_w, + &win32.STARTUPINFOW{ + cb = size_of(win32.STARTUPINFOW), + hStdError = stderr_handle, + hStdOutput = stdout_handle, + hStdInput = stdin_handle, + dwFlags = win32.STARTF_USESTDHANDLES, + }, + &process_info, + ) + if !ok { + err = _get_platform_error() + return + } + process = {pid = int(process_info.dwProcessId), handle = uintptr(process_info.hProcess)} + return +} + +@(private="package") +_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { + handle := win32.HANDLE(process.handle) + timeout_ms := u32(timeout / time.Millisecond) if timeout >= 0 else win32.INFINITE + + switch win32.WaitForSingleObject(handle, timeout_ms) { + case win32.WAIT_OBJECT_0: + exit_code: u32 + if !win32.GetExitCodeProcess(handle, &exit_code) { + err =_get_platform_error() + return + } + time_created: win32.FILETIME + time_exited: win32.FILETIME + time_kernel: win32.FILETIME + time_user: win32.FILETIME + if !win32.GetProcessTimes(handle, &time_created, &time_exited, &time_kernel, &time_user) { + err = _get_platform_error() + return + } + process_state = { + exit_code = int(exit_code), + exited = true, + pid = process.pid, + success = true, + system_time = _filetime_to_duration(time_kernel), + user_time = _filetime_to_duration(time_user), + } + return + case win32.WAIT_TIMEOUT: + err = General_Error.Timeout + return + case: + err = _get_platform_error() + return + } +} + +@(private="package") +_process_close :: proc(process: Process) -> Error { + if !win32.CloseHandle(win32.HANDLE(process.handle)) { + return _get_platform_error() + } + return nil +} + +@(private="package") +_process_kill :: proc(process: Process) -> Error { + // Note(flysand): This is different than what the task manager's "kill process" + // functionality does, as we don't try to send WM_CLOSE message first. This + // is quite a rough way to kill the process, which should be consistent with + // linux. The error code 9 is to mimic SIGKILL event. + if !win32.TerminateProcess(win32.HANDLE(process.handle), 9) { + return _get_platform_error() + } + return nil +} + +_filetime_to_duration :: proc(filetime: win32.FILETIME) -> time.Duration { + ticks := u64(filetime.dwHighDateTime)<<32 | u64(filetime.dwLowDateTime) + return time.Duration(ticks * 100) +} + +_process_entry_by_pid :: proc(pid: int) -> (entry: win32.PROCESSENTRY32W, err: Error) { + snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0) + if snap == win32.INVALID_HANDLE_VALUE { + err = _get_platform_error() + return + } + defer win32.CloseHandle(snap) + + entry = win32.PROCESSENTRY32W{dwSize = size_of(win32.PROCESSENTRY32W)} + status := win32.Process32FirstW(snap, &entry) + for status { + if u32(pid) == entry.th32ProcessID { + return + } + status = win32.Process32NextW(snap, &entry) + } + err = General_Error.Not_Exist + return +} + +// Note(flysand): Not sure which way it's better to get the executable path: +// via toolhelp snapshots or by reading other process' PEB memory. I have +// a slight suspicion that if both exe path and command line are desired, +// it's faster to just read both from PEB, but maybe the toolhelp snapshots +// are just better...? +@(private="package") +_process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path: string, err: Error) { + snap := win32.CreateToolhelp32Snapshot( + win32.TH32CS_SNAPMODULE|win32.TH32CS_SNAPMODULE32, + u32(pid), + ) + if snap == win32.INVALID_HANDLE_VALUE { + err =_get_platform_error() + return + } + defer win32.CloseHandle(snap) + + entry := win32.MODULEENTRY32W { dwSize = size_of(win32.MODULEENTRY32W) } + status := win32.Module32FirstW(snap, &entry) + if !status { + err =_get_platform_error() + return + } + return win32_wstring_to_utf8(raw_data(entry.szExePath[:]), allocator) +} + +_get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + token_handle: win32.HANDLE + if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) { + err = _get_platform_error() + return + } + token_user_size: u32 + if !win32.GetTokenInformation(token_handle, .TokenUser, nil, 0, &token_user_size) { + // Note(flysand): Make sure the buffer too small error comes out, and not any other error + err = _get_platform_error() + if v, ok := is_platform_error(err); !ok || v != i32(win32.ERROR_INSUFFICIENT_BUFFER) { + return + } + err = nil + } + token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator()) or_return)) + if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) { + err = _get_platform_error() + return + } + + sid_type: win32.SID_NAME_USE + username_w: [256]u16 + domain_w: [256]u16 + username_chrs := u32(256) + domain_chrs := u32(256) + + if !win32.LookupAccountSidW(nil, token_user.User.Sid, &username_w[0], &username_chrs, &domain_w[0], &domain_chrs, &sid_type) { + err = _get_platform_error() + return + } + username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator()) or_return + domain := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator()) or_return + return strings.concatenate({domain, "\\", username}, allocator) +} + +_parse_command_line :: proc(cmd_line_w: [^]u16, allocator: runtime.Allocator) -> (argv: []string, err: Error) { + argc: i32 + argv_w := win32.CommandLineToArgvW(cmd_line_w, &argc) + if argv_w == nil { + return nil, _get_platform_error() + } + argv = make([]string, argc, allocator) or_return + defer if err != nil { + for arg in argv { + delete(arg, allocator) + } + delete(argv, allocator) + } + for arg_w, i in argv_w[:argc] { + argv[i] = win32_wstring_to_utf8(arg_w, allocator) or_return + } + return +} + +_build_command_line :: proc(command: []string, allocator: runtime.Allocator) -> string { + _write_byte_n_times :: #force_inline proc(builder: ^strings.Builder, b: byte, n: int) { + for _ in 0 ..< n { + strings.write_byte(builder, b) + } + } + builder := strings.builder_make(allocator) + for arg, i in command { + if i != 0 { + strings.write_byte(&builder, ' ') + } + j := 0 + if strings.contains_any(arg, "()[]{}^=;!'+,`~\" ") { + 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, arg[j]) + } else { + _write_byte_n_times(&builder, '\\', backslashes) + strings.write_byte(&builder, arg[j]) + } + j += 1 + } + strings.write_byte(&builder, '"') + } else { + strings.write_string(&builder, arg) + } + } + 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 24a01fb0a..b53ebb3ab 100644 --- a/core/os/os2/stat.odin +++ b/core/os/os2/stat.odin @@ -1,22 +1,37 @@ package os2 +import "base:runtime" +import "core:path/filepath" +import "core:strings" import "core:time" -import "core: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_dir: bool, + fullpath: string, + name: string, + + inode: u128, // might be zero if cannot be determined + size: i64 `fmt:"M"`, + mode: int `fmt:"o"`, + type: File_Type, + creation_time: time.Time, modification_time: time.Time, access_time: time.Time, } +@(require_results) +file_info_clone :: proc(fi: File_Info, allocator: runtime.Allocator) -> (cloned: File_Info, err: runtime.Allocator_Error) { + cloned = fi + cloned.fullpath = strings.clone(fi.fullpath) or_return + cloned.name = filepath.base(cloned.fullpath) + return +} + file_info_slice_delete :: proc(infos: []File_Info, allocator: runtime.Allocator) { - for i := len(infos)-1; i >= 0; i -= 1 { - file_info_delete(infos[i], allocator) + #reverse for info in infos { + file_info_delete(info, allocator) } delete(infos, allocator) } @@ -25,19 +40,48 @@ 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 :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { +lstat :: stat_do_not_follow_links + +@(require_results) +stat_do_not_follow_links :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { return _lstat(name, allocator) } +@(require_results) same_file :: proc(fi1, fi2: File_Info) -> bool { return _same_file(fi1, fi2) } + + +last_write_time :: modification_time +last_write_time_by_name :: modification_time_by_path + +@(require_results) +modification_time :: proc(f: ^File) -> (time.Time, Error) { + TEMP_ALLOCATOR_GUARD() + fi, err := fstat(f, temp_allocator()) + return fi.modification_time, err +} + +@(require_results) +modification_time_by_path :: proc(path: string) -> (time.Time, Error) { + TEMP_ALLOCATOR_GUARD() + fi, err := stat(path, temp_allocator()) + return fi.modification_time, err +} diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index 530e0e7d0..0433c1a61 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -1,145 +1,76 @@ -//+private +#+private package os2 import "core:time" -import "core:runtime" -import "core:strings" -import "core:sys/unix" +import "base:runtime" +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) -> (fi: File_Info, err: Error) { + s: linux.Stat + errno := linux.fstat(fd, &s) + if errno != .NONE { + return {}, _get_platform_error(errno) } + type := File_Type.Regular + switch s.mode & linux.S_IFMT { + case linux.S_IFBLK: type = .Block_Device + case linux.S_IFCHR: type = .Character_Device + case linux.S_IFDIR: type = .Directory + case linux.S_IFIFO: type = .Named_Pipe + case linux.S_IFLNK: type = .Symlink + case linux.S_IFREG: type = .Regular + case linux.S_IFSOCK: type = .Socket + } + mode := int(0o7777 & transmute(u32)s.mode) // TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time - fi := File_Info { - fullpath = _get_full_path(fd, allocator), - name = "", - size = s.size, - mode = 0, - is_dir = 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 + fi = File_Info { + fullpath = _get_full_path(fd, allocator) or_return, + name = "", + inode = u128(u64(s.ino)), + size = i64(s.size), + mode = mode, + type = type, + modification_time = time.Time {i64(s.mtime.time_sec) * i64(time.Second) + i64(s.mtime.time_nsec)}, + access_time = time.Time {i64(s.atime.time_sec) * i64(time.Second) + i64(s.atime.time_nsec)}, + creation_time = time.Time{i64(s.ctime.time_sec) * i64(time.Second) + i64(s.ctime.time_nsec)}, // regular stat does not provide this } - + fi.creation_time = fi.modification_time fi.name = filepath.base(fi.fullpath) - return fi, nil + return } // NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath -_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_posix.odin b/core/os/os2/stat_posix.odin new file mode 100644 index 000000000..88029c1f5 --- /dev/null +++ b/core/os/os2/stat_posix.odin @@ -0,0 +1,137 @@ +#+private +#+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" + +import "core:path/filepath" +import "core:sys/posix" +import "core:time" + +internal_stat :: proc(stat: posix.stat_t, fullpath: string) -> (fi: File_Info) { + fi.fullpath = fullpath + fi.name = filepath.base(fi.fullpath) + + fi.inode = u128(stat.st_ino) + fi.size = i64(stat.st_size) + + fi.mode = int(transmute(posix._mode_t)(stat.st_mode - posix.S_IFMT)) + + fi.type = .Undetermined + switch { + case posix.S_ISBLK(stat.st_mode): + fi.type = .Block_Device + case posix.S_ISCHR(stat.st_mode): + fi.type = .Character_Device + case posix.S_ISDIR(stat.st_mode): + fi.type = .Directory + case posix.S_ISFIFO(stat.st_mode): + fi.type = .Named_Pipe + case posix.S_ISLNK(stat.st_mode): + fi.type = .Symlink + case posix.S_ISREG(stat.st_mode): + fi.type = .Regular + case posix.S_ISSOCK(stat.st_mode): + fi.type = .Socket + } + + fi.creation_time = timespec_time(stat.st_birthtimespec) + fi.modification_time = timespec_time(stat.st_mtim) + fi.access_time = timespec_time(stat.st_atim) + + timespec_time :: proc(t: posix.timespec) -> time.Time { + return time.Time{_nsec = i64(t.tv_sec) * 1e9 + i64(t.tv_nsec)} + } + + return +} + +_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if f == nil || f.impl == nil { + err = .Invalid_File + return + } + + impl := (^File_Impl)(f.impl) + + stat: posix.stat_t + if posix.fstat(impl.fd, &stat) != .OK { + err = _get_platform_error() + return + } + + fullpath := clone_string(impl.name, allocator) or_return + return internal_stat(stat, fullpath), nil +} + +_stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + TEMP_ALLOCATOR_GUARD() + cname := temp_cstring(name) or_return + + fd := posix.open(cname, {}) + if fd == -1 { + err = _get_platform_error() + return + } + defer posix.close(fd) + + fullpath := _posix_absolute_path(fd, name, allocator) or_return + + stat: posix.stat_t + if posix.stat(fullpath, &stat) != .OK { + err = _get_platform_error() + return + } + + return internal_stat(stat, string(fullpath)), nil +} + +_lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + TEMP_ALLOCATOR_GUARD() + + // NOTE: can't use realpath or open (+ fcntl F_GETPATH) here because it tries to resolve symlinks. + + // NOTE: This might not be correct when given "/symlink/foo.txt", + // you would want that to resolve "/symlink", but not resolve "foo.txt". + + fullpath := filepath.clean(name, temp_allocator()) + assert(len(fullpath) > 0) + switch { + case fullpath[0] == '/': + // nothing. + case fullpath == ".": + fullpath = getwd(temp_allocator()) or_return + case len(fullpath) > 1 && fullpath[0] == '.' && fullpath[1] == '/': + fullpath = fullpath[2:] + fallthrough + case: + fullpath = concatenate({ + getwd(temp_allocator()) or_return, + "/", + fullpath, + }, temp_allocator()) or_return + } + + stat: posix.stat_t + if posix.lstat(temp_cstring(fullpath), &stat) != .OK { + err = _get_platform_error() + return + } + + fullpath = clone_string(fullpath, allocator) or_return + return internal_stat(stat, fullpath), nil +} + +_same_file :: proc(fi1, fi2: File_Info) -> bool { + return fi1.fullpath == fi2.fullpath +} diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index 5de5269d7..31f5d9e88 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -1,77 +1,82 @@ -//+private +#+private package os2 -import "core:runtime" +import "base:runtime" import "core:time" import "core:strings" import win32 "core:sys/windows" -_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { - if f == nil || f.impl.fd == nil { - return {}, nil +_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if f == nil || (^File_Impl)(f.impl).fd == nil { + return } - path, err := _cleanpath_from_handle(f, allocator) - if err != nil { - return {}, err - } + path := _cleanpath_from_handle(f, allocator) or_return h := _handle(f) switch win32.GetFileType(h) { case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR: - fi: File_Info - fi.fullpath = path - fi.name = basename(path) - fi.mode |= file_type_mode(h) - return fi, nil + fi = File_Info { + fullpath = path, + name = basename(path), + type = file_type(h), + } + return } return _file_info_from_get_file_information_by_handle(path, h, allocator) } + _stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS, allocator) } + _lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT, allocator) } + _same_file :: proc(fi1, fi2: File_Info) -> bool { return fi1.fullpath == fi2.fullpath } - - - full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path: string, err: Error) { name := name if name == "" { name = "." } - p := win32.utf8_to_utf16(name, _temp_allocator()) + + TEMP_ALLOCATOR_GUARD() + + p := win32_utf8_to_utf16(name, temp_allocator()) or_return 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() } - return win32.utf16_to_utf8(buf[:n], allocator) + return win32_utf16_to_utf8(buf[:n], allocator) } - internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { if len(name) == 0 { return {}, .Not_Exist } + TEMP_ALLOCATOR_GUARD() - wname := _fix_long_path(name) + wname := _fix_long_path(name, temp_allocator()) or_return fa: win32.WIN32_FILE_ATTRIBUTE_DATA ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa) if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { // Not a symlink - return _file_info_from_win32_file_attribute_data(&fa, name, allocator) + fi = _file_info_from_win32_file_attribute_data(&fa, name, allocator) or_return + if fi.type == .Undetermined { + fi.type = _file_type_from_create_file(wname, create_file_attributes) + } + return } err := 0 if ok else win32.GetLastError() @@ -85,7 +90,11 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt } win32.FindClose(sh) - return _file_info_from_win32_find_data(&fd, name, allocator) + fi = _file_info_from_win32_find_data(&fd, name, allocator) or_return + if fi.type == .Undetermined { + fi.type = _file_type_from_create_file(wname, create_file_attributes) + } + return } h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil) @@ -97,7 +106,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 @@ -118,9 +126,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) @@ -129,13 +136,16 @@ _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (strin if n == 0 { return "", _get_platform_error() } - 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) @@ -144,7 +154,10 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { if n == 0 { return nil, _get_platform_error() } - 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 } @@ -152,10 +165,9 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { _cleanpath_from_buf :: proc(buf: []u16, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { buf := buf buf = _cleanpath_strip_prefix(buf) - return win32.utf16_to_utf8(buf, allocator) + return win32_utf16_to_utf8(buf, allocator) } - basename :: proc(name: string) -> (base: string) { name := name if len(name) > 3 && name[:3] == `\\?` { @@ -181,20 +193,25 @@ 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_type_from_create_file :: proc(wname: win32.wstring, create_file_attributes: u32) -> File_Type { + h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil) + if h == win32.INVALID_HANDLE_VALUE { + return .Undetermined + } + defer win32.CloseHandle(h) + return file_type(h) +} - -_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 { @@ -209,55 +226,42 @@ _file_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.H } if is_sym { - mode |= File_Mode_Sym_Link - } else { - if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { - mode |= 0o111 | File_Mode_Dir - } - - if h != nil { - mode |= file_type_mode(h) - } + type = .Symlink + } else if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { + type = .Directory + mode |= 0o111 + } else if h != nil { + 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_dir = 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_dir = 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) { @@ -274,25 +278,20 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA // Indicate this is a symlink on FAT file systems ti.ReparseTag = 0 } - fi: File_Info - fi.fullpath = path fi.name = basename(path) - fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) - - fi.mode |= _file_mode_from_file_attributes(ti.FileAttributes, h, ti.ReparseTag) - fi.is_dir = fi.mode & File_Mode_Dir != 0 - + fi.inode = u128(u64(d.nFileIndexHigh)<<32 + u64(d.nFileIndexLow)) + fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) + type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, h, 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", @@ -353,7 +352,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 b05c186a0..5ca4e1453 100644 --- a/core/os/os2/temp_file.odin +++ b/core/os/os2/temp_file.odin @@ -1,15 +1,97 @@ package os2 -import "core:runtime" +import "base:runtime" -create_temp :: 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 string, `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 :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (string, Error) { - return _mkdir_temp(dir, pattern, allocator) +mkdir_temp :: make_directory_temp +// 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 :: proc(allocator: runtime.Allocator) -> (string, Error) { +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 201fb0e93..4eacbc54a 100644 --- a/core/os/os2/temp_file_linux.odin +++ b/core/os/os2/temp_file_linux.odin @@ -1,20 +1,13 @@ -//+private +#+private package os2 -import "core:runtime" +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 +_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { + TEMP_ALLOCATOR_GUARD() + tmpdir := get_env("TMPDIR", temp_allocator()) + if tmpdir == "" { + tmpdir = "/tmp" + } + return clone_string(tmpdir, allocator) } diff --git a/core/os/os2/temp_file_posix.odin b/core/os/os2/temp_file_posix.odin new file mode 100644 index 000000000..b44ea13a7 --- /dev/null +++ b/core/os/os2/temp_file_posix.odin @@ -0,0 +1,20 @@ +#+private +#+build darwin, netbsd, freebsd, openbsd +package os2 + +import "base:runtime" + +@(require) +import "core:sys/posix" + +_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { + if tmp, ok := _lookup_env("TMPDIR", allocator); ok { + return tmp, nil + } + + when #defined(posix.P_tmpdir) { + return clone_string(posix.P_tmpdir, allocator) + } + + return clone_string("/tmp/", allocator) +} diff --git a/core/os/os2/temp_file_windows.odin b/core/os/os2/temp_file_windows.odin index 08837f7f0..3e3e1285c 100644 --- a/core/os/os2/temp_file_windows.odin +++ b/core/os/os2/temp_file_windows.odin @@ -1,23 +1,17 @@ -//+private +#+private package os2 -import "core:runtime" +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 } - b := make([]u16, max(win32.MAX_PATH, n), _temp_allocator()) + TEMP_ALLOCATOR_GUARD() + + 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] == '\\' { @@ -25,5 +19,5 @@ _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Er } else if n > 0 && b[n-1] == '\\' { n -= 1 } - return win32.utf16_to_utf8(b[:n], allocator) + return win32_utf16_to_utf8(b[:n], allocator) } diff --git a/core/os/os2/user.odin b/core/os/os2/user.odin index 0e9f126aa..a0a7a839d 100644 --- a/core/os/os2/user.odin +++ b/core/os/os2/user.odin @@ -1,28 +1,30 @@ package os2 -import "core:strings" -import "core:runtime" +import "base:runtime" +@(require_results) user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + #partial switch ODIN_OS { case .Windows: - dir = get_env("LocalAppData", allocator) + dir = get_env("LocalAppData", temp_allocator()) if dir != "" { - dir = strings.clone(dir, allocator) or_return + dir = clone_string(dir, allocator) or_return } case .Darwin: - dir = get_env("HOME", allocator) + dir = get_env("HOME", temp_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) if dir == "" { - dir = get_env("HOME", allocator) + dir = get_env("HOME", temp_allocator()) if dir == "" { return } - dir = strings.concatenate({dir, "/.cache"}, allocator) or_return + dir = concatenate({dir, "/.cache"}, allocator) or_return } } if dir == "" { @@ -31,26 +33,29 @@ 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) { + TEMP_ALLOCATOR_GUARD() + #partial switch ODIN_OS { case .Windows: - dir = get_env("AppData", allocator) + dir = get_env("AppData", temp_allocator()) if dir != "" { - dir = strings.clone(dir, allocator) or_return + dir = clone_string(dir, allocator) or_return } case .Darwin: - dir = get_env("HOME", allocator) + dir = get_env("HOME", temp_allocator()) if dir != "" { - dir = strings.concatenate({dir, "/Library/Application Support"}, allocator) or_return + dir = concatenate({dir, "/.config"}, allocator) or_return } case: // All other UNIX systems dir = get_env("XDG_CACHE_HOME", allocator) if dir == "" { - dir = get_env("HOME", allocator) + dir = get_env("HOME", temp_allocator()) if dir == "" { return } - dir = strings.concatenate({dir, "/.config"}, allocator) or_return + dir = concatenate({dir, "/.config"}, allocator) or_return } } if dir == "" { @@ -59,6 +64,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 82cf5e1f3..d4435ec63 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -1,155 +1,293 @@ package os foreign import dl "system:dl" -foreign import libc "System.framework" -foreign import pthread "System.framework" +foreign import libc "system:System.framework" +foreign import pthread "system:System.framework" -import "core:runtime" +import "base:runtime" import "core:strings" import "core:c" Handle :: distinct i32 File_Time :: distinct u64 -Errno :: distinct int INVALID_HANDLE :: ~Handle(0) -ERROR_NONE: Errno : 0 -EPERM: Errno : 1 /* Operation not permitted */ -ENOENT: Errno : 2 /* No such file or directory */ -ESRCH: Errno : 3 /* No such process */ -EINTR: Errno : 4 /* Interrupted system call */ -EIO: Errno : 5 /* Input/output error */ -ENXIO: Errno : 6 /* Device not configured */ -E2BIG: Errno : 7 /* Argument list too long */ -ENOEXEC: Errno : 8 /* Exec format error */ -EBADF: Errno : 9 /* Bad file descriptor */ -ECHILD: Errno : 10 /* No child processes */ -EDEADLK: Errno : 11 /* Resource deadlock avoided */ -ENOMEM: Errno : 12 /* Cannot allocate memory */ -EACCES: Errno : 13 /* Permission denied */ -EFAULT: Errno : 14 /* Bad address */ -ENOTBLK: Errno : 15 /* Block device required */ -EBUSY: Errno : 16 /* Device / Resource busy */ -EEXIST: Errno : 17 /* File exists */ -EXDEV: Errno : 18 /* Cross-device link */ -ENODEV: Errno : 19 /* Operation not supported by device */ -ENOTDIR: Errno : 20 /* Not a directory */ -EISDIR: Errno : 21 /* Is a directory */ -EINVAL: Errno : 22 /* Invalid argument */ -ENFILE: Errno : 23 /* Too many open files in system */ -EMFILE: Errno : 24 /* Too many open files */ -ENOTTY: Errno : 25 /* Inappropriate ioctl for device */ -ETXTBSY: Errno : 26 /* Text file busy */ -EFBIG: Errno : 27 /* File too large */ -ENOSPC: Errno : 28 /* No space left on device */ -ESPIPE: Errno : 29 /* Illegal seek */ -EROFS: Errno : 30 /* Read-only file system */ -EMLINK: Errno : 31 /* Too many links */ -EPIPE: Errno : 32 /* Broken pipe */ +_Platform_Error :: enum i32 { + NONE = 0, + EPERM = 1, /* Operation not permitted */ + ENOENT = 2, /* No such file or directory */ + ESRCH = 3, /* No such process */ + EINTR = 4, /* Interrupted system call */ + EIO = 5, /* Input/output error */ + ENXIO = 6, /* Device not configured */ + E2BIG = 7, /* Argument list too long */ + ENOEXEC = 8, /* Exec format error */ + EBADF = 9, /* Bad file descriptor */ + ECHILD = 10, /* No child processes */ + EDEADLK = 11, /* Resource deadlock avoided */ + ENOMEM = 12, /* Cannot allocate memory */ + EACCES = 13, /* Permission denied */ + EFAULT = 14, /* Bad address */ + ENOTBLK = 15, /* Block device required */ + EBUSY = 16, /* Device / Resource busy */ + EEXIST = 17, /* File exists */ + EXDEV = 18, /* Cross-device link */ + ENODEV = 19, /* Operation not supported by device */ + ENOTDIR = 20, /* Not a directory */ + EISDIR = 21, /* Is a directory */ + EINVAL = 22, /* Invalid argument */ + ENFILE = 23, /* Too many open files in system */ + EMFILE = 24, /* Too many open files */ + ENOTTY = 25, /* Inappropriate ioctl for device */ + ETXTBSY = 26, /* Text file busy */ + EFBIG = 27, /* File too large */ + ENOSPC = 28, /* No space left on device */ + ESPIPE = 29, /* Illegal seek */ + EROFS = 30, /* Read-only file system */ + EMLINK = 31, /* Too many links */ + EPIPE = 32, /* Broken pipe */ + + /* math software */ + EDOM = 33, /* Numerical argument out of domain */ + ERANGE = 34, /* Result too large */ + + /* non-blocking and interrupt i/o */ + EAGAIN = 35, /* Resource temporarily unavailable */ + EWOULDBLOCK = EAGAIN, /* Operation would block */ + EINPROGRESS = 36, /* Operation now in progress */ + EALREADY = 37, /* Operation already in progress */ + + /* ipc/network software -- argument errors */ + ENOTSOCK = 38, /* Socket operation on non-socket */ + EDESTADDRREQ = 39, /* Destination address required */ + EMSGSIZE = 40, /* Message too long */ + EPROTOTYPE = 41, /* Protocol wrong type for socket */ + ENOPROTOOPT = 42, /* Protocol not available */ + EPROTONOSUPPORT = 43, /* Protocol not supported */ + ESOCKTNOSUPPORT = 44, /* Socket type not supported */ + ENOTSUP = 45, /* Operation not supported */ + EOPNOTSUPP = ENOTSUP, + EPFNOSUPPORT = 46, /* Protocol family not supported */ + EAFNOSUPPORT = 47, /* Address family not supported by protocol family */ + EADDRINUSE = 48, /* Address already in use */ + EADDRNOTAVAIL = 49, /* Can't assign requested address */ + + /* ipc/network software -- operational errors */ + ENETDOWN = 50, /* Network is down */ + ENETUNREACH = 51, /* Network is unreachable */ + ENETRESET = 52, /* Network dropped connection on reset */ + ECONNABORTED = 53, /* Software caused connection abort */ + ECONNRESET = 54, /* Connection reset by peer */ + ENOBUFS = 55, /* No buffer space available */ + EISCONN = 56, /* Socket is already connected */ + ENOTCONN = 57, /* Socket is not connected */ + ESHUTDOWN = 58, /* Can't send after socket shutdown */ + ETOOMANYREFS = 59, /* Too many references: can't splice */ + ETIMEDOUT = 60, /* Operation timed out */ + ECONNREFUSED = 61, /* Connection refused */ + + ELOOP = 62, /* Too many levels of symbolic links */ + ENAMETOOLONG = 63, /* File name too long */ + + /* should be rearranged */ + EHOSTDOWN = 64, /* Host is down */ + EHOSTUNREACH = 65, /* No route to host */ + ENOTEMPTY = 66, /* Directory not empty */ + + /* quotas & mush */ + EPROCLIM = 67, /* Too many processes */ + EUSERS = 68, /* Too many users */ + EDQUOT = 69, /* Disc quota exceeded */ + + /* Network File System */ + ESTALE = 70, /* Stale NFS file handle */ + EREMOTE = 71, /* Too many levels of remote in path */ + EBADRPC = 72, /* RPC struct is bad */ + ERPCMISMATCH = 73, /* RPC version wrong */ + EPROGUNAVAIL = 74, /* RPC prog. not avail */ + EPROGMISMATCH = 75, /* Program version wrong */ + EPROCUNAVAIL = 76, /* Bad procedure for program */ + + ENOLCK = 77, /* No locks available */ + ENOSYS = 78, /* Function not implemented */ + + EFTYPE = 79, /* Inappropriate file type or format */ + EAUTH = 80, /* Authentication error */ + ENEEDAUTH = 81, /* Need authenticator */ + + /* Intelligent device errors */ + EPWROFF = 82, /* Device power is off */ + EDEVERR = 83, /* Device error, e.g. paper out */ + EOVERFLOW = 84, /* Value too large to be stored in data type */ + + /* Program loading errors */ + EBADEXEC = 85, /* Bad executable */ + EBADARCH = 86, /* Bad CPU type in executable */ + ESHLIBVERS = 87, /* Shared library version mismatch */ + EBADMACHO = 88, /* Malformed Macho file */ + + ECANCELED = 89, /* Operation canceled */ + + EIDRM = 90, /* Identifier removed */ + ENOMSG = 91, /* No message of desired type */ + EILSEQ = 92, /* Illegal byte sequence */ + ENOATTR = 93, /* Attribute not found */ + + EBADMSG = 94, /* Bad message */ + EMULTIHOP = 95, /* Reserved */ + ENODATA = 96, /* No message available on STREAM */ + ENOLINK = 97, /* Reserved */ + ENOSR = 98, /* No STREAM resources */ + ENOSTR = 99, /* Not a STREAM */ + EPROTO = 100, /* Protocol error */ + ETIME = 101, /* STREAM ioctl timeout */ + + ENOPOLICY = 103, /* No such policy registered */ + + ENOTRECOVERABLE = 104, /* State not recoverable */ + EOWNERDEAD = 105, /* Previous owner died */ + + EQFULL = 106, /* Interface output queue is full */ + ELAST = 106, /* Must be equal largest errno */ +} + +EPERM :: _Platform_Error.EPERM +ENOENT :: _Platform_Error.ENOENT +ESRCH :: _Platform_Error.ESRCH +EINTR :: _Platform_Error.EINTR +EIO :: _Platform_Error.EIO +ENXIO :: _Platform_Error.ENXIO +E2BIG :: _Platform_Error.E2BIG +ENOEXEC :: _Platform_Error.ENOEXEC +EBADF :: _Platform_Error.EBADF +ECHILD :: _Platform_Error.ECHILD +EDEADLK :: _Platform_Error.EDEADLK +ENOMEM :: _Platform_Error.ENOMEM +EACCES :: _Platform_Error.EACCES +EFAULT :: _Platform_Error.EFAULT +ENOTBLK :: _Platform_Error.ENOTBLK +EBUSY :: _Platform_Error.EBUSY +EEXIST :: _Platform_Error.EEXIST +EXDEV :: _Platform_Error.EXDEV +ENODEV :: _Platform_Error.ENODEV +ENOTDIR :: _Platform_Error.ENOTDIR +EISDIR :: _Platform_Error.EISDIR +EINVAL :: _Platform_Error.EINVAL +ENFILE :: _Platform_Error.ENFILE +EMFILE :: _Platform_Error.EMFILE +ENOTTY :: _Platform_Error.ENOTTY +ETXTBSY :: _Platform_Error.ETXTBSY +EFBIG :: _Platform_Error.EFBIG +ENOSPC :: _Platform_Error.ENOSPC +ESPIPE :: _Platform_Error.ESPIPE +EROFS :: _Platform_Error.EROFS +EMLINK :: _Platform_Error.EMLINK +EPIPE :: _Platform_Error.EPIPE /* math software */ -EDOM: Errno : 33 /* Numerical argument out of domain */ -ERANGE: Errno : 34 /* Result too large */ +EDOM :: _Platform_Error.EDOM +ERANGE :: _Platform_Error.ERANGE /* non-blocking and interrupt i/o */ -EAGAIN: Errno : 35 /* Resource temporarily unavailable */ -EWOULDBLOCK: Errno : EAGAIN /* Operation would block */ -EINPROGRESS: Errno : 36 /* Operation now in progress */ -EALREADY: Errno : 37 /* Operation already in progress */ +EAGAIN :: _Platform_Error.EAGAIN +EWOULDBLOCK :: _Platform_Error.EWOULDBLOCK +EINPROGRESS :: _Platform_Error.EINPROGRESS +EALREADY :: _Platform_Error.EALREADY /* ipc/network software -- argument errors */ -ENOTSOCK: Errno : 38 /* Socket operation on non-socket */ -EDESTADDRREQ: Errno : 39 /* Destination address required */ -EMSGSIZE: Errno : 40 /* Message too long */ -EPROTOTYPE: Errno : 41 /* Protocol wrong type for socket */ -ENOPROTOOPT: Errno : 42 /* Protocol not available */ -EPROTONOSUPPORT: Errno : 43 /* Protocol not supported */ -ESOCKTNOSUPPORT: Errno : 44 /* Socket type not supported */ -ENOTSUP: Errno : 45 /* Operation not supported */ -EOPNOTSUPP:: ENOTSUP -EPFNOSUPPORT: Errno : 46 /* Protocol family not supported */ -EAFNOSUPPORT: Errno : 47 /* Address family not supported by protocol family */ -EADDRINUSE: Errno : 48 /* Address already in use */ -EADDRNOTAVAIL: Errno : 49 /* Can't assign requested address */ +ENOTSOCK :: _Platform_Error.ENOTSOCK +EDESTADDRREQ :: _Platform_Error.EDESTADDRREQ +EMSGSIZE :: _Platform_Error.EMSGSIZE +EPROTOTYPE :: _Platform_Error.EPROTOTYPE +ENOPROTOOPT :: _Platform_Error.ENOPROTOOPT +EPROTONOSUPPORT :: _Platform_Error.EPROTONOSUPPORT +ESOCKTNOSUPPORT :: _Platform_Error.ESOCKTNOSUPPORT +ENOTSUP :: _Platform_Error.ENOTSUP +EOPNOTSUPP :: _Platform_Error.EOPNOTSUPP +EPFNOSUPPORT :: _Platform_Error.EPFNOSUPPORT +EAFNOSUPPORT :: _Platform_Error.EAFNOSUPPORT +EADDRINUSE :: _Platform_Error.EADDRINUSE +EADDRNOTAVAIL :: _Platform_Error.EADDRNOTAVAIL /* ipc/network software -- operational errors */ -ENETDOWN: Errno : 50 /* Network is down */ -ENETUNREACH: Errno : 51 /* Network is unreachable */ -ENETRESET: Errno : 52 /* Network dropped connection on reset */ -ECONNABORTED: Errno : 53 /* Software caused connection abort */ -ECONNRESET: Errno : 54 /* Connection reset by peer */ -ENOBUFS: Errno : 55 /* No buffer space available */ -EISCONN: Errno : 56 /* Socket is already connected */ -ENOTCONN: Errno : 57 /* Socket is not connected */ -ESHUTDOWN: Errno : 58 /* Can't send after socket shutdown */ -ETOOMANYREFS: Errno : 59 /* Too many references: can't splice */ -ETIMEDOUT: Errno : 60 /* Operation timed out */ -ECONNREFUSED: Errno : 61 /* Connection refused */ +ENETDOWN :: _Platform_Error.ENETDOWN +ENETUNREACH :: _Platform_Error.ENETUNREACH +ENETRESET :: _Platform_Error.ENETRESET +ECONNABORTED :: _Platform_Error.ECONNABORTED +ECONNRESET :: _Platform_Error.ECONNRESET +ENOBUFS :: _Platform_Error.ENOBUFS +EISCONN :: _Platform_Error.EISCONN +ENOTCONN :: _Platform_Error.ENOTCONN +ESHUTDOWN :: _Platform_Error.ESHUTDOWN +ETOOMANYREFS :: _Platform_Error.ETOOMANYREFS +ETIMEDOUT :: _Platform_Error.ETIMEDOUT +ECONNREFUSED :: _Platform_Error.ECONNREFUSED -ELOOP: Errno : 62 /* Too many levels of symbolic links */ -ENAMETOOLONG: Errno : 63 /* File name too long */ +ELOOP :: _Platform_Error.ELOOP +ENAMETOOLONG :: _Platform_Error.ENAMETOOLONG /* should be rearranged */ -EHOSTDOWN: Errno : 64 /* Host is down */ -EHOSTUNREACH: Errno : 65 /* No route to host */ -ENOTEMPTY: Errno : 66 /* Directory not empty */ +EHOSTDOWN :: _Platform_Error.EHOSTDOWN +EHOSTUNREACH :: _Platform_Error.EHOSTUNREACH +ENOTEMPTY :: _Platform_Error.ENOTEMPTY /* quotas & mush */ -EPROCLIM: Errno : 67 /* Too many processes */ -EUSERS: Errno : 68 /* Too many users */ -EDQUOT: Errno : 69 /* Disc quota exceeded */ +EPROCLIM :: _Platform_Error.EPROCLIM +EUSERS :: _Platform_Error.EUSERS +EDQUOT :: _Platform_Error.EDQUOT /* Network File System */ -ESTALE: Errno : 70 /* Stale NFS file handle */ -EREMOTE: Errno : 71 /* Too many levels of remote in path */ -EBADRPC: Errno : 72 /* RPC struct is bad */ -ERPCMISMATCH: Errno : 73 /* RPC version wrong */ -EPROGUNAVAIL: Errno : 74 /* RPC prog. not avail */ -EPROGMISMATCH: Errno : 75 /* Program version wrong */ -EPROCUNAVAIL: Errno : 76 /* Bad procedure for program */ +ESTALE :: _Platform_Error.ESTALE +EREMOTE :: _Platform_Error.EREMOTE +EBADRPC :: _Platform_Error.EBADRPC +ERPCMISMATCH :: _Platform_Error.ERPCMISMATCH +EPROGUNAVAIL :: _Platform_Error.EPROGUNAVAIL +EPROGMISMATCH :: _Platform_Error.EPROGMISMATCH +EPROCUNAVAIL :: _Platform_Error.EPROCUNAVAIL -ENOLCK: Errno : 77 /* No locks available */ -ENOSYS: Errno : 78 /* Function not implemented */ +ENOLCK :: _Platform_Error.ENOLCK +ENOSYS :: _Platform_Error.ENOSYS -EFTYPE: Errno : 79 /* Inappropriate file type or format */ -EAUTH: Errno : 80 /* Authentication error */ -ENEEDAUTH: Errno : 81 /* Need authenticator */ +EFTYPE :: _Platform_Error.EFTYPE +EAUTH :: _Platform_Error.EAUTH +ENEEDAUTH :: _Platform_Error.ENEEDAUTH /* Intelligent device errors */ -EPWROFF: Errno : 82 /* Device power is off */ -EDEVERR: Errno : 83 /* Device error, e.g. paper out */ -EOVERFLOW: Errno : 84 /* Value too large to be stored in data type */ +EPWROFF :: _Platform_Error.EPWROFF +EDEVERR :: _Platform_Error.EDEVERR +EOVERFLOW :: _Platform_Error.EOVERFLOW /* Program loading errors */ -EBADEXEC: Errno : 85 /* Bad executable */ -EBADARCH: Errno : 86 /* Bad CPU type in executable */ -ESHLIBVERS: Errno : 87 /* Shared library version mismatch */ -EBADMACHO: Errno : 88 /* Malformed Macho file */ +EBADEXEC :: _Platform_Error.EBADEXEC +EBADARCH :: _Platform_Error.EBADARCH +ESHLIBVERS :: _Platform_Error.ESHLIBVERS +EBADMACHO :: _Platform_Error.EBADMACHO -ECANCELED: Errno : 89 /* Operation canceled */ +ECANCELED :: _Platform_Error.ECANCELED -EIDRM: Errno : 90 /* Identifier removed */ -ENOMSG: Errno : 91 /* No message of desired type */ -EILSEQ: Errno : 92 /* Illegal byte sequence */ -ENOATTR: Errno : 93 /* Attribute not found */ +EIDRM :: _Platform_Error.EIDRM +ENOMSG :: _Platform_Error.ENOMSG +EILSEQ :: _Platform_Error.EILSEQ +ENOATTR :: _Platform_Error.ENOATTR -EBADMSG: Errno : 94 /* Bad message */ -EMULTIHOP: Errno : 95 /* Reserved */ -ENODATA: Errno : 96 /* No message available on STREAM */ -ENOLINK: Errno : 97 /* Reserved */ -ENOSR: Errno : 98 /* No STREAM resources */ -ENOSTR: Errno : 99 /* Not a STREAM */ -EPROTO: Errno : 100 /* Protocol error */ -ETIME: Errno : 101 /* STREAM ioctl timeout */ +EBADMSG :: _Platform_Error.EBADMSG +EMULTIHOP :: _Platform_Error.EMULTIHOP +ENODATA :: _Platform_Error.ENODATA +ENOLINK :: _Platform_Error.ENOLINK +ENOSR :: _Platform_Error.ENOSR +ENOSTR :: _Platform_Error.ENOSTR +EPROTO :: _Platform_Error.EPROTO +ETIME :: _Platform_Error.ETIME -ENOPOLICY: Errno : 103 /* No such policy registered */ +ENOPOLICY :: _Platform_Error.ENOPOLICY -ENOTRECOVERABLE: Errno : 104 /* State not recoverable */ -EOWNERDEAD: Errno : 105 /* Previous owner died */ +ENOTRECOVERABLE :: _Platform_Error.ENOTRECOVERABLE +EOWNERDEAD :: _Platform_Error.EOWNERDEAD + +EQFULL :: _Platform_Error.EQFULL +ELAST :: _Platform_Error.ELAST -EQFULL: Errno : 106 /* Interface output queue is full */ -ELAST: Errno : 106 /* Must be equal largest errno */ O_RDONLY :: 0x0000 O_WRONLY :: 0x0001 @@ -354,6 +492,10 @@ in6_addr :: struct #packed { s6_addr: [16]u8, } +// https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/sys/socket.h#L1025-L1027 +// Prevent the raising of SIGPIPE on writing to a closed network socket. +MSG_NOSIGNAL :: 0x80000 + SIOCGIFFLAG :: enum c.int { UP = 0, /* Interface is up. */ BROADCAST = 1, /* Broadcast address valid. */ @@ -424,13 +566,13 @@ S_ISUID :: 0o4000 // Set user id on execution S_ISGID :: 0o2000 // Set group id on execution S_ISVTX :: 0o1000 // Directory restrcted delete -S_ISLNK :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFLNK } -S_ISREG :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFREG } -S_ISDIR :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFDIR } -S_ISCHR :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFCHR } -S_ISBLK :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFBLK } -S_ISFIFO :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFIFO } -S_ISSOCK :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFSOCK } +@(require_results) S_ISLNK :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFLNK } +@(require_results) S_ISREG :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFREG } +@(require_results) S_ISDIR :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFDIR } +@(require_results) S_ISCHR :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFCHR } +@(require_results) S_ISBLK :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFBLK } +@(require_results) S_ISFIFO :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFIFO } +@(require_results) S_ISSOCK :: #force_inline proc(m: u16) -> bool { return (m & S_IFMT) == S_IFSOCK } R_OK :: 4 // Test for read permission W_OK :: 2 // Test for write permission @@ -442,20 +584,22 @@ 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 mode: ..u16) -> Handle --- @(link_name="close") _unix_close :: proc(handle: Handle) -> c.int --- @(link_name="read") _unix_read :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int --- @(link_name="write") _unix_write :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int --- @(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="dup") _unix_dup :: proc(handle: Handle) -> Handle --- @(link_name="fdopendir$INODE64") _unix_fdopendir_amd64 :: proc(fd: Handle) -> Dir --- @(link_name="readdir_r$INODE64") _unix_readdir_r_amd64 :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- @@ -476,27 +620,31 @@ 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 --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- @(link_name="strerror") _darwin_string_error :: proc(num : c.int) -> cstring --- @(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int --- - @(link_name="socket") _unix_socket :: proc(domain: int, type: int, protocol: int) -> int --- - @(link_name="listen") _unix_listen :: proc(socket: int, backlog: int) -> int --- - @(link_name="accept") _unix_accept :: proc(socket: int, addr: rawptr, addr_len: rawptr) -> int --- - @(link_name="connect") _unix_connect :: proc(socket: int, addr: rawptr, addr_len: socklen_t) -> int --- - @(link_name="bind") _unix_bind :: proc(socket: int, addr: rawptr, addr_len: socklen_t) -> int --- - @(link_name="setsockopt") _unix_setsockopt :: proc(socket: int, level: int, opt_name: int, opt_val: rawptr, opt_len: socklen_t) -> int --- - @(link_name="getsockopt") _unix_getsockopt :: proc(socket: int, level: int, opt_name: int, opt_val: rawptr, opt_len: socklen_t) -> int --- - @(link_name="recvfrom") _unix_recvfrom :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int, addr: rawptr, addr_len: ^socklen_t) -> c.ssize_t --- - @(link_name="recv") _unix_recv :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int) -> c.ssize_t --- - @(link_name="sendto") _unix_sendto :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int, addr: rawptr, addr_len: socklen_t) -> c.ssize_t --- - @(link_name="send") _unix_send :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int) -> c.ssize_t --- - @(link_name="shutdown") _unix_shutdown :: proc(socket: int, how: int) -> int --- + @(link_name="socket") _unix_socket :: proc(domain: c.int, type: c.int, protocol: c.int) -> c.int --- + @(link_name="listen") _unix_listen :: proc(socket: c.int, backlog: c.int) -> c.int --- + @(link_name="accept") _unix_accept :: proc(socket: c.int, addr: rawptr, addr_len: rawptr) -> c.int --- + @(link_name="connect") _unix_connect :: proc(socket: c.int, addr: rawptr, addr_len: socklen_t) -> c.int --- + @(link_name="bind") _unix_bind :: proc(socket: c.int, addr: rawptr, addr_len: socklen_t) -> c.int --- + @(link_name="setsockopt") _unix_setsockopt :: proc(socket: c.int, level: c.int, opt_name: c.int, opt_val: rawptr, opt_len: socklen_t) -> c.int --- + @(link_name="getsockopt") _unix_getsockopt :: proc(socket: c.int, level: c.int, opt_name: c.int, opt_val: rawptr, opt_len: ^socklen_t) -> c.int --- + @(link_name="recvfrom") _unix_recvfrom :: proc(socket: c.int, buffer: rawptr, buffer_len: c.size_t, flags: c.int, addr: rawptr, addr_len: ^socklen_t) -> c.ssize_t --- + @(link_name="recv") _unix_recv :: proc(socket: c.int, buffer: rawptr, buffer_len: c.size_t, flags: c.int) -> c.ssize_t --- + @(link_name="sendto") _unix_sendto :: proc(socket: c.int, buffer: rawptr, buffer_len: c.size_t, flags: c.int, addr: rawptr, addr_len: socklen_t) -> c.ssize_t --- + @(link_name="send") _unix_send :: proc(socket: c.int, buffer: rawptr, buffer_len: c.size_t, flags: c.int) -> c.ssize_t --- + @(link_name="shutdown") _unix_shutdown :: proc(socket: c.int, how: c.int) -> c.int --- @(link_name="getifaddrs") _getifaddrs :: proc(ifap: ^^ifaddrs) -> (c.int) --- @(link_name="freeifaddrs") _freeifaddrs :: proc(ifa: ^ifaddrs) --- @@ -513,21 +661,25 @@ when ODIN_ARCH != .arm64 { } foreign dl { - @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: int) -> rawptr --- + @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- - @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> int --- + @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- } -get_last_error :: proc "contextless" () -> int { - return int(__error()^) +@(require_results, no_instrumentation) +get_last_error :: proc "contextless" () -> Error { + return Platform_Error(__error()^) } +@(require_results) get_last_error_string :: proc() -> string { - return cast(string)_darwin_string_error(cast(c.int)get_last_error()) + return string(_darwin_string_error(__error()^)) } -open :: proc(path: string, flags: int = O_RDWR, mode: int = 0) -> (Handle, Errno) { + +@(require_results) +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (handle: Handle, err: Error) { isDir := is_dir_path(path) flags := flags if isDir { @@ -540,95 +692,112 @@ open :: proc(path: string, flags: int = O_RDWR, mode: int = 0) -> (Handle, Errno runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) - handle := _unix_open(cstr, i32(flags), u16(mode)) - if handle == -1 { - return INVALID_HANDLE, cast(Errno)get_last_error() + handle = _unix_open(cstr, i32(flags), u16(mode)) + if handle == INVALID_HANDLE { + err = get_last_error() + return } - /* - @INFO(Platin): this is only done because O_CREATE for some reason fails to apply mode - should not happen if the handle is a directory - */ - if mode != 0 && !isDir { - err := fchmod(handle, cast(u16)mode) - if err != 0 { - _unix_close(handle) - return INVALID_HANDLE, cast(Errno)err - } - } - - return handle, 0 + return } -fchmod :: proc(fd: Handle, mode: u16) -> Errno { - return cast(Errno)_unix_fchmod(fd, mode) +fchmod :: proc(fd: Handle, mode: u16) -> Error { + return cast(Platform_Error)_unix_fchmod(fd, mode) } -close :: proc(fd: Handle) -> bool { - return _unix_close(fd) == 0 +close :: proc(fd: Handle) -> Error { + return cast(Platform_Error)_unix_close(fd) } +// If you read or write more than `SSIZE_MAX` bytes, most darwin implementations will return `EINVAL` +// but it is really implementation defined. `SSIZE_MAX` is also implementation defined but usually +// the max of an i32 on Darwin. +// In practice a read/write call would probably never read/write these big buffers all at once, +// which is why the number of bytes is returned and why there are procs that will call this in a +// loop for you. +// We set a max of 1GB to keep alignment and to be safe. @(private) -MAX_RW :: 0x7fffffff // The limit on Darwin is max(i32), trying to read/write more than that fails. +MAX_RW :: 1 << 30 -write :: proc(fd: Handle, data: []byte) -> (int, Errno) { +write :: proc(fd: Handle, data: []byte) -> (int, Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } - bytes_written := _unix_write(fd, raw_data(data), c.size_t(len(data))) + to_write := min(c.size_t(len(data)), MAX_RW) + + bytes_written := _unix_write(fd, raw_data(data), to_write) if bytes_written < 0 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return bytes_written, ERROR_NONE + return bytes_written, nil } -read :: proc(fd: Handle, data: []u8) -> (int, Errno) { +read :: proc(fd: Handle, data: []u8) -> (int, Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } - bytes_read := _unix_read(fd, raw_data(data), c.size_t(len(data))) + to_read := min(c.size_t(len(data)), MAX_RW) + + bytes_read := _unix_read(fd, raw_data(data), to_read) if bytes_read < 0 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return bytes_read, ERROR_NONE + return bytes_read, nil } -read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { + +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } - bytes_read := _unix_pread(fd, raw_data(data), c.size_t(len(data)), offset) + to_read := min(c.size_t(len(data)), MAX_RW) + + bytes_read := _unix_pread(fd, raw_data(data), to_read, offset) if bytes_read < 0 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return bytes_read, ERROR_NONE + return bytes_read, nil } -write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } - bytes_written := _unix_pwrite(fd, raw_data(data), c.size_t(len(data)), offset) + to_write := min(c.size_t(len(data)), MAX_RW) + + bytes_written := _unix_pwrite(fd, raw_data(data), to_write, offset) if bytes_written < 0 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return bytes_written, ERROR_NONE + return bytes_written, nil } -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { assert(fd != -1) - - final_offset := i64(_unix_lseek(fd, int(offset), whence)) - if final_offset == -1 { - return 0, 1 + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence } - return final_offset, 0 + + final_offset := i64(_unix_lseek(fd, int(offset), c.int(whence))) + if final_offset == -1 { + errno := get_last_error() + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + } + return 0, errno + } + return final_offset, nil } -file_size :: proc(fd: Handle) -> (i64, Errno) { +@(require_results) +file_size :: proc(fd: Handle) -> (i64, Error) { prev, _ := seek(fd, 0, SEEK_CUR) size, err := seek(fd, 0, SEEK_END) seek(fd, prev, SEEK_SET) @@ -642,55 +811,70 @@ stdin: Handle = 0 // get_std_handle(win32.STD_INPUT_HANDLE); stdout: Handle = 1 // get_std_handle(win32.STD_OUTPUT_HANDLE); stderr: Handle = 2 // get_std_handle(win32.STD_ERROR_HANDLE); -/* TODO(zangent): Implement these! -last_write_time :: proc(fd: Handle) -> File_Time {} -last_write_time_by_name :: proc(name: string) -> File_Time {} -*/ +@(require_results) +last_write_time :: proc(fd: Handle) -> (time: File_Time, err: Error) { + s := _fstat(fd) or_return + modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds + return File_Time(modified), nil +} +@(require_results) +last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) { + s := _stat(name) or_return + modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds + return File_Time(modified), nil +} + + +@(require_results) is_path_separator :: proc(r: rune) -> bool { return r == '/' } +@(require_results) is_file_handle :: proc(fd: Handle) -> bool { s, err := _fstat(fd) - if err != ERROR_NONE { + if err != nil { return false } return S_ISREG(s.mode) } +@(require_results) is_file_path :: proc(path: string, follow_links: bool = true) -> bool { s: OS_Stat - err: Errno + err: Error if follow_links { s, err = _stat(path) } else { s, err = _lstat(path) } - if err != ERROR_NONE { + if err != nil { return false } return S_ISREG(s.mode) } +@(require_results) is_dir_handle :: proc(fd: Handle) -> bool { s, err := _fstat(fd) - if err != ERROR_NONE { + if err != nil { return false } return S_ISDIR(s.mode) } +@(require_results) is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { s: OS_Stat - err: Errno + err: Error if follow_links { s, err = _stat(path) } else { s, err = _lstat(path) } - if err != ERROR_NONE { + if err != nil { return false } return S_ISDIR(s.mode) @@ -699,6 +883,7 @@ is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { is_file :: proc {is_file_path, is_file_handle} is_dir :: proc {is_dir_path, is_dir_handle} +@(require_results) exists :: proc(path: string) -> bool { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cpath := strings.clone_to_cstring(path, context.temp_allocator) @@ -713,81 +898,84 @@ rename :: proc(old: string, new: string) -> bool { return _unix_rename(old_cstr, new_cstr) != -1 } -remove :: proc(path: string) -> bool { +remove :: proc(path: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - return _unix_remove(path_cstr) != -1 + res := _unix_remove(path_cstr) + if res == -1 { + return get_last_error() + } + return nil } -@private -_stat :: proc(path: string) -> (OS_Stat, Errno) { +@(private, require_results) +_stat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) s: OS_Stat result := _unix_stat(cstr, &s) if result == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_lstat :: proc(path: string) -> (OS_Stat, Errno) { +@(private, require_results) +_lstat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) s: OS_Stat result := _unix_lstat(cstr, &s) if result == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) { +@(private, require_results) +_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { s: OS_Stat result := _unix_fstat(fd, &s) if result == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_fdopendir :: proc(fd: Handle) -> (Dir, Errno) { +@(private, require_results) +_fdopendir :: proc(fd: Handle) -> (Dir, Error) { dirp := _unix_fdopendir(fd) if dirp == cast(Dir)nil { - return nil, Errno(get_last_error()) + return nil, get_last_error() } - return dirp, ERROR_NONE + return dirp, nil } -@private -_closedir :: proc(dirp: Dir) -> Errno { +@(private) +_closedir :: proc(dirp: Dir) -> Error { rc := _unix_closedir(dirp) if rc != 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -@private +@(private) _rewinddir :: proc(dirp: Dir) { _unix_rewinddir(dirp) } -@private -_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) { +@(private, require_results) +_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { result: ^Dirent rc := _unix_readdir_r(dirp, &entry, &result) if rc != 0 { - err = Errno(get_last_error()) + err = get_last_error() return } - err = ERROR_NONE if result == nil { end_of_stream = true @@ -798,8 +986,8 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) return } -@private -_readlink :: proc(path: string) -> (string, Errno) { +@(private, require_results) +_readlink :: proc(path: string) -> (string, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) path_cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -809,30 +997,36 @@ _readlink :: proc(path: string) -> (string, Errno) { rc := _unix_readlink(path_cstr, &(buf[0]), bufsz) if rc == -1 { delete(buf) - return "", Errno(get_last_error()) + return "", get_last_error() } else if rc == int(bufsz) { // NOTE(laleksic, 2021-01-21): Any cleaner way to resize the slice? bufsz *= 2 delete(buf) buf = make([]byte, bufsz) } else { - return strings.string_from_ptr(&buf[0], rc), ERROR_NONE + return strings.string_from_ptr(&buf[0], rc), nil } } } -absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) { - buf: [DARWIN_MAXPATHLEN]byte - _, err := fcntl(int(fd), F_GETPATH, int(uintptr(&buf[0]))) - if err != ERROR_NONE { - return "", err +@(private, require_results) +_dup :: proc(fd: Handle) -> (Handle, Error) { + dup := _unix_dup(fd) + if dup == -1 { + return INVALID_HANDLE, get_last_error() } - - path := strings.clone_from_cstring(cstring(&buf[0])) - return path, err + return dup, nil } -absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { +@(require_results) +absolute_path_from_handle :: proc(fd: Handle) -> (path: string, err: Error) { + buf: [DARWIN_MAXPATHLEN]byte + _ = fcntl(int(fd), F_GETPATH, int(uintptr(&buf[0]))) or_return + return strings.clone_from_cstring(cstring(&buf[0])) +} + +@(require_results) +absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { rel := rel if rel == "" { rel = "." @@ -843,41 +1037,24 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { path_ptr := _unix_realpath(rel_cstr, nil) if path_ptr == nil { - return "", Errno(get_last_error()) + return "", get_last_error() } - defer _unix_free(path_ptr) + defer _unix_free(rawptr(path_ptr)) - path_cstr := transmute(cstring)path_ptr - path = strings.clone( string(path_cstr) ) - - return path, ERROR_NONE + return strings.clone(string(path_ptr), allocator) } 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 } -heap_alloc :: proc(size: int, zero_memory := true) -> rawptr { - if size <= 0 { - return nil - } - if zero_memory { - return _unix_calloc(1, size) - } else { - return _unix_malloc(size) - } -} -heap_resize :: proc(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) { - _unix_free(ptr) +flush :: proc(fd: Handle) -> Error { + return cast(Platform_Error)_unix_fsync(fd) } +@(require_results) lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator) @@ -888,12 +1065,36 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin return strings.clone(string(cstr), allocator), true } +@(require_results) get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } -get_current_directory :: proc() -> string { +set_env :: proc(key, value: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + key_cstring := strings.clone_to_cstring(key, context.temp_allocator) + value_cstring := strings.clone_to_cstring(value, context.temp_allocator) + res := _unix_setenv(key_cstring, value_cstring, 1) + if res < 0 { + return get_last_error() + } + return nil +} + +unset_env :: proc(key: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + s := strings.clone_to_cstring(key, context.temp_allocator) + res := _unix_unsetenv(s) + if res < 0 { + return get_last_error() + } + return nil +} + +@(require_results) +get_current_directory :: proc(allocator := context.allocator) -> string { + context.allocator = allocator page_size := get_page_size() // NOTE(tetra): See note in os_linux.odin/get_current_directory. buf := make([dynamic]u8, page_size) for { @@ -901,7 +1102,7 @@ get_current_directory :: proc() -> string { if cwd != nil { return string(cwd) } - if Errno(get_last_error()) != ERANGE { + if get_last_error() != ERANGE { delete(buf) return "" } @@ -910,24 +1111,24 @@ get_current_directory :: proc() -> string { unreachable() } -set_current_directory :: proc(path: string) -> (err: Errno) { +set_current_directory :: proc(path: string) -> (err: Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_chdir(cstr) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -make_directory :: proc(path: string, mode: u16 = 0o775) -> Errno { +make_directory :: proc(path: string, mode: u16 = 0o775) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_mkdir(path_cstr, mode) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } exit :: proc "contextless" (code: int) -> ! { @@ -935,6 +1136,7 @@ exit :: proc "contextless" (code: int) -> ! { _unix_exit(i32(code)) } +@(require_results) current_thread_id :: proc "contextless" () -> int { tid: u64 // NOTE(Oskar): available from OSX 10.6 and iOS 3.2. @@ -945,12 +1147,14 @@ current_thread_id :: proc "contextless" () -> int { return int(tid) } +@(require_results) dlopen :: proc(filename: string, flags: int) -> rawptr { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(filename, context.temp_allocator) - handle := _unix_dlopen(cstr, flags) + handle := _unix_dlopen(cstr, c.int(flags)) return handle } +@(require_results) dlsym :: proc(handle: rawptr, symbol: string) -> rawptr { assert(handle != nil) runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() @@ -966,6 +1170,7 @@ dlerror :: proc() -> string { return string(_unix_dlerror()) } +@(require_results) get_page_size :: proc() -> int { // NOTE(tetra): The page size never changes, so why do anything complicated // if we don't have to. @@ -978,7 +1183,7 @@ get_page_size :: proc() -> int { return page_size } -@(private) +@(private, require_results) _processor_core_count :: proc() -> int { count : int = 0 count_size := size_of(count) @@ -991,6 +1196,7 @@ _processor_core_count :: proc() -> int { return 1 } +@(require_results) _alloc_command_line_arguments :: proc() -> []string { res := make([]string, len(runtime.args__)) for _, i in res { @@ -999,106 +1205,107 @@ _alloc_command_line_arguments :: proc() -> []string { return res } -socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Errno) { - result := _unix_socket(domain, type, protocol) +socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) { + result := _unix_socket(c.int(domain), c.int(type), c.int(protocol)) if result < 0 { - return 0, Errno(get_last_error()) + return 0, get_last_error() } - return Socket(result), ERROR_NONE + return Socket(result), nil } -connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) { - result := _unix_connect(int(sd), addr, len) +connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> Error { + result := _unix_connect(c.int(sd), addr, len) if result < 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) { - result := _unix_bind(int(sd), addr, len) +bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Error) { + result := _unix_bind(c.int(sd), addr, len) if result < 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Errno) { - result := _unix_accept(int(sd), rawptr(addr), len) +accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Error) { + result := _unix_accept(c.int(sd), rawptr(addr), len) if result < 0 { - return 0, Errno(get_last_error()) + return 0, get_last_error() } - return Socket(result), ERROR_NONE + return Socket(result), nil } -listen :: proc(sd: Socket, backlog: int) -> (Errno) { - result := _unix_listen(int(sd), backlog) +listen :: proc(sd: Socket, backlog: int) -> (Error) { + result := _unix_listen(c.int(sd), c.int(backlog)) if result < 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> (Errno) { - result := _unix_setsockopt(int(sd), level, optname, optval, optlen) +setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> Error { + result := _unix_setsockopt(c.int(sd), c.int(level), c.int(optname), optval, optlen) if result < 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -getsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> Errno { - result := _unix_getsockopt(int(sd), level, optname, optval, optlen) +getsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> Error { + optlen := optlen + result := _unix_getsockopt(c.int(sd), c.int(level), c.int(optname), optval, &optlen) if result < 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Errno) { - result := _unix_recvfrom(int(sd), raw_data(data), len(data), flags, addr, addr_size) +recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Error) { + result := _unix_recvfrom(c.int(sd), raw_data(data), len(data), c.int(flags), addr, addr_size) if result < 0 { - return 0, Errno(get_last_error()) + return 0, get_last_error() } - return u32(result), ERROR_NONE + return u32(result), nil } -recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) { - result := _unix_recv(int(sd), raw_data(data), len(data), flags) +recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { + result := _unix_recv(c.int(sd), raw_data(data), len(data), c.int(flags)) if result < 0 { - return 0, Errno(get_last_error()) + return 0, get_last_error() } - return u32(result), ERROR_NONE + return u32(result), nil } -sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Errno) { - result := _unix_sendto(int(sd), raw_data(data), len(data), flags, addr, addrlen) +sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Error) { + result := _unix_sendto(c.int(sd), raw_data(data), len(data), c.int(flags), addr, addrlen) if result < 0 { - return 0, Errno(get_last_error()) + return 0, get_last_error() } - return u32(result), ERROR_NONE + return u32(result), nil } -send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) { - result := _unix_send(int(sd), raw_data(data), len(data), 0) +send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { + result := _unix_send(c.int(sd), raw_data(data), len(data), 0) if result < 0 { - return 0, Errno(get_last_error()) + return 0, get_last_error() } - return u32(result), ERROR_NONE + return u32(result), nil } -shutdown :: proc(sd: Socket, how: int) -> (Errno) { - result := _unix_shutdown(int(sd), how) +shutdown :: proc(sd: Socket, how: int) -> (Error) { + result := _unix_shutdown(c.int(sd), c.int(how)) if result < 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Errno) { +fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Error) { result := _unix__fcntl(Handle(fd), c.int(cmd), uintptr(arg)) if result < 0 { - return 0, Errno(get_last_error()) + return 0, get_last_error() } - return int(result), ERROR_NONE + return int(result), nil } diff --git a/core/os/os_essence.odin b/core/os/os_essence.odin index e4281f6c9..75c4c1156 100644 --- a/core/os/os_essence.odin +++ b/core/os/os_essence.odin @@ -2,54 +2,59 @@ package os import "core:sys/es" -Handle :: distinct int; -Errno :: distinct int; +Handle :: distinct int +_Platform_Error :: enum i32 {NONE} -ERROR_NONE :: (Errno) (es.SUCCESS); +// ERROR_NONE :: Error(es.SUCCESS) -O_RDONLY :: 0x1; -O_WRONLY :: 0x2; -O_CREATE :: 0x4; -O_TRUNC :: 0x8; +O_RDONLY :: 0x1 +O_WRONLY :: 0x2 +O_CREATE :: 0x4 +O_TRUNC :: 0x8 -stderr : Handle = 0; +stderr : Handle = 0 current_thread_id :: proc "contextless" () -> int { - return (int) (es.ThreadGetID(es.CURRENT_THREAD)); + return (int) (es.ThreadGetID(es.CURRENT_THREAD)) } heap_alloc :: proc(size: int, zero_memory := true) -> rawptr { - return es.HeapAllocate(size, zero_memory); + return es.HeapAllocate(size, zero_memory) } heap_free :: proc(ptr: rawptr) { - es.HeapFree(ptr); + es.HeapFree(ptr) } heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr { - return es.HeapReallocate(ptr, new_size, false); + return es.HeapReallocate(ptr, new_size, false) } -open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) { - return (Handle) (0), (Errno) (1); +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) { + return (Handle) (0), (Error) (1) } -close :: proc(fd: Handle) -> Errno { - return (Errno) (1); +close :: proc(fd: Handle) -> Error { + return (Error) (1) } -file_size :: proc(fd: Handle) -> (i64, Errno) { - return (i64) (0), (Errno) (1); +file_size :: proc(fd: Handle) -> (i64, Error) { + return (i64) (0), (Error) (1) } -read :: proc(fd: Handle, data: []byte) -> (int, Errno) { - return (int) (0), (Errno) (1); +read :: proc(fd: Handle, data: []byte) -> (int, Error) { + return (int) (0), (Error) (1) } -write :: proc(fd: Handle, data: []u8) -> (int, Errno) { - return (int) (0), (Errno) (1); +write :: proc(fd: Handle, data: []u8) -> (int, Error) { + return (int) (0), (Error) (1) } -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { - return (i64) (0), (Errno) (1); +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + return (i64) (0), (Error) (1) } + +flush :: proc(fd: Handle) -> Error { + // do nothing + return nil +} \ No newline at end of file diff --git a/core/os/os_freebsd.odin b/core/os/os_freebsd.odin index c2ea82bf5..837e79f4d 100644 --- a/core/os/os_freebsd.odin +++ b/core/os/os_freebsd.odin @@ -3,124 +3,220 @@ package os foreign import dl "system:dl" foreign import libc "system:c" -import "core:runtime" +import "base:runtime" import "core:strings" import "core:c" +import "core:sys/freebsd" Handle :: distinct i32 File_Time :: distinct u64 -Errno :: distinct i32 INVALID_HANDLE :: ~Handle(0) -ERROR_NONE: Errno : 0 -EPERM: Errno : 1 -ENOENT: Errno : 2 -ESRCH: Errno : 3 -EINTR: Errno : 4 -EIO: Errno : 5 -ENXIO: Errno : 6 -E2BIG: Errno : 7 -ENOEXEC: Errno : 8 -EBADF: Errno : 9 -ECHILD: Errno : 10 -EBEADLK: Errno : 11 -ENOMEM: Errno : 12 -EACCESS: Errno : 13 -EFAULT: Errno : 14 -ENOTBLK: Errno : 15 -EBUSY: Errno : 16 -EEXIST: Errno : 17 -EXDEV: Errno : 18 -ENODEV: Errno : 19 -ENOTDIR: Errno : 20 -EISDIR: Errno : 21 -EINVAL: Errno : 22 -ENFILE: Errno : 23 -EMFILE: Errno : 24 -ENOTTY: Errno : 25 -ETXTBSY: Errno : 26 -EFBIG: Errno : 27 -ENOSPC: Errno : 28 -ESPIPE: Errno : 29 -EROFS: Errno : 30 -EMLINK: Errno : 31 -EPIPE: Errno : 32 -EDOM: Errno : 33 -ERANGE: Errno : 34 /* Result too large */ -EAGAIN: Errno : 35 -EINPROGRESS: Errno : 36 -EALREADY: Errno : 37 -ENOTSOCK: Errno : 38 -EDESTADDRREQ: Errno : 39 -EMSGSIZE: Errno : 40 -EPROTOTYPE: Errno : 41 -ENOPROTOOPT: Errno : 42 -EPROTONOSUPPORT: Errno : 43 -ESOCKTNOSUPPORT: Errno : 44 -EOPNOTSUPP: Errno : 45 -EPFNOSUPPORT: Errno : 46 -EAFNOSUPPORT: Errno : 47 -EADDRINUSE: Errno : 48 -EADDRNOTAVAIL: Errno : 49 -ENETDOWN: Errno : 50 -ENETUNREACH: Errno : 51 -ENETRESET: Errno : 52 -ECONNABORTED: Errno : 53 -ECONNRESET: Errno : 54 -ENOBUFS: Errno : 55 -EISCONN: Errno : 56 -ENOTCONN: Errno : 57 -ESHUTDOWN: Errno : 58 -ETIMEDOUT: Errno : 60 -ECONNREFUSED: Errno : 61 -ELOOP: Errno : 62 -ENAMETOOLING: Errno : 63 -EHOSTDOWN: Errno : 64 -EHOSTUNREACH: Errno : 65 -ENOTEMPTY: Errno : 66 -EPROCLIM: Errno : 67 -EUSERS: Errno : 68 -EDQUOT: Errno : 69 -ESTALE: Errno : 70 -EBADRPC: Errno : 72 -ERPCMISMATCH: Errno : 73 -EPROGUNAVAIL: Errno : 74 -EPROGMISMATCH: Errno : 75 -EPROCUNAVAIL: Errno : 76 -ENOLCK: Errno : 77 -ENOSYS: Errno : 78 -EFTYPE: Errno : 79 -EAUTH: Errno : 80 -ENEEDAUTH: Errno : 81 -EIDRM: Errno : 82 -ENOMSG: Errno : 83 -EOVERFLOW: Errno : 84 -ECANCELED: Errno : 85 -EILSEQ: Errno : 86 -ENOATTR: Errno : 87 -EDOOFUS: Errno : 88 -EBADMSG: Errno : 89 -EMULTIHOP: Errno : 90 -ENOLINK: Errno : 91 -EPROTO: Errno : 92 -ENOTCAPABLE: Errno : 93 -ECAPMODE: Errno : 94 -ENOTRECOVERABLE: Errno : 95 -EOWNERDEAD: Errno : 96 +_Platform_Error :: enum i32 { + NONE = 0, + EPERM = 1, + ENOENT = 2, + ESRCH = 3, + EINTR = 4, + EIO = 5, + ENXIO = 6, + E2BIG = 7, + ENOEXEC = 8, + EBADF = 9, + ECHILD = 10, + EBEADLK = 11, + ENOMEM = 12, + EACCESS = 13, + EFAULT = 14, + ENOTBLK = 15, + EBUSY = 16, + EEXIST = 17, + EXDEV = 18, + ENODEV = 19, + ENOTDIR = 20, + EISDIR = 21, + EINVAL = 22, + ENFILE = 23, + EMFILE = 24, + ENOTTY = 25, + ETXTBSY = 26, + EFBIG = 27, + ENOSPC = 28, + ESPIPE = 29, + EROFS = 30, + EMLINK = 31, + EPIPE = 32, + EDOM = 33, + ERANGE = 34, /* Result too large */ + EAGAIN = 35, + EINPROGRESS = 36, + EALREADY = 37, + ENOTSOCK = 38, + EDESTADDRREQ = 39, + EMSGSIZE = 40, + EPROTOTYPE = 41, + ENOPROTOOPT = 42, + EPROTONOSUPPORT = 43, + ESOCKTNOSUPPORT = 44, + EOPNOTSUPP = 45, + EPFNOSUPPORT = 46, + EAFNOSUPPORT = 47, + EADDRINUSE = 48, + EADDRNOTAVAIL = 49, + ENETDOWN = 50, + ENETUNREACH = 51, + ENETRESET = 52, + ECONNABORTED = 53, + ECONNRESET = 54, + ENOBUFS = 55, + EISCONN = 56, + ENOTCONN = 57, + ESHUTDOWN = 58, + ETIMEDOUT = 60, + ECONNREFUSED = 61, + ELOOP = 62, + ENAMETOOLING = 63, + EHOSTDOWN = 64, + EHOSTUNREACH = 65, + ENOTEMPTY = 66, + EPROCLIM = 67, + EUSERS = 68, + EDQUOT = 69, + ESTALE = 70, + EBADRPC = 72, + ERPCMISMATCH = 73, + EPROGUNAVAIL = 74, + EPROGMISMATCH = 75, + EPROCUNAVAIL = 76, + ENOLCK = 77, + ENOSYS = 78, + EFTYPE = 79, + EAUTH = 80, + ENEEDAUTH = 81, + EIDRM = 82, + ENOMSG = 83, + EOVERFLOW = 84, + ECANCELED = 85, + EILSEQ = 86, + ENOATTR = 87, + EDOOFUS = 88, + EBADMSG = 89, + EMULTIHOP = 90, + ENOLINK = 91, + EPROTO = 92, + ENOTCAPABLE = 93, + ECAPMODE = 94, + ENOTRECOVERABLE = 95, + EOWNERDEAD = 96, +} +EPERM :: Platform_Error.EPERM +ENOENT :: Platform_Error.ENOENT +ESRCH :: Platform_Error.ESRCH +EINTR :: Platform_Error.EINTR +EIO :: Platform_Error.EIO +ENXIO :: Platform_Error.ENXIO +E2BIG :: Platform_Error.E2BIG +ENOEXEC :: Platform_Error.ENOEXEC +EBADF :: Platform_Error.EBADF +ECHILD :: Platform_Error.ECHILD +EBEADLK :: Platform_Error.EBEADLK +ENOMEM :: Platform_Error.ENOMEM +EACCESS :: Platform_Error.EACCESS +EFAULT :: Platform_Error.EFAULT +ENOTBLK :: Platform_Error.ENOTBLK +EBUSY :: Platform_Error.EBUSY +EEXIST :: Platform_Error.EEXIST +EXDEV :: Platform_Error.EXDEV +ENODEV :: Platform_Error.ENODEV +ENOTDIR :: Platform_Error.ENOTDIR +EISDIR :: Platform_Error.EISDIR +EINVAL :: Platform_Error.EINVAL +ENFILE :: Platform_Error.ENFILE +EMFILE :: Platform_Error.EMFILE +ENOTTY :: Platform_Error.ENOTTY +ETXTBSY :: Platform_Error.ETXTBSY +EFBIG :: Platform_Error.EFBIG +ENOSPC :: Platform_Error.ENOSPC +ESPIPE :: Platform_Error.ESPIPE +EROFS :: Platform_Error.EROFS +EMLINK :: Platform_Error.EMLINK +EPIPE :: Platform_Error.EPIPE +EDOM :: Platform_Error.EDOM +ERANGE :: Platform_Error.ERANGE +EAGAIN :: Platform_Error.EAGAIN +EINPROGRESS :: Platform_Error.EINPROGRESS +EALREADY :: Platform_Error.EALREADY +ENOTSOCK :: Platform_Error.ENOTSOCK +EDESTADDRREQ :: Platform_Error.EDESTADDRREQ +EMSGSIZE :: Platform_Error.EMSGSIZE +EPROTOTYPE :: Platform_Error.EPROTOTYPE +ENOPROTOOPT :: Platform_Error.ENOPROTOOPT +EPROTONOSUPPORT :: Platform_Error.EPROTONOSUPPORT +ESOCKTNOSUPPORT :: Platform_Error.ESOCKTNOSUPPORT +EOPNOTSUPP :: Platform_Error.EOPNOTSUPP +EPFNOSUPPORT :: Platform_Error.EPFNOSUPPORT +EAFNOSUPPORT :: Platform_Error.EAFNOSUPPORT +EADDRINUSE :: Platform_Error.EADDRINUSE +EADDRNOTAVAIL :: Platform_Error.EADDRNOTAVAIL +ENETDOWN :: Platform_Error.ENETDOWN +ENETUNREACH :: Platform_Error.ENETUNREACH +ENETRESET :: Platform_Error.ENETRESET +ECONNABORTED :: Platform_Error.ECONNABORTED +ECONNRESET :: Platform_Error.ECONNRESET +ENOBUFS :: Platform_Error.ENOBUFS +EISCONN :: Platform_Error.EISCONN +ENOTCONN :: Platform_Error.ENOTCONN +ESHUTDOWN :: Platform_Error.ESHUTDOWN +ETIMEDOUT :: Platform_Error.ETIMEDOUT +ECONNREFUSED :: Platform_Error.ECONNREFUSED +ELOOP :: Platform_Error.ELOOP +ENAMETOOLING :: Platform_Error.ENAMETOOLING +EHOSTDOWN :: Platform_Error.EHOSTDOWN +EHOSTUNREACH :: Platform_Error.EHOSTUNREACH +ENOTEMPTY :: Platform_Error.ENOTEMPTY +EPROCLIM :: Platform_Error.EPROCLIM +EUSERS :: Platform_Error.EUSERS +EDQUOT :: Platform_Error.EDQUOT +ESTALE :: Platform_Error.ESTALE +EBADRPC :: Platform_Error.EBADRPC +ERPCMISMATCH :: Platform_Error.ERPCMISMATCH +EPROGUNAVAIL :: Platform_Error.EPROGUNAVAIL +EPROGMISMATCH :: Platform_Error.EPROGMISMATCH +EPROCUNAVAIL :: Platform_Error.EPROCUNAVAIL +ENOLCK :: Platform_Error.ENOLCK +ENOSYS :: Platform_Error.ENOSYS +EFTYPE :: Platform_Error.EFTYPE +EAUTH :: Platform_Error.EAUTH +ENEEDAUTH :: Platform_Error.ENEEDAUTH +EIDRM :: Platform_Error.EIDRM +ENOMSG :: Platform_Error.ENOMSG +EOVERFLOW :: Platform_Error.EOVERFLOW +ECANCELED :: Platform_Error.ECANCELED +EILSEQ :: Platform_Error.EILSEQ +ENOATTR :: Platform_Error.ENOATTR +EDOOFUS :: Platform_Error.EDOOFUS +EBADMSG :: Platform_Error.EBADMSG +EMULTIHOP :: Platform_Error.EMULTIHOP +ENOLINK :: Platform_Error.ENOLINK +EPROTO :: Platform_Error.EPROTO +ENOTCAPABLE :: Platform_Error.ENOTCAPABLE +ECAPMODE :: Platform_Error.ECAPMODE +ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE +EOWNERDEAD :: Platform_Error.EOWNERDEAD O_RDONLY :: 0x00000 O_WRONLY :: 0x00001 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 +236,8 @@ RTLD_NOLOAD :: 0x02000 MAX_PATH :: 1024 +KINFO_FILE_SIZE :: 1392 + args := _alloc_command_line_arguments() Unix_File_Time :: struct { @@ -159,7 +257,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 +289,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 { @@ -241,23 +354,25 @@ S_ISGID :: 0o2000 // Set group id on execution S_ISVTX :: 0o1000 // Directory restrcted delete -S_ISLNK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK } -S_ISREG :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG } -S_ISDIR :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR } -S_ISCHR :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR } -S_ISBLK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK } -S_ISFIFO :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO } -S_ISSOCK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK } +@(require_results) S_ISLNK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK } +@(require_results) S_ISREG :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG } +@(require_results) S_ISDIR :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR } +@(require_results) S_ISCHR :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR } +@(require_results) S_ISBLK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK } +@(require_results) S_ISFIFO :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO } +@(require_results) S_ISSOCK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK } F_OK :: 0 // Test for file existance X_OK :: 1 // Test for execute permission W_OK :: 2 // Test for write permission R_OK :: 4 // Test for read permission -foreign libc { - @(link_name="__error") __errno_location :: proc() -> ^int --- +F_KINFO :: 22 - @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, mode: c.int) -> Handle --- +foreign libc { + @(link_name="__error") __Error_location :: proc() -> ^c.int --- + + @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u16) -> Handle --- @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- @@ -274,6 +389,8 @@ foreign libc { @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- + @(link_name="fcntl") _unix_fcntl :: proc(fd: Handle, cmd: c.int, #c_vararg args: ..any) -> c.int --- + @(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle --- @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- @@ -286,7 +403,7 @@ foreign libc { @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- @(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int --- @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- @@ -300,147 +417,205 @@ foreign dl { @(link_name="pthread_getthreadid_np") pthread_getthreadid_np :: proc() -> c.int --- } +@(require_results) is_path_separator :: proc(r: rune) -> bool { return r == '/' } -get_last_error :: proc "contextless" () -> int { - return __errno_location()^ +@(require_results, no_instrumentation) +get_last_error :: proc "contextless" () -> Error { + return Platform_Error(__Error_location()^) } -open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) { +@(require_results) +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) - handle := _unix_open(cstr, c.int(flags), c.int(mode)) + handle := _unix_open(cstr, c.int(flags), u16(mode)) if handle == -1 { - return INVALID_HANDLE, Errno(get_last_error()) + return INVALID_HANDLE, get_last_error() } - return handle, ERROR_NONE + return handle, nil } -close :: proc(fd: Handle) -> Errno { +close :: proc(fd: Handle) -> Error { result := _unix_close(fd) if result == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -read :: proc(fd: Handle, data: []byte) -> (int, Errno) { - bytes_read := _unix_read(fd, &data[0], c.size_t(len(data))) +flush :: proc(fd: Handle) -> Error { + return cast(_Platform_Error)freebsd.fsync(cast(freebsd.Fd)fd) +} + +// If you read or write more than `INT_MAX` bytes, FreeBSD returns `EINVAL`. +// In practice a read/write call would probably never read/write these big buffers all at once, +// which is why the number of bytes is returned and why there are procs that will call this in a +// loop for you. +// We set a max of 1GB to keep alignment and to be safe. +@(private) +MAX_RW :: 1 << 30 + +read :: proc(fd: Handle, data: []byte) -> (int, Error) { + to_read := min(c.size_t(len(data)), MAX_RW) + bytes_read := _unix_read(fd, &data[0], to_read) if bytes_read == -1 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return int(bytes_read), ERROR_NONE + return int(bytes_read), nil } -write :: proc(fd: Handle, data: []byte) -> (int, Errno) { +write :: proc(fd: Handle, data: []byte) -> (int, Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } - bytes_written := _unix_write(fd, &data[0], c.size_t(len(data))) + + to_write := min(c.size_t(len(data)), MAX_RW) + bytes_written := _unix_write(fd, &data[0], to_write) if bytes_written == -1 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return int(bytes_written), ERROR_NONE + return int(bytes_written), nil } -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + if len(data) == 0 { + return 0, nil + } + + to_read := min(uint(len(data)), MAX_RW) + + bytes_read, errno := freebsd.pread(cast(freebsd.Fd)fd, data[:to_read], cast(freebsd.off_t)offset) + + return bytes_read, cast(_Platform_Error)errno +} + +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + if len(data) == 0 { + return 0, nil + } + + to_write := min(uint(len(data)), MAX_RW) + + bytes_written, errno := freebsd.pwrite(cast(freebsd.Fd)fd, data[:to_write], cast(freebsd.off_t)offset) + + return bytes_written, cast(_Platform_Error)errno +} + +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } res := _unix_seek(fd, offset, c.int(whence)) if res == -1 { - return -1, Errno(get_last_error()) + errno := get_last_error() + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + case: + return 0, errno + } } - return res, ERROR_NONE + return res, nil } -file_size :: proc(fd: Handle) -> (i64, Errno) { - s, err := fstat(fd) - if err != ERROR_NONE { - return -1, err - } - return s.size, ERROR_NONE +@(require_results) +file_size :: proc(fd: Handle) -> (size: i64, err: Error) { + size = -1 + s := _fstat(fd) or_return + size = s.size + return } -rename :: proc(old_path, new_path: string) -> Errno { +rename :: proc(old_path, new_path: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator) new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator) res := _unix_rename(old_path_cstr, new_path_cstr) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -remove :: proc(path: string) -> Errno { +remove :: proc(path: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_unlink(path_cstr) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno { +make_directory :: proc(path: string, mode: mode_t = 0o775) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_mkdir(path_cstr, mode) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -remove_directory :: proc(path: string) -> Errno { +remove_directory :: proc(path: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_rmdir(path_cstr) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } +@(require_results) is_file_handle :: proc(fd: Handle) -> bool { s, err := _fstat(fd) - if err != ERROR_NONE { + if err != nil { return false } return S_ISREG(s.mode) } +@(require_results) is_file_path :: proc(path: string, follow_links: bool = true) -> bool { s: OS_Stat - err: Errno + err: Error if follow_links { s, err = _stat(path) } else { s, err = _lstat(path) } - if err != ERROR_NONE { + if err != nil { return false } return S_ISREG(s.mode) } +@(require_results) is_dir_handle :: proc(fd: Handle) -> bool { s, err := _fstat(fd) - if err != ERROR_NONE { + if err != nil { return false } return S_ISDIR(s.mode) } +@(require_results) is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { s: OS_Stat - err: Errno + err: Error if follow_links { s, err = _stat(path) } else { s, err = _lstat(path) } - if err != ERROR_NONE { + if err != nil { return false } return S_ISDIR(s.mode) @@ -459,38 +634,40 @@ stderr: Handle = 2 last_write_time :: proc(fd: Handle) -> File_Time {} last_write_time_by_name :: proc(name: string) -> File_Time {} */ -last_write_time :: proc(fd: Handle) -> (File_Time, Errno) { +@(require_results) +last_write_time :: proc(fd: Handle) -> (File_Time, Error) { s, err := _fstat(fd) - if err != ERROR_NONE { + if err != nil { return 0, err } modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), ERROR_NONE + return File_Time(modified), nil } -last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) { +@(require_results) +last_write_time_by_name :: proc(name: string) -> (File_Time, Error) { s, err := _stat(name) - if err != ERROR_NONE { + if err != nil { return 0, err } modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), ERROR_NONE + return File_Time(modified), nil } -@private -_stat :: proc(path: string) -> (OS_Stat, Errno) { +@(private, require_results) +_stat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) s: OS_Stat = --- result := _unix_lstat(cstr, &s) if result == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_lstat :: proc(path: string) -> (OS_Stat, Errno) { +@(private, require_results) +_lstat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -498,54 +675,53 @@ _lstat :: proc(path: string) -> (OS_Stat, Errno) { s: OS_Stat = --- res := _unix_lstat(cstr, &s) if res == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) { +@(private, require_results) +_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { s: OS_Stat = --- result := _unix_fstat(fd, &s) if result == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_fdopendir :: proc(fd: Handle) -> (Dir, Errno) { +@(private, require_results) +_fdopendir :: proc(fd: Handle) -> (Dir, Error) { dirp := _unix_fdopendir(fd) if dirp == cast(Dir)nil { - return nil, Errno(get_last_error()) + return nil, get_last_error() } - return dirp, ERROR_NONE + return dirp, nil } -@private -_closedir :: proc(dirp: Dir) -> Errno { +@(private) +_closedir :: proc(dirp: Dir) -> Error { rc := _unix_closedir(dirp) if rc != 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -@private +@(private) _rewinddir :: proc(dirp: Dir) { _unix_rewinddir(dirp) } -@private -_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) { +@(private, require_results) +_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { result: ^Dirent rc := _unix_readdir_r(dirp, &entry, &result) if rc != 0 { - err = Errno(get_last_error()) + err = get_last_error() return } - err = ERROR_NONE if result == nil { end_of_stream = true @@ -555,8 +731,8 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) return } -@private -_readlink :: proc(path: string) -> (string, Errno) { +@(private, require_results) +_readlink :: proc(path: string) -> (string, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) path_cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -567,25 +743,53 @@ _readlink :: proc(path: string) -> (string, Errno) { rc := _unix_readlink(path_cstr, &(buf[0]), bufsz) if rc == -1 { delete(buf) - return "", Errno(get_last_error()) + return "", get_last_error() } else if rc == int(bufsz) { bufsz += MAX_PATH delete(buf) buf = make([]byte, bufsz) } else { - return strings.string_from_ptr(&buf[0], rc), ERROR_NONE + return strings.string_from_ptr(&buf[0], rc), nil } } - return "", Errno{} + return "", Error{} } -// XXX FreeBSD -absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) { - return "", Errno(ENOSYS) +@(private, require_results) +_dup :: proc(fd: Handle) -> (Handle, Error) { + dup := _unix_dup(fd) + if dup == -1 { + return INVALID_HANDLE, get_last_error() + } + return dup, nil } -absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { +@(require_results) +absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { + // NOTE(Feoramund): The situation isn't ideal, but this was the best way I + // could find to implement this. There are a couple outstanding bug reports + // regarding the desire to retrieve an absolute path from a handle, but to + // 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 "", get_last_error() + } + + path := strings.clone_from_cstring_bounded(cast(cstring)&kinfo.path[0], len(kinfo.path)) + return path, nil +} + +@(require_results) +absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { rel := rel if rel == "" { rel = "." @@ -596,48 +800,25 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { path_ptr := _unix_realpath(rel_cstr, nil) if path_ptr == nil { - return "", Errno(get_last_error()) + return "", get_last_error() } - defer _unix_free(path_ptr) + defer _unix_free(rawptr(path_ptr)) - path_cstr := transmute(cstring)path_ptr - path = strings.clone( string(path_cstr) ) - - return path, ERROR_NONE + return strings.clone(string(path_ptr), allocator) } -access :: proc(path: string, mask: int) -> (bool, Errno) { +access :: proc(path: string, mask: int) -> (bool, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) result := _unix_access(cstr, c.int(mask)) if result == -1 { - return false, Errno(get_last_error()) + return false, get_last_error() } - return true, ERROR_NONE -} - -heap_alloc :: proc(size: int, zero_memory := true) -> rawptr { - if size <= 0 { - return nil - } - if zero_memory { - return _unix_calloc(1, c.size_t(size)) - } else { - return _unix_malloc(c.size_t(size)) - } -} - -heap_resize :: proc(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, c.size_t(new_size)) -} - -heap_free :: proc(ptr: rawptr) { - _unix_free(ptr) + return true, nil } +@(require_results) lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) @@ -649,12 +830,15 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin return strings.clone(string(cstr), allocator), true } +@(require_results) get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } -get_current_directory :: proc() -> string { +@(require_results) +get_current_directory :: proc(allocator := context.allocator) -> string { + context.allocator = allocator // 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. @@ -665,7 +849,7 @@ get_current_directory :: proc() -> string { if cwd != nil { return string(cwd) } - if Errno(get_last_error()) != ERANGE { + if get_last_error() != ERANGE { delete(buf) return "" } @@ -674,12 +858,14 @@ get_current_directory :: proc() -> string { unreachable() } -set_current_directory :: proc(path: string) -> (err: Errno) { +set_current_directory :: proc(path: string) -> (err: Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_chdir(cstr) - if res == -1 do return Errno(get_last_error()) - return ERROR_NONE + if res == -1 { + return get_last_error() + } + return nil } exit :: proc "contextless" (code: int) -> ! { @@ -687,16 +873,19 @@ exit :: proc "contextless" (code: int) -> ! { _unix_exit(c.int(code)) } +@(require_results) current_thread_id :: proc "contextless" () -> int { return cast(int) pthread_getthreadid_np() } +@(require_results) dlopen :: proc(filename: string, flags: int) -> rawptr { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(filename, context.temp_allocator) handle := _unix_dlopen(cstr, c.int(flags)) return handle } +@(require_results) dlsym :: proc(handle: rawptr, symbol: string) -> rawptr { assert(handle != nil) runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() @@ -712,21 +901,24 @@ dlerror :: proc() -> string { return string(_unix_dlerror()) } +@(require_results) get_page_size :: proc() -> int { // NOTE(tetra): The page size never changes, so why do anything complicated // if we don't have to. @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 } -@(private) +@(private, require_results) _processor_core_count :: proc() -> int { count : int = 0 count_size := size_of(count) - if _sysctlbyname("hw.logicalcpu", &count, &count_size, nil, 0) == 0 { + if _sysctlbyname("hw.ncpu", &count, &count_size, nil, 0) == 0 { if count > 0 { return count } @@ -736,6 +928,7 @@ _processor_core_count :: proc() -> int { } +@(require_results) _alloc_command_line_arguments :: proc() -> []string { res := make([]string, len(runtime.args__)) for arg, i in runtime.args__ { diff --git a/core/os/os_freestanding.odin b/core/os/os_freestanding.odin index 55ce1d12e..c22a6d7d5 100644 --- a/core/os/os_freestanding.odin +++ b/core/os/os_freestanding.odin @@ -1,4 +1,4 @@ -//+freestanding +#+build freestanding package os -#panic("package os does not support a freestanding target") \ No newline at end of file +#panic("package os does not support a freestanding target") diff --git a/core/os/os_haiku.odin b/core/os/os_haiku.odin new file mode 100644 index 000000000..4ad370724 --- /dev/null +++ b/core/os/os_haiku.odin @@ -0,0 +1,490 @@ +package os + +foreign import libc "system:c" + +import "base:runtime" +import "core:c" +import "core:strings" +import "core:sys/haiku" + +Handle :: i32 +Pid :: i32 +File_Time :: i64 +_Platform_Error :: haiku.Errno + +MAX_PATH :: haiku.PATH_MAX + +ENOSYS :: _Platform_Error(i32(haiku.Errno.POSIX_ERROR_BASE) + 9) + +INVALID_HANDLE :: ~Handle(0) + +stdin: Handle = 0 +stdout: Handle = 1 +stderr: Handle = 2 + +pid_t :: haiku.pid_t +off_t :: haiku.off_t +dev_t :: haiku.dev_t +ino_t :: haiku.ino_t +mode_t :: haiku.mode_t +nlink_t :: haiku.nlink_t +uid_t :: haiku.uid_t +gid_t :: haiku.gid_t +blksize_t :: haiku.blksize_t +blkcnt_t :: haiku.blkcnt_t +time_t :: haiku.time_t + + +Unix_File_Time :: struct { + seconds: time_t, + nanoseconds: c.long, +} + +OS_Stat :: struct { + device_id: dev_t, // device ID that this file resides on + serial: ino_t, // this file's serial inode ID + mode: mode_t, // file mode (rwx for user, group, etc) + nlink: nlink_t, // number of hard links to this file + uid: uid_t, // user ID of the file's owner + gid: gid_t, // group ID of the file's group + size: off_t, // file size, in bytes + rdev: dev_t, // device type (not used) + block_size: blksize_t, // optimal blocksize for I/O + + last_access: Unix_File_Time, // time of last access + modified: Unix_File_Time, // time of last data modification + status_change: Unix_File_Time, // time of last file status change + birthtime: Unix_File_Time, // time of file creation + + type: u32, // attribute/index type + + blocks: blkcnt_t, // blocks allocated for file +} + +/* file access modes for open() */ +O_RDONLY :: 0x0000 /* read only */ +O_WRONLY :: 0x0001 /* write only */ +O_RDWR :: 0x0002 /* read and write */ +O_ACCMODE :: 0x0003 /* mask to get the access modes above */ +O_RWMASK :: O_ACCMODE + +/* flags for open() */ +O_EXCL :: 0x0100 /* exclusive creat */ +O_CREATE :: 0x0200 /* create and open file */ +O_TRUNC :: 0x0400 /* open with truncation */ +O_NOCTTY :: 0x1000 /* don't make tty the controlling tty */ +O_NOTRAVERSE :: 0x2000 /* do not traverse leaf link */ + +// 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 +S_ISVTX :: 0o001000 // Save swapped text even after use + +// 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_ISTXT :: 0o1000 // Sticky bit + +S_ISLNK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK } +S_ISREG :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG } +S_ISDIR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR } +S_ISCHR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR } +S_ISBLK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK } +S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO } +S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK } + + +foreign libc { + @(link_name="_errorp") __error :: proc() -> ^c.int --- + + @(link_name="fork") _unix_fork :: proc() -> pid_t --- + @(link_name="getthrid") _unix_getthrid :: proc() -> int --- + + @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u16) -> Handle --- + @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- + @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pread") _unix_pread :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pwrite") _unix_pwrite :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t --- + @(link_name="stat") _unix_stat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="fstat") _unix_fstat :: proc(fd: Handle, sb: ^OS_Stat) -> c.int --- + @(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t --- + @(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int --- + @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- + @(link_name="chdir") _unix_chdir :: proc(path: cstring) -> c.int --- + @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- + @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- + @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- + @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- + @(link_name="fsync") _unix_fsync :: proc(fd: Handle) -> c.int --- + + @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- + @(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long --- + @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- + @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- + @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- + @(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- + + @(link_name="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="exit") _unix_exit :: proc(status: c.int) -> ! --- + + @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- + @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- + @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- + @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- +} + +MAXNAMLEN :: haiku.NAME_MAX + +Dirent :: struct { + dev: dev_t, + pdef: dev_t, + ino: ino_t, + pino: ino_t, + reclen: u16, + name: [MAXNAMLEN + 1]byte, // name +} + +Dir :: distinct rawptr // DIR* + +@(require_results) +is_path_separator :: proc(r: rune) -> bool { + return r == '/' +} + +@(require_results, no_instrumentation) +get_last_error :: proc "contextless" () -> Error { + return Platform_Error(__error()^) +} + +@(require_results) +fork :: proc() -> (Pid, Error) { + pid := _unix_fork() + if pid == -1 { + return Pid(-1), get_last_error() + } + return Pid(pid), nil +} + +@(require_results) +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + handle := _unix_open(cstr, c.int(flags), u16(mode)) + if handle == -1 { + return INVALID_HANDLE, get_last_error() + } + return handle, nil +} + +close :: proc(fd: Handle) -> Error { + result := _unix_close(fd) + if result == -1 { + return get_last_error() + } + return nil +} + +flush :: proc(fd: Handle) -> Error { + result := _unix_fsync(fd) + if result == -1 { + return get_last_error() + } + return nil +} + +// In practice a read/write call would probably never read/write these big buffers all at once, +// which is why the number of bytes is returned and why there are procs that will call this in a +// loop for you. +// We set a max of 1GB to keep alignment and to be safe. +@(private) +MAX_RW :: 1 << 30 + +read :: proc(fd: Handle, data: []byte) -> (int, Error) { + to_read := min(c.size_t(len(data)), MAX_RW) + bytes_read := _unix_read(fd, &data[0], to_read) + if bytes_read == -1 { + return -1, get_last_error() + } + return int(bytes_read), nil +} + +write :: proc(fd: Handle, data: []byte) -> (int, Error) { + if len(data) == 0 { + return 0, nil + } + + to_write := min(c.size_t(len(data)), MAX_RW) + bytes_written := _unix_write(fd, &data[0], to_write) + if bytes_written == -1 { + return -1, get_last_error() + } + return int(bytes_written), nil +} + +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + if len(data) == 0 { + return 0, nil + } + + to_read := min(uint(len(data)), MAX_RW) + + bytes_read := _unix_pread(fd, raw_data(data), to_read, offset) + if bytes_read < 0 { + return -1, get_last_error() + } + return bytes_read, nil +} + +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + if len(data) == 0 { + return 0, nil + } + + to_write := min(uint(len(data)), MAX_RW) + + bytes_written := _unix_pwrite(fd, raw_data(data), to_write, offset) + if bytes_written < 0 { + return -1, get_last_error() + } + return bytes_written, nil +} + +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } + res := _unix_seek(fd, offset, c.int(whence)) + if res == -1 { + errno := get_last_error() + switch errno { + case .BAD_VALUE: + return 0, .Invalid_Offset + } + return 0, errno + } + return res, nil +} + +@(require_results) +file_size :: proc(fd: Handle) -> (i64, Error) { + s, err := _fstat(fd) + if err != nil { + return -1, err + } + return s.size, nil +} + +// "Argv" arguments converted to Odin strings +args := _alloc_command_line_arguments() + +@(require_results) +_alloc_command_line_arguments :: proc() -> []string { + res := make([]string, len(runtime.args__)) + for arg, i in runtime.args__ { + res[i] = string(arg) + } + return res +} + +@(private, require_results) +_stat :: proc(path: string) -> (OS_Stat, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + + // deliberately uninitialized + s: OS_Stat = --- + res := _unix_stat(cstr, &s) + if res == -1 { + return s, get_last_error() + } + return s, nil +} + +@(private, require_results) +_lstat :: proc(path: string) -> (OS_Stat, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + + // deliberately uninitialized + s: OS_Stat = --- + res := _unix_lstat(cstr, &s) + if res == -1 { + return s, get_last_error() + } + return s, nil +} + +@(private, require_results) +_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { + // deliberately uninitialized + s: OS_Stat = --- + res := _unix_fstat(fd, &s) + if res == -1 { + return s, get_last_error() + } + return s, nil +} + +@(private) +_fdopendir :: proc(fd: Handle) -> (Dir, Error) { + dirp := _unix_fdopendir(fd) + if dirp == cast(Dir)nil { + return nil, get_last_error() + } + return dirp, nil +} + +@(private) +_closedir :: proc(dirp: Dir) -> Error { + rc := _unix_closedir(dirp) + if rc != 0 { + return get_last_error() + } + return nil +} + +@(private) +_rewinddir :: proc(dirp: Dir) { + _unix_rewinddir(dirp) +} + +@(private, require_results) +_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { + result: ^Dirent + rc := _unix_readdir_r(dirp, &entry, &result) + + if rc != 0 { + err = get_last_error() + return + } + + if result == nil { + end_of_stream = true + return + } + + return +} + +@(private, require_results) +_readlink :: proc(path: string) -> (string, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + + bufsz : uint = MAX_PATH + buf := make([]byte, MAX_PATH) + for { + rc := _unix_readlink(path_cstr, &(buf[0]), bufsz) + if rc == -1 { + delete(buf) + return "", get_last_error() + } else if rc == int(bufsz) { + bufsz += MAX_PATH + delete(buf) + buf = make([]byte, bufsz) + } else { + return strings.string_from_ptr(&buf[0], rc), nil + } + } +} + +@(require_results) +absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { + return "", Error(ENOSYS) +} + +@(require_results) +absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { + 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 "", get_last_error() + } + defer _unix_free(path_ptr) + + path_cstr := cstring(path_ptr) + return strings.clone(string(path_cstr), allocator) +} + +access :: proc(path: string, mask: int) -> (bool, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_access(cstr, c.int(mask)) + if res == -1 { + return false, get_last_error() + } + return true, nil +} + +@(require_results) +lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) + path_str := strings.clone_to_cstring(key, context.temp_allocator) + cstr := _unix_getenv(path_str) + if cstr == nil { + return "", false + } + return strings.clone(string(cstr), allocator), true +} + +@(require_results) +get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { + value, _ = lookup_env(key, allocator) + return +} + +@(private, require_results) +_processor_core_count :: proc() -> int { + info: haiku.system_info + haiku.get_system_info(&info) + return int(info.cpu_count) +} + +exit :: proc "contextless" (code: int) -> ! { + runtime._cleanup_runtime_contextless() + _unix_exit(i32(code)) +} diff --git a/core/os/os_js.odin b/core/os/os_js.odin index 5d7eb784e..348554728 100644 --- a/core/os/os_js.odin +++ b/core/os/os_js.odin @@ -1,174 +1,174 @@ -//+build js +#+build js package os -import "core:intrinsics" -import "core:runtime" -import "core:unicode/utf16" +foreign import "odin_env" +@(require_results) is_path_separator :: proc(c: byte) -> bool { return c == '/' || c == '\\' } -open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errno) { +Handle :: distinct u32 + +stdout: Handle = 1 +stderr: Handle = 2 + +@(require_results) +open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Error) { unimplemented("core:os procedure not supported on JS target") } -close :: proc(fd: Handle) -> Errno { +close :: proc(fd: Handle) -> Error { + return nil +} + +flush :: proc(fd: Handle) -> (err: Error) { + return nil +} + +write :: proc(fd: Handle, data: []byte) -> (int, Error) { + foreign odin_env { + @(link_name="write") + _write :: proc "contextless" (fd: Handle, p: []byte) --- + } + _write(fd, data) + return len(data), nil +} + +read :: proc(fd: Handle, data: []byte) -> (int, Error) { unimplemented("core:os procedure not supported on JS target") } -flush :: proc(fd: Handle) -> (err: Errno) { +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { unimplemented("core:os procedure not supported on JS target") } - - -write :: proc(fd: Handle, data: []byte) -> (int, Errno) { +@(require_results) +file_size :: proc(fd: Handle) -> (i64, Error) { unimplemented("core:os procedure not supported on JS target") } -@(private="file") -read_console :: proc(handle: Handle, b: []byte) -> (n: int, err: Errno) { +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + unimplemented("core:os procedure not supported on JS target") +} +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { unimplemented("core:os procedure not supported on JS target") } -read :: proc(fd: Handle, data: []byte) -> (int, Errno) { - unimplemented("core:os procedure not supported on JS target") -} - -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { - unimplemented("core:os procedure not supported on JS target") -} - -file_size :: proc(fd: Handle) -> (i64, Errno) { - unimplemented("core:os procedure not supported on JS target") -} - - -@(private) -MAX_RW :: 1<<30 - -@(private) -pread :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { - unimplemented("core:os procedure not supported on JS target") -} -@(private) -pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { - unimplemented("core:os procedure not supported on JS target") -} - -read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Errno) { - unimplemented("core:os procedure not supported on JS target") -} -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)) - - -get_std_handle :: proc "contextless" (h: uint) -> Handle { - context = runtime.default_context() - unimplemented("core:os procedure not supported on JS target") -} - - +@(require_results) exists :: proc(path: string) -> bool { unimplemented("core:os procedure not supported on JS target") } +@(require_results) is_file :: proc(path: string) -> bool { unimplemented("core:os procedure not supported on JS target") } +@(require_results) is_dir :: proc(path: string) -> bool { unimplemented("core:os procedure not supported on JS target") } -// NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName -//@private cwd_lock := win32.SRWLOCK{} // zero is initialized - +@(require_results) get_current_directory :: proc(allocator := context.allocator) -> string { unimplemented("core:os procedure not supported on JS target") } -set_current_directory :: proc(path: string) -> (err: Errno) { +set_current_directory :: proc(path: string) -> (err: Error) { unimplemented("core:os procedure not supported on JS target") } -change_directory :: proc(path: string) -> (err: Errno) { +change_directory :: proc(path: string) -> (err: Error) { unimplemented("core:os procedure not supported on JS target") } -make_directory :: proc(path: string, mode: u32 = 0) -> (err: Errno) { +make_directory :: proc(path: string, mode: u32 = 0) -> (err: Error) { unimplemented("core:os procedure not supported on JS target") } -remove_directory :: proc(path: string) -> (err: Errno) { +remove_directory :: proc(path: string) -> (err: Error) { + unimplemented("core:os procedure not supported on JS target") +} + + +link :: proc(old_name, new_name: string) -> (err: Error) { + unimplemented("core:os procedure not supported on JS target") +} + +unlink :: proc(path: string) -> (err: Error) { unimplemented("core:os procedure not supported on JS target") } -@(private) -is_abs :: proc(path: string) -> bool { - unimplemented("core:os procedure not supported on JS target") -} - -@(private) -fix_long_path :: proc(path: string) -> string { +rename :: proc(old_path, new_path: string) -> (err: Error) { unimplemented("core:os procedure not supported on JS target") } -link :: proc(old_name, new_name: string) -> (err: Errno) { +ftruncate :: proc(fd: Handle, length: i64) -> (err: Error) { unimplemented("core:os procedure not supported on JS target") } -unlink :: proc(path: string) -> (err: Errno) { +truncate :: proc(path: string, length: i64) -> (err: Error) { unimplemented("core:os procedure not supported on JS target") } - -rename :: proc(old_path, new_path: string) -> (err: Errno) { +remove :: proc(name: string) -> Error { unimplemented("core:os procedure not supported on JS target") } -ftruncate :: proc(fd: Handle, length: i64) -> (err: Errno) { +@(require_results) +pipe :: proc() -> (r, w: Handle, err: Error) { unimplemented("core:os procedure not supported on JS target") } -truncate :: proc(path: string, length: i64) -> (err: Errno) { +@(require_results) +read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Error) { unimplemented("core:os procedure not supported on JS target") } - -remove :: proc(name: string) -> Errno { - unimplemented("core:os procedure not supported on JS target") -} - - -pipe :: proc() -> (r, w: Handle, err: Errno) { - unimplemented("core:os procedure not supported on JS target") -} - -read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) { - unimplemented("core:os procedure not supported on JS target") -} - -Handle :: distinct uintptr File_Time :: distinct u64 -Errno :: distinct int + +_Platform_Error :: enum i32 { + NONE = 0, + FILE_NOT_FOUND = 2, + PATH_NOT_FOUND = 3, + ACCESS_DENIED = 5, + INVALID_HANDLE = 6, + NOT_ENOUGH_MEMORY = 8, + NO_MORE_FILES = 18, + HANDLE_EOF = 38, + NETNAME_DELETED = 64, + FILE_EXISTS = 80, + INVALID_PARAMETER = 87, + BROKEN_PIPE = 109, + BUFFER_OVERFLOW = 111, + INSUFFICIENT_BUFFER = 122, + MOD_NOT_FOUND = 126, + PROC_NOT_FOUND = 127, + DIR_NOT_EMPTY = 145, + ALREADY_EXISTS = 183, + ENVVAR_NOT_FOUND = 203, + MORE_DATA = 234, + OPERATION_ABORTED = 995, + IO_PENDING = 997, + NOT_FOUND = 1168, + PRIVILEGE_NOT_HELD = 1314, + WSAEACCES = 10013, + WSAECONNRESET = 10054, + + // Windows reserves errors >= 1<<29 for application use + FILE_IS_PIPE = 1<<29 + 0, + FILE_IS_NOT_DIR = 1<<29 + 1, + NEGATIVE_OFFSET = 1<<29 + 2, +} INVALID_HANDLE :: ~Handle(0) @@ -189,89 +189,63 @@ O_ASYNC :: 0x02000 O_CLOEXEC :: 0x80000 -ERROR_NONE: Errno : 0 -ERROR_FILE_NOT_FOUND: Errno : 2 -ERROR_PATH_NOT_FOUND: Errno : 3 -ERROR_ACCESS_DENIED: Errno : 5 -ERROR_INVALID_HANDLE: Errno : 6 -ERROR_NOT_ENOUGH_MEMORY: Errno : 8 -ERROR_NO_MORE_FILES: Errno : 18 -ERROR_HANDLE_EOF: Errno : 38 -ERROR_NETNAME_DELETED: Errno : 64 -ERROR_FILE_EXISTS: Errno : 80 -ERROR_INVALID_PARAMETER: Errno : 87 -ERROR_BROKEN_PIPE: Errno : 109 -ERROR_BUFFER_OVERFLOW: Errno : 111 -ERROR_INSUFFICIENT_BUFFER: Errno : 122 -ERROR_MOD_NOT_FOUND: Errno : 126 -ERROR_PROC_NOT_FOUND: Errno : 127 -ERROR_DIR_NOT_EMPTY: Errno : 145 -ERROR_ALREADY_EXISTS: Errno : 183 -ERROR_ENVVAR_NOT_FOUND: Errno : 203 -ERROR_MORE_DATA: Errno : 234 -ERROR_OPERATION_ABORTED: Errno : 995 -ERROR_IO_PENDING: Errno : 997 -ERROR_NOT_FOUND: Errno : 1168 -ERROR_PRIVILEGE_NOT_HELD: Errno : 1314 -WSAEACCES: Errno : 10013 -WSAECONNRESET: Errno : 10054 +ERROR_FILE_NOT_FOUND :: Platform_Error.FILE_NOT_FOUND +ERROR_PATH_NOT_FOUND :: Platform_Error.PATH_NOT_FOUND +ERROR_ACCESS_DENIED :: Platform_Error.ACCESS_DENIED +ERROR_INVALID_HANDLE :: Platform_Error.INVALID_HANDLE +ERROR_NOT_ENOUGH_MEMORY :: Platform_Error.NOT_ENOUGH_MEMORY +ERROR_NO_MORE_FILES :: Platform_Error.NO_MORE_FILES +ERROR_HANDLE_EOF :: Platform_Error.HANDLE_EOF +ERROR_NETNAME_DELETED :: Platform_Error.NETNAME_DELETED +ERROR_FILE_EXISTS :: Platform_Error.FILE_EXISTS +ERROR_INVALID_PARAMETER :: Platform_Error.INVALID_PARAMETER +ERROR_BROKEN_PIPE :: Platform_Error.BROKEN_PIPE +ERROR_BUFFER_OVERFLOW :: Platform_Error.BUFFER_OVERFLOW +ERROR_INSUFFICIENT_BUFFER :: Platform_Error.INSUFFICIENT_BUFFER +ERROR_MOD_NOT_FOUND :: Platform_Error.MOD_NOT_FOUND +ERROR_PROC_NOT_FOUND :: Platform_Error.PROC_NOT_FOUND +ERROR_DIR_NOT_EMPTY :: Platform_Error.DIR_NOT_EMPTY +ERROR_ALREADY_EXISTS :: Platform_Error.ALREADY_EXISTS +ERROR_ENVVAR_NOT_FOUND :: Platform_Error.ENVVAR_NOT_FOUND +ERROR_MORE_DATA :: Platform_Error.MORE_DATA +ERROR_OPERATION_ABORTED :: Platform_Error.OPERATION_ABORTED +ERROR_IO_PENDING :: Platform_Error.IO_PENDING +ERROR_NOT_FOUND :: Platform_Error.NOT_FOUND +ERROR_PRIVILEGE_NOT_HELD :: Platform_Error.PRIVILEGE_NOT_HELD +WSAEACCES :: Platform_Error.WSAEACCES +WSAECONNRESET :: Platform_Error.WSAECONNRESET -// Windows reserves errors >= 1<<29 for application use -ERROR_FILE_IS_PIPE: Errno : 1<<29 + 0 -ERROR_FILE_IS_NOT_DIR: Errno : 1<<29 + 1 -ERROR_NEGATIVE_OFFSET: Errno : 1<<29 + 2 +ERROR_FILE_IS_PIPE :: General_Error.File_Is_Pipe +ERROR_FILE_IS_NOT_DIR :: General_Error.Not_Dir -// "Argv" arguments converted to Odin strings -args := _alloc_command_line_arguments() +args: []string - - - - -last_write_time :: proc(fd: Handle) -> (File_Time, Errno) { +@(require_results) +last_write_time :: proc(fd: Handle) -> (File_Time, Error) { unimplemented("core:os procedure not supported on JS target") } -last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) { +@(require_results) +last_write_time_by_name :: proc(name: string) -> (File_Time, Error) { unimplemented("core:os procedure not supported on JS target") } - -heap_alloc :: proc(size: int, zero_memory := true) -> rawptr { - unimplemented("core:os procedure not supported on JS target") -} -heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr { - unimplemented("core:os procedure not supported on JS target") -} -heap_free :: proc(ptr: rawptr) { - unimplemented("core:os procedure not supported on JS target") -} - +@(require_results) get_page_size :: proc() -> int { unimplemented("core:os procedure not supported on JS target") } -@(private) +@(private, require_results) _processor_core_count :: proc() -> int { - unimplemented("core:os procedure not supported on JS target") + return 1 } exit :: proc "contextless" (code: int) -> ! { - context = runtime.default_context() - unimplemented("core:os procedure not supported on JS target") + unimplemented_contextless("core:os procedure not supported on JS target") } - - +@(require_results) current_thread_id :: proc "contextless" () -> int { - context = runtime.default_context() - unimplemented("core:os procedure not supported on JS target") + return 0 } - - - -_alloc_command_line_arguments :: proc() -> []string { - return nil -} - diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index aabf42574..e023ce7cb 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -3,11 +3,10 @@ package os foreign import dl "system:dl" foreign import libc "system:c" -import "core:runtime" +import "base:runtime" import "core:strings" import "core:c" import "core:strconv" -import "core:intrinsics" // NOTE(flysand): For compatibility we'll make core:os package // depend on the old (scheduled for removal) linux package. @@ -20,148 +19,148 @@ import "core:intrinsics" // all that about compatibility. But we don't want to push experimental changes // and have people's code break while it's still work in progress. import unix "core:sys/unix" +import linux "core:sys/linux" Handle :: distinct i32 Pid :: distinct i32 File_Time :: distinct u64 -Errno :: distinct i32 Socket :: distinct int INVALID_HANDLE :: ~Handle(0) -ERROR_NONE: Errno : 0 -EPERM: Errno : 1 -ENOENT: Errno : 2 -ESRCH: Errno : 3 -EINTR: Errno : 4 -EIO: Errno : 5 -ENXIO: Errno : 6 -EBADF: Errno : 9 -EAGAIN: Errno : 11 -ENOMEM: Errno : 12 -EACCES: Errno : 13 -EFAULT: Errno : 14 -EEXIST: Errno : 17 -ENODEV: Errno : 19 -ENOTDIR: Errno : 20 -EISDIR: Errno : 21 -EINVAL: Errno : 22 -ENFILE: Errno : 23 -EMFILE: Errno : 24 -ETXTBSY: Errno : 26 -EFBIG: Errno : 27 -ENOSPC: Errno : 28 -ESPIPE: Errno : 29 -EROFS: Errno : 30 -EPIPE: Errno : 32 +_Platform_Error :: linux.Errno +EPERM :: Platform_Error.EPERM +ENOENT :: Platform_Error.ENOENT +ESRCH :: Platform_Error.ESRCH +EINTR :: Platform_Error.EINTR +EIO :: Platform_Error.EIO +ENXIO :: Platform_Error.ENXIO +EBADF :: Platform_Error.EBADF +EAGAIN :: Platform_Error.EAGAIN +ENOMEM :: Platform_Error.ENOMEM +EACCES :: Platform_Error.EACCES +EFAULT :: Platform_Error.EFAULT +EEXIST :: Platform_Error.EEXIST +ENODEV :: Platform_Error.ENODEV +ENOTDIR :: Platform_Error.ENOTDIR +EISDIR :: Platform_Error.EISDIR +EINVAL :: Platform_Error.EINVAL +ENFILE :: Platform_Error.ENFILE +EMFILE :: Platform_Error.EMFILE +ETXTBSY :: Platform_Error.ETXTBSY +EFBIG :: Platform_Error.EFBIG +ENOSPC :: Platform_Error.ENOSPC +ESPIPE :: Platform_Error.ESPIPE +EROFS :: Platform_Error.EROFS +EPIPE :: Platform_Error.EPIPE -ERANGE: Errno : 34 /* Result too large */ -EDEADLK: Errno : 35 /* Resource deadlock would occur */ -ENAMETOOLONG: Errno : 36 /* File name too long */ -ENOLCK: Errno : 37 /* No record locks available */ +ERANGE :: Platform_Error.ERANGE /* Result too large */ +EDEADLK :: Platform_Error.EDEADLK /* Resource deadlock would occur */ +ENAMETOOLONG :: Platform_Error.ENAMETOOLONG /* File name too long */ +ENOLCK :: Platform_Error.ENOLCK /* No record locks available */ -ENOSYS: Errno : 38 /* Invalid system call number */ +ENOSYS :: Platform_Error.ENOSYS /* Invalid system call number */ -ENOTEMPTY: Errno : 39 /* Directory not empty */ -ELOOP: Errno : 40 /* Too many symbolic links encountered */ -EWOULDBLOCK: Errno : EAGAIN /* Operation would block */ -ENOMSG: Errno : 42 /* No message of desired type */ -EIDRM: Errno : 43 /* Identifier removed */ -ECHRNG: Errno : 44 /* Channel number out of range */ -EL2NSYNC: Errno : 45 /* Level 2 not synchronized */ -EL3HLT: Errno : 46 /* Level 3 halted */ -EL3RST: Errno : 47 /* Level 3 reset */ -ELNRNG: Errno : 48 /* Link number out of range */ -EUNATCH: Errno : 49 /* Protocol driver not attached */ -ENOCSI: Errno : 50 /* No CSI structure available */ -EL2HLT: Errno : 51 /* Level 2 halted */ -EBADE: Errno : 52 /* Invalid exchange */ -EBADR: Errno : 53 /* Invalid request descriptor */ -EXFULL: Errno : 54 /* Exchange full */ -ENOANO: Errno : 55 /* No anode */ -EBADRQC: Errno : 56 /* Invalid request code */ -EBADSLT: Errno : 57 /* Invalid slot */ -EDEADLOCK: Errno : EDEADLK -EBFONT: Errno : 59 /* Bad font file format */ -ENOSTR: Errno : 60 /* Device not a stream */ -ENODATA: Errno : 61 /* No data available */ -ETIME: Errno : 62 /* Timer expired */ -ENOSR: Errno : 63 /* Out of streams resources */ -ENONET: Errno : 64 /* Machine is not on the network */ -ENOPKG: Errno : 65 /* Package not installed */ -EREMOTE: Errno : 66 /* Object is remote */ -ENOLINK: Errno : 67 /* Link has been severed */ -EADV: Errno : 68 /* Advertise error */ -ESRMNT: Errno : 69 /* Srmount error */ -ECOMM: Errno : 70 /* Communication error on send */ -EPROTO: Errno : 71 /* Protocol error */ -EMULTIHOP: Errno : 72 /* Multihop attempted */ -EDOTDOT: Errno : 73 /* RFS specific error */ -EBADMSG: Errno : 74 /* Not a data message */ -EOVERFLOW: Errno : 75 /* Value too large for defined data type */ -ENOTUNIQ: Errno : 76 /* Name not unique on network */ -EBADFD: Errno : 77 /* File descriptor in bad state */ -EREMCHG: Errno : 78 /* Remote address changed */ -ELIBACC: Errno : 79 /* Can not access a needed shared library */ -ELIBBAD: Errno : 80 /* Accessing a corrupted shared library */ -ELIBSCN: Errno : 81 /* .lib section in a.out corrupted */ -ELIBMAX: Errno : 82 /* Attempting to link in too many shared libraries */ -ELIBEXEC: Errno : 83 /* Cannot exec a shared library directly */ -EILSEQ: Errno : 84 /* Illegal byte sequence */ -ERESTART: Errno : 85 /* Interrupted system call should be restarted */ -ESTRPIPE: Errno : 86 /* Streams pipe error */ -EUSERS: Errno : 87 /* Too many users */ -ENOTSOCK: Errno : 88 /* Socket operation on non-socket */ -EDESTADDRREQ: Errno : 89 /* Destination address required */ -EMSGSIZE: Errno : 90 /* Message too long */ -EPROTOTYPE: Errno : 91 /* Protocol wrong type for socket */ -ENOPROTOOPT: Errno : 92 /* Protocol not available */ -EPROTONOSUPPORT:Errno : 93 /* Protocol not supported */ -ESOCKTNOSUPPORT:Errno : 94 /* Socket type not supported */ -EOPNOTSUPP: Errno : 95 /* Operation not supported on transport endpoint */ -EPFNOSUPPORT: Errno : 96 /* Protocol family not supported */ -EAFNOSUPPORT: Errno : 97 /* Address family not supported by protocol */ -EADDRINUSE: Errno : 98 /* Address already in use */ -EADDRNOTAVAIL: Errno : 99 /* Cannot assign requested address */ -ENETDOWN: Errno : 100 /* Network is down */ -ENETUNREACH: Errno : 101 /* Network is unreachable */ -ENETRESET: Errno : 102 /* Network dropped connection because of reset */ -ECONNABORTED: Errno : 103 /* Software caused connection abort */ -ECONNRESET: Errno : 104 /* Connection reset by peer */ -ENOBUFS: Errno : 105 /* No buffer space available */ -EISCONN: Errno : 106 /* Transport endpoint is already connected */ -ENOTCONN: Errno : 107 /* Transport endpoint is not connected */ -ESHUTDOWN: Errno : 108 /* Cannot send after transport endpoint shutdown */ -ETOOMANYREFS: Errno : 109 /* Too many references: cannot splice */ -ETIMEDOUT: Errno : 110 /* Connection timed out */ -ECONNREFUSED: Errno : 111 /* Connection refused */ -EHOSTDOWN: Errno : 112 /* Host is down */ -EHOSTUNREACH: Errno : 113 /* No route to host */ -EALREADY: Errno : 114 /* Operation already in progress */ -EINPROGRESS: Errno : 115 /* Operation now in progress */ -ESTALE: Errno : 116 /* Stale file handle */ -EUCLEAN: Errno : 117 /* Structure needs cleaning */ -ENOTNAM: Errno : 118 /* Not a XENIX named type file */ -ENAVAIL: Errno : 119 /* No XENIX semaphores available */ -EISNAM: Errno : 120 /* Is a named type file */ -EREMOTEIO: Errno : 121 /* Remote I/O error */ -EDQUOT: Errno : 122 /* Quota exceeded */ +ENOTEMPTY :: Platform_Error.ENOTEMPTY /* Directory not empty */ +ELOOP :: Platform_Error.ELOOP /* Too many symbolic links encountered */ +EWOULDBLOCK :: Platform_Error.EWOULDBLOCK /* Operation would block */ +ENOMSG :: Platform_Error.ENOMSG /* No message of desired type */ +EIDRM :: Platform_Error.EIDRM /* Identifier removed */ +ECHRNG :: Platform_Error.ECHRNG /* Channel number out of range */ +EL2NSYNC :: Platform_Error.EL2NSYNC /* Level 2 not synchronized */ +EL3HLT :: Platform_Error.EL3HLT /* Level 3 halted */ +EL3RST :: Platform_Error.EL3RST /* Level 3 reset */ +ELNRNG :: Platform_Error.ELNRNG /* Link number out of range */ +EUNATCH :: Platform_Error.EUNATCH /* Protocol driver not attached */ +ENOCSI :: Platform_Error.ENOCSI /* No CSI structure available */ +EL2HLT :: Platform_Error.EL2HLT /* Level 2 halted */ +EBADE :: Platform_Error.EBADE /* Invalid exchange */ +EBADR :: Platform_Error.EBADR /* Invalid request descriptor */ +EXFULL :: Platform_Error.EXFULL /* Exchange full */ +ENOANO :: Platform_Error.ENOANO /* No anode */ +EBADRQC :: Platform_Error.EBADRQC /* Invalid request code */ +EBADSLT :: Platform_Error.EBADSLT /* Invalid slot */ +EDEADLOCK :: Platform_Error.EDEADLOCK +EBFONT :: Platform_Error.EBFONT /* Bad font file format */ +ENOSTR :: Platform_Error.ENOSTR /* Device not a stream */ +ENODATA :: Platform_Error.ENODATA /* No data available */ +ETIME :: Platform_Error.ETIME /* Timer expired */ +ENOSR :: Platform_Error.ENOSR /* Out of streams resources */ +ENONET :: Platform_Error.ENONET /* Machine is not on the network */ +ENOPKG :: Platform_Error.ENOPKG /* Package not installed */ +EREMOTE :: Platform_Error.EREMOTE /* Object is remote */ +ENOLINK :: Platform_Error.ENOLINK /* Link has been severed */ +EADV :: Platform_Error.EADV /* Advertise error */ +ESRMNT :: Platform_Error.ESRMNT /* Srmount error */ +ECOMM :: Platform_Error.ECOMM /* Communication error on send */ +EPROTO :: Platform_Error.EPROTO /* Protocol error */ +EMULTIHOP :: Platform_Error.EMULTIHOP /* Multihop attempted */ +EDOTDOT :: Platform_Error.EDOTDOT /* RFS specific error */ +EBADMSG :: Platform_Error.EBADMSG /* Not a data message */ +EOVERFLOW :: Platform_Error.EOVERFLOW /* Value too large for defined data type */ +ENOTUNIQ :: Platform_Error.ENOTUNIQ /* Name not unique on network */ +EBADFD :: Platform_Error.EBADFD /* File descriptor in bad state */ +EREMCHG :: Platform_Error.EREMCHG /* Remote address changed */ +ELIBACC :: Platform_Error.ELIBACC /* Can not access a needed shared library */ +ELIBBAD :: Platform_Error.ELIBBAD /* Accessing a corrupted shared library */ +ELIBSCN :: Platform_Error.ELIBSCN /* .lib section in a.out corrupted */ +ELIBMAX :: Platform_Error.ELIBMAX /* Attempting to link in too many shared libraries */ +ELIBEXEC :: Platform_Error.ELIBEXEC /* Cannot exec a shared library directly */ +EILSEQ :: Platform_Error.EILSEQ /* Illegal byte sequence */ +ERESTART :: Platform_Error.ERESTART /* Interrupted system call should be restarted */ +ESTRPIPE :: Platform_Error.ESTRPIPE /* Streams pipe error */ +EUSERS :: Platform_Error.EUSERS /* Too many users */ +ENOTSOCK :: Platform_Error.ENOTSOCK /* Socket operation on non-socket */ +EDESTADDRREQ :: Platform_Error.EDESTADDRREQ /* Destination address required */ +EMSGSIZE :: Platform_Error.EMSGSIZE /* Message too long */ +EPROTOTYPE :: Platform_Error.EPROTOTYPE /* Protocol wrong type for socket */ +ENOPROTOOPT :: Platform_Error.ENOPROTOOPT /* Protocol not available */ +EPROTONOSUPPOR :: Platform_Error.EPROTONOSUPPORT /* Protocol not supported */ +ESOCKTNOSUPPOR :: Platform_Error.ESOCKTNOSUPPORT /* Socket type not supported */ +EOPNOTSUPP :: Platform_Error.EOPNOTSUPP /* Operation not supported on transport endpoint */ +EPFNOSUPPORT :: Platform_Error.EPFNOSUPPORT /* Protocol family not supported */ +EAFNOSUPPORT :: Platform_Error.EAFNOSUPPORT /* Address family not supported by protocol */ +EADDRINUSE :: Platform_Error.EADDRINUSE /* Address already in use */ +EADDRNOTAVAIL :: Platform_Error.EADDRNOTAVAIL /* Cannot assign requested address */ +ENETDOWN :: Platform_Error.ENETDOWN /* Network is down */ +ENETUNREACH :: Platform_Error.ENETUNREACH /* Network is unreachable */ +ENETRESET :: Platform_Error.ENETRESET /* Network dropped connection because of reset */ +ECONNABORTED :: Platform_Error.ECONNABORTED /* Software caused connection abort */ +ECONNRESET :: Platform_Error.ECONNRESET /* Connection reset by peer */ +ENOBUFS :: Platform_Error.ENOBUFS /* No buffer space available */ +EISCONN :: Platform_Error.EISCONN /* Transport endpoint is already connected */ +ENOTCONN :: Platform_Error.ENOTCONN /* Transport endpoint is not connected */ +ESHUTDOWN :: Platform_Error.ESHUTDOWN /* Cannot send after transport endpoint shutdown */ +ETOOMANYREFS :: Platform_Error.ETOOMANYREFS /* Too many references: cannot splice */ +ETIMEDOUT :: Platform_Error.ETIMEDOUT /* Connection timed out */ +ECONNREFUSED :: Platform_Error.ECONNREFUSED /* Connection refused */ +EHOSTDOWN :: Platform_Error.EHOSTDOWN /* Host is down */ +EHOSTUNREACH :: Platform_Error.EHOSTUNREACH /* No route to host */ +EALREADY :: Platform_Error.EALREADY /* Operation already in progress */ +EINPROGRESS :: Platform_Error.EINPROGRESS /* Operation now in progress */ +ESTALE :: Platform_Error.ESTALE /* Stale file handle */ +EUCLEAN :: Platform_Error.EUCLEAN /* Structure needs cleaning */ +ENOTNAM :: Platform_Error.ENOTNAM /* Not a XENIX named type file */ +ENAVAIL :: Platform_Error.ENAVAIL /* No XENIX semaphores available */ +EISNAM :: Platform_Error.EISNAM /* Is a named type file */ +EREMOTEIO :: Platform_Error.EREMOTEIO /* Remote I/O error */ +EDQUOT :: Platform_Error.EDQUOT /* Quota exceeded */ -ENOMEDIUM: Errno : 123 /* No medium found */ -EMEDIUMTYPE: Errno : 124 /* Wrong medium type */ -ECANCELED: Errno : 125 /* Operation Canceled */ -ENOKEY: Errno : 126 /* Required key not available */ -EKEYEXPIRED: Errno : 127 /* Key has expired */ -EKEYREVOKED: Errno : 128 /* Key has been revoked */ -EKEYREJECTED: Errno : 129 /* Key was rejected by service */ +ENOMEDIUM :: Platform_Error.ENOMEDIUM /* No medium found */ +EMEDIUMTYPE :: Platform_Error.EMEDIUMTYPE /* Wrong medium type */ +ECANCELED :: Platform_Error.ECANCELED /* Operation Canceled */ +ENOKEY :: Platform_Error.ENOKEY /* Required key not available */ +EKEYEXPIRED :: Platform_Error.EKEYEXPIRED /* Key has expired */ +EKEYREVOKED :: Platform_Error.EKEYREVOKED /* Key has been revoked */ +EKEYREJECTED :: Platform_Error.EKEYREJECTED /* Key was rejected by service */ /* for robust mutexes */ -EOWNERDEAD: Errno : 130 /* Owner died */ -ENOTRECOVERABLE: Errno : 131 /* State not recoverable */ +EOWNERDEAD :: Platform_Error.EOWNERDEAD /* Owner died */ +ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE /* State not recoverable */ -ERFKILL: Errno : 132 /* Operation not possible due to RF-kill */ +ERFKILL :: Platform_Error.ERFKILL /* Operation not possible due to RF-kill */ -EHWPOISON: Errno : 133 /* Memory page has hardware error */ +EHWPOISON :: Platform_Error.EHWPOISON /* Memory page has hardware error */ ADDR_NO_RANDOMIZE :: 0x40000 @@ -243,10 +242,13 @@ F_SETFL: int : 4 /* Set file flags */ // NOTE(zangent): These are OS specific! // Do not mix these up! -RTLD_LAZY :: 0x001 -RTLD_NOW :: 0x002 -RTLD_BINDING_MASK :: 0x3 -RTLD_GLOBAL :: 0x100 +RTLD_LAZY :: 0x0001 +RTLD_NOW :: 0x0002 +RTLD_BINDING_MASK :: 0x0003 +RTLD_GLOBAL :: 0x0100 +RTLD_NOLOAD :: 0x0004 +RTLD_DEEPBIND :: 0x0008 +RTLD_NODELETE :: 0x1000 socklen_t :: c.int @@ -263,26 +265,48 @@ Unix_File_Time :: struct { nanoseconds: i64, } -OS_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 +when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { + OS_Stat :: struct { + device_id: u64, // ID of device containing file + serial: u64, // File serial number + mode: u32, // Mode of the file + nlink: u32, // Number of hard links + uid: u32, // User ID of the file's owner + gid: u32, // Group ID of the file's group + rdev: u64, // Device ID, if device + _: u64, // Padding + size: i64, // Size of the file, in bytes + block_size: i32, // Optimal blocksize for I/O + _: i32, // Padding + 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 + 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, + _reserved: [2]i32, + } + #assert(size_of(OS_Stat) == 128) +} else { + OS_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 + _: 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 + + _reserved: [3]i64, + } } // NOTE(laleksic, 2021-01-21): Comment and rename these to match OS_Stat above @@ -374,9 +398,9 @@ SIOCGIFFLAG :: enum c.int { PORTSEL = 13, /* Can set media type. */ AUTOMEDIA = 14, /* Auto media select active. */ DYNAMIC = 15, /* Dialup device with changing addresses. */ - LOWER_UP = 16, - DORMANT = 17, - ECHO = 18, + LOWER_UP = 16, + DORMANT = 17, + ECHO = 18, } SIOCGIFFLAGS :: bit_set[SIOCGIFFLAG; c.int] @@ -426,13 +450,13 @@ S_ISGID :: 0o2000 // Set group id on execution S_ISVTX :: 0o1000 // Directory restrcted delete -S_ISLNK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK } -S_ISREG :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG } -S_ISDIR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR } -S_ISCHR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR } -S_ISBLK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK } -S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO } -S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK } +@(require_results) S_ISLNK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK } +@(require_results) S_ISREG :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG } +@(require_results) S_ISDIR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR } +@(require_results) S_ISCHR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR } +@(require_results) S_ISBLK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK } +@(require_results) S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO } +@(require_results) S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK } F_OK :: 0 // Test for file existance X_OK :: 1 // Test for execute permission @@ -466,11 +490,11 @@ foreign libc { @(link_name="free") _unix_free :: proc(ptr: rawptr) --- @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- - @(link_name="execvp") _unix_execvp :: proc(path: cstring, argv: [^]cstring) -> int --- + @(link_name="execvp") _unix_execvp :: proc(path: cstring, argv: [^]cstring) -> c.int --- @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- @(link_name="putenv") _unix_putenv :: proc(cstring) -> c.int --- @(link_name="setenv") _unix_setenv :: proc(key: cstring, value: cstring, overwrite: c.int) -> c.int --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- } @@ -484,41 +508,55 @@ foreign dl { @(link_name="freeifaddrs") _freeifaddrs :: proc(ifa: ^ifaddrs) --- } +@(require_results) is_path_separator :: proc(r: rune) -> bool { return r == '/' } // determine errno from syscall return value -@private -_get_errno :: proc(res: int) -> Errno { +@(private, require_results) +_get_errno :: proc(res: int) -> Error { if res < 0 && res > -4096 { - return Errno(-res) + return Platform_Error(-res) } - return 0 + return nil } // get errno from libc -get_last_error :: proc "contextless" () -> int { - return int(__errno_location()^) +@(require_results, no_instrumentation) +get_last_error :: proc "contextless" () -> Error { + err := Platform_Error(__errno_location()^) + #partial switch err { + case .NONE: + return nil + case .EPERM: + return .Permission_Denied + case .EEXIST: + return .Exist + case .ENOENT: + return .Not_Exist + } + return err } -personality :: proc(persona: u64) -> (Errno) { +personality :: proc(persona: u64) -> Error { res := unix.sys_personality(persona) if res == -1 { return _get_errno(res) } - return ERROR_NONE + return nil } -fork :: proc() -> (Pid, Errno) { +@(require_results) +fork :: proc() -> (Pid, Error) { pid := unix.sys_fork() if pid == -1 { return -1, _get_errno(pid) } - return Pid(pid), ERROR_NONE + return Pid(pid), nil } -execvp :: proc(path: string, args: []string) -> Errno { +execvp :: proc(path: string, args: []string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -529,154 +567,193 @@ execvp :: proc(path: string, args: []string) -> Errno { } _unix_execvp(path_cstr, raw_data(args_cstrs)) - return Errno(get_last_error()) + return get_last_error() } -open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0o000) -> (Handle, Errno) { +@(require_results) +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0o000) -> (Handle, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) handle := unix.sys_open(cstr, flags, uint(mode)) if handle < 0 { return INVALID_HANDLE, _get_errno(handle) } - return Handle(handle), ERROR_NONE + return Handle(handle), nil } -close :: proc(fd: Handle) -> Errno { +close :: proc(fd: Handle) -> Error { return _get_errno(unix.sys_close(int(fd))) } -read :: proc(fd: Handle, data: []byte) -> (int, Errno) { +flush :: proc(fd: Handle) -> Error { + return _get_errno(unix.sys_fsync(int(fd))) +} + +// If you read or write more than `SSIZE_MAX` bytes, result is implementation defined (probably an error). +// `SSIZE_MAX` is also implementation defined but usually the max of a `ssize_t` which is `max(int)` in Odin. +// In practice a read/write call would probably never read/write these big buffers all at once, +// which is why the number of bytes is returned and why there are procs that will call this in a +// loop for you. +// We set a max of 1GB to keep alignment and to be safe. +@(private) +MAX_RW :: 1 << 30 + +read :: proc(fd: Handle, data: []byte) -> (int, Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } - bytes_read := unix.sys_read(int(fd), raw_data(data), len(data)) + to_read := min(uint(len(data)), MAX_RW) + + bytes_read := unix.sys_read(int(fd), raw_data(data), to_read) if bytes_read < 0 { return -1, _get_errno(bytes_read) } - return bytes_read, ERROR_NONE + return bytes_read, nil } -write :: proc(fd: Handle, data: []byte) -> (int, Errno) { +write :: proc(fd: Handle, data: []byte) -> (int, Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } - bytes_written := unix.sys_write(int(fd), raw_data(data), len(data)) + to_write := min(uint(len(data)), MAX_RW) + + bytes_written := unix.sys_write(int(fd), raw_data(data), to_write) if bytes_written < 0 { return -1, _get_errno(bytes_written) } - return bytes_written, ERROR_NONE + return bytes_written, nil } -read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { + +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } - bytes_read := unix.sys_pread(int(fd), raw_data(data), len(data), offset) + to_read := min(uint(len(data)), MAX_RW) + + bytes_read := unix.sys_pread(int(fd), raw_data(data), to_read, offset) if bytes_read < 0 { return -1, _get_errno(bytes_read) } - return bytes_read, ERROR_NONE + return bytes_read, nil } -write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } - bytes_written := unix.sys_pwrite(int(fd), raw_data(data), uint(len(data)), offset) + to_write := min(uint(len(data)), MAX_RW) + + bytes_written := unix.sys_pwrite(int(fd), raw_data(data), to_write, offset) if bytes_written < 0 { return -1, _get_errno(bytes_written) } - return bytes_written, ERROR_NONE + return bytes_written, nil } -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } res := unix.sys_lseek(int(fd), offset, whence) if res < 0 { - return -1, _get_errno(int(res)) + errno := _get_errno(int(res)) + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + } + return 0, errno } - return i64(res), ERROR_NONE + return i64(res), nil } -file_size :: proc(fd: Handle) -> (i64, Errno) { +@(require_results) +file_size :: proc(fd: Handle) -> (i64, Error) { // deliberately uninitialized; the syscall fills this buffer for us s: OS_Stat = --- result := unix.sys_fstat(int(fd), rawptr(&s)) if result < 0 { return 0, _get_errno(result) } - return max(s.size, 0), ERROR_NONE + return max(s.size, 0), nil } -rename :: proc(old_path, new_path: string) -> Errno { +rename :: proc(old_path, new_path: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator) new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator) return _get_errno(unix.sys_rename(old_path_cstr, new_path_cstr)) } -remove :: proc(path: string) -> Errno { +remove :: proc(path: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) return _get_errno(unix.sys_unlink(path_cstr)) } -make_directory :: proc(path: string, mode: u32 = 0o775) -> Errno { +make_directory :: proc(path: string, mode: u32 = 0o775) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) return _get_errno(unix.sys_mkdir(path_cstr, uint(mode))) } -remove_directory :: proc(path: string) -> Errno { +remove_directory :: proc(path: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) return _get_errno(unix.sys_rmdir(path_cstr)) } +@(require_results) is_file_handle :: proc(fd: Handle) -> bool { s, err := _fstat(fd) - if err != ERROR_NONE { + if err != nil { return false } return S_ISREG(s.mode) } +@(require_results) is_file_path :: proc(path: string, follow_links: bool = true) -> bool { s: OS_Stat - err: Errno + err: Error if follow_links { s, err = _stat(path) } else { s, err = _lstat(path) } - if err != ERROR_NONE { + if err != nil { return false } return S_ISREG(s.mode) } +@(require_results) is_dir_handle :: proc(fd: Handle) -> bool { s, err := _fstat(fd) - if err != ERROR_NONE { + if err != nil { return false } return S_ISDIR(s.mode) } +@(require_results) is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { s: OS_Stat - err: Errno + err: Error if follow_links { s, err = _stat(path) } else { s, err = _lstat(path) } - if err != ERROR_NONE { + if err != nil { return false } return S_ISDIR(s.mode) @@ -685,6 +762,7 @@ is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { is_file :: proc {is_file_path, is_file_handle} is_dir :: proc {is_dir_path, is_dir_handle} +@(require_results) exists :: proc(path: string) -> bool { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cpath := strings.clone_to_cstring(path, context.temp_allocator) @@ -702,26 +780,22 @@ stderr: Handle = 2 last_write_time :: proc(fd: Handle) -> File_Time {} last_write_time_by_name :: proc(name: string) -> File_Time {} */ -last_write_time :: proc(fd: Handle) -> (File_Time, Errno) { - s, err := _fstat(fd) - if err != ERROR_NONE { - return 0, err - } +@(require_results) +last_write_time :: proc(fd: Handle) -> (time: File_Time, err: Error) { + s := _fstat(fd) or_return modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), ERROR_NONE + return File_Time(modified), nil } -last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) { - s, err := _stat(name) - if err != ERROR_NONE { - return 0, err - } +@(require_results) +last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) { + s := _stat(name) or_return modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), ERROR_NONE + return File_Time(modified), nil } -@private -_stat :: proc(path: string) -> (OS_Stat, Errno) { +@(private, require_results) +_stat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -731,11 +805,11 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) { if result < 0 { return s, _get_errno(result) } - return s, ERROR_NONE + return s, nil } -@private -_lstat :: proc(path: string) -> (OS_Stat, Errno) { +@(private, require_results) +_lstat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -745,53 +819,53 @@ _lstat :: proc(path: string) -> (OS_Stat, Errno) { if result < 0 { return s, _get_errno(result) } - return s, ERROR_NONE + return s, nil } -@private -_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) { +@(private, require_results) +_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { // deliberately uninitialized; the syscall fills this buffer for us s: OS_Stat = --- result := unix.sys_fstat(int(fd), rawptr(&s)) if result < 0 { return s, _get_errno(result) } - return s, ERROR_NONE + return s, nil } -@private -_fdopendir :: proc(fd: Handle) -> (Dir, Errno) { +@(private, require_results) +_fdopendir :: proc(fd: Handle) -> (Dir, Error) { dirp := _unix_fdopendir(fd) if dirp == cast(Dir)nil { - return nil, Errno(get_last_error()) + return nil, get_last_error() } - return dirp, ERROR_NONE + return dirp, nil } -@private -_closedir :: proc(dirp: Dir) -> Errno { +@(private) +_closedir :: proc(dirp: Dir) -> Error { rc := _unix_closedir(dirp) if rc != 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -@private +@(private) _rewinddir :: proc(dirp: Dir) { _unix_rewinddir(dirp) } -@private -_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) { +@(private, require_results) +_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { result: ^Dirent rc := _unix_readdir_r(dirp, &entry, &result) if rc != 0 { - err = Errno(get_last_error()) + err = get_last_error() return } - err = ERROR_NONE + err = nil if result == nil { end_of_stream = true @@ -802,8 +876,8 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) return } -@private -_readlink :: proc(path: string) -> (string, Errno) { +@(private, require_results) +_readlink :: proc(path: string) -> (string, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) path_cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -820,12 +894,19 @@ _readlink :: proc(path: string) -> (string, Errno) { delete(buf) buf = make([]byte, bufsz) } else { - return strings.string_from_ptr(&buf[0], rc), ERROR_NONE + return strings.string_from_ptr(&buf[0], rc), nil } } } -absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) { +@(private, require_results) +_dup :: proc(fd: Handle) -> (Handle, Error) { + dup, err := linux.dup(linux.Fd(fd)) + return Handle(dup), err +} + +@(require_results) +absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { buf : [256]byte fd_str := strconv.itoa( buf[:], cast(int)fd ) @@ -835,7 +916,8 @@ absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) { return _readlink(procfs_path) } -absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { +@(require_results) +absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { rel := rel if rel == "" { rel = "." @@ -846,47 +928,24 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { path_ptr := _unix_realpath(rel_cstr, nil) if path_ptr == nil { - return "", Errno(get_last_error()) + return "", get_last_error() } - defer _unix_free(path_ptr) + defer _unix_free(rawptr(path_ptr)) - path_cstr := transmute(cstring)path_ptr - path = strings.clone( string(path_cstr) ) - - return path, ERROR_NONE + return strings.clone(string(path_ptr), allocator) } -access :: proc(path: string, mask: int) -> (bool, Errno) { +access :: proc(path: string, mask: int) -> (bool, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) result := unix.sys_access(cstr, mask) if result < 0 { return false, _get_errno(result) } - return true, ERROR_NONE -} - -heap_alloc :: proc(size: int, zero_memory := true) -> rawptr { - if size <= 0 { - return nil - } - if zero_memory { - return _unix_calloc(1, c.size_t(size)) - } else { - return _unix_malloc(c.size_t(size)) - } -} - -heap_resize :: proc(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, c.size_t(new_size)) -} - -heap_free :: proc(ptr: rawptr) { - _unix_free(ptr) + return true, nil } +@(require_results) lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator) @@ -898,34 +957,37 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin return strings.clone(string(cstr), allocator), true } +@(require_results) get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } -set_env :: proc(key, value: string) -> Errno { +set_env :: proc(key, value: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() key_cstring := strings.clone_to_cstring(key, context.temp_allocator) value_cstring := strings.clone_to_cstring(value, context.temp_allocator) // NOTE(GoNZooo): `setenv` instead of `putenv` because it copies both key and value more commonly res := _unix_setenv(key_cstring, value_cstring, 1) if res < 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -unset_env :: proc(key: string) -> Errno { +unset_env :: proc(key: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() s := strings.clone_to_cstring(key, context.temp_allocator) res := _unix_putenv(s) if res < 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -get_current_directory :: proc() -> string { +@(require_results) +get_current_directory :: proc(allocator := context.allocator) -> string { + context.allocator = allocator // 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. @@ -946,14 +1008,14 @@ get_current_directory :: proc() -> string { unreachable() } -set_current_directory :: proc(path: string) -> (err: Errno) { +set_current_directory :: proc(path: string) -> (err: Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) res := unix.sys_chdir(cstr) if res < 0 { return _get_errno(res) } - return ERROR_NONE + return nil } exit :: proc "contextless" (code: int) -> ! { @@ -961,16 +1023,19 @@ exit :: proc "contextless" (code: int) -> ! { _unix_exit(c.int(code)) } +@(require_results) current_thread_id :: proc "contextless" () -> int { return unix.sys_gettid() } +@(require_results) dlopen :: proc(filename: string, flags: int) -> rawptr { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(filename, context.temp_allocator) handle := _unix_dlopen(cstr, c.int(flags)) return handle } +@(require_results) dlsym :: proc(handle: rawptr, symbol: string) -> rawptr { assert(handle != nil) runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() @@ -986,6 +1051,7 @@ dlerror :: proc() -> string { return string(_unix_dlerror()) } +@(require_results) get_page_size :: proc() -> int { // NOTE(tetra): The page size never changes, so why do anything complicated // if we don't have to. @@ -998,11 +1064,12 @@ get_page_size :: proc() -> int { return page_size } -@(private) +@(private, require_results) _processor_core_count :: proc() -> int { return int(_unix_get_nprocs()) } +@(require_results) _alloc_command_line_arguments :: proc() -> []string { res := make([]string, len(runtime.args__)) for arg, i in runtime.args__ { @@ -1011,117 +1078,120 @@ _alloc_command_line_arguments :: proc() -> []string { return res } -socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Errno) { +@(require_results) +socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) { result := unix.sys_socket(domain, type, protocol) if result < 0 { return 0, _get_errno(result) } - return Socket(result), ERROR_NONE + return Socket(result), nil } -bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) { +bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> Error { result := unix.sys_bind(int(sd), addr, len) if result < 0 { return _get_errno(result) } - return ERROR_NONE + return nil } -connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) { +connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> Error { result := unix.sys_connect(int(sd), addr, len) if result < 0 { return _get_errno(result) } - return ERROR_NONE + return nil } -accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Errno) { +accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Error) { result := unix.sys_accept(int(sd), rawptr(addr), len) if result < 0 { return 0, _get_errno(result) } - return Socket(result), ERROR_NONE + return Socket(result), nil } -listen :: proc(sd: Socket, backlog: int) -> (Errno) { +listen :: proc(sd: Socket, backlog: int) -> Error { result := unix.sys_listen(int(sd), backlog) if result < 0 { return _get_errno(result) } - return ERROR_NONE + return nil } -setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> (Errno) { +setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> Error { result := unix.sys_setsockopt(int(sd), level, optname, optval, optlen) if result < 0 { return _get_errno(result) } - return ERROR_NONE + return nil } -recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Errno) { +recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Error) { result := unix.sys_recvfrom(int(sd), raw_data(data), len(data), flags, addr, uintptr(addr_size)) if result < 0 { return 0, _get_errno(int(result)) } - return u32(result), ERROR_NONE + return u32(result), nil } -recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) { +recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { result := unix.sys_recvfrom(int(sd), raw_data(data), len(data), flags, nil, 0) if result < 0 { return 0, _get_errno(int(result)) } - return u32(result), ERROR_NONE + return u32(result), nil } -sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Errno) { +sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Error) { result := unix.sys_sendto(int(sd), raw_data(data), len(data), flags, addr, addrlen) if result < 0 { return 0, _get_errno(int(result)) } - return u32(result), ERROR_NONE + return u32(result), nil } -send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) { +send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { result := unix.sys_sendto(int(sd), raw_data(data), len(data), 0, nil, 0) if result < 0 { return 0, _get_errno(int(result)) } - return u32(result), ERROR_NONE + return u32(result), nil } -shutdown :: proc(sd: Socket, how: int) -> (Errno) { +shutdown :: proc(sd: Socket, how: int) -> Error { result := unix.sys_shutdown(int(sd), how) if result < 0 { return _get_errno(result) } - return ERROR_NONE + return nil } -fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Errno) { +fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Error) { result := unix.sys_fcntl(fd, cmd, arg) if result < 0 { return 0, _get_errno(result) } - return result, ERROR_NONE + return result, nil } -poll :: proc(fds: []pollfd, timeout: int) -> (int, Errno) { +@(require_results) +poll :: proc(fds: []pollfd, timeout: int) -> (int, Error) { result := unix.sys_poll(raw_data(fds), uint(len(fds)), timeout) if result < 0 { return 0, _get_errno(result) } - return result, ERROR_NONE + return result, nil } -ppoll :: proc(fds: []pollfd, timeout: ^unix.timespec, sigmask: ^sigset_t) -> (int, Errno) { +@(require_results) +ppoll :: proc(fds: []pollfd, timeout: ^unix.timespec, sigmask: ^sigset_t) -> (int, Error) { result := unix.sys_ppoll(raw_data(fds), uint(len(fds)), timeout, sigmask, size_of(sigset_t)) if result < 0 { return 0, _get_errno(result) } - return result, ERROR_NONE + return result, nil } diff --git a/core/os/os_netbsd.odin b/core/os/os_netbsd.odin new file mode 100644 index 000000000..e3ba760a4 --- /dev/null +++ b/core/os/os_netbsd.odin @@ -0,0 +1,996 @@ +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 + +INVALID_HANDLE :: ~Handle(0) + +_Platform_Error :: enum i32 { + NONE = 0, + EPERM = 1, /* Operation not permitted */ + ENOENT = 2, /* No such file or directory */ + EINTR = 4, /* Interrupted system call */ + ESRCH = 3, /* No such process */ + EIO = 5, /* Input/output error */ + ENXIO = 6, /* Device not configured */ + E2BIG = 7, /* Argument list too long */ + ENOEXEC = 8, /* Exec format error */ + EBADF = 9, /* Bad file descriptor */ + ECHILD = 10, /* No child processes */ + EDEADLK = 11, /* Resource deadlock avoided. 11 was EAGAIN */ + ENOMEM = 12, /* Cannot allocate memory */ + EACCES = 13, /* Permission denied */ + EFAULT = 14, /* Bad address */ + ENOTBLK = 15, /* Block device required */ + EBUSY = 16, /* Device busy */ + EEXIST = 17, /* File exists */ + EXDEV = 18, /* Cross-device link */ + ENODEV = 19, /* Operation not supported by device */ + ENOTDIR = 20, /* Not a directory */ + EISDIR = 21, /* Is a directory */ + EINVAL = 22, /* Invalid argument */ + ENFILE = 23, /* Too many open files in system */ + EMFILE = 24, /* Too many open files */ + ENOTTY = 25, /* Inappropriate ioctl for device */ + ETXTBSY = 26, /* Text file busy */ + EFBIG = 27, /* File too large */ + ENOSPC = 28, /* No space left on device */ + ESPIPE = 29, /* Illegal seek */ + EROFS = 30, /* Read-only file system */ + EMLINK = 31, /* Too many links */ + EPIPE = 32, /* Broken pipe */ + + /* math software */ + EDOM = 33, /* Numerical argument out of domain */ + ERANGE = 34, /* Result too large or too small */ + + /* non-blocking and interrupt i/o */ + EAGAIN = 35, /* Resource temporarily unavailable */ + EWOULDBLOCK = EAGAIN, /* Operation would block */ + EINPROGRESS = 36, /* Operation now in progress */ + EALREADY = 37, /* Operation already in progress */ + + /* ipc/network software -- argument errors */ + ENOTSOCK = 38, /* Socket operation on non-socket */ + EDESTADDRREQ = 39, /* Destination address required */ + EMSGSIZE = 40, /* Message too long */ + EPROTOTYPE = 41, /* Protocol wrong type for socket */ + ENOPROTOOPT = 42, /* Protocol option not available */ + EPROTONOSUPPORT = 43, /* Protocol not supported */ + ESOCKTNOSUPPORT = 44, /* Socket type not supported */ + EOPNOTSUPP = 45, /* Operation not supported */ + EPFNOSUPPORT = 46, /* Protocol family not supported */ + EAFNOSUPPORT = 47, /* Address family not supported by protocol family */ + EADDRINUSE = 48, /* Address already in use */ + EADDRNOTAVAIL = 49, /* Can't assign requested address */ + + /* ipc/network software -- operational errors */ + ENETDOWN = 50, /* Network is down */ + ENETUNREACH = 51, /* Network is unreachable */ + ENETRESET = 52, /* Network dropped connection on reset */ + ECONNABORTED = 53, /* Software caused connection abort */ + ECONNRESET = 54, /* Connection reset by peer */ + ENOBUFS = 55, /* No buffer space available */ + EISCONN = 56, /* Socket is already connected */ + ENOTCONN = 57, /* Socket is not connected */ + ESHUTDOWN = 58, /* Can't send after socket shutdown */ + ETOOMANYREFS = 59, /* Too many references: can't splice */ + ETIMEDOUT = 60, /* Operation timed out */ + ECONNREFUSED = 61, /* Connection refused */ + + ELOOP = 62, /* Too many levels of symbolic links */ + ENAMETOOLONG = 63, /* File name too long */ + + /* should be rearranged */ + EHOSTDOWN = 64, /* Host is down */ + EHOSTUNREACH = 65, /* No route to host */ + ENOTEMPTY = 66, /* Directory not empty */ + + /* quotas & mush */ + EPROCLIM = 67, /* Too many processes */ + EUSERS = 68, /* Too many users */ + EDQUOT = 69, /* Disc quota exceeded */ + + /* Network File System */ + ESTALE = 70, /* Stale NFS file handle */ + EREMOTE = 71, /* Too many levels of remote in path */ + EBADRPC = 72, /* RPC struct is bad */ + ERPCMISMATCH = 73, /* RPC version wrong */ + EPROGUNAVAIL = 74, /* RPC prog. not avail */ + EPROGMISMATCH = 75, /* Program version wrong */ + EPROCUNAVAIL = 76, /* Bad procedure for program */ + + ENOLCK = 77, /* No locks available */ + ENOSYS = 78, /* Function not implemented */ + + EFTYPE = 79, /* Inappropriate file type or format */ + EAUTH = 80, /* Authentication error */ + ENEEDAUTH = 81, /* Need authenticator */ + + /* SystemV IPC */ + EIDRM = 82, /* Identifier removed */ + ENOMSG = 83, /* No message of desired type */ + EOVERFLOW = 84, /* Value too large to be stored in data type */ + + /* Wide/multibyte-character handling, ISO/IEC 9899/AMD1:1995 */ + EILSEQ = 85, /* Illegal byte sequence */ + + /* From IEEE Std 1003.1-2001 */ + /* Base, Realtime, Threads or Thread Priority Scheduling option errors */ + ENOTSUP = 86, /* Not supported */ + + /* Realtime option errors */ + ECANCELED = 87, /* Operation canceled */ + + /* Realtime, XSI STREAMS option errors */ + EBADMSG = 88, /* Bad or Corrupt message */ + + /* XSI STREAMS option errors */ + ENODATA = 89, /* No message available */ + ENOSR = 90, /* No STREAM resources */ + ENOSTR = 91, /* Not a STREAM */ + ETIME = 92, /* STREAM ioctl timeout */ + + /* File system extended attribute errors */ + ENOATTR = 93, /* Attribute not found */ + + /* Realtime, XSI STREAMS option errors */ + EMULTIHOP = 94, /* Multihop attempted */ + ENOLINK = 95, /* Link has been severed */ + EPROTO = 96, /* Protocol error */ + + /* Robust mutexes */ + EOWNERDEAD = 97, /* Previous owner died */ + ENOTRECOVERABLE = 98, /* State not recoverable */ + + ELAST = 98, /* Must equal largest Error */ +} + +EPERM :: Platform_Error.EPERM /* Operation not permitted */ +ENOENT :: Platform_Error.ENOENT /* No such file or directory */ +EINTR :: Platform_Error.EINTR /* Interrupted system call */ +ESRCH :: Platform_Error.ESRCH /* No such process */ +EIO :: Platform_Error.EIO /* Input/output error */ +ENXIO :: Platform_Error.ENXIO /* Device not configured */ +E2BIG :: Platform_Error.E2BIG /* Argument list too long */ +ENOEXEC :: Platform_Error.ENOEXEC /* Exec format error */ +EBADF :: Platform_Error.EBADF /* Bad file descriptor */ +ECHILD :: Platform_Error.ECHILD /* No child processes */ +EDEADLK :: Platform_Error.EDEADLK /* Resource deadlock avoided. 11 was EAGAIN */ +ENOMEM :: Platform_Error.ENOMEM /* Cannot allocate memory */ +EACCES :: Platform_Error.EACCES /* Permission denied */ +EFAULT :: Platform_Error.EFAULT /* Bad address */ +ENOTBLK :: Platform_Error.ENOTBLK /* Block device required */ +EBUSY :: Platform_Error.EBUSY /* Device busy */ +EEXIST :: Platform_Error.EEXIST /* File exists */ +EXDEV :: Platform_Error.EXDEV /* Cross-device link */ +ENODEV :: Platform_Error.ENODEV /* Operation not supported by device */ +ENOTDIR :: Platform_Error.ENOTDIR /* Not a directory */ +EISDIR :: Platform_Error.EISDIR /* Is a directory */ +EINVAL :: Platform_Error.EINVAL /* Invalid argument */ +ENFILE :: Platform_Error.ENFILE /* Too many open files in system */ +EMFILE :: Platform_Error.EMFILE /* Too many open files */ +ENOTTY :: Platform_Error.ENOTTY /* Inappropriate ioctl for device */ +ETXTBSY :: Platform_Error.ETXTBSY /* Text file busy */ +EFBIG :: Platform_Error.EFBIG /* File too large */ +ENOSPC :: Platform_Error.ENOSPC /* No space left on device */ +ESPIPE :: Platform_Error.ESPIPE /* Illegal seek */ +EROFS :: Platform_Error.EROFS /* Read-only file system */ +EMLINK :: Platform_Error.EMLINK /* Too many links */ +EPIPE :: Platform_Error.EPIPE /* Broken pipe */ + +/* math software */ +EDOM :: Platform_Error.EDOM /* Numerical argument out of domain */ +ERANGE :: Platform_Error.ERANGE /* Result too large or too small */ + +/* non-blocking and interrupt i/o */ +EAGAIN :: Platform_Error.EAGAIN /* Resource temporarily unavailable */ +EWOULDBLOCK :: EAGAIN /* Operation would block */ +EINPROGRESS :: Platform_Error.EINPROGRESS /* Operation now in progress */ +EALREADY :: Platform_Error.EALREADY /* Operation already in progress */ + +/* ipc/network software -- argument errors */ +ENOTSOCK :: Platform_Error.ENOTSOCK /* Socket operation on non-socket */ +EDESTADDRREQ :: Platform_Error.EDESTADDRREQ /* Destination address required */ +EMSGSIZE :: Platform_Error.EMSGSIZE /* Message too long */ +EPROTOTYPE :: Platform_Error.EPROTOTYPE /* Protocol wrong type for socket */ +ENOPROTOOPT :: Platform_Error.ENOPROTOOPT /* Protocol option not available */ +EPROTONOSUPPORT :: Platform_Error.EPROTONOSUPPORT /* Protocol not supported */ +ESOCKTNOSUPPORT :: Platform_Error.ESOCKTNOSUPPORT /* Socket type not supported */ +EOPNOTSUPP :: Platform_Error.EOPNOTSUPP /* Operation not supported */ +EPFNOSUPPORT :: Platform_Error.EPFNOSUPPORT /* Protocol family not supported */ +EAFNOSUPPORT :: Platform_Error.EAFNOSUPPORT /* Address family not supported by protocol family */ +EADDRINUSE :: Platform_Error.EADDRINUSE /* Address already in use */ +EADDRNOTAVAIL :: Platform_Error.EADDRNOTAVAIL /* Can't assign requested address */ + +/* ipc/network software -- operational errors */ +ENETDOWN :: Platform_Error.ENETDOWN /* Network is down */ +ENETUNREACH :: Platform_Error.ENETUNREACH /* Network is unreachable */ +ENETRESET :: Platform_Error.ENETRESET /* Network dropped connection on reset */ +ECONNABORTED :: Platform_Error.ECONNABORTED /* Software caused connection abort */ +ECONNRESET :: Platform_Error.ECONNRESET /* Connection reset by peer */ +ENOBUFS :: Platform_Error.ENOBUFS /* No buffer space available */ +EISCONN :: Platform_Error.EISCONN /* Socket is already connected */ +ENOTCONN :: Platform_Error.ENOTCONN /* Socket is not connected */ +ESHUTDOWN :: Platform_Error.ESHUTDOWN /* Can't send after socket shutdown */ +ETOOMANYREFS :: Platform_Error.ETOOMANYREFS /* Too many references: can't splice */ +ETIMEDOUT :: Platform_Error.ETIMEDOUT /* Operation timed out */ +ECONNREFUSED :: Platform_Error.ECONNREFUSED /* Connection refused */ + +ELOOP :: Platform_Error.ELOOP /* Too many levels of symbolic links */ +ENAMETOOLONG :: Platform_Error.ENAMETOOLONG /* File name too long */ + +/* should be rearranged */ +EHOSTDOWN :: Platform_Error.EHOSTDOWN /* Host is down */ +EHOSTUNREACH :: Platform_Error.EHOSTUNREACH /* No route to host */ +ENOTEMPTY :: Platform_Error.ENOTEMPTY /* Directory not empty */ + +/* quotas & mush */ +EPROCLIM :: Platform_Error.EPROCLIM /* Too many processes */ +EUSERS :: Platform_Error.EUSERS /* Too many users */ +EDQUOT :: Platform_Error.EDQUOT /* Disc quota exceeded */ + +/* Network File System */ +ESTALE :: Platform_Error.ESTALE /* Stale NFS file handle */ +EREMOTE :: Platform_Error.EREMOTE /* Too many levels of remote in path */ +EBADRPC :: Platform_Error.EBADRPC /* RPC struct is bad */ +ERPCMISMATCH :: Platform_Error.ERPCMISMATCH /* RPC version wrong */ +EPROGUNAVAIL :: Platform_Error.EPROGUNAVAIL /* RPC prog. not avail */ +EPROGMISMATCH :: Platform_Error.EPROGMISMATCH /* Program version wrong */ +EPROCUNAVAIL :: Platform_Error.EPROCUNAVAIL /* Bad procedure for program */ + +ENOLCK :: Platform_Error.ENOLCK /* No locks available */ +ENOSYS :: Platform_Error.ENOSYS /* Function not implemented */ + +EFTYPE :: Platform_Error.EFTYPE /* Inappropriate file type or format */ +EAUTH :: Platform_Error.EAUTH /* Authentication error */ +ENEEDAUTH :: Platform_Error.ENEEDAUTH /* Need authenticator */ + +/* SystemV IPC */ +EIDRM :: Platform_Error.EIDRM /* Identifier removed */ +ENOMSG :: Platform_Error.ENOMSG /* No message of desired type */ +EOVERFLOW :: Platform_Error.EOVERFLOW /* Value too large to be stored in data type */ + +/* Wide/multibyte-character handling, ISO/IEC 9899/AMD1:1995 */ +EILSEQ :: Platform_Error.EILSEQ /* Illegal byte sequence */ + +/* From IEEE Std 1003.1-2001 */ +/* Base, Realtime, Threads or Thread Priority Scheduling option errors */ +ENOTSUP :: Platform_Error.ENOTSUP /* Not supported */ + +/* Realtime option errors */ +ECANCELED :: Platform_Error.ECANCELED /* Operation canceled */ + +/* Realtime, XSI STREAMS option errors */ +EBADMSG :: Platform_Error.EBADMSG /* Bad or Corrupt message */ + +/* XSI STREAMS option errors */ +ENODATA :: Platform_Error.ENODATA /* No message available */ +ENOSR :: Platform_Error.ENOSR /* No STREAM resources */ +ENOSTR :: Platform_Error.ENOSTR /* Not a STREAM */ +ETIME :: Platform_Error.ETIME /* STREAM ioctl timeout */ + +/* File system extended attribute errors */ +ENOATTR :: Platform_Error.ENOATTR /* Attribute not found */ + +/* Realtime, XSI STREAMS option errors */ +EMULTIHOP :: Platform_Error.EMULTIHOP /* Multihop attempted */ +ENOLINK :: Platform_Error.ENOLINK /* Link has been severed */ +EPROTO :: Platform_Error.EPROTO /* Protocol error */ + +/* Robust mutexes */ +EOWNERDEAD :: Platform_Error.EOWNERDEAD /* Previous owner died */ +ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE /* State not recoverable */ + +ELAST :: Platform_Error.ELAST /* Must equal largest Error */ + +/* end of Error */ + +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 :: u32 +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 + +@(require_results) S_ISLNK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK } +@(require_results) S_ISREG :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG } +@(require_results) S_ISDIR :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR } +@(require_results) S_ISCHR :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR } +@(require_results) S_ISBLK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK } +@(require_results) S_ISFIFO :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO } +@(require_results) S_ISSOCK :: #force_inline proc "contextless" (m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK } + +F_OK :: 0 // Test for file existance +X_OK :: 1 // Test for execute permission +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, #c_vararg mode: ..u32) -> Handle --- + @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- + @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pread") _unix_pread :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pwrite") _unix_pwrite :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: i64, whence: c.int) -> i64 --- + @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- + @(link_name="stat") _unix_stat :: proc(path: cstring, stat: ^OS_Stat) -> c.int --- + @(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, #c_vararg args: ..any) -> c.int --- + @(link_name="fsync") _unix_fsync :: proc(fd: Handle) -> c.int --- + @(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle --- + + @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- + @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- + @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- + @(link_name="__readdir_r30") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- + + @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- + @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- + @(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: [^]byte = nil) -> cstring --- + @(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int --- + + @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- +} + +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. + +@(require_results) +is_path_separator :: proc(r: rune) -> bool { + return r == '/' +} + +@(require_results, no_instrumentation) +get_last_error :: proc "contextless" () -> Error { + return Platform_Error(__errno_location()^) +} + +@(require_results) +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + handle := _unix_open(cstr, c.int(flags), c.uint(mode)) + if handle == -1 { + return INVALID_HANDLE, get_last_error() + } + return handle, nil +} + +close :: proc(fd: Handle) -> Error { + result := _unix_close(fd) + if result == -1 { + return get_last_error() + } + return nil +} + +flush :: proc(fd: Handle) -> Error { + result := _unix_fsync(fd) + if result == -1 { + return get_last_error() + } + return nil +} + +// We set a max of 1GB to keep alignment and to be safe. +@(private) +MAX_RW :: 1 << 30 + +read :: proc(fd: Handle, data: []byte) -> (int, Error) { + to_read := min(c.size_t(len(data)), MAX_RW) + bytes_read := _unix_read(fd, &data[0], to_read) + if bytes_read == -1 { + return -1, get_last_error() + } + return int(bytes_read), nil +} + +write :: proc(fd: Handle, data: []byte) -> (int, Error) { + if len(data) == 0 { + return 0, nil + } + + to_write := min(c.size_t(len(data)), MAX_RW) + bytes_written := _unix_write(fd, &data[0], to_write) + if bytes_written == -1 { + return -1, get_last_error() + } + return int(bytes_written), nil +} + +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + if len(data) == 0 { + return 0, nil + } + + to_read := min(uint(len(data)), MAX_RW) + + bytes_read := _unix_pread(fd, raw_data(data), to_read, offset) + if bytes_read < 0 { + return -1, get_last_error() + } + return bytes_read, nil +} + +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + if len(data) == 0 { + return 0, nil + } + + to_write := min(uint(len(data)), MAX_RW) + + bytes_written := _unix_pwrite(fd, raw_data(data), to_write, offset) + if bytes_written < 0 { + return -1, get_last_error() + } + return bytes_written, nil +} + +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } + res := _unix_seek(fd, offset, c.int(whence)) + if res == -1 { + errno := get_last_error() + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + } + return 0, errno + } + return res, nil +} + +@(require_results) +file_size :: proc(fd: Handle) -> (size: i64, err: Error) { + size = -1 + s := _fstat(fd) or_return + size = s.size + return +} + +rename :: proc(old_path, new_path: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator) + new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator) + res := _unix_rename(old_path_cstr, new_path_cstr) + if res == -1 { + return get_last_error() + } + return nil +} + +remove :: proc(path: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_unlink(path_cstr) + if res == -1 { + return get_last_error() + } + return nil +} + +make_directory :: proc(path: string, mode: mode_t = 0o775) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_mkdir(path_cstr, mode) + if res == -1 { + return get_last_error() + } + return nil +} + +remove_directory :: proc(path: string) -> Error { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_rmdir(path_cstr) + if res == -1 { + return get_last_error() + } + return nil +} + +@(require_results) +is_file_handle :: proc(fd: Handle) -> bool { + s, err := _fstat(fd) + if err != nil { + return false + } + return S_ISREG(s.mode) +} + +@(require_results) +is_file_path :: proc(path: string, follow_links: bool = true) -> bool { + s: OS_Stat + err: Error + if follow_links { + s, err = _stat(path) + } else { + s, err = _lstat(path) + } + if err != nil { + return false + } + return S_ISREG(s.mode) +} + +@(require_results) +is_dir_handle :: proc(fd: Handle) -> bool { + s, err := _fstat(fd) + if err != nil { + return false + } + return S_ISDIR(s.mode) +} + +@(require_results) +is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { + s: OS_Stat + err: Error + if follow_links { + s, err = _stat(path) + } else { + s, err = _lstat(path) + } + if err != nil { + 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} + +@(require_results) +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 +} + +@(require_results) +fcntl :: proc(fd: int, cmd: int, arg: int) -> (int, Error) { + result := _unix_fcntl(Handle(fd), c.int(cmd), uintptr(arg)) + if result < 0 { + return 0, get_last_error() + } + return int(result), nil +} + +// NOTE(bill): Uses startup to initialize it + +stdin: Handle = 0 +stdout: Handle = 1 +stderr: Handle = 2 + +@(require_results) +last_write_time :: proc(fd: Handle) -> (time: File_Time, err: Error) { + s := _fstat(fd) or_return + modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds + return File_Time(modified), nil +} + +@(require_results) +last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) { + s := _stat(name) or_return + modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds + return File_Time(modified), nil +} + +@(private, require_results) +_stat :: proc(path: string) -> (OS_Stat, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + s: OS_Stat = --- + result := _unix_lstat(cstr, &s) + if result == -1 { + return s, get_last_error() + } + return s, nil +} + +@(private, require_results) +_lstat :: proc(path: string) -> (OS_Stat, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + + // deliberately uninitialized + s: OS_Stat = --- + res := _unix_lstat(cstr, &s) + if res == -1 { + return s, get_last_error() + } + return s, nil +} + +@(private, require_results) +_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { + s: OS_Stat = --- + result := _unix_fstat(fd, &s) + if result == -1 { + return s, get_last_error() + } + return s, nil +} + +@(private, require_results) +_fdopendir :: proc(fd: Handle) -> (Dir, Error) { + dirp := _unix_fdopendir(fd) + if dirp == cast(Dir)nil { + return nil, get_last_error() + } + return dirp, nil +} + +@(private) +_closedir :: proc(dirp: Dir) -> Error { + rc := _unix_closedir(dirp) + if rc != 0 { + return get_last_error() + } + return nil +} + +@(private) +_rewinddir :: proc(dirp: Dir) { + _unix_rewinddir(dirp) +} + +@(private, require_results) +_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { + result: ^Dirent + rc := _unix_readdir_r(dirp, &entry, &result) + + if rc != 0 { + err = get_last_error() + return + } + err = nil + + if result == nil { + end_of_stream = true + return + } + + return +} + +@(private, require_results) +_readlink :: proc(path: string) -> (string, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) + + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + + bufsz : uint = MAX_PATH + buf := make([]byte, MAX_PATH) + for { + rc := _unix_readlink(path_cstr, &(buf[0]), bufsz) + if rc == -1 { + delete(buf) + return "", get_last_error() + } else if rc == int(bufsz) { + bufsz += MAX_PATH + delete(buf) + buf = make([]byte, bufsz) + } else { + return strings.string_from_ptr(&buf[0], rc), nil + } + } + + return "", Error{} +} + +@(private, require_results) +_dup :: proc(fd: Handle) -> (Handle, Error) { + dup := _unix_dup(fd) + if dup == -1 { + return INVALID_HANDLE, get_last_error() + } + return dup, nil +} + +@(require_results) +absolute_path_from_handle :: proc(fd: Handle) -> (path: string, err: Error) { + buf: [MAX_PATH]byte + _ = fcntl(int(fd), F_GETPATH, int(uintptr(&buf[0]))) or_return + return strings.clone_from_cstring(cstring(&buf[0])) +} + +@(require_results) +absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { + 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 "", get_last_error() + } + defer _unix_free(rawptr(path_ptr)) + + return strings.clone(string(path_ptr), allocator) +} + +access :: proc(path: string, mask: int) -> (bool, Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + cstr := strings.clone_to_cstring(path, context.temp_allocator) + result := _unix_access(cstr, c.int(mask)) + if result == -1 { + return false, get_last_error() + } + return true, nil +} + +@(require_results) +lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) + + path_str := strings.clone_to_cstring(key, context.temp_allocator) + cstr := _unix_getenv(path_str) + if cstr == nil { + return "", false + } + return strings.clone(string(cstr), allocator), true +} + +@(require_results) +get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { + value, _ = lookup_env(key, allocator) + return +} + +@(require_results) +get_current_directory :: proc(allocator := context.allocator) -> string { + context.allocator = allocator + // 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 get_last_error() != ERANGE { + delete(buf) + return "" + } + resize(&buf, len(buf)+page_size) + } + unreachable() +} + +set_current_directory :: proc(path: string) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_chdir(cstr) + if res == -1 { + return get_last_error() + } + return nil +} + +exit :: proc "contextless" (code: int) -> ! { + runtime._cleanup_runtime_contextless() + _unix_exit(c.int(code)) +} + +@(require_results) +current_thread_id :: proc "contextless" () -> int { + return int(_lwp_self()) +} + +@(require_results) +dlopen :: proc(filename: string, flags: int) -> rawptr { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(filename, context.temp_allocator) + handle := _unix_dlopen(cstr, c.int(flags)) + return handle +} + +@(require_results) +dlsym :: proc(handle: rawptr, symbol: string) -> rawptr { + assert(handle != nil) + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + 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 +} + +@(require_results) +dlerror :: proc() -> string { + return string(_unix_dlerror()) +} + +@(require_results) +get_page_size :: proc() -> int { + // NOTE(tetra): The page size never changes, so why do anything complicated + // if we don't have to. + @static page_size := -1 + if page_size != -1 { + return page_size + } + + page_size = int(_unix_getpagesize()) + return page_size +} + +@(private, require_results) +_processor_core_count :: proc() -> int { + count : int = 0 + count_size := size_of(count) + if _sysctlbyname("hw.ncpu", &count, &count_size, nil, 0) == 0 { + if count > 0 { + return count + } + } + + return 1 +} + +@(require_results) +_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 957873a0b..3c377968c 100644 --- a/core/os/os_openbsd.odin +++ b/core/os/os_openbsd.odin @@ -4,113 +4,210 @@ foreign import libc "system:c" import "core:strings" import "core:c" -import "core:runtime" +import "base:runtime" Handle :: distinct i32 Pid :: distinct i32 File_Time :: distinct u64 -Errno :: distinct i32 INVALID_HANDLE :: ~Handle(0) -ERROR_NONE: Errno: 0 +_Platform_Error :: enum i32 { + NONE = 0, + EPERM = 1, + ENOENT = 2, + ESRCH = 3, + EINTR = 4, + EIO = 5, + ENXIO = 6, + E2BIG = 7, + ENOEXEC = 8, + EBADF = 9, + ECHILD = 10, + EDEADLK = 11, + ENOMEM = 12, + EACCES = 13, + EFAULT = 14, + ENOTBLK = 15, + EBUSY = 16, + EEXIST = 17, + EXDEV = 18, + ENODEV = 19, + ENOTDIR = 20, + EISDIR = 21, + EINVAL = 22, + ENFILE = 23, + EMFILE = 24, + ENOTTY = 25, + ETXTBSY = 26, + EFBIG = 27, + ENOSPC = 28, + ESPIPE = 29, + EROFS = 30, + EMLINK = 31, + EPIPE = 32, + EDOM = 33, + ERANGE = 34, + EAGAIN = 35, + EWOULDBLOCK = EAGAIN, + EINPROGRESS = 36, + EALREADY = 37, + ENOTSOCK = 38, + EDESTADDRREQ = 39, + EMSGSIZE = 40, + EPROTOTYPE = 41, + ENOPROTOOPT = 42, + EPROTONOSUPPORT = 43, + ESOCKTNOSUPPORT = 44, + EOPNOTSUPP = 45, + EPFNOSUPPORT = 46, + EAFNOSUPPORT = 47, + EADDRINUSE = 48, + EADDRNOTAVAIL = 49, + ENETDOWN = 50, + ENETUNREACH = 51, + ENETRESET = 52, + ECONNABORTED = 53, + ECONNRESET = 54, + ENOBUFS = 55, + EISCONN = 56, + ENOTCONN = 57, + ESHUTDOWN = 58, + ETOOMANYREFS = 59, + ETIMEDOUT = 60, + ECONNREFUSED = 61, + ELOOP = 62, + ENAMETOOLONG = 63, + EHOSTDOWN = 64, + EHOSTUNREACH = 65, + ENOTEMPTY = 66, + EPROCLIM = 67, + EUSERS = 68, + EDQUOT = 69, + ESTALE = 70, + EREMOTE = 71, + EBADRPC = 72, + ERPCMISMATCH = 73, + EPROGUNAVAIL = 74, + EPROGMISMATCH = 75, + EPROCUNAVAIL = 76, + ENOLCK = 77, + ENOSYS = 78, + EFTYPE = 79, + EAUTH = 80, + ENEEDAUTH = 81, + EIPSEC = 82, + ENOATTR = 83, + EILSEQ = 84, + ENOMEDIUM = 85, + EMEDIUMTYPE = 86, + EOVERFLOW = 87, + ECANCELED = 88, + EIDRM = 89, + ENOMSG = 90, + ENOTSUP = 91, + EBADMSG = 92, + ENOTRECOVERABLE = 93, + EOWNERDEAD = 94, + EPROTO = 95, +} -EPERM: Errno: 1 -ENOENT: Errno: 2 -ESRCH: Errno: 3 -EINTR: Errno: 4 -EIO: Errno: 5 -ENXIO: Errno: 6 -E2BIG: Errno: 7 -ENOEXEC: Errno: 8 -EBADF: Errno: 9 -ECHILD: Errno: 10 -EDEADLK: Errno: 11 -ENOMEM: Errno: 12 -EACCES: Errno: 13 -EFAULT: Errno: 14 -ENOTBLK: Errno: 15 -EBUSY: Errno: 16 -EEXIST: Errno: 17 -EXDEV: Errno: 18 -ENODEV: Errno: 19 -ENOTDIR: Errno: 20 -EISDIR: Errno: 21 -EINVAL: Errno: 22 -ENFILE: Errno: 23 -EMFILE: Errno: 24 -ENOTTY: Errno: 25 -ETXTBSY: Errno: 26 -EFBIG: Errno: 27 -ENOSPC: Errno: 28 -ESPIPE: Errno: 29 -EROFS: Errno: 30 -EMLINK: Errno: 31 -EPIPE: Errno: 32 -EDOM: Errno: 33 -ERANGE: Errno: 34 -EAGAIN: Errno: 35 -EWOULDBLOCK: Errno: EAGAIN -EINPROGRESS: Errno: 36 -EALREADY: Errno: 37 -ENOTSOCK: Errno: 38 -EDESTADDRREQ: Errno: 39 -EMSGSIZE: Errno: 40 -EPROTOTYPE: Errno: 41 -ENOPROTOOPT: Errno: 42 -EPROTONOSUPPORT: Errno: 43 -ESOCKTNOSUPPORT: Errno: 44 -EOPNOTSUPP: Errno: 45 -EPFNOSUPPORT: Errno: 46 -EAFNOSUPPORT: Errno: 47 -EADDRINUSE: Errno: 48 -EADDRNOTAVAIL: Errno: 49 -ENETDOWN: Errno: 50 -ENETUNREACH: Errno: 51 -ENETRESET: Errno: 52 -ECONNABORTED: Errno: 53 -ECONNRESET: Errno: 54 -ENOBUFS: Errno: 55 -EISCONN: Errno: 56 -ENOTCONN: Errno: 57 -ESHUTDOWN: Errno: 58 -ETOOMANYREFS: Errno: 59 -ETIMEDOUT: Errno: 60 -ECONNREFUSED: Errno: 61 -ELOOP: Errno: 62 -ENAMETOOLONG: Errno: 63 -EHOSTDOWN: Errno: 64 -EHOSTUNREACH: Errno: 65 -ENOTEMPTY: Errno: 66 -EPROCLIM: Errno: 67 -EUSERS: Errno: 68 -EDQUOT: Errno: 69 -ESTALE: Errno: 70 -EREMOTE: Errno: 71 -EBADRPC: Errno: 72 -ERPCMISMATCH: Errno: 73 -EPROGUNAVAIL: Errno: 74 -EPROGMISMATCH: Errno: 75 -EPROCUNAVAIL: Errno: 76 -ENOLCK: Errno: 77 -ENOSYS: Errno: 78 -EFTYPE: Errno: 79 -EAUTH: Errno: 80 -ENEEDAUTH: Errno: 81 -EIPSEC: Errno: 82 -ENOATTR: Errno: 83 -EILSEQ: Errno: 84 -ENOMEDIUM: Errno: 85 -EMEDIUMTYPE: Errno: 86 -EOVERFLOW: Errno: 87 -ECANCELED: Errno: 88 -EIDRM: Errno: 89 -ENOMSG: Errno: 90 -ENOTSUP: Errno: 91 -EBADMSG: Errno: 92 -ENOTRECOVERABLE: Errno: 93 -EOWNERDEAD: Errno: 94 -EPROTO: Errno: 95 +EPERM :: Platform_Error.EPERM +ENOENT :: Platform_Error.ENOENT +ESRCH :: Platform_Error.ESRCH +EINTR :: Platform_Error.EINTR +EIO :: Platform_Error.EIO +ENXIO :: Platform_Error.ENXIO +E2BIG :: Platform_Error.E2BIG +ENOEXEC :: Platform_Error.ENOEXEC +EBADF :: Platform_Error.EBADF +ECHILD :: Platform_Error.ECHILD +EDEADLK :: Platform_Error.EDEADLK +ENOMEM :: Platform_Error.ENOMEM +EACCES :: Platform_Error.EACCES +EFAULT :: Platform_Error.EFAULT +ENOTBLK :: Platform_Error.ENOTBLK +EBUSY :: Platform_Error.EBUSY +EEXIST :: Platform_Error.EEXIST +EXDEV :: Platform_Error.EXDEV +ENODEV :: Platform_Error.ENODEV +ENOTDIR :: Platform_Error.ENOTDIR +EISDIR :: Platform_Error.EISDIR +EINVAL :: Platform_Error.EINVAL +ENFILE :: Platform_Error.ENFILE +EMFILE :: Platform_Error.EMFILE +ENOTTY :: Platform_Error.ENOTTY +ETXTBSY :: Platform_Error.ETXTBSY +EFBIG :: Platform_Error.EFBIG +ENOSPC :: Platform_Error.ENOSPC +ESPIPE :: Platform_Error.ESPIPE +EROFS :: Platform_Error.EROFS +EMLINK :: Platform_Error.EMLINK +EPIPE :: Platform_Error.EPIPE +EDOM :: Platform_Error.EDOM +ERANGE :: Platform_Error.ERANGE +EAGAIN :: Platform_Error.EAGAIN +EWOULDBLOCK :: Platform_Error.EWOULDBLOCK +EINPROGRESS :: Platform_Error.EINPROGRESS +EALREADY :: Platform_Error.EALREADY +ENOTSOCK :: Platform_Error.ENOTSOCK +EDESTADDRREQ :: Platform_Error.EDESTADDRREQ +EMSGSIZE :: Platform_Error.EMSGSIZE +EPROTOTYPE :: Platform_Error.EPROTOTYPE +ENOPROTOOPT :: Platform_Error.ENOPROTOOPT +EPROTONOSUPPORT :: Platform_Error.EPROTONOSUPPORT +ESOCKTNOSUPPORT :: Platform_Error.ESOCKTNOSUPPORT +EOPNOTSUPP :: Platform_Error.EOPNOTSUPP +EPFNOSUPPORT :: Platform_Error.EPFNOSUPPORT +EAFNOSUPPORT :: Platform_Error.EAFNOSUPPORT +EADDRINUSE :: Platform_Error.EADDRINUSE +EADDRNOTAVAIL :: Platform_Error.EADDRNOTAVAIL +ENETDOWN :: Platform_Error.ENETDOWN +ENETUNREACH :: Platform_Error.ENETUNREACH +ENETRESET :: Platform_Error.ENETRESET +ECONNABORTED :: Platform_Error.ECONNABORTED +ECONNRESET :: Platform_Error.ECONNRESET +ENOBUFS :: Platform_Error.ENOBUFS +EISCONN :: Platform_Error.EISCONN +ENOTCONN :: Platform_Error.ENOTCONN +ESHUTDOWN :: Platform_Error.ESHUTDOWN +ETOOMANYREFS :: Platform_Error.ETOOMANYREFS +ETIMEDOUT :: Platform_Error.ETIMEDOUT +ECONNREFUSED :: Platform_Error.ECONNREFUSED +ELOOP :: Platform_Error.ELOOP +ENAMETOOLONG :: Platform_Error.ENAMETOOLONG +EHOSTDOWN :: Platform_Error.EHOSTDOWN +EHOSTUNREACH :: Platform_Error.EHOSTUNREACH +ENOTEMPTY :: Platform_Error.ENOTEMPTY +EPROCLIM :: Platform_Error.EPROCLIM +EUSERS :: Platform_Error.EUSERS +EDQUOT :: Platform_Error.EDQUOT +ESTALE :: Platform_Error.ESTALE +EREMOTE :: Platform_Error.EREMOTE +EBADRPC :: Platform_Error.EBADRPC +ERPCMISMATCH :: Platform_Error.ERPCMISMATCH +EPROGUNAVAIL :: Platform_Error.EPROGUNAVAIL +EPROGMISMATCH :: Platform_Error.EPROGMISMATCH +EPROCUNAVAIL :: Platform_Error.EPROCUNAVAIL +ENOLCK :: Platform_Error.ENOLCK +ENOSYS :: Platform_Error.ENOSYS +EFTYPE :: Platform_Error.EFTYPE +EAUTH :: Platform_Error.EAUTH +ENEEDAUTH :: Platform_Error.ENEEDAUTH +EIPSEC :: Platform_Error.EIPSEC +ENOATTR :: Platform_Error.ENOATTR +EILSEQ :: Platform_Error.EILSEQ +ENOMEDIUM :: Platform_Error.ENOMEDIUM +EMEDIUMTYPE :: Platform_Error.EMEDIUMTYPE +EOVERFLOW :: Platform_Error.EOVERFLOW +ECANCELED :: Platform_Error.ECANCELED +EIDRM :: Platform_Error.EIDRM +ENOMSG :: Platform_Error.ENOMSG +ENOTSUP :: Platform_Error.ENOTSUP +EBADMSG :: Platform_Error.EBADMSG +ENOTRECOVERABLE :: Platform_Error.ENOTRECOVERABLE +EOWNERDEAD :: Platform_Error.EOWNERDEAD +EPROTO :: Platform_Error.EPROTO O_RDONLY :: 0x00000 O_WRONLY :: 0x00001 @@ -225,13 +322,13 @@ S_ISUID :: 0o4000 // Set user id on execution S_ISGID :: 0o2000 // Set group id on execution S_ISTXT :: 0o1000 // Sticky bit -S_ISLNK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK } -S_ISREG :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG } -S_ISDIR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR } -S_ISCHR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR } -S_ISBLK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK } -S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO } -S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK } +@(require_results) S_ISLNK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK } +@(require_results) S_ISREG :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG } +@(require_results) S_ISDIR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR } +@(require_results) S_ISCHR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR } +@(require_results) S_ISBLK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK } +@(require_results) S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO } +@(require_results) S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK } F_OK :: 0x00 // Test for file existance X_OK :: 0x01 // Test for execute permission @@ -246,200 +343,270 @@ AT_REMOVEDIR :: 0x08 @(default_calling_convention="c") foreign libc { - @(link_name="__errno") __errno :: proc() -> ^int --- + @(link_name="__error") __error :: proc() -> ^c.int --- - @(link_name="fork") _unix_fork :: proc() -> pid_t --- - @(link_name="getthrid") _unix_getthrid :: proc() -> int --- + @(link_name="fork") _unix_fork :: proc() -> pid_t --- + @(link_name="getthrid") _unix_getthrid :: proc() -> int --- - @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, mode: c.int) -> Handle --- - @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- - @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- - @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- - @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t --- - @(link_name="stat") _unix_stat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- - @(link_name="fstat") _unix_fstat :: proc(fd: Handle, sb: ^OS_Stat) -> c.int --- - @(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- - @(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t --- - @(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int --- - @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- - @(link_name="chdir") _unix_chdir :: proc(path: cstring) -> c.int --- - @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- - @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- - @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- - @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- + @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, #c_vararg mode: ..u32) -> Handle --- + @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- + @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pread") _unix_pread :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="pwrite") _unix_pwrite :: proc(fd: Handle, buf: rawptr, size: c.size_t, offset: i64) -> c.ssize_t --- + @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: off_t, whence: c.int) -> off_t --- + @(link_name="stat") _unix_stat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="fstat") _unix_fstat :: proc(fd: Handle, sb: ^OS_Stat) -> c.int --- + @(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t --- + @(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int --- + @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- + @(link_name="chdir") _unix_chdir :: proc(path: cstring) -> c.int --- + @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- + @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- + @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- + @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- + @(link_name="fsync") _unix_fsync :: proc(fd: Handle) -> c.int --- + @(link_name="dup") _unix_dup :: proc(fd: Handle) -> Handle --- - @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- - @(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long --- - @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- - @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- - @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- - @(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- + @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- + @(link_name="sysconf") _sysconf :: proc(name: c.int) -> c.long --- + @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- + @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- + @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- + @(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- - @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- - @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- - @(link_name="free") _unix_free :: proc(ptr: rawptr) --- - @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- + @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- + @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- + @(link_name="free") _unix_free :: proc(ptr: rawptr) --- + @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- - @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- - @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- + @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: [^]byte = nil) -> cstring --- - @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- + @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- - @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- - @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- - @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- - @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- + @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- + @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- + @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- + @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- } +@(require_results) is_path_separator :: proc(r: rune) -> bool { return r == '/' } -get_last_error :: proc "contextless" () -> int { - return __errno()^ +@(require_results, no_instrumentation) +get_last_error :: proc "contextless" () -> Error { + return Platform_Error(__error()^) } -fork :: proc() -> (Pid, Errno) { +@(require_results) +fork :: proc() -> (Pid, Error) { pid := _unix_fork() if pid == -1 { - return Pid(-1), Errno(get_last_error()) + return Pid(-1), get_last_error() } - return Pid(pid), ERROR_NONE + return Pid(pid), nil } -open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) { +@(require_results) +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) - handle := _unix_open(cstr, c.int(flags), c.int(mode)) + handle := _unix_open(cstr, c.int(flags), c.uint(mode)) if handle == -1 { - return INVALID_HANDLE, Errno(get_last_error()) + return INVALID_HANDLE, get_last_error() } - return handle, ERROR_NONE + return handle, nil } -close :: proc(fd: Handle) -> Errno { +close :: proc(fd: Handle) -> Error { result := _unix_close(fd) if result == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -read :: proc(fd: Handle, data: []byte) -> (int, Errno) { - bytes_read := _unix_read(fd, &data[0], c.size_t(len(data))) +flush :: proc(fd: Handle) -> Error { + result := _unix_fsync(fd) + if result == -1 { + return get_last_error() + } + return nil +} + +// If you read or write more than `SSIZE_MAX` bytes, OpenBSD returns `EINVAL`. +// In practice a read/write call would probably never read/write these big buffers all at once, +// which is why the number of bytes is returned and why there are procs that will call this in a +// loop for you. +// We set a max of 1GB to keep alignment and to be safe. +@(private) +MAX_RW :: 1 << 30 + +read :: proc(fd: Handle, data: []byte) -> (int, Error) { + to_read := min(c.size_t(len(data)), MAX_RW) + bytes_read := _unix_read(fd, &data[0], to_read) if bytes_read == -1 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return int(bytes_read), ERROR_NONE + return int(bytes_read), nil } -write :: proc(fd: Handle, data: []byte) -> (int, Errno) { +write :: proc(fd: Handle, data: []byte) -> (int, Error) { if len(data) == 0 { - return 0, ERROR_NONE + return 0, nil } - bytes_written := _unix_write(fd, &data[0], c.size_t(len(data))) + + to_write := min(c.size_t(len(data)), MAX_RW) + bytes_written := _unix_write(fd, &data[0], to_write) if bytes_written == -1 { - return -1, Errno(get_last_error()) + return -1, get_last_error() } - return int(bytes_written), ERROR_NONE + return int(bytes_written), nil } -seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + if len(data) == 0 { + return 0, nil + } + + to_read := min(uint(len(data)), MAX_RW) + + bytes_read := _unix_pread(fd, raw_data(data), to_read, offset) + if bytes_read < 0 { + return -1, get_last_error() + } + return bytes_read, nil +} + +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + if len(data) == 0 { + return 0, nil + } + + to_write := min(uint(len(data)), MAX_RW) + + bytes_written := _unix_pwrite(fd, raw_data(data), to_write, offset) + if bytes_written < 0 { + return -1, get_last_error() + } + return bytes_written, nil +} + +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + switch whence { + case SEEK_SET, SEEK_CUR, SEEK_END: + break + case: + return 0, .Invalid_Whence + } res := _unix_seek(fd, offset, c.int(whence)) if res == -1 { - return -1, Errno(get_last_error()) + errno := get_last_error() + switch errno { + case .EINVAL: + return 0, .Invalid_Offset + } + return 0, errno } - return res, ERROR_NONE + return res, nil } -file_size :: proc(fd: Handle) -> (i64, Errno) { - s, err := _fstat(fd) - if err != ERROR_NONE { - return -1, err - } - return s.size, ERROR_NONE +@(require_results) +file_size :: proc(fd: Handle) -> (size: i64, err: Error) { + size = -1 + s := _fstat(fd) or_return + size = s.size + return } -rename :: proc(old_path, new_path: string) -> Errno { +rename :: proc(old_path, new_path: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator) new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator) res := _unix_rename(old_path_cstr, new_path_cstr) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -remove :: proc(path: string) -> Errno { +remove :: proc(path: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_unlink(path_cstr) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno { +make_directory :: proc(path: string, mode: mode_t = 0o775) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_mkdir(path_cstr, mode) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -remove_directory :: proc(path: string) -> Errno { +remove_directory :: proc(path: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() path_cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_rmdir(path_cstr) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } +@(require_results) is_file_handle :: proc(fd: Handle) -> bool { s, err := _fstat(fd) - if err != ERROR_NONE { + if err != nil { return false } return S_ISREG(s.mode) } +@(require_results) is_file_path :: proc(path: string, follow_links: bool = true) -> bool { s: OS_Stat - err: Errno + err: Error if follow_links { s, err = _stat(path) } else { s, err = _lstat(path) } - if err != ERROR_NONE { + if err != nil { return false } return S_ISREG(s.mode) } +@(require_results) is_dir_handle :: proc(fd: Handle) -> bool { s, err := _fstat(fd) - if err != ERROR_NONE { + if err != nil { return false } return S_ISDIR(s.mode) } +@(require_results) is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { s: OS_Stat - err: Errno + err: Error if follow_links { s, err = _stat(path) } else { s, err = _lstat(path) } - if err != ERROR_NONE { + if err != nil { return false } return S_ISDIR(s.mode) @@ -458,26 +625,22 @@ stderr: Handle = 2 last_write_time :: proc(fd: Handle) -> File_Time {} last_write_time_by_name :: proc(name: string) -> File_Time {} */ -last_write_time :: proc(fd: Handle) -> (File_Time, Errno) { - s, err := _fstat(fd) - if err != ERROR_NONE { - return 0, err - } +@(require_results) +last_write_time :: proc(fd: Handle) -> (time: File_Time, err: Error) { + s := _fstat(fd) or_return modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), ERROR_NONE + return File_Time(modified), nil } -last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) { - s, err := _stat(name) - if err != ERROR_NONE { - return 0, err - } +@(require_results) +last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) { + s := _stat(name) or_return modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds - return File_Time(modified), ERROR_NONE + return File_Time(modified), nil } -@private -_stat :: proc(path: string) -> (OS_Stat, Errno) { +@(private, require_results) +_stat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -485,13 +648,13 @@ _stat :: proc(path: string) -> (OS_Stat, Errno) { s: OS_Stat = --- res := _unix_stat(cstr, &s) if res == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_lstat :: proc(path: string) -> (OS_Stat, Errno) { +@(private, require_results) +_lstat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -499,55 +662,55 @@ _lstat :: proc(path: string) -> (OS_Stat, Errno) { s: OS_Stat = --- res := _unix_lstat(cstr, &s) if res == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) { +@(private, require_results) +_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { // deliberately uninitialized s: OS_Stat = --- res := _unix_fstat(fd, &s) if res == -1 { - return s, Errno(get_last_error()) + return s, get_last_error() } - return s, ERROR_NONE + return s, nil } -@private -_fdopendir :: proc(fd: Handle) -> (Dir, Errno) { +@(private, require_results) +_fdopendir :: proc(fd: Handle) -> (Dir, Error) { dirp := _unix_fdopendir(fd) if dirp == cast(Dir)nil { - return nil, Errno(get_last_error()) + return nil, get_last_error() } - return dirp, ERROR_NONE + return dirp, nil } -@private -_closedir :: proc(dirp: Dir) -> Errno { +@(private) +_closedir :: proc(dirp: Dir) -> Error { rc := _unix_closedir(dirp) if rc != 0 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } -@private +@(private) _rewinddir :: proc(dirp: Dir) { _unix_rewinddir(dirp) } -@private -_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) { +@(private, require_results) +_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Error, end_of_stream: bool) { result: ^Dirent rc := _unix_readdir_r(dirp, &entry, &result) if rc != 0 { - err = Errno(get_last_error()) + err = get_last_error() return } - err = ERROR_NONE + err = nil if result == nil { end_of_stream = true @@ -557,8 +720,8 @@ _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) return } -@private -_readlink :: proc(path: string) -> (string, Errno) { +@(private, require_results) +_readlink :: proc(path: string) -> (string, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) path_cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -568,23 +731,34 @@ _readlink :: proc(path: string) -> (string, Errno) { rc := _unix_readlink(path_cstr, &(buf[0]), bufsz) if rc == -1 { delete(buf) - return "", Errno(get_last_error()) + return "", get_last_error() } else if rc == int(bufsz) { bufsz += MAX_PATH delete(buf) buf = make([]byte, bufsz) } else { - return strings.string_from_ptr(&buf[0], rc), ERROR_NONE + return strings.string_from_ptr(&buf[0], rc), nil } } } -// XXX OpenBSD -absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) { - return "", Errno(ENOSYS) +@(private, require_results) +_dup :: proc(fd: Handle) -> (Handle, Error) { + dup := _unix_dup(fd) + if dup == -1 { + return INVALID_HANDLE, get_last_error() + } + return dup, nil } -absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { +// XXX OpenBSD +@(require_results) +absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { + return "", Error(ENOSYS) +} + +@(require_results) +absolute_path_from_relative :: proc(rel: string, allocator := context.allocator) -> (path: string, err: Error) { rel := rel if rel == "" { rel = "." @@ -595,47 +769,24 @@ absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { path_ptr := _unix_realpath(rel_cstr, nil) if path_ptr == nil { - return "", Errno(get_last_error()) + return "", get_last_error() } - defer _unix_free(path_ptr) + defer _unix_free(rawptr(path_ptr)) - path_cstr := transmute(cstring)path_ptr - path = strings.clone( string(path_cstr) ) - - return path, ERROR_NONE + return strings.clone(string(path_ptr), allocator) } -access :: proc(path: string, mask: int) -> (bool, Errno) { +access :: proc(path: string, mask: int) -> (bool, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_access(cstr, c.int(mask)) if res == -1 { - return false, Errno(get_last_error()) + return false, get_last_error() } - return true, ERROR_NONE -} - -heap_alloc :: proc(size: int, zero_memory := true) -> rawptr { - if size <= 0 { - return nil - } - if zero_memory { - return _unix_calloc(1, c.size_t(size)) - } else { - return _unix_malloc(c.size_t(size)) - } -} - -heap_resize :: proc(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, c.size_t(new_size)) -} - -heap_free :: proc(ptr: rawptr) { - _unix_free(ptr) + return true, nil } +@(require_results) lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator) @@ -646,19 +797,22 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin return strings.clone(string(cstr), allocator), true } +@(require_results) get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } -get_current_directory :: proc() -> string { +@(require_results) +get_current_directory :: proc(allocator := context.allocator) -> string { + context.allocator = allocator buf := make([dynamic]u8, MAX_PATH) for { cwd := _unix_getcwd(cstring(raw_data(buf)), c.size_t(len(buf))) if cwd != nil { return string(cwd) } - if Errno(get_last_error()) != ERANGE { + if get_last_error() != ERANGE { delete(buf) return "" } @@ -667,14 +821,14 @@ get_current_directory :: proc() -> string { unreachable() } -set_current_directory :: proc(path: string) -> (err: Errno) { +set_current_directory :: proc(path: string) -> (err: Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) res := _unix_chdir(cstr) if res == -1 { - return Errno(get_last_error()) + return get_last_error() } - return ERROR_NONE + return nil } exit :: proc "contextless" (code: int) -> ! { @@ -682,16 +836,19 @@ exit :: proc "contextless" (code: int) -> ! { _unix_exit(c.int(code)) } +@(require_results) current_thread_id :: proc "contextless" () -> int { return _unix_getthrid() } +@(require_results) dlopen :: proc(filename: string, flags: int) -> rawptr { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(filename, context.temp_allocator) handle := _unix_dlopen(cstr, c.int(flags)) return handle } +@(require_results) dlsym :: proc(handle: rawptr, symbol: string) -> rawptr { assert(handle != nil) runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() @@ -703,10 +860,12 @@ dlclose :: proc(handle: rawptr) -> bool { assert(handle != nil) return _unix_dlclose(handle) == 0 } +@(require_results) dlerror :: proc() -> string { return string(_unix_dlerror()) } +@(require_results) get_page_size :: proc() -> int { // NOTE(tetra): The page size never changes, so why do anything complicated // if we don't have to. @@ -721,11 +880,12 @@ get_page_size :: proc() -> int { _SC_NPROCESSORS_ONLN :: 503 -@(private) +@(private, require_results) _processor_core_count :: proc() -> int { return int(_sysconf(_SC_NPROCESSORS_ONLN)) } +@(require_results) _alloc_command_line_arguments :: proc() -> []string { res := make([]string, len(runtime.args__)) for arg, i in runtime.args__ { diff --git a/core/os/os_wasi.odin b/core/os/os_wasi.odin index c407acdb4..28f470357 100644 --- a/core/os/os_wasi.odin +++ b/core/os/os_wasi.odin @@ -1,12 +1,12 @@ package os import "core:sys/wasm/wasi" -import "core:runtime" +import "base:runtime" Handle :: distinct i32 -Errno :: distinct i32 +_Platform_Error :: wasi.errno_t -ERROR_NONE :: Errno(wasi.errno_t.SUCCESS) +INVALID_HANDLE :: -1 O_RDONLY :: 0x00000 O_WRONLY :: 0x00001 @@ -24,28 +24,148 @@ O_CLOEXEC :: 0x80000 stdin: Handle = 0 stdout: Handle = 1 stderr: Handle = 2 -current_dir: Handle = 3 + +args := _alloc_command_line_arguments() + +@(require_results) +_alloc_command_line_arguments :: proc() -> (args: []string) { + args = make([]string, len(runtime.args__)) + for &arg, i in args { + 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[:] +} + +@(require_results) +wasi_match_preopen :: proc(path: string) -> (wasi.fd_t, string, bool) { + @(require_results) + prefix_matches :: proc(prefix, path: string) -> bool { + // Empty is valid for any relative path. + if len(prefix) == 0 && len(path) > 0 && path[0] != '/' { + return true + } + + if len(path) < len(prefix) { + return false + } + + if path[:len(prefix)] != prefix { + return false + } + + // Only match on full components. + i := len(prefix) + for i > 0 && prefix[i-1] == '/' { + i -= 1 + } + return path[i] == '/' + } + + path := path + for len(path) > 0 && path[0] == '/' { + path = path[1:] + } + + match: Preopen + #reverse for preopen in preopens { + if (match.fd == 0 || len(preopen.prefix) > len(match.prefix)) && prefix_matches(preopen.prefix, path) { + match = preopen + } + } + + if match.fd == 0 { + return 0, "", false + } + + relative := path[len(match.prefix):] + for len(relative) > 0 && relative[0] == '/' { + relative = relative[1:] + } + + if len(relative) == 0 { + relative = "." + } + + return match.fd, relative, true +} write :: proc(fd: Handle, data: []byte) -> (int, Errno) { iovs := wasi.ciovec_t(data) n, err := wasi.fd_write(wasi.fd_t(fd), {iovs}) - return int(n), Errno(err) + return int(n), Platform_Error(err) } read :: proc(fd: Handle, data: []byte) -> (int, Errno) { iovs := wasi.iovec_t(data) n, err := wasi.fd_read(wasi.fd_t(fd), {iovs}) - return int(n), Errno(err) + return int(n), Platform_Error(err) } write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { iovs := wasi.ciovec_t(data) n, err := wasi.fd_pwrite(wasi.fd_t(fd), {iovs}, wasi.filesize_t(offset)) - return int(n), Errno(err) + return int(n), Platform_Error(err) } read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (int, Errno) { iovs := wasi.iovec_t(data) n, err := wasi.fd_pread(wasi.fd_t(fd), {iovs}, wasi.filesize_t(offset)) - return int(n), Errno(err) + return int(n), Platform_Error(err) } +@(require_results) open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errno) { oflags: wasi.oflags_t if mode & O_CREATE == O_CREATE { @@ -75,53 +195,43 @@ open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Errn if mode & O_SYNC == O_SYNC { fdflags += {.SYNC} } - fd, err := wasi.path_open(wasi.fd_t(current_dir),{.SYMLINK_FOLLOW},path,oflags,rights,{},fdflags) - return Handle(fd), Errno(err) + + dir_fd, relative, ok := wasi_match_preopen(path) + if !ok { + return INVALID_HANDLE, Errno(wasi.errno_t.BADF) + } + + fd, err := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, oflags, rights, {}, fdflags) + return Handle(fd), Platform_Error(err) } close :: proc(fd: Handle) -> Errno { err := wasi.fd_close(wasi.fd_t(fd)) - return Errno(err) + return Platform_Error(err) } + +flush :: proc(fd: Handle) -> Error { + // do nothing + return nil +} + seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { n, err := wasi.fd_seek(wasi.fd_t(fd), wasi.filedelta_t(offset), wasi.whence_t(whence)) - return i64(n), Errno(err) + return i64(n), Platform_Error(err) } +@(require_results) current_thread_id :: proc "contextless" () -> int { return 0 } -@(private) +@(private, require_results) _processor_core_count :: proc() -> int { return 1 } -file_size :: proc(fd: Handle) -> (i64, Errno) { - stat, err := wasi.fd_filestat_get(wasi.fd_t(fd)) - if err != nil { - return 0, Errno(err) - } - return i64(stat.size), 0 -} - - - -heap_alloc :: proc(size: int, zero_memory := true) -> rawptr { - return nil -} -heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr { - if new_size == 0 { - heap_free(ptr) - return nil - } - if ptr == nil { - return heap_alloc(new_size) - } - - return nil -} -heap_free :: proc(ptr: rawptr) { - if ptr == nil { - return - } +@(require_results) +file_size :: proc(fd: Handle) -> (size: i64, err: Errno) { + stat := wasi.fd_filestat_get(wasi.fd_t(fd)) or_return + size = i64(stat.size) + return } diff --git a/core/os/os_windows.odin b/core/os/os_windows.odin index 7b4c2f6c2..0c532bf14 100644 --- a/core/os/os_windows.odin +++ b/core/os/os_windows.odin @@ -1,19 +1,16 @@ -// +build windows +#+build windows package os import win32 "core:sys/windows" -import "core:runtime" -import "core:intrinsics" +import "base:runtime" +import "base:intrinsics" +import "core:unicode/utf16" Handle :: distinct uintptr File_Time :: distinct u64 -Errno :: distinct int - INVALID_HANDLE :: ~Handle(0) - - O_RDONLY :: 0x00000 O_WRONLY :: 0x00001 O_RDWR :: 0x00002 @@ -27,92 +24,123 @@ O_SYNC :: 0x01000 O_ASYNC :: 0x02000 O_CLOEXEC :: 0x80000 +_Platform_Error :: win32.System_Error -ERROR_NONE: Errno : 0 -ERROR_FILE_NOT_FOUND: Errno : 2 -ERROR_PATH_NOT_FOUND: Errno : 3 -ERROR_ACCESS_DENIED: Errno : 5 -ERROR_INVALID_HANDLE: Errno : 6 -ERROR_NOT_ENOUGH_MEMORY: Errno : 8 -ERROR_NO_MORE_FILES: Errno : 18 -ERROR_HANDLE_EOF: Errno : 38 -ERROR_NETNAME_DELETED: Errno : 64 -ERROR_FILE_EXISTS: Errno : 80 -ERROR_INVALID_PARAMETER: Errno : 87 -ERROR_BROKEN_PIPE: Errno : 109 -ERROR_BUFFER_OVERFLOW: Errno : 111 -ERROR_INSUFFICIENT_BUFFER: Errno : 122 -ERROR_MOD_NOT_FOUND: Errno : 126 -ERROR_PROC_NOT_FOUND: Errno : 127 -ERROR_DIR_NOT_EMPTY: Errno : 145 -ERROR_ALREADY_EXISTS: Errno : 183 -ERROR_ENVVAR_NOT_FOUND: Errno : 203 -ERROR_MORE_DATA: Errno : 234 -ERROR_OPERATION_ABORTED: Errno : 995 -ERROR_IO_PENDING: Errno : 997 -ERROR_NOT_FOUND: Errno : 1168 -ERROR_PRIVILEGE_NOT_HELD: Errno : 1314 -WSAEACCES: Errno : 10013 -WSAECONNRESET: Errno : 10054 +ERROR_FILE_NOT_FOUND :: _Platform_Error(2) +ERROR_PATH_NOT_FOUND :: _Platform_Error(3) +ERROR_ACCESS_DENIED :: _Platform_Error(5) +ERROR_INVALID_HANDLE :: _Platform_Error(6) +ERROR_NOT_ENOUGH_MEMORY :: _Platform_Error(8) +ERROR_NO_MORE_FILES :: _Platform_Error(18) +ERROR_HANDLE_EOF :: _Platform_Error(38) +ERROR_NETNAME_DELETED :: _Platform_Error(64) +ERROR_FILE_EXISTS :: _Platform_Error(80) +ERROR_INVALID_PARAMETER :: _Platform_Error(87) +ERROR_BROKEN_PIPE :: _Platform_Error(109) +ERROR_BUFFER_OVERFLOW :: _Platform_Error(111) +ERROR_INSUFFICIENT_BUFFER :: _Platform_Error(122) +ERROR_MOD_NOT_FOUND :: _Platform_Error(126) +ERROR_PROC_NOT_FOUND :: _Platform_Error(127) +ERROR_NEGATIVE_SEEK :: _Platform_Error(131) +ERROR_DIR_NOT_EMPTY :: _Platform_Error(145) +ERROR_ALREADY_EXISTS :: _Platform_Error(183) +ERROR_ENVVAR_NOT_FOUND :: _Platform_Error(203) +ERROR_MORE_DATA :: _Platform_Error(234) +ERROR_OPERATION_ABORTED :: _Platform_Error(995) +ERROR_IO_PENDING :: _Platform_Error(997) +ERROR_NOT_FOUND :: _Platform_Error(1168) +ERROR_PRIVILEGE_NOT_HELD :: _Platform_Error(1314) +WSAEACCES :: _Platform_Error(10013) +WSAECONNRESET :: _Platform_Error(10054) -// Windows reserves errors >= 1<<29 for application use -ERROR_FILE_IS_PIPE: Errno : 1<<29 + 0 -ERROR_FILE_IS_NOT_DIR: Errno : 1<<29 + 1 -ERROR_NEGATIVE_OFFSET: Errno : 1<<29 + 2 +ERROR_FILE_IS_PIPE :: General_Error.File_Is_Pipe +ERROR_FILE_IS_NOT_DIR :: General_Error.Not_Dir // "Argv" arguments converted to Odin strings args := _alloc_command_line_arguments() +@(require_results, no_instrumentation) +get_last_error :: proc "contextless" () -> Error { + err := win32.GetLastError() + if err == 0 { + return nil + } + switch err { + case win32.ERROR_ACCESS_DENIED, win32.ERROR_SHARING_VIOLATION: + return .Permission_Denied + + case win32.ERROR_FILE_EXISTS, win32.ERROR_ALREADY_EXISTS: + return .Exist + + case win32.ERROR_FILE_NOT_FOUND, win32.ERROR_PATH_NOT_FOUND: + return .Not_Exist + + case win32.ERROR_NO_DATA: + return .Closed + + case win32.ERROR_TIMEOUT, win32.WAIT_TIMEOUT: + return .Timeout + + case win32.ERROR_NOT_SUPPORTED: + return .Unsupported + + case win32.ERROR_HANDLE_EOF: + return .EOF + + case win32.ERROR_INVALID_HANDLE: + return .Invalid_File + + case win32.ERROR_NEGATIVE_SEEK: + return .Invalid_Offset + + case + win32.ERROR_BAD_ARGUMENTS, + win32.ERROR_INVALID_PARAMETER, + win32.ERROR_NOT_ENOUGH_MEMORY, + win32.ERROR_NO_MORE_FILES, + win32.ERROR_LOCK_VIOLATION, + win32.ERROR_BROKEN_PIPE, + win32.ERROR_CALL_NOT_IMPLEMENTED, + win32.ERROR_INSUFFICIENT_BUFFER, + win32.ERROR_INVALID_NAME, + win32.ERROR_LOCK_FAILED, + win32.ERROR_ENVVAR_NOT_FOUND, + win32.ERROR_OPERATION_ABORTED, + win32.ERROR_IO_PENDING, + win32.ERROR_NO_UNICODE_TRANSLATION: + // fallthrough + } + return Platform_Error(err) +} - - -last_write_time :: proc(fd: Handle) -> (File_Time, Errno) { +@(require_results) +last_write_time :: proc(fd: Handle) -> (File_Time, Error) { file_info: win32.BY_HANDLE_FILE_INFORMATION if !win32.GetFileInformationByHandle(win32.HANDLE(fd), &file_info) { - return 0, Errno(win32.GetLastError()) + return 0, get_last_error() } lo := File_Time(file_info.ftLastWriteTime.dwLowDateTime) hi := File_Time(file_info.ftLastWriteTime.dwHighDateTime) - return lo | hi << 32, ERROR_NONE + return lo | hi << 32, nil } -last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) { +@(require_results) +last_write_time_by_name :: proc(name: string) -> (File_Time, Error) { data: win32.WIN32_FILE_ATTRIBUTE_DATA wide_path := win32.utf8_to_wstring(name) if !win32.GetFileAttributesExW(wide_path, win32.GetFileExInfoStandard, &data) { - return 0, Errno(win32.GetLastError()) + return 0, get_last_error() } l := File_Time(data.ftLastWriteTime.dwLowDateTime) h := File_Time(data.ftLastWriteTime.dwHighDateTime) - return l | h << 32, ERROR_NONE + return l | h << 32, nil } - -heap_alloc :: proc(size: int, zero_memory := true) -> rawptr { - return win32.HeapAlloc(win32.GetProcessHeap(), win32.HEAP_ZERO_MEMORY if zero_memory else 0, uint(size)) -} -heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr { - if new_size == 0 { - heap_free(ptr) - return nil - } - if ptr == nil { - return heap_alloc(new_size) - } - - return win32.HeapReAlloc(win32.GetProcessHeap(), win32.HEAP_ZERO_MEMORY, ptr, uint(new_size)) -} -heap_free :: proc(ptr: rawptr) { - if ptr == nil { - return - } - win32.HeapFree(win32.GetProcessHeap(), 0, ptr) -} - +@(require_results) get_page_size :: proc() -> int { // NOTE(tetra): The page size never changes, so why do anything complicated // if we don't have to. @@ -127,7 +155,7 @@ get_page_size :: proc() -> int { return page_size } -@(private) +@(private, require_results) _processor_core_count :: proc() -> int { length : win32.DWORD = 0 result := win32.GetLogicalProcessorInformation(nil, &length) @@ -158,12 +186,14 @@ exit :: proc "contextless" (code: int) -> ! { +@(require_results) current_thread_id :: proc "contextless" () -> int { return int(win32.GetCurrentThreadId()) } +@(require_results) _alloc_command_line_arguments :: proc() -> []string { arg_count: i32 arg_list_ptr := win32.CommandLineToArgvW(win32.GetCommandLineW(), &arg_count) @@ -197,44 +227,629 @@ _alloc_command_line_arguments :: proc() -> []string { */ WINDOWS_11_BUILD_CUTOFF :: 22_000 -get_windows_version_w :: proc() -> win32.OSVERSIONINFOEXW { +@(require_results) +get_windows_version_w :: proc "contextless" () -> win32.OSVERSIONINFOEXW { osvi : win32.OSVERSIONINFOEXW osvi.dwOSVersionInfoSize = size_of(win32.OSVERSIONINFOEXW) - win32.RtlGetVersion(&osvi) - return osvi + win32.RtlGetVersion(&osvi) + return osvi } -is_windows_xp :: proc() -> bool { +@(require_results) +is_windows_xp :: proc "contextless" () -> bool { osvi := get_windows_version_w() return (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1) } -is_windows_vista :: proc() -> bool { +@(require_results) +is_windows_vista :: proc "contextless" () -> bool { osvi := get_windows_version_w() return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0) } -is_windows_7 :: proc() -> bool { +@(require_results) +is_windows_7 :: proc "contextless" () -> bool { osvi := get_windows_version_w() return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1) } -is_windows_8 :: proc() -> bool { +@(require_results) +is_windows_8 :: proc "contextless" () -> bool { osvi := get_windows_version_w() return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 2) } -is_windows_8_1 :: proc() -> bool { +@(require_results) +is_windows_8_1 :: proc "contextless" () -> bool { osvi := get_windows_version_w() return (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 3) } -is_windows_10 :: proc() -> bool { +@(require_results) +is_windows_10 :: proc "contextless" () -> bool { osvi := get_windows_version_w() return (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber < WINDOWS_11_BUILD_CUTOFF) } -is_windows_11 :: proc() -> bool { +@(require_results) +is_windows_11 :: proc "contextless" () -> bool { osvi := get_windows_version_w() return (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0 && osvi.dwBuildNumber >= WINDOWS_11_BUILD_CUTOFF) } + +@(require_results) +is_path_separator :: proc(c: byte) -> bool { + return c == '/' || c == '\\' +} + +@(require_results) +open :: proc(path: string, mode: int = O_RDONLY, perm: int = 0) -> (Handle, Error) { + if len(path) == 0 { + return INVALID_HANDLE, General_Error.Not_Exist + } + + access: u32 + switch mode & (O_RDONLY|O_WRONLY|O_RDWR) { + case O_RDONLY: access = win32.FILE_GENERIC_READ + case O_WRONLY: access = win32.FILE_GENERIC_WRITE + case O_RDWR: access = win32.FILE_GENERIC_READ | win32.FILE_GENERIC_WRITE + } + + if mode&O_CREATE != 0 { + access |= win32.FILE_GENERIC_WRITE + } + if mode&O_APPEND != 0 { + access &~= win32.FILE_GENERIC_WRITE + access |= win32.FILE_APPEND_DATA + } + + share_mode := win32.FILE_SHARE_READ|win32.FILE_SHARE_WRITE + sa: ^win32.SECURITY_ATTRIBUTES = nil + sa_inherit := win32.SECURITY_ATTRIBUTES{nLength = size_of(win32.SECURITY_ATTRIBUTES), bInheritHandle = true} + if mode&O_CLOEXEC == 0 { + sa = &sa_inherit + } + + create_mode: u32 + switch { + case mode&(O_CREATE|O_EXCL) == (O_CREATE | O_EXCL): + create_mode = win32.CREATE_NEW + case mode&(O_CREATE|O_TRUNC) == (O_CREATE | O_TRUNC): + create_mode = win32.CREATE_ALWAYS + case mode&O_CREATE == O_CREATE: + create_mode = win32.OPEN_ALWAYS + case mode&O_TRUNC == O_TRUNC: + create_mode = win32.TRUNCATE_EXISTING + case: + create_mode = win32.OPEN_EXISTING + } + wide_path := win32.utf8_to_wstring(path) + handle := Handle(win32.CreateFileW(wide_path, access, share_mode, sa, create_mode, win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS, nil)) + if handle != INVALID_HANDLE { + return handle, nil + } + + return INVALID_HANDLE, get_last_error() +} + +close :: proc(fd: Handle) -> Error { + if !win32.CloseHandle(win32.HANDLE(fd)) { + return get_last_error() + } + return nil +} + +flush :: proc(fd: Handle) -> (err: Error) { + if !win32.FlushFileBuffers(win32.HANDLE(fd)) { + err = get_last_error() + } + return +} + + + +write :: proc(fd: Handle, data: []byte) -> (int, Error) { + if len(data) == 0 { + return 0, nil + } + + single_write_length: win32.DWORD + total_write: i64 + length := i64(len(data)) + + for total_write < length { + remaining := length - total_write + to_write := win32.DWORD(min(i32(remaining), MAX_RW)) + + e := win32.WriteFile(win32.HANDLE(fd), &data[total_write], to_write, &single_write_length, nil) + if single_write_length <= 0 || !e { + return int(total_write), get_last_error() + } + total_write += i64(single_write_length) + } + return int(total_write), nil +} + +@(private="file", require_results) +read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) { + if len(b) == 0 { + return 0, nil + } + + BUF_SIZE :: 386 + buf16: [BUF_SIZE]u16 + buf8: [4*BUF_SIZE]u8 + + for n < len(b) && err == nil { + min_read := max(len(b)/4, 1 if len(b) > 0 else 0) + max_read := u32(min(BUF_SIZE, min_read)) + if max_read == 0 { + break + } + + single_read_length: u32 + ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil) + if !ok { + err = get_last_error() + } + + buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length]) + src := buf8[:buf8_len] + + ctrl_z := false + for i := 0; i < len(src) && n < len(b); i += 1 { + x := src[i] + if x == 0x1a { // ctrl-z + ctrl_z = true + break + } + b[n] = x + n += 1 + } + if ctrl_z || single_read_length < max_read { + break + } + + // NOTE(bill): if the last two values were a newline, then it is expected that + // this is the end of the input + if n >= 2 && single_read_length == max_read && string(b[n-2:n]) == "\r\n" { + break + } + + } + + return +} + +read :: proc(fd: Handle, data: []byte) -> (total_read: int, err: Error) { + if len(data) == 0 { + return 0, nil + } + + handle := win32.HANDLE(fd) + + m: u32 + is_console := win32.GetConsoleMode(handle, &m) + length := len(data) + + // NOTE(Jeroen): `length` can't be casted to win32.DWORD here because it'll overflow if > 4 GiB and return 0 if exactly that. + to_read := min(i64(length), MAX_RW) + + if is_console { + total_read, err = read_console(handle, data[total_read:][:to_read]) + if err != nil { + return total_read, err + } + } else { + // NOTE(Jeroen): So we cast it here *after* we've ensured that `to_read` is at most MAX_RW (1 GiB) + bytes_read: win32.DWORD + if e := win32.ReadFile(handle, &data[total_read], win32.DWORD(to_read), &bytes_read, nil); e { + // Successful read can mean two things, including EOF, see: + // https://learn.microsoft.com/en-us/windows/win32/fileio/testing-for-the-end-of-a-file + if bytes_read == 0 { + return 0, .EOF + } else { + return int(bytes_read), nil + } + } else { + return 0, get_last_error() + } + } + return total_read, nil +} + +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { + w: u32 + switch whence { + case 0: w = win32.FILE_BEGIN + case 1: w = win32.FILE_CURRENT + case 2: w = win32.FILE_END + case: + return 0, .Invalid_Whence + } + hi := i32(offset>>32) + lo := i32(offset) + ft := win32.GetFileType(win32.HANDLE(fd)) + if ft == win32.FILE_TYPE_PIPE { + return 0, .File_Is_Pipe + } + + dw_ptr := win32.SetFilePointer(win32.HANDLE(fd), lo, &hi, w) + if dw_ptr == win32.INVALID_SET_FILE_POINTER { + err := get_last_error() + return 0, err + } + return i64(hi)<<32 + i64(dw_ptr), nil +} + +@(require_results) +file_size :: proc(fd: Handle) -> (i64, Error) { + length: win32.LARGE_INTEGER + err: Error + if !win32.GetFileSizeEx(win32.HANDLE(fd), &length) { + err = get_last_error() + } + return i64(length), err +} + + +@(private) +MAX_RW :: 1<<30 + +@(private) +pread :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + curr_off := seek(fd, 0, 1) or_return + defer seek(fd, curr_off, 0) + + buf := data + if len(buf) > MAX_RW { + buf = buf[:MAX_RW] + } + + o := win32.OVERLAPPED{ + OffsetHigh = u32(offset>>32), + Offset = u32(offset), + } + + // TODO(bill): Determine the correct behaviour for consoles + + h := win32.HANDLE(fd) + done: win32.DWORD + e: Error + if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) { + e = get_last_error() + done = 0 + } + return int(done), e +} +@(private) +pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + curr_off := seek(fd, 0, 1) or_return + defer seek(fd, curr_off, 0) + + buf := data + if len(buf) > MAX_RW { + buf = buf[:MAX_RW] + } + + o := win32.OVERLAPPED{ + OffsetHigh = u32(offset>>32), + Offset = u32(offset), + } + + h := win32.HANDLE(fd) + done: win32.DWORD + e: Error + if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) { + e = get_last_error() + done = 0 + } + return int(done), e +} + +/* +read_at returns n: 0, err: 0 on EOF +*/ +read_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + if offset < 0 { + return 0, .Invalid_Offset + } + + b, offset := data, offset + for len(b) > 0 { + m, e := pread(fd, b, offset) + if e == ERROR_EOF { + err = nil + break + } + if e != nil { + err = e + break + } + n += m + b = b[m:] + offset += i64(m) + } + return +} + +write_at :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { + if offset < 0 { + return 0, .Invalid_Offset + } + + b, offset := data, offset + for len(b) > 0 { + m := pwrite(fd, b, offset) or_return + n += m + b = b[m:] + offset += i64(m) + } + return +} + + + +// 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)) + + +@(require_results) +get_std_handle :: proc "contextless" (h: uint) -> Handle { + fd := win32.GetStdHandle(win32.DWORD(h)) + return Handle(fd) +} + + +exists :: proc(path: string) -> bool { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + wpath := win32.utf8_to_wstring(path, context.temp_allocator) + attribs := win32.GetFileAttributesW(wpath) + + return attribs != win32.INVALID_FILE_ATTRIBUTES +} + +@(require_results) +is_file :: proc(path: string) -> bool { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + wpath := win32.utf8_to_wstring(path, context.temp_allocator) + attribs := win32.GetFileAttributesW(wpath) + + if attribs != win32.INVALID_FILE_ATTRIBUTES { + return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == 0 + } + return false +} + +@(require_results) +is_dir :: proc(path: string) -> bool { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + wpath := win32.utf8_to_wstring(path, context.temp_allocator) + attribs := win32.GetFileAttributesW(wpath) + + if attribs != win32.INVALID_FILE_ATTRIBUTES { + return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != 0 + } + return false +} + +// NOTE(tetra): GetCurrentDirectory is not thread safe with SetCurrentDirectory and GetFullPathName +@private cwd_lock := win32.SRWLOCK{} // zero is initialized + +@(require_results) +get_current_directory :: proc(allocator := context.allocator) -> string { + win32.AcquireSRWLockExclusive(&cwd_lock) + + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) + + sz_utf16 := win32.GetCurrentDirectoryW(0, nil) + dir_buf_wstr, _ := make([]u16, sz_utf16, context.temp_allocator) // the first time, it _includes_ the NUL. + + sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr)) + assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL. + + win32.ReleaseSRWLockExclusive(&cwd_lock) + + return win32.utf16_to_utf8(dir_buf_wstr, allocator) or_else "" +} + +set_current_directory :: proc(path: string) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + wstr := win32.utf8_to_wstring(path, context.temp_allocator) + + win32.AcquireSRWLockExclusive(&cwd_lock) + + if !win32.SetCurrentDirectoryW(wstr) { + err = get_last_error() + } + + win32.ReleaseSRWLockExclusive(&cwd_lock) + + return +} +change_directory :: set_current_directory + +make_directory :: proc(path: string, mode: u32 = 0) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + // Mode is unused on Windows, but is needed on *nix + wpath := win32.utf8_to_wstring(path, context.temp_allocator) + + if !win32.CreateDirectoryW(wpath, nil) { + err = get_last_error() + } + return +} + + +remove_directory :: proc(path: string) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + wpath := win32.utf8_to_wstring(path, context.temp_allocator) + + if !win32.RemoveDirectoryW(wpath) { + err = get_last_error() + } + return +} + + + +@(private, require_results) +is_abs :: proc(path: string) -> bool { + if len(path) > 0 && path[0] == '/' { + return true + } + when ODIN_OS == .Windows { + if len(path) > 2 { + switch path[0] { + case 'A'..='Z', 'a'..='z': + return path[1] == ':' && is_path_separator(path[2]) + } + } + } + return false +} + +@(private, require_results) +fix_long_path :: proc(path: string) -> string { + if len(path) < 248 { + return path + } + + if len(path) >= 2 && path[:2] == `\\` { + return path + } + if !is_abs(path) { + return path + } + + prefix :: `\\?` + + path_buf, _ := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator) + copy(path_buf, prefix) + n := len(path) + r, w := 0, len(prefix) + for r < n { + switch { + case is_path_separator(path[r]): + r += 1 + case path[r] == '.' && (r+1 == n || is_path_separator(path[r+1])): + r += 1 + case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_path_separator(path[r+2])): + return path + case: + path_buf[w] = '\\' + w += 1 + for ; r < n && !is_path_separator(path[r]); r += 1 { + path_buf[w] = path[r] + w += 1 + } + } + } + + if w == len(`\\?\c:`) { + path_buf[w] = '\\' + w += 1 + } + return string(path_buf[:w]) +} + + +link :: proc(old_name, new_name: string) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + n := win32.utf8_to_wstring(fix_long_path(new_name)) + o := win32.utf8_to_wstring(fix_long_path(old_name)) + return Platform_Error(win32.CreateHardLinkW(n, o, nil)) +} + +unlink :: proc(path: string) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + wpath := win32.utf8_to_wstring(path, context.temp_allocator) + + if !win32.DeleteFileW(wpath) { + err = get_last_error() + } + return +} + + + +rename :: proc(old_path, new_path: string) -> (err: Error) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + from := win32.utf8_to_wstring(old_path, context.temp_allocator) + to := win32.utf8_to_wstring(new_path, context.temp_allocator) + + if !win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) { + err = get_last_error() + } + return +} + + +ftruncate :: proc(fd: Handle, length: i64) -> (err: Error) { + curr_off := seek(fd, 0, 1) or_return + defer seek(fd, curr_off, 0) + _= seek(fd, length, 0) or_return + ok := win32.SetEndOfFile(win32.HANDLE(fd)) + if !ok { + return get_last_error() + } + return nil +} + +truncate :: proc(path: string, length: i64) -> (err: Error) { + fd := open(path, O_WRONLY|O_CREATE, 0o666) or_return + defer close(fd) + return ftruncate(fd, length) +} + + +remove :: proc(name: string) -> Error { + p := win32.utf8_to_wstring(fix_long_path(name)) + err, err1: win32.DWORD + if !win32.DeleteFileW(p) { + err = win32.GetLastError() + } + if err == 0 { + return nil + } + if !win32.RemoveDirectoryW(p) { + err1 = win32.GetLastError() + } + if err1 == 0 { + return nil + } + + if err != err1 { + a := win32.GetFileAttributesW(p) + if a == ~u32(0) { + err = win32.GetLastError() + } else { + if a & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { + err = err1 + } else if a & win32.FILE_ATTRIBUTE_READONLY != 0 { + if win32.SetFileAttributesW(p, a &~ win32.FILE_ATTRIBUTE_READONLY) { + err = 0 + if !win32.DeleteFileW(p) { + err = win32.GetLastError() + } + } + } + } + } + + return Platform_Error(err) +} + + +@(require_results) +pipe :: proc() -> (r, w: Handle, err: Error) { + sa: win32.SECURITY_ATTRIBUTES + sa.nLength = size_of(win32.SECURITY_ATTRIBUTES) + sa.bInheritHandle = true + if !win32.CreatePipe((^win32.HANDLE)(&r), (^win32.HANDLE)(&w), &sa, 0) { + err = get_last_error() + } + return +} \ No newline at end of file diff --git a/core/os/stat.odin b/core/os/stat.odin index 1b64ad33b..21a4961d1 100644 --- a/core/os/stat.odin +++ b/core/os/stat.odin @@ -3,8 +3,8 @@ package os import "core:time" File_Info :: struct { - fullpath: string, - name: string, + fullpath: string, // allocated + name: string, // uses `fullpath` as underlying data size: i64, mode: File_Mode, is_dir: bool, diff --git a/core/os/stat_unix.odin b/core/os/stat_unix.odin index dae7ab2fb..7f7985e83 100644 --- a/core/os/stat_unix.odin +++ b/core/os/stat_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd +#+build linux, darwin, freebsd, openbsd, netbsd, haiku package os import "core:time" @@ -50,14 +50,14 @@ File_Info :: struct { } */ -@private +@(private, require_results) _make_time_from_unix_file_time :: proc(uft: Unix_File_Time) -> time.Time { return time.Time{ _nsec = uft.nanoseconds + uft.seconds * 1_000_000_000, } } -@private +@(private) _fill_file_info_from_stat :: proc(fi: ^File_Info, s: OS_Stat) { fi.size = s.size fi.mode = cast(File_Mode)s.mode @@ -71,7 +71,7 @@ _fill_file_info_from_stat :: proc(fi: ^File_Info, s: OS_Stat) { } -@private +@(private, require_results) path_base :: proc(path: string) -> string { is_separator :: proc(c: byte) -> bool { return c == '/' @@ -100,55 +100,35 @@ path_base :: proc(path: string) -> string { } -lstat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Errno) { - +@(require_results) +lstat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Error) { context.allocator = allocator - s: OS_Stat - s, err = _lstat(name) - if err != ERROR_NONE { - return fi, err - } + s := _lstat(name) or_return _fill_file_info_from_stat(&fi, s) - fi.fullpath, err = absolute_path_from_relative(name) - if err != ERROR_NONE { - return - } + fi.fullpath = absolute_path_from_relative(name) or_return fi.name = path_base(fi.fullpath) - return fi, ERROR_NONE + return } -stat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Errno) { +@(require_results) +stat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Error) { context.allocator = allocator - s: OS_Stat - s, err = _stat(name) - if err != ERROR_NONE { - return fi, err - } + s := _stat(name) or_return _fill_file_info_from_stat(&fi, s) - fi.fullpath, err = absolute_path_from_relative(name) - if err != ERROR_NONE { - return - } + fi.fullpath = absolute_path_from_relative(name) or_return fi.name = path_base(fi.fullpath) - return fi, ERROR_NONE + return } -fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err: Errno) { - +@(require_results) +fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err: Error) { context.allocator = allocator - s: OS_Stat - s, err = _fstat(fd) - if err != ERROR_NONE { - return fi, err - } + s := _fstat(fd) or_return _fill_file_info_from_stat(&fi, s) - fi.fullpath, err = absolute_path_from_handle(fd) - if err != ERROR_NONE { - return - } + fi.fullpath = absolute_path_from_handle(fd) or_return fi.name = path_base(fi.fullpath) - return fi, ERROR_NONE + return } diff --git a/core/os/stat_windows.odin b/core/os/stat_windows.odin index efea329ce..ca4f87668 100644 --- a/core/os/stat_windows.odin +++ b/core/os/stat_windows.odin @@ -1,10 +1,10 @@ package os import "core:time" -import "core:runtime" +import "base:runtime" import win32 "core:sys/windows" -@(private) +@(private, require_results) full_path_from_name :: proc(name: string, allocator := context.allocator) -> (path: string, err: Errno) { context.allocator = allocator @@ -19,10 +19,10 @@ full_path_from_name :: proc(name: string, allocator := context.allocator) -> (pa for { n := win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil) if n == 0 { - return "", Errno(win32.GetLastError()) + return "", get_last_error() } if n <= u32(len(buf)) { - return win32.utf16_to_utf8(buf[:n], allocator) or_else "", ERROR_NONE + return win32.utf16_to_utf8(buf[:n], allocator) or_else "", nil } resize(&buf, len(buf)*2) } @@ -30,7 +30,7 @@ full_path_from_name :: proc(name: string, allocator := context.allocator) -> (pa return } -@(private) +@(private, require_results) _stat :: proc(name: string, create_file_attributes: u32, allocator := context.allocator) -> (fi: File_Info, e: Errno) { if len(name) == 0 { return {}, ERROR_PATH_NOT_FOUND @@ -54,7 +54,7 @@ _stat :: proc(name: string, create_file_attributes: u32, allocator := context.al fd: win32.WIN32_FIND_DATAW sh := win32.FindFirstFileW(wname, &fd) if sh == win32.INVALID_HANDLE_VALUE { - e = Errno(win32.GetLastError()) + e = get_last_error() return } win32.FindClose(sh) @@ -64,7 +64,7 @@ _stat :: proc(name: string, create_file_attributes: u32, allocator := context.al h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil) if h == win32.INVALID_HANDLE_VALUE { - e = Errno(win32.GetLastError()) + e = get_last_error() return } defer win32.CloseHandle(h) @@ -72,26 +72,29 @@ _stat :: proc(name: string, create_file_attributes: u32, allocator := context.al } +@(require_results) lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Errno) { attrs := win32.FILE_FLAG_BACKUP_SEMANTICS attrs |= win32.FILE_FLAG_OPEN_REPARSE_POINT return _stat(name, attrs, allocator) } +@(require_results) stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Errno) { attrs := win32.FILE_FLAG_BACKUP_SEMANTICS return _stat(name, attrs, allocator) } -fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, errno: Errno) { +@(require_results) +fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err: Errno) { if fd == 0 { - return {}, ERROR_INVALID_HANDLE + err = ERROR_INVALID_HANDLE } context.allocator = allocator - path, err := cleanpath_from_handle(fd) - if err != ERROR_NONE { - return {}, err + path := cleanpath_from_handle(fd) or_return + defer if err != nil { + delete(path) } h := win32.HANDLE(fd) @@ -99,16 +102,16 @@ fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR: fi.name = basename(path) fi.mode |= file_type_mode(h) - errno = ERROR_NONE + err = nil case: - fi, errno = file_info_from_get_file_information_by_handle(path, h) + fi = file_info_from_get_file_information_by_handle(path, h) or_return } fi.fullpath = path return } -@(private) +@(private, require_results) cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { buf := buf N := 0 @@ -133,16 +136,13 @@ cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { return buf } -@(private) -cleanpath_from_handle :: proc(fd: Handle) -> (string, Errno) { +@(private, require_results) +cleanpath_from_handle :: proc(fd: Handle) -> (s: string, err: Errno) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) - buf, err := cleanpath_from_handle_u16(fd, context.temp_allocator) - if err != 0 { - return "", err - } - return win32.utf16_to_utf8(buf, context.allocator) or_else "", err + buf := cleanpath_from_handle_u16(fd, context.temp_allocator) or_return + return win32.utf16_to_utf8(buf, context.allocator) } -@(private) +@(private, require_results) cleanpath_from_handle_u16 :: proc(fd: Handle, allocator: runtime.Allocator) -> ([]u16, Errno) { if fd == 0 { return nil, ERROR_INVALID_HANDLE @@ -151,20 +151,20 @@ cleanpath_from_handle_u16 :: proc(fd: Handle, allocator: runtime.Allocator) -> ( n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0) if n == 0 { - return nil, Errno(win32.GetLastError()) + return nil, get_last_error() } buf := make([]u16, max(n, win32.DWORD(260))+1, allocator) buf_len := win32.GetFinalPathNameByHandleW(h, raw_data(buf), n, 0) - return buf[:buf_len], ERROR_NONE + return buf[:buf_len], nil } -@(private) +@(private, require_results) cleanpath_from_buf :: proc(buf: []u16) -> string { buf := buf buf = cleanpath_strip_prefix(buf) return win32.utf16_to_utf8(buf, context.allocator) or_else "" } -@(private) +@(private, require_results) basename :: proc(name: string) -> (base: string) { name := name if len(name) > 3 && name[:3] == `\\?` { @@ -190,7 +190,7 @@ basename :: proc(name: string) -> (base: string) { return name } -@(private) +@(private, require_results) file_type_mode :: proc(h: win32.HANDLE) -> File_Mode { switch win32.GetFileType(h) { case win32.FILE_TYPE_PIPE: @@ -202,7 +202,7 @@ file_type_mode :: proc(h: win32.HANDLE) -> File_Mode { } -@(private) +@(private, require_results) file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) { if FileAttributes & win32.FILE_ATTRIBUTE_READONLY != 0 { mode |= 0o444 @@ -239,7 +239,7 @@ windows_set_file_info_times :: proc(fi: ^File_Info, d: ^$T) { fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) } -@(private) +@(private, require_results) file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string) -> (fi: File_Info, e: Errno) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) @@ -254,7 +254,7 @@ file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_ return } -@(private) +@(private, require_results) file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) -> (fi: File_Info, e: Errno) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) @@ -269,20 +269,20 @@ file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) return } -@(private) +@(private, require_results) file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE) -> (File_Info, Errno) { d: win32.BY_HANDLE_FILE_INFORMATION if !win32.GetFileInformationByHandle(h, &d) { - err := Errno(win32.GetLastError()) + err := get_last_error() return {}, err } ti: win32.FILE_ATTRIBUTE_TAG_INFO if !win32.GetFileInformationByHandleEx(h, .FileAttributeTagInfo, &ti, size_of(ti)) { - err := win32.GetLastError() - if err != u32(ERROR_INVALID_PARAMETER) { - return {}, Errno(err) + err := get_last_error() + if err != ERROR_INVALID_PARAMETER { + return {}, err } // Indicate this is a symlink on FAT file systems ti.ReparseTag = 0 @@ -299,5 +299,5 @@ file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HAN windows_set_file_info_times(&fi, &d) - return fi, ERROR_NONE + return fi, nil } diff --git a/core/os/stream.odin b/core/os/stream.odin index 2b4c83663..39edc9cd5 100644 --- a/core/os/stream.odin +++ b/core/os/stream.odin @@ -14,34 +14,48 @@ stream_from_handle :: proc(fd: Handle) -> io.Stream { _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { fd := Handle(uintptr(stream_data)) n_int: int - os_err: Errno + os_err: Error switch mode { case .Close: - close(fd) + os_err = close(fd) case .Flush: - when ODIN_OS == .Windows { - flush(fd) - } else { - // TOOD(bill): other operating systems - } + os_err = flush(fd) case .Read: + if len(p) == 0 { + return 0, nil + } n_int, os_err = read(fd, p) n = i64(n_int) - if os_err != 0 { - err = .Unknown + if n == 0 && os_err == nil { + err = .EOF } + case .Read_At: - when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD) { - n_int, os_err = read_at(fd, p, offset) - n = i64(n_int) + if len(p) == 0 { + return 0, nil + } + n_int, os_err = read_at(fd, p, offset) + n = i64(n_int) + if n == 0 && os_err == nil { + err = .EOF } case .Write: + if len(p) == 0 { + return 0, nil + } n_int, os_err = write(fd, p) n = i64(n_int) + if n == 0 && os_err == nil { + err = .EOF + } case .Write_At: - when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD) { - n_int, os_err = write_at(fd, p, offset) - n = i64(n_int) + if len(p) == 0 { + return 0, nil + } + n_int, os_err = write_at(fd, p, offset) + n = i64(n_int) + if n == 0 && os_err == nil { + err = .EOF } case .Seek: n, os_err = seek(fd, offset, int(whence)) @@ -50,14 +64,14 @@ _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 { - return io.query_utility({.Close, .Flush, .Read, .Write, .Seek, .Size, .Query}) - } else { - return io.query_utility({.Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query}) - } + return io.query_utility({.Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query}) } - if err == nil && os_err != 0 { - err = .Unknown + + if err == nil && os_err != nil { + err = error_to_io_error(os_err) + } + if err != nil { + n = 0 } return } diff --git a/core/path/filepath/match.odin b/core/path/filepath/match.odin index 1279bdd84..003f8046d 100644 --- a/core/path/filepath/match.odin +++ b/core/path/filepath/match.odin @@ -246,6 +246,13 @@ glob :: proc(pattern: string, allocator := context.allocator) -> (matches: []str if err != .None { return } + defer { + for s in m { + delete(s) + } + delete(m) + } + dmatches := make([dynamic]string, 0, 0) for d in m { dmatches, err = _glob(d, file, &dmatches) @@ -271,7 +278,7 @@ _glob :: proc(dir, pattern: string, matches: ^[dynamic]string, allocator := cont d, derr := os.open(dir, os.O_RDONLY) - if derr != 0 { + if derr != nil { return } defer os.close(d) @@ -280,7 +287,7 @@ _glob :: proc(dir, pattern: string, matches: ^[dynamic]string, allocator := cont file_info, ferr := os.fstat(d) defer os.file_info_delete(file_info) - if ferr != 0 { + if ferr != nil { return } if !file_info.is_dir { diff --git a/core/path/filepath/path.odin b/core/path/filepath/path.odin index 21183e0f7..e23183b02 100644 --- a/core/path/filepath/path.odin +++ b/core/path/filepath/path.odin @@ -2,6 +2,7 @@ // To process paths such as URLs that depend on forward slashes regardless of the OS, use the path package package filepath +import "base:runtime" import "core:strings" SEPARATOR_CHARS :: `/\` @@ -244,7 +245,7 @@ long_ext :: proc(path: string) -> string { If the result of the path is an empty string, the returned path with be `"."`. */ -clean :: proc(path: string, allocator := context.allocator) -> string { +clean :: proc(path: string, allocator := context.allocator) -> (cleaned: string, err: runtime.Allocator_Error) #optional_allocator_error { context.allocator = allocator path := path @@ -256,9 +257,9 @@ clean :: proc(path: string, allocator := context.allocator) -> string { if vol_len > 1 && original_path[1] != ':' { s, ok := from_slash(original_path) if !ok { - s = strings.clone(s) + s = strings.clone(s) or_return } - return s + return s, nil } return strings.concatenate({original_path, "."}) } @@ -275,7 +276,7 @@ clean :: proc(path: string, allocator := context.allocator) -> string { r, dot_dot := 0, 0 if rooted { - lazy_buffer_append(out, SEPARATOR) + lazy_buffer_append(out, SEPARATOR) or_return r, dot_dot = 1, 1 } @@ -295,33 +296,35 @@ clean :: proc(path: string, allocator := context.allocator) -> string { } case !rooted: if out.w > 0 { - lazy_buffer_append(out, SEPARATOR) + lazy_buffer_append(out, SEPARATOR) or_return } - lazy_buffer_append(out, '.') - lazy_buffer_append(out, '.') + lazy_buffer_append(out, '.') or_return + lazy_buffer_append(out, '.') or_return dot_dot = out.w } case: if rooted && out.w != 1 || !rooted && out.w != 0 { - lazy_buffer_append(out, SEPARATOR) + lazy_buffer_append(out, SEPARATOR) or_return } for ; r < n && !is_separator(path[r]); r += 1 { - lazy_buffer_append(out, path[r]) + lazy_buffer_append(out, path[r]) or_return } } } if out.w == 0 { - lazy_buffer_append(out, '.') + lazy_buffer_append(out, '.') or_return } - s := lazy_buffer_string(out) - cleaned, new_allocation := from_slash(s) + s := lazy_buffer_string(out) or_return + + new_allocation: bool + cleaned, new_allocation = from_slash(s) if new_allocation { delete(s) } - return cleaned + return } // Returns the result of replacing each forward slash `/` character in the path with the separate OS specific character. @@ -356,28 +359,24 @@ Relative_Error :: enum { */ rel :: proc(base_path, target_path: string, allocator := context.allocator) -> (string, Relative_Error) { context.allocator = allocator - base_clean, target_clean := clean(base_path), clean(target_path) - - delete_target := true - defer { - if delete_target { - delete(target_clean) - } - delete(base_clean) - } + base_clean := clean(base_path, allocator) + target_clean := clean(target_path, allocator) + defer delete(base_clean, allocator) + defer delete(target_clean, allocator) if strings.equal_fold(target_clean, base_clean) { - return strings.clone("."), .None + return strings.clone(".", allocator), .None } - base_vol, target_vol := volume_name(base_path), volume_name(target_path) - base := base_clean[len(base_vol):] + base_vol := volume_name(base_path) + target_vol := volume_name(target_path) + base := base_clean [len(base_vol):] target := target_clean[len(target_vol):] if base == "." { base = "" } - base_slashed := len(base) > 0 && base[0] == SEPARATOR + base_slashed := len(base) > 0 && base [0] == SEPARATOR target_slashed := len(target) > 0 && target[0] == SEPARATOR if base_slashed != target_slashed || !strings.equal_fold(base_vol, target_vol) { return "", .Cannot_Relate @@ -413,7 +412,7 @@ rel :: proc(base_path, target_path: string, allocator := context.allocator) -> ( if tl != t0 { size += 1 + tl - t0 } - buf := make([]byte, size) + buf := make([]byte, size, allocator) n := copy(buf, "..") for _ in 0.. ( return string(buf), .None } - delete_target = false - return target[t0:], .None + return strings.clone(target[t0:], allocator), .None } /* @@ -437,7 +435,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]) { @@ -458,9 +456,9 @@ dir :: proc(path: string, allocator := context.allocator) -> string { // An empty string returns nil. A non-empty string with no separators returns a 1-element array. // Any empty components will be included, e.g. `a::b` will return a 3-element array, as will `::`. // Separators within pairs of double-quotes will be ignored and stripped, e.g. `"a:b"c:d` will return []{`a:bc`, `d`}. -split_list :: proc(path: string, allocator := context.allocator) -> []string { +split_list :: proc(path: string, allocator := context.allocator) -> (list: []string, err: runtime.Allocator_Error) #optional_allocator_error { if path == "" { - return nil + return nil, nil } start: int @@ -480,7 +478,7 @@ split_list :: proc(path: string, allocator := context.allocator) -> []string { } start, quote = 0, false - list := make([]string, count + 1, allocator) + list = make([]string, count + 1, allocator) or_return index := 0 for i := 0; i < len(path); i += 1 { c := path[i] @@ -499,12 +497,12 @@ split_list :: proc(path: string, allocator := context.allocator) -> []string { for s0, i in list { s, new := strings.replace_all(s0, `"`, ``, allocator) if !new { - s = strings.clone(s, allocator) + s = strings.clone(s, allocator) or_return } list[i] = s } - return list + return list, nil } @@ -531,33 +529,35 @@ lazy_buffer_index :: proc(lb: ^Lazy_Buffer, i: int) -> byte { return lb.s[i] } @(private) -lazy_buffer_append :: proc(lb: ^Lazy_Buffer, c: byte) { +lazy_buffer_append :: proc(lb: ^Lazy_Buffer, c: byte) -> (err: runtime.Allocator_Error) { if lb.b == nil { if lb.w < len(lb.s) && lb.s[lb.w] == c { lb.w += 1 return } - lb.b = make([]byte, len(lb.s)) + lb.b = make([]byte, len(lb.s)) or_return copy(lb.b, lb.s[:lb.w]) } lb.b[lb.w] = c lb.w += 1 + return } @(private) -lazy_buffer_string :: proc(lb: ^Lazy_Buffer) -> string { +lazy_buffer_string :: proc(lb: ^Lazy_Buffer) -> (s: string, err: runtime.Allocator_Error) { if lb.b == nil { return strings.clone(lb.vol_and_path[:lb.vol_len+lb.w]) } x := lb.vol_and_path[:lb.vol_len] y := string(lb.b[:lb.w]) - z := make([]byte, len(x)+len(y)) + z := make([]byte, len(x)+len(y)) or_return copy(z, x) copy(z[len(x):], y) - return string(z) + return string(z), nil } @(private) -lazy_buffer_destroy :: proc(lb: ^Lazy_Buffer) { - delete(lb.b) +lazy_buffer_destroy :: proc(lb: ^Lazy_Buffer) -> runtime.Allocator_Error { + err := delete(lb.b) lb^ = {} + return err } diff --git a/core/path/filepath/path_unix.odin b/core/path/filepath/path_unix.odin index 898f34b6a..35b98a7ae 100644 --- a/core/path/filepath/path_unix.odin +++ b/core/path/filepath/path_unix.odin @@ -1,14 +1,10 @@ -//+build linux, darwin, freebsd, openbsd +#+build linux, darwin, freebsd, openbsd, netbsd package filepath -when ODIN_OS == .Darwin { - foreign import libc "System.framework" -} else { - foreign import libc "system:c" -} +import "base:runtime" -import "core:runtime" import "core:strings" +import "core:sys/posix" SEPARATOR :: '/' SEPARATOR_STRING :: `/` @@ -28,47 +24,23 @@ abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { rel = "." } rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator) - path_ptr := realpath(rel_cstr, nil) + path_ptr := posix.realpath(rel_cstr, nil) if path_ptr == nil { - return "", __error()^ == 0 + return "", posix.errno() == nil } - defer _unix_free(path_ptr) + defer posix.free(path_ptr) - path_cstr := cstring(path_ptr) - path_str := strings.clone(string(path_cstr), allocator) + path_str := strings.clone(string(path_ptr), allocator) return path_str, true } -join :: proc(elems: []string, allocator := context.allocator) -> string { +join :: proc(elems: []string, allocator := context.allocator) -> (joined: string, err: runtime.Allocator_Error) #optional_allocator_error { for e, i in elems { if e != "" { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - p := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) + p := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) or_return return clean(p, allocator) } } - return "" -} - -@(private) -foreign libc { - realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- - @(link_name="free") _unix_free :: proc(ptr: rawptr) --- - -} -when ODIN_OS == .Darwin { - @(private) - foreign libc { - @(link_name="__error") __error :: proc() -> ^i32 --- - } -} else when ODIN_OS == .OpenBSD { - @(private) - foreign libc { - @(link_name="__errno") __error :: proc() -> ^i32 --- - } -} else { - @(private) - foreign libc { - @(link_name="__errno_location") __error :: proc() -> ^i32 --- - } + return "", nil } diff --git a/core/path/filepath/path_windows.odin b/core/path/filepath/path_windows.odin index e7dd4ab3e..0dcb28cf8 100644 --- a/core/path/filepath/path_windows.odin +++ b/core/path/filepath/path_windows.odin @@ -1,7 +1,7 @@ package filepath import "core:strings" -import "core:runtime" +import "base:runtime" import "core:os" import win32 "core:sys/windows" @@ -52,7 +52,7 @@ is_abs :: proc(path: string) -> bool { @(private) -temp_full_path :: proc(name: string) -> (path: string, err: os.Errno) { +temp_full_path :: proc(name: string) -> (path: string, err: os.Error) { ta := context.temp_allocator name := name @@ -63,17 +63,17 @@ temp_full_path :: proc(name: string) -> (path: string, err: os.Errno) { p := win32.utf8_to_utf16(name, ta) n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil) if n == 0 { - return "", os.Errno(win32.GetLastError()) + return "", os.get_last_error() } buf := make([]u16, n, ta) n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil) if n == 0 { delete(buf) - return "", os.Errno(win32.GetLastError()) + return "", os.get_last_error() } - return win32.utf16_to_utf8(buf[:n], ta) or_else "", os.ERROR_NONE + return win32.utf16_to_utf8(buf[:n], ta) } @@ -81,7 +81,7 @@ temp_full_path :: proc(name: string) -> (path: string, err: os.Errno) { abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator) full_path, err := temp_full_path(path) - if err != 0 { + if err != nil { return "", false } p := clean(full_path, allocator) diff --git a/core/path/filepath/walk.odin b/core/path/filepath/walk.odin index 9ba3165dc..51dfa71d2 100644 --- a/core/path/filepath/walk.odin +++ b/core/path/filepath/walk.odin @@ -14,7 +14,7 @@ import "core:slice" // The sole exception is if 'skip_dir' is returned as true: // when 'skip_dir' is invoked on a directory. 'walk' skips directory contents // when 'skip_dir' is invoked on a non-directory. 'walk' skips the remaining files in the containing directory -Walk_Proc :: #type proc(info: os.File_Info, in_err: os.Errno, user_data: rawptr) -> (err: os.Errno, skip_dir: bool) +Walk_Proc :: #type proc(info: os.File_Info, in_err: os.Error, user_data: rawptr) -> (err: os.Error, skip_dir: bool) // walk walks the file tree rooted at 'root', calling 'walk_proc' for each file or directory in the tree, including 'root' // All errors that happen visiting files and directories are filtered by walk_proc @@ -22,44 +22,44 @@ Walk_Proc :: #type proc(info: os.File_Info, in_err: os.Errno, user_data: rawptr) // NOTE: Walking large directories can be inefficient due to the lexical sort // NOTE: walk does not follow symbolic links // NOTE: os.File_Info uses the 'context.temp_allocator' to allocate, and will delete when it is done -walk :: proc(root: string, walk_proc: Walk_Proc, user_data: rawptr) -> os.Errno { +walk :: proc(root: string, walk_proc: Walk_Proc, user_data: rawptr) -> os.Error { info, err := os.lstat(root, context.temp_allocator) defer os.file_info_delete(info, context.temp_allocator) skip_dir: bool - if err != 0 { + if err != nil { err, skip_dir = walk_proc(info, err, user_data) } else { err, skip_dir = _walk(info, walk_proc, user_data) } - return 0 if skip_dir else err + return nil if skip_dir else err } @(private) -_walk :: proc(info: os.File_Info, walk_proc: Walk_Proc, user_data: rawptr) -> (err: os.Errno, skip_dir: bool) { +_walk :: proc(info: os.File_Info, walk_proc: Walk_Proc, user_data: rawptr) -> (err: os.Error, skip_dir: bool) { if !info.is_dir { if info.fullpath == "" && info.name == "" { // ignore empty things return } - return walk_proc(info, 0, user_data) + return walk_proc(info, nil, user_data) } fis: []os.File_Info - err1: os.Errno + err1: os.Error fis, err = read_dir(info.fullpath, context.temp_allocator) defer os.file_info_slice_delete(fis, context.temp_allocator) err1, skip_dir = walk_proc(info, err, user_data) - if err != 0 || err1 != 0 || skip_dir { + if err != nil || err1 != nil || skip_dir { err = err1 return } for fi in fis { err, skip_dir = _walk(fi, walk_proc, user_data) - if err != 0 || skip_dir { + if err != nil || skip_dir { if !fi.is_dir || !skip_dir { return } @@ -70,19 +70,12 @@ _walk :: proc(info: os.File_Info, walk_proc: Walk_Proc, user_data: rawptr) -> (e } @(private) -read_dir :: proc(dir_name: string, allocator := context.temp_allocator) -> ([]os.File_Info, os.Errno) { - f, err := os.open(dir_name, os.O_RDONLY) - if err != 0 { - return nil, err - } - fis: []os.File_Info - fis, err = os.read_dir(f, -1, allocator) - os.close(f) - if err != 0 { - return nil, err - } +read_dir :: proc(dir_name: string, allocator := context.temp_allocator) -> (fis: []os.File_Info, err: os.Error) { + f := os.open(dir_name, os.O_RDONLY) or_return + defer os.close(f) + fis = os.read_dir(f, -1, allocator) or_return slice.sort_by(fis, proc(a, b: os.File_Info) -> bool { return a.name < b.name }) - return fis, 0 + return } diff --git a/core/path/slashpath/path.odin b/core/path/slashpath/path.odin index ada473c34..52b4878bc 100644 --- a/core/path/slashpath/path.odin +++ b/core/path/slashpath/path.odin @@ -5,7 +5,7 @@ // To manipulate operating system specific paths, use the path/filepath package package slashpath -import "core:runtime" +import "base:runtime" import "core:strings" // is_separator checks whether the byte is a valid separator character diff --git a/core/prof/spall/doc.odin b/core/prof/spall/doc.odin index 0f3cc8bb8..b007ad4cb 100644 --- a/core/prof/spall/doc.odin +++ b/core/prof/spall/doc.odin @@ -1,26 +1,45 @@ /* -import "core:prof/spall" +Example: + package main -spall_ctx: spall.Context -spall_buffer: spall.Buffer + import "base:runtime" + import "core:prof/spall" + import "core:sync" -foo :: proc() { - spall.SCOPED_EVENT(&spall_ctx, &spall_buffer, #procedure) -} + spall_ctx: spall.Context + @(thread_local) spall_buffer: spall.Buffer -main :: proc() { - spall_ctx = spall.context_create("trace_test.spall") - defer spall.context_destroy(&spall_ctx) + foo :: proc() { + spall.SCOPED_EVENT(&spall_ctx, &spall_buffer, #procedure) + } - buffer_backing := make([]u8, spall.BUFFER_DEFAULT_SIZE) - spall_buffer = spall.buffer_create(buffer_backing) - defer spall.buffer_destroy(&spall_ctx, &spall_buffer) + main :: proc() { + spall_ctx = spall.context_create("trace_test.spall") + defer spall.context_destroy(&spall_ctx) - spall.SCOPED_EVENT(&spall_ctx, &spall_buffer, #procedure) + buffer_backing := make([]u8, spall.BUFFER_DEFAULT_SIZE) + defer delete(buffer_backing) - for i := 0; i < 9001; i += 1 { - foo() - } -} + spall_buffer = spall.buffer_create(buffer_backing, u32(sync.current_thread_id())) + defer spall.buffer_destroy(&spall_ctx, &spall_buffer) + + spall.SCOPED_EVENT(&spall_ctx, &spall_buffer, #procedure) + + for i := 0; i < 9001; i += 1 { + foo() + } + } + + // Automatic profiling of every procedure: + + @(instrumentation_enter) + spall_enter :: proc "contextless" (proc_address, call_site_return_address: rawptr, loc: runtime.Source_Code_Location) { + spall._buffer_begin(&spall_ctx, &spall_buffer, "", "", loc) + } + + @(instrumentation_exit) + spall_exit :: proc "contextless" (proc_address, call_site_return_address: rawptr, loc: runtime.Source_Code_Location) { + spall._buffer_end(&spall_ctx, &spall_buffer) + } */ package spall diff --git a/core/prof/spall/spall.odin b/core/prof/spall/spall.odin index 19a05d70a..12f082b2c 100644 --- a/core/prof/spall/spall.odin +++ b/core/prof/spall/spall.odin @@ -2,8 +2,7 @@ package spall import "core:os" import "core:time" -import "core:intrinsics" -import "core:mem" +import "base:intrinsics" // File Format @@ -69,7 +68,7 @@ BUFFER_DEFAULT_SIZE :: 0x10_0000 context_create_with_scale :: proc(filename: string, precise_time: bool, timestamp_scale: f64) -> (ctx: Context, ok: bool) #optional_ok { fd, err := os.open(filename, os.O_WRONLY | os.O_APPEND | os.O_CREATE | os.O_TRUNC, 0o600) - if err != os.ERROR_NONE { + if err != nil { return } @@ -111,9 +110,10 @@ buffer_create :: proc(data: []byte, tid: u32 = 0, pid: u32 = 0) -> (buffer: Buff return } -buffer_flush :: proc(ctx: ^Context, buffer: ^Buffer) { +@(no_instrumentation) +buffer_flush :: proc "contextless" (ctx: ^Context, buffer: ^Buffer) #no_bounds_check /* bounds check would segfault instrumentation */ { start := _trace_now(ctx) - os.write(ctx.fd, buffer.data[:buffer.head]) + write(ctx.fd, buffer.data[:buffer.head]) buffer.head = 0 end := _trace_now(ctx) @@ -140,15 +140,16 @@ _scoped_buffer_end :: proc(ctx: ^Context, buffer: ^Buffer, _, _: string, _ := #c _buffer_end(ctx, buffer) } - +@(no_instrumentation) _trace_now :: proc "contextless" (ctx: ^Context) -> f64 { if !ctx.precise_time { - return f64(time.tick_now()._nsec) / 1_000 + return f64(tick_now()) / 1_000 } return f64(intrinsics.read_cycle_counter()) } +@(no_instrumentation) _build_header :: proc "contextless" (buffer: []u8, timestamp_scale: f64) -> (header_size: int, ok: bool) #optional_ok { header_size = size_of(Manual_Header) if header_size > len(buffer) { @@ -164,7 +165,8 @@ _build_header :: proc "contextless" (buffer: []u8, timestamp_scale: f64) -> (hea return } -_build_begin :: proc "contextless" (buffer: []u8, name: string, args: string, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok { +@(no_instrumentation) +_build_begin :: #force_inline proc "contextless" (buffer: []u8, name: string, args: string, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok #no_bounds_check /* bounds check would segfault instrumentation */ { ev := (^Begin_Event)(raw_data(buffer)) name_len := min(len(name), 255) args_len := min(len(args), 255) @@ -180,13 +182,14 @@ _build_begin :: proc "contextless" (buffer: []u8, name: string, args: string, ts ev.ts = f64le(ts) ev.name_len = u8(name_len) ev.args_len = u8(args_len) - mem.copy(raw_data(buffer[size_of(Begin_Event):]), raw_data(name), name_len) - mem.copy(raw_data(buffer[size_of(Begin_Event)+name_len:]), raw_data(args), args_len) + intrinsics.mem_copy_non_overlapping(raw_data(buffer[size_of(Begin_Event):]), raw_data(name), name_len) + intrinsics.mem_copy_non_overlapping(raw_data(buffer[size_of(Begin_Event)+name_len:]), raw_data(args), args_len) ok = true return } +@(no_instrumentation) _build_end :: proc "contextless" (buffer: []u8, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok { ev := (^End_Event)(raw_data(buffer)) event_size = size_of(End_Event) @@ -203,7 +206,8 @@ _build_end :: proc "contextless" (buffer: []u8, ts: f64, tid: u32, pid: u32) -> return } -_buffer_begin :: proc(ctx: ^Context, buffer: ^Buffer, name: string, args: string = "", location := #caller_location) { +@(no_instrumentation) +_buffer_begin :: proc "contextless" (ctx: ^Context, buffer: ^Buffer, name: string, args: string = "", location := #caller_location) #no_bounds_check /* bounds check would segfault instrumentation */ { if buffer.head + BEGIN_EVENT_MAX > len(buffer.data) { buffer_flush(ctx, buffer) } @@ -211,7 +215,8 @@ _buffer_begin :: proc(ctx: ^Context, buffer: ^Buffer, name: string, args: string buffer.head += _build_begin(buffer.data[buffer.head:], name, args, _trace_now(ctx), buffer.tid, buffer.pid) } -_buffer_end :: proc(ctx: ^Context, buffer: ^Buffer) { +@(no_instrumentation) +_buffer_end :: proc "contextless" (ctx: ^Context, buffer: ^Buffer) #no_bounds_check /* bounds check would segfault instrumentation */ { ts := _trace_now(ctx) if buffer.head + size_of(End_Event) > len(buffer.data) { @@ -220,3 +225,13 @@ _buffer_end :: proc(ctx: ^Context, buffer: ^Buffer) { buffer.head += _build_end(buffer.data[buffer.head:], ts, buffer.tid, buffer.pid) } + +@(no_instrumentation) +write :: proc "contextless" (fd: os.Handle, buf: []byte) -> (n: int, err: os.Error) { + return _write(fd, buf) +} + +@(no_instrumentation) +tick_now :: proc "contextless" () -> (ns: i64) { + return _tick_now() +} diff --git a/core/prof/spall/spall_linux.odin b/core/prof/spall/spall_linux.odin new file mode 100644 index 000000000..8060af448 --- /dev/null +++ b/core/prof/spall/spall_linux.odin @@ -0,0 +1,27 @@ +#+private +package spall + +// Only for types and constants. +import "core:os" + +// Package is `#+no-instrumentation`, safe to use. +import "core:sys/linux" + +MAX_RW :: 0x7fffffff + +@(no_instrumentation) +_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.Error) #no_bounds_check /* bounds check would segfault instrumentation */ { + for n < len(data) { + chunk := data[:min(len(data), MAX_RW)] + n += linux.write(linux.Fd(fd), chunk) or_return + } + return +} + +CLOCK_MONOTONIC_RAW :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP. + +@(no_instrumentation) +_tick_now :: proc "contextless" () -> (ns: i64) { + t, _ := linux.clock_gettime(.MONOTONIC_RAW) + return i64(t.time_sec)*1e9 + i64(t.time_nsec) +} diff --git a/core/prof/spall/spall_unix.odin b/core/prof/spall/spall_unix.odin new file mode 100644 index 000000000..455245aad --- /dev/null +++ b/core/prof/spall/spall_unix.odin @@ -0,0 +1,43 @@ +#+private +#+build darwin, freebsd, openbsd, netbsd +package spall + +// Only for types. +import "core:os" + +import "core:sys/posix" + +MAX_RW :: 0x7fffffff + +@(no_instrumentation) +_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (n: int, err: os.Error) #no_bounds_check /* bounds check would segfault instrumentation */ { + if len(data) == 0 { + return 0, nil + } + + for n < len(data) { + chunk := data[:min(len(data), MAX_RW)] + written := posix.write(posix.FD(fd), raw_data(chunk), len(chunk)) + if written < 0 { + return n, os.get_last_error() + } + n += written + } + + return n, nil +} + +// NOTE(tetra): "RAW" means: Not adjusted by NTP. +when ODIN_OS == .Darwin { + CLOCK :: posix.Clock(4) // CLOCK_MONOTONIC_RAW +} else { + // It looks like the BSDs don't have a CLOCK_MONOTONIC_RAW equivalent. + CLOCK :: posix.Clock.MONOTONIC +} + +@(no_instrumentation) +_tick_now :: proc "contextless" () -> (ns: i64) { + t: posix.timespec + posix.clock_gettime(CLOCK, &t) + return i64(t.tv_sec)*1e9 + i64(t.tv_nsec) +} diff --git a/core/prof/spall/spall_windows.odin b/core/prof/spall/spall_windows.odin new file mode 100644 index 000000000..11e216b63 --- /dev/null +++ b/core/prof/spall/spall_windows.odin @@ -0,0 +1,53 @@ +#+private +package spall + +// Only for types. +import "core:os" + +// Package is `#+no-instrumentation`, safe to use. +import win32 "core:sys/windows" + +MAX_RW :: 1<<30 + +@(no_instrumentation) +_write :: proc "contextless" (fd: os.Handle, data: []byte) -> (int, os.Error) #no_bounds_check /* bounds check would segfault instrumentation */ { + if len(data) == 0 { + return 0, nil + } + + single_write_length: win32.DWORD + total_write: i64 + length := i64(len(data)) + + for total_write < length { + remaining := length - total_write + to_write := win32.DWORD(min(i32(remaining), MAX_RW)) + + e := win32.WriteFile(win32.HANDLE(fd), &data[total_write], to_write, &single_write_length, nil) + if single_write_length <= 0 || !e { + return int(total_write), os.get_last_error() + } + total_write += i64(single_write_length) + } + return int(total_write), nil +} + +@(no_instrumentation) +_tick_now :: proc "contextless" () -> (ns: i64) { + @(no_instrumentation) + mul_div_u64 :: #force_inline proc "contextless" (val, num, den: i64) -> i64 { + q := val / den + r := val % den + return q * num + r * num / den + } + + @thread_local qpc_frequency: win32.LARGE_INTEGER + + if qpc_frequency == 0 { + win32.QueryPerformanceFrequency(&qpc_frequency) + } + now: win32.LARGE_INTEGER + win32.QueryPerformanceCounter(&now) + + return mul_div_u64(i64(now), 1e9, i64(qpc_frequency)) +} diff --git a/core/reflect/iterator.odin b/core/reflect/iterator.odin index 2e143284a..090fe04cc 100644 --- a/core/reflect/iterator.odin +++ b/core/reflect/iterator.odin @@ -1,6 +1,6 @@ package reflect -import "core:runtime" +import "base:runtime" @(require_results) iterate_array :: proc(val: any, it: ^int) -> (elem: any, index: int, ok: bool) { @@ -19,6 +19,7 @@ iterate_array :: proc(val: any, it: ^int) -> (elem: any, index: int, ok: bool) { elem.data = rawptr(uintptr(val.data) + uintptr(it^ * info.elem_size)) elem.id = info.elem.id ok = true + index = it^ it^ += 1 } case Type_Info_Slice: @@ -27,6 +28,7 @@ iterate_array :: proc(val: any, it: ^int) -> (elem: any, index: int, ok: bool) { elem.data = rawptr(uintptr(array.data) + uintptr(it^ * info.elem_size)) elem.id = info.elem.id ok = true + index = it^ it^ += 1 } case Type_Info_Dynamic_Array: @@ -35,6 +37,7 @@ iterate_array :: proc(val: any, it: ^int) -> (elem: any, index: int, ok: bool) { elem.data = rawptr(uintptr(array.data) + uintptr(it^ * info.elem_size)) elem.id = info.elem.id ok = true + index = it^ it^ += 1 } } @@ -69,10 +72,12 @@ iterate_map :: proc(val: any, it: ^int) -> (key, value: any, ok: bool) { key.id = info.key.id value.id = info.value.id ok = true + it^ += 1 break } } } return -} \ No newline at end of file +} + diff --git a/core/reflect/reflect.odin b/core/reflect/reflect.odin index 24a826f04..7f79acb77 100644 --- a/core/reflect/reflect.odin +++ b/core/reflect/reflect.odin @@ -1,9 +1,7 @@ package reflect -import "core:runtime" -import "core:intrinsics" -import "core:mem" -_ :: mem +import "base:runtime" +import "base:intrinsics" _ :: intrinsics Type_Info :: runtime.Type_Info @@ -33,10 +31,9 @@ Type_Info_Enum :: runtime.Type_Info_Enum Type_Info_Map :: runtime.Type_Info_Map Type_Info_Bit_Set :: runtime.Type_Info_Bit_Set Type_Info_Simd_Vector :: runtime.Type_Info_Simd_Vector -Type_Info_Relative_Pointer :: runtime.Type_Info_Relative_Pointer -Type_Info_Relative_Multi_Pointer :: runtime.Type_Info_Relative_Multi_Pointer Type_Info_Matrix :: runtime.Type_Info_Matrix Type_Info_Soa_Pointer :: runtime.Type_Info_Soa_Pointer +Type_Info_Bit_Field :: runtime.Type_Info_Bit_Field Type_Info_Enum_Value :: runtime.Type_Info_Enum_Value @@ -68,10 +65,9 @@ Type_Kind :: enum { Map, Bit_Set, Simd_Vector, - Relative_Pointer, - Relative_Multi_Pointer, Matrix, Soa_Pointer, + Bit_Field, } @@ -80,34 +76,33 @@ type_kind :: proc(T: typeid) -> Type_Kind { ti := type_info_of(T) if ti != nil { switch _ in ti.variant { - case Type_Info_Named: return .Named - case Type_Info_Integer: return .Integer - case Type_Info_Rune: return .Rune - case Type_Info_Float: return .Float - case Type_Info_Complex: return .Complex - case Type_Info_Quaternion: return .Quaternion - case Type_Info_String: return .String - case Type_Info_Boolean: return .Boolean - case Type_Info_Any: return .Any - case Type_Info_Type_Id: return .Type_Id - case Type_Info_Pointer: return .Pointer - case Type_Info_Multi_Pointer: return .Multi_Pointer - case Type_Info_Procedure: return .Procedure - case Type_Info_Array: return .Array - case Type_Info_Enumerated_Array: return .Enumerated_Array - case Type_Info_Dynamic_Array: return .Dynamic_Array - case Type_Info_Slice: return .Slice - case Type_Info_Parameters: return .Tuple - case Type_Info_Struct: return .Struct - case Type_Info_Union: return .Union - case Type_Info_Enum: return .Enum - case Type_Info_Map: return .Map - case Type_Info_Bit_Set: return .Bit_Set - case Type_Info_Simd_Vector: return .Simd_Vector - case Type_Info_Relative_Pointer: return .Relative_Pointer - case Type_Info_Relative_Multi_Pointer: return .Relative_Multi_Pointer - case Type_Info_Matrix: return .Matrix - case Type_Info_Soa_Pointer: return .Soa_Pointer + case Type_Info_Named: return .Named + case Type_Info_Integer: return .Integer + case Type_Info_Rune: return .Rune + case Type_Info_Float: return .Float + case Type_Info_Complex: return .Complex + case Type_Info_Quaternion: return .Quaternion + case Type_Info_String: return .String + case Type_Info_Boolean: return .Boolean + case Type_Info_Any: return .Any + case Type_Info_Type_Id: return .Type_Id + case Type_Info_Pointer: return .Pointer + case Type_Info_Multi_Pointer: return .Multi_Pointer + case Type_Info_Procedure: return .Procedure + case Type_Info_Array: return .Array + case Type_Info_Enumerated_Array: return .Enumerated_Array + case Type_Info_Dynamic_Array: return .Dynamic_Array + case Type_Info_Slice: return .Slice + case Type_Info_Parameters: return .Tuple + case Type_Info_Struct: return .Struct + case Type_Info_Union: return .Union + case Type_Info_Enum: return .Enum + case Type_Info_Map: return .Map + case Type_Info_Bit_Set: return .Bit_Set + case Type_Info_Simd_Vector: return .Simd_Vector + case Type_Info_Matrix: return .Matrix + case Type_Info_Soa_Pointer: return .Soa_Pointer + case Type_Info_Bit_Field: return .Bit_Field } } @@ -142,7 +137,7 @@ when !ODIN_NO_RTTI { @(require_results) any_base :: proc(v: any) -> any { v := v - if v != nil { + if v.id != nil { v.id = typeid_base(v.id) } return v @@ -150,7 +145,7 @@ any_base :: proc(v: any) -> any { @(require_results) any_core :: proc(v: any) -> any { v := v - if v != nil { + if v.id != nil { v.id = typeid_core(v.id) } return v @@ -390,7 +385,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]) @@ -405,7 +400,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] @@ -426,7 +421,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]), @@ -462,7 +457,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 } @@ -471,7 +466,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 } @@ -481,7 +476,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 } @@ -490,21 +485,79 @@ 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 } +Struct_Field_Count_Method :: enum { + Top_Level, + Using, + Recursive, +} + +/* +Counts the number of fields in a struct + +This procedure returns the number of fields in a struct, counting in one of three ways: +- .Top_Level: Only counts the top-level fields +- .Using: Same count as .Top_Level, and adds the field count of any `using s: Struct` it encounters (in addition to itself) +- .Recursive: The count of all top-level fields, plus the count of any child struct's fields, recursively + +Inputs: +- T: The struct type +- method: The counting method + +Returns: +- The `count`, enumerated using the `method`, which will be `0` if the type is not a struct + +Example: + symbols_loaded, ok := dynlib.initialize_symbols(&game_api, "game.dll") + symbols_expected := reflect.struct_field_count(Game_Api) - API_PRIVATE_COUNT + + if symbols_loaded != symbols_expected { + fmt.eprintf("Expected %v symbols, got %v", symbols_expected, symbols_loaded) + return + } +*/ +@(require_results) +struct_field_count :: proc(T: typeid, method := Struct_Field_Count_Method.Top_Level) -> (count: int) { + ti := runtime.type_info_base(type_info_of(T)) + if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { + switch method { + case .Top_Level: + return int(s.field_count) + + case .Using: + count = int(s.field_count) + for type, i in s.types[:s.field_count] { + if s.usings[i] { + count += struct_field_count(type.id) + } + } + + case .Recursive: + count = int(s.field_count) + for type in s.types[:s.field_count] { + count += struct_field_count(type.id) + } + + case: return 0 + } + } + return +} + @(require_results) struct_fields_zipped :: proc(T: typeid) -> (fields: #soa[]Struct_Field) { ti := runtime.type_info_base(type_info_of(T)) 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 @@ -513,13 +566,13 @@ struct_fields_zipped :: proc(T: typeid) -> (fields: #soa[]Struct_Field) { @(require_results) -struct_tag_get :: proc(tag: Struct_Tag, key: string) -> (value: Struct_Tag) { - value, _ = struct_tag_lookup(tag, key) - return +struct_tag_get :: proc(tag: Struct_Tag, key: string) -> (value: string) { + v, _ := struct_tag_lookup(tag, key) + return string(v) } @(require_results) -struct_tag_lookup :: proc(tag: Struct_Tag, key: string) -> (value: Struct_Tag, ok: bool) { +struct_tag_lookup :: proc(tag: Struct_Tag, key: string) -> (value: string, ok: bool) { for t := tag; t != ""; /**/ { i := 0 for i < len(t) && t[i] == ' ' { // Skip whitespace @@ -570,7 +623,7 @@ struct_tag_lookup :: proc(tag: Struct_Tag, key: string) -> (value: Struct_Tag, o t = t[i+1:] if key == name { - return Struct_Tag(val[1:i]), true + return val[1:i], true } } return @@ -629,6 +682,64 @@ enum_from_name_any :: proc(Enum_Type: typeid, name: string) -> (value: Type_Info return } +@(require_results) +enum_name_from_value :: proc(value: $Enum_Type) -> (name: string, ok: bool) where intrinsics.type_is_enum(Enum_Type) { + ti := type_info_base(type_info_of(Enum_Type)) + e := ti.variant.(runtime.Type_Info_Enum) or_return + if len(e.values) == 0 { + return + } + ev := Type_Info_Enum_Value(value) + for val, idx in e.values { + if val == ev { + return e.names[idx], true + } + } + return +} + +@(require_results) +enum_name_from_value_any :: proc(value: any) -> (name: string, ok: bool) { + if value.id == nil { + return + } + ti := type_info_base(type_info_of(value.id)) + e := ti.variant.(runtime.Type_Info_Enum) or_return + if len(e.values) == 0 { + return + } + ev := Type_Info_Enum_Value(as_i64(value) or_return) + for val, idx in e.values { + if val == ev { + return e.names[idx], true + } + } + return +} + +/* +Returns whether the value given has a defined name in the enum type. +*/ +@(require_results) +enum_value_has_name :: proc(value: $T) -> bool where intrinsics.type_is_enum(T) { + when len(T) == cap(T) { + return value >= min(T) && value <= max(T) + } else { + if value < min(T) || value > max(T) { + return false + } + + for valid_value in T { + if valid_value == value { + return true + } + } + + return false + } +} + + @(require_results) enum_field_names :: proc(Enum_Type: typeid) -> []string { @@ -671,7 +782,7 @@ union_variant_type_info :: proc(a: any) -> ^Type_Info { @(require_results) type_info_union_is_pure_maybe :: proc(info: runtime.Type_Info_Union) -> bool { - return len(info.variants) == 1 && is_pointer(info.variants[0]) + return len(info.variants) == 1 && is_pointer_internally(info.variants[0]) } @(require_results) @@ -761,7 +872,7 @@ get_union_variant :: proc(a: any) -> any { get_union_as_ptr_variants :: proc(val: ^$T) -> (res: intrinsics.type_convert_variants_to_pointers(T)) where intrinsics.type_is_union(T) { ptr := rawptr(val) tag := get_union_variant_raw_tag(val^) - mem.copy(&res, &ptr, size_of(ptr)) + intrinsics.mem_copy(&res, &ptr, size_of(ptr)) set_union_variant_raw_tag(res, tag) return } @@ -896,7 +1007,96 @@ set_union_value :: proc(dst: any, value: any) -> bool { panic("expected a union to reflect.set_union_variant_typeid") } +@(require_results) +bit_set_is_big_endian :: proc(value: any, loc := #caller_location) -> bool { + if value == nil { return ODIN_ENDIAN == .Big } + + ti := runtime.type_info_base(type_info_of(value.id)) + if info, ok := ti.variant.(runtime.Type_Info_Bit_Set); ok { + if info.underlying == nil { return ODIN_ENDIAN == .Big } + underlying_ti := runtime.type_info_base(info.underlying) + if underlying_info, uok := underlying_ti.variant.(runtime.Type_Info_Integer); uok { + switch underlying_info.endianness { + case .Platform: return ODIN_ENDIAN == .Big + case .Little: return false + case .Big: return true + } + } + + return ODIN_ENDIAN == .Big + } + panic("expected a bit_set to reflect.bit_set_is_big_endian", loc) +} + + +Bit_Field :: struct { + name: string, + type: ^Type_Info, + size: uintptr, // Size in bits + offset: uintptr, // Offset in bits + tag: Struct_Tag, +} + +@(require_results) +bit_fields_zipped :: proc(T: typeid) -> (fields: #soa[]Bit_Field) { + ti := runtime.type_info_base(type_info_of(T)) + if s, ok := ti.variant.(runtime.Type_Info_Bit_Field); ok { + return soa_zip( + name = s.names[:s.field_count], + type = s.types[:s.field_count], + size = s.bit_sizes[:s.field_count], + offset = s.bit_offsets[:s.field_count], + tag = ([^]Struct_Tag)(s.tags)[:s.field_count], + ) + } + return nil +} + +@(require_results) +bit_field_names :: proc(T: typeid) -> []string { + ti := runtime.type_info_base(type_info_of(T)) + if s, ok := ti.variant.(runtime.Type_Info_Bit_Field); ok { + return s.names[:s.field_count] + } + return nil +} + +@(require_results) +bit_field_types :: proc(T: typeid) -> []^Type_Info { + ti := runtime.type_info_base(type_info_of(T)) + if s, ok := ti.variant.(runtime.Type_Info_Bit_Field); ok { + return s.types[:s.field_count] + } + return nil +} + +@(require_results) +bit_field_sizes :: proc(T: typeid) -> []uintptr { + ti := runtime.type_info_base(type_info_of(T)) + if s, ok := ti.variant.(runtime.Type_Info_Bit_Field); ok { + return s.bit_sizes[:s.field_count] + } + return nil +} + +@(require_results) +bit_field_offsets :: proc(T: typeid) -> []uintptr { + ti := runtime.type_info_base(type_info_of(T)) + if s, ok := ti.variant.(runtime.Type_Info_Bit_Field); ok { + return s.bit_offsets[:s.field_count] + } + return nil +} + +@(require_results) +bit_field_tags :: proc(T: typeid) -> []Struct_Tag { + ti := runtime.type_info_base(type_info_of(T)) + if s, ok := ti.variant.(runtime.Type_Info_Bit_Field); ok { + return transmute([]Struct_Tag)s.tags[:s.field_count] + } + return nil +} @(require_results) as_bool :: proc(a: any) -> (value: bool, valid: bool) { @@ -1282,21 +1482,6 @@ as_string :: proc(a: any) -> (value: string, valid: bool) { return } -@(require_results) -relative_pointer_to_absolute :: proc(a: any) -> rawptr { - if a == nil { return nil } - a := a - ti := runtime.type_info_core(type_info_of(a.id)) - a.id = ti.id - - #partial switch info in ti.variant { - case Type_Info_Relative_Pointer: - return relative_pointer_to_absolute_raw(a.data, info.base_integer.id) - } - return nil -} - - @(require_results) relative_pointer_to_absolute_raw :: proc(data: rawptr, base_integer_id: typeid) -> rawptr { _handle :: proc(ptr: ^$T) -> rawptr where intrinsics.type_is_integer(T) { @@ -1350,7 +1535,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 @@ -1358,10 +1543,6 @@ as_pointer :: proc(a: any) -> (value: rawptr, valid: bool) { case cstring: value = rawptr(v) case: valid = false } - - case Type_Info_Relative_Pointer: - valid = true - value = relative_pointer_to_absolute_raw(a.data, info.base_integer.id) } return @@ -1471,8 +1652,6 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_ Type_Info_Bit_Set, Type_Info_Enum, Type_Info_Simd_Vector, - Type_Info_Relative_Pointer, - Type_Info_Relative_Multi_Pointer, Type_Info_Soa_Pointer, Type_Info_Matrix: return runtime.memory_compare(a.data, b.data, t.size) == 0 @@ -1510,7 +1689,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 @@ -1569,9 +1748,16 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_ } } return true + + case Type_Info_Bit_Field: + x, y := a, b + x.id = v.backing_type.id + y.id = v.backing_type.id + return equal(x, y, including_indirect_array_recursion, recursion_level+0) + } runtime.print_typeid(a.id) runtime.print_string("\n") return true -} +} \ No newline at end of file diff --git a/core/reflect/types.odin b/core/reflect/types.odin index cbe108d82..cb31a27e2 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_Simd_Vector: y := b.variant.(Type_Info_Simd_Vector) or_return return x.count == y.count && x.elem == y.elem - - case Type_Info_Relative_Pointer: - y := b.variant.(Type_Info_Relative_Pointer) or_return - return x.base_integer == y.base_integer && x.pointer == y.pointer - - case Type_Info_Relative_Multi_Pointer: - y := b.variant.(Type_Info_Relative_Multi_Pointer) or_return - return x.base_integer == y.base_integer && x.pointer == y.pointer case Type_Info_Matrix: y := b.variant.(Type_Info_Matrix) or_return if x.row_count != y.row_count { return false } if x.column_count != y.column_count { return false } + if x.layout != y.layout { return false } return are_types_identical(x.elem, y.elem) + + 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 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 + } + if !are_types_identical(x.types[i], y.types[i]) { + return false + } + if x.bit_sizes[i] != y.bit_sizes[i] { + return false + } + } + return true } return false @@ -350,13 +358,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 { @@ -376,21 +384,70 @@ is_simd_vector :: proc(info: ^Type_Info) -> bool { _, ok := type_info_base(info).variant.(Type_Info_Simd_Vector) return ok } + + @(require_results) -is_relative_pointer :: proc(info: ^Type_Info) -> bool { - if info == nil { return false } - _, ok := type_info_base(info).variant.(Type_Info_Relative_Pointer) - return ok -} -@(require_results) -is_relative_multi_pointer :: proc(info: ^Type_Info) -> bool { - if info == nil { return false } - _, ok := type_info_base(info).variant.(Type_Info_Relative_Multi_Pointer) - return ok +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 +} @@ -416,7 +473,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 } @@ -577,15 +634,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 @@ -639,25 +697,30 @@ write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) - } io.write_byte(w, ']', &n) or_return + case Type_Info_Bit_Field: + 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[: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 + write_type(w, info.types[i], &n) or_return + io.write_string(w, " | ", &n) or_return + io.write_u64(w, u64(info.bit_sizes[i]), 10, &n) or_return + } + io.write_string(w, "}", &n) or_return + case Type_Info_Simd_Vector: io.write_string(w, "#simd[", &n) or_return io.write_i64(w, i64(info.count), 10, &n) or_return io.write_byte(w, ']', &n) or_return write_type(w, info.elem, &n) or_return - - case Type_Info_Relative_Pointer: - io.write_string(w, "#relative(", &n) or_return - write_type(w, info.base_integer, &n) or_return - io.write_string(w, ") ", &n) or_return - write_type(w, info.pointer, &n) or_return - - case Type_Info_Relative_Multi_Pointer: - io.write_string(w, "#relative(", &n) or_return - write_type(w, info.base_integer, &n) or_return - io.write_string(w, ") ", &n) or_return - write_type(w, info.pointer, &n) or_return case Type_Info_Matrix: + if info.layout == .Row_Major { + io.write_string(w, "#row_major ", &n) or_return + } io.write_string(w, "matrix[", &n) or_return io.write_i64(w, i64(info.row_count), 10, &n) or_return io.write_string(w, ", ", &n) or_return diff --git a/core/relative/relative.odin b/core/relative/relative.odin new file mode 100644 index 000000000..30a7b86ae --- /dev/null +++ b/core/relative/relative.odin @@ -0,0 +1,171 @@ +package relative_types + +import "base:intrinsics" + +Pointer :: struct($Type: typeid, $Backing: typeid) + where + intrinsics.type_is_pointer(Type) || intrinsics.type_is_multi_pointer(Type), + intrinsics.type_is_integer(Backing) { + offset: Backing, +} + +Slice :: struct($Type: typeid, $Backing: typeid) + where + intrinsics.type_is_slice(Type), + intrinsics.type_is_integer(Backing) { + offset: Backing, + len: Backing, +} + + + +@(require_results) +pointer_get :: proc "contextless" (p: ^$P/Pointer($T, $B)) -> T { + if p.offset == 0 { + return nil + } + ptr := ([^]byte)(p)[p.offset:] + return (T)(ptr) +} + +pointer_set :: proc "contextless" (p: ^$P/Pointer($T, $B), ptr: T) { + if ptr == nil { + p.offset = 0 + } else { + p.offset = B(int(uintptr(ptr)) - int(uintptr(p))) + } +} + +@(require_results) +slice_get :: proc "contextless" (p: ^$S/Slice($T/[]$E, $B)) -> (slice: T) { + if p.offset == 0 { + when size_of(E) == 0 { + slice = T(([^]E)(nil)[:p.len]) + } + } else { + ptr := ([^]E)(([^]byte)(p)[p.offset:]) + slice = T(ptr[:p.len]) + } + return +} + +slice_set :: proc "contextless" (p: ^$S/Slice($T, $B), slice: T) { + if slice == nil { + p.offset, p.len = 0, 0 + } else { + ptr := raw_data(slice) + p.offset = B(int(uintptr(ptr)) - int(uintptr(p))) + p.len = B(len(slice)) + } +} + +get :: proc{ + pointer_get, + slice_get, +} + +set :: proc{ + pointer_set, + slice_set, +} + + + +Set_Safe_Error :: enum { + None, + Memory_Too_Far_Apart, + Length_Out_Of_Bounds, +} + + +@(require_results) +pointer_set_safe :: proc "contextless" (p: ^$P/Pointer($T, $B), ptr: T) -> Set_Safe_Error { + if ptr == nil { + p.offset = 0 + } else { + when intrinsics.type_is_unsigned(B) { + diff := uint(uintptr(ptr) - uintptr(p)) + when size_of(B) < size_of(uint) { + if diff > uint(max(B)) { + return .Memory_Too_Far_Apart + } + } else { + if B(diff) > max(B) { + return .Memory_Too_Far_Apart + } + } + } else { + diff := int(uintptr(ptr)) - int(uintptr(p)) + when size_of(B) < size_of(int) { + if diff > int(max(B)) { + return .Memory_Too_Far_Apart + } + } else { + if B(diff) > max(B) { + return .Memory_Too_Far_Apart + } + } + } + p.offset = B(diff) + } + return .None +} + +@(require_results) +slice_set_safe :: proc "contextless" (p: ^$S/Slice($T, $B), slice: T) -> Set_Safe_Error { + if slice == nil { + p.offset, p.len = 0, 0 + } else { + ptr := raw_data(slice) + when intrinsics.type_is_unsigned(B) { + diff := uint(uintptr(ptr) - uintptr(p)) + when size_of(B) < size_of(uint) { + if diff > uint(max(B)) { + return .Memory_Too_Far_Apart + } + + if uint(len(slice)) > uint(max(B)) { + return .Length_Out_Of_Bounds + } + } else { + if B(diff) > max(B) { + return .Memory_Too_Far_Apart + } + if B(len(slice)) > max(B) { + return .Length_Out_Of_Bounds + } + } + p.offset = B(diff) + p.len = B(len(slice)) + } else { + diff := int(uintptr(ptr)) - int(uintptr(p)) + when size_of(B) < size_of(int) { + if diff > int(max(B)) { + return .Memory_Too_Far_Apart + } + if len(slice) > int(max(B)) || len(slice) < int(min(B)) { + return .Length_Out_Of_Bounds + } + } else { + if B(diff) > max(B) { + return .Memory_Too_Far_Apart + } + if B(len(slice)) > max(B) { + return .Length_Out_Of_Bounds + } + if B(len(slice)) > max(B) || B(len(slice)) < min(B) { + return .Length_Out_Of_Bounds + } + } + } + p.offset = B(diff) + p.len = B(len(slice)) + } + return .None +} + + +set_safe :: proc{ + pointer_set_safe, + slice_set_safe, +} \ No newline at end of file diff --git a/core/runtime/core_builtin_matrix.odin b/core/runtime/core_builtin_matrix.odin deleted file mode 100644 index 7d60d625c..000000000 --- a/core/runtime/core_builtin_matrix.odin +++ /dev/null @@ -1,274 +0,0 @@ -package runtime - -import "core:intrinsics" -_ :: intrinsics - - -@(builtin) -determinant :: proc{ - matrix1x1_determinant, - matrix2x2_determinant, - matrix3x3_determinant, - matrix4x4_determinant, -} - -@(builtin) -adjugate :: proc{ - matrix1x1_adjugate, - matrix2x2_adjugate, - matrix3x3_adjugate, - matrix4x4_adjugate, -} - -@(builtin) -inverse_transpose :: proc{ - matrix1x1_inverse_transpose, - matrix2x2_inverse_transpose, - matrix3x3_inverse_transpose, - matrix4x4_inverse_transpose, -} - - -@(builtin) -inverse :: proc{ - matrix1x1_inverse, - matrix2x2_inverse, - matrix3x3_inverse, - matrix4x4_inverse, -} - -@(builtin, require_results) -hermitian_adjoint :: proc "contextless" (m: $M/matrix[$N, N]$T) -> M where intrinsics.type_is_complex(T), N >= 1 { - return conj(transpose(m)) -} - -@(builtin, require_results) -matrix_trace :: proc "contextless" (m: $M/matrix[$N, N]$T) -> (trace: T) { - for i in 0.. (minor: T) where N > 1 { - K :: N-1 - cut_down: matrix[K, K]T - for col_idx in 0..= column) - for row_idx in 0..= row) - cut_down[row_idx, col_idx] = m[i, j] - } - } - return determinant(cut_down) -} - - - -@(builtin, require_results) -matrix1x1_determinant :: proc "contextless" (m: $M/matrix[1, 1]$T) -> (det: T) { - return m[0, 0] -} - -@(builtin, require_results) -matrix2x2_determinant :: proc "contextless" (m: $M/matrix[2, 2]$T) -> (det: T) { - return m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] -} -@(builtin, require_results) -matrix3x3_determinant :: proc "contextless" (m: $M/matrix[3, 3]$T) -> (det: T) { - a := +m[0, 0] * (m[1, 1] * m[2, 2] - m[1, 2] * m[2, 1]) - b := -m[0, 1] * (m[1, 0] * m[2, 2] - m[1, 2] * m[2, 0]) - c := +m[0, 2] * (m[1, 0] * m[2, 1] - m[1, 1] * m[2, 0]) - return a + b + c -} -@(builtin, require_results) -matrix4x4_determinant :: proc "contextless" (m: $M/matrix[4, 4]$T) -> (det: T) { - a := adjugate(m) - #no_bounds_check for i in 0..<4 { - det += m[0, i] * a[0, i] - } - return -} - - - - -@(builtin, require_results) -matrix1x1_adjugate :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) { - y = x - return -} - -@(builtin, require_results) -matrix2x2_adjugate :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) { - y[0, 0] = +x[1, 1] - y[0, 1] = -x[1, 0] - y[1, 0] = -x[0, 1] - y[1, 1] = +x[0, 0] - return -} - -@(builtin, require_results) -matrix3x3_adjugate :: proc "contextless" (m: $M/matrix[3, 3]$T) -> (y: M) { - y[0, 0] = +(m[1, 1] * m[2, 2] - m[2, 1] * m[1, 2]) - y[0, 1] = -(m[1, 0] * m[2, 2] - m[2, 0] * m[1, 2]) - y[0, 2] = +(m[1, 0] * m[2, 1] - m[2, 0] * m[1, 1]) - y[1, 0] = -(m[0, 1] * m[2, 2] - m[2, 1] * m[0, 2]) - y[1, 1] = +(m[0, 0] * m[2, 2] - m[2, 0] * m[0, 2]) - y[1, 2] = -(m[0, 0] * m[2, 1] - m[2, 0] * m[0, 1]) - y[2, 0] = +(m[0, 1] * m[1, 2] - m[1, 1] * m[0, 2]) - y[2, 1] = -(m[0, 0] * m[1, 2] - m[1, 0] * m[0, 2]) - y[2, 2] = +(m[0, 0] * m[1, 1] - m[1, 0] * m[0, 1]) - return -} - - -@(builtin, require_results) -matrix4x4_adjugate :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) { - for i in 0..<4 { - for j in 0..<4 { - sign: T = 1 if (i + j) % 2 == 0 else -1 - y[i, j] = sign * matrix_minor(x, i, j) - } - } - return -} - -@(builtin, require_results) -matrix1x1_inverse_transpose :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) { - y[0, 0] = 1/x[0, 0] - return -} - -@(builtin, require_results) -matrix2x2_inverse_transpose :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) { - d := x[0, 0]*x[1, 1] - x[0, 1]*x[1, 0] - when intrinsics.type_is_integer(T) { - y[0, 0] = +x[1, 1] / d - y[1, 0] = -x[0, 1] / d - y[0, 1] = -x[1, 0] / d - y[1, 1] = +x[0, 0] / d - } else { - id := 1 / d - y[0, 0] = +x[1, 1] * id - y[1, 0] = -x[0, 1] * id - y[0, 1] = -x[1, 0] * id - y[1, 1] = +x[0, 0] * id - } - return -} - -@(builtin, require_results) -matrix3x3_inverse_transpose :: proc "contextless" (x: $M/matrix[3, 3]$T) -> (y: M) #no_bounds_check { - a := adjugate(x) - d := determinant(x) - when intrinsics.type_is_integer(T) { - for i in 0..<3 { - for j in 0..<3 { - y[i, j] = a[i, j] / d - } - } - } else { - id := 1/d - for i in 0..<3 { - for j in 0..<3 { - y[i, j] = a[i, j] * id - } - } - } - return -} - -@(builtin, require_results) -matrix4x4_inverse_transpose :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) #no_bounds_check { - a := adjugate(x) - d: T - for i in 0..<4 { - d += x[0, i] * a[0, i] - } - when intrinsics.type_is_integer(T) { - for i in 0..<4 { - for j in 0..<4 { - y[i, j] = a[i, j] / d - } - } - } else { - id := 1/d - for i in 0..<4 { - for j in 0..<4 { - y[i, j] = a[i, j] * id - } - } - } - return -} - -@(builtin, require_results) -matrix1x1_inverse :: proc "contextless" (x: $M/matrix[1, 1]$T) -> (y: M) { - y[0, 0] = 1/x[0, 0] - return -} - -@(builtin, require_results) -matrix2x2_inverse :: proc "contextless" (x: $M/matrix[2, 2]$T) -> (y: M) { - d := x[0, 0]*x[1, 1] - x[0, 1]*x[1, 0] - when intrinsics.type_is_integer(T) { - y[0, 0] = +x[1, 1] / d - y[0, 1] = -x[0, 1] / d - y[1, 0] = -x[1, 0] / d - y[1, 1] = +x[0, 0] / d - } else { - id := 1 / d - y[0, 0] = +x[1, 1] * id - y[0, 1] = -x[0, 1] * id - y[1, 0] = -x[1, 0] * id - y[1, 1] = +x[0, 0] * id - } - return -} - -@(builtin, require_results) -matrix3x3_inverse :: proc "contextless" (x: $M/matrix[3, 3]$T) -> (y: M) #no_bounds_check { - a := adjugate(x) - d := determinant(x) - when intrinsics.type_is_integer(T) { - for i in 0..<3 { - for j in 0..<3 { - y[i, j] = a[j, i] / d - } - } - } else { - id := 1/d - for i in 0..<3 { - for j in 0..<3 { - y[i, j] = a[j, i] * id - } - } - } - return -} - -@(builtin, require_results) -matrix4x4_inverse :: proc "contextless" (x: $M/matrix[4, 4]$T) -> (y: M) #no_bounds_check { - a := adjugate(x) - d: T - for i in 0..<4 { - d += x[0, i] * a[0, i] - } - when intrinsics.type_is_integer(T) { - for i in 0..<4 { - for j in 0..<4 { - y[i, j] = a[j, i] / d - } - } - } else { - id := 1/d - for i in 0..<4 { - for j in 0..<4 { - y[i, j] = a[j, i] * id - } - } - } - return -} diff --git a/core/runtime/default_allocators_general.odin b/core/runtime/default_allocators_general.odin deleted file mode 100644 index 994a672b0..000000000 --- a/core/runtime/default_allocators_general.odin +++ /dev/null @@ -1,23 +0,0 @@ -//+build !windows -//+build !freestanding -//+build !wasi -//+build !js -package runtime - -// TODO(bill): reimplement these procedures in the os_specific stuff -import "core:os" - -when ODIN_DEFAULT_TO_NIL_ALLOCATOR { - _ :: os - - // mem.nil_allocator reimplementation - default_allocator_proc :: nil_allocator_proc - default_allocator :: nil_allocator -} else { - - default_allocator_proc :: os.heap_allocator_proc - - default_allocator :: proc() -> Allocator { - return os.heap_allocator() - } -} diff --git a/core/runtime/default_allocators_js.odin b/core/runtime/default_allocators_js.odin deleted file mode 100644 index 715073f08..000000000 --- a/core/runtime/default_allocators_js.odin +++ /dev/null @@ -1,5 +0,0 @@ -//+build js -package runtime - -default_allocator_proc :: panic_allocator_proc -default_allocator :: panic_allocator diff --git a/core/runtime/default_allocators_wasi.odin b/core/runtime/default_allocators_wasi.odin deleted file mode 100644 index a7e6842a6..000000000 --- a/core/runtime/default_allocators_wasi.odin +++ /dev/null @@ -1,5 +0,0 @@ -//+build wasi -package runtime - -default_allocator_proc :: panic_allocator_proc -default_allocator :: panic_allocator diff --git a/core/runtime/default_allocators_windows.odin b/core/runtime/default_allocators_windows.odin deleted file mode 100644 index a78a4d04e..000000000 --- a/core/runtime/default_allocators_windows.odin +++ /dev/null @@ -1,44 +0,0 @@ -//+build windows -package runtime - -when ODIN_DEFAULT_TO_NIL_ALLOCATOR { - // mem.nil_allocator reimplementation - default_allocator_proc :: nil_allocator_proc - default_allocator :: nil_allocator -} else { - default_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - data, err = _windows_default_alloc(size, alignment, mode == .Alloc) - - case .Free: - _windows_default_free(old_memory) - - case .Free_All: - return nil, .Mode_Not_Implemented - - case .Resize: - data, err = _windows_default_resize(old_memory, old_size, size, alignment) - - case .Query_Features: - set := (^Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Resize, .Query_Features} - } - - case .Query_Info: - return nil, .Mode_Not_Implemented - } - - return - } - - default_allocator :: proc() -> Allocator { - return Allocator{ - procedure = default_allocator_proc, - data = nil, - } - } -} \ No newline at end of file diff --git a/core/runtime/entry_wasm.odin b/core/runtime/entry_wasm.odin deleted file mode 100644 index 235d5611b..000000000 --- a/core/runtime/entry_wasm.odin +++ /dev/null @@ -1,19 +0,0 @@ -//+private -//+build wasm32, wasm64p32 -package runtime - -import "core:intrinsics" - -when !ODIN_TEST && !ODIN_NO_ENTRY_POINT { - @(link_name="_start", linkage="strong", require, export) - _start :: proc "c" () { - context = default_context() - #force_no_inline _startup_runtime() - intrinsics.__entry_point() - } - @(link_name="_end", linkage="strong", require, export) - _end :: proc "c" () { - context = default_context() - #force_no_inline _cleanup_runtime() - } -} \ No newline at end of file diff --git a/core/runtime/os_specific.odin b/core/runtime/os_specific.odin deleted file mode 100644 index 022d315d4..000000000 --- a/core/runtime/os_specific.odin +++ /dev/null @@ -1,7 +0,0 @@ -package runtime - -_OS_Errno :: distinct int - -os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { - return _os_write(data) -} diff --git a/core/runtime/os_specific_any.odin b/core/runtime/os_specific_any.odin deleted file mode 100644 index afa106138..000000000 --- a/core/runtime/os_specific_any.odin +++ /dev/null @@ -1,12 +0,0 @@ -//+build !freestanding !wasi !windows !js -package runtime - -import "core:os" - -// TODO(bill): reimplement `os.write` so that it does not rely on package os -// NOTE: Use os_specific_linux.odin, os_specific_darwin.odin, etc -_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { - context = default_context() - n, err := os.write(os.stderr, data) - return int(n), _OS_Errno(err) -} diff --git a/core/runtime/os_specific_freestanding.odin b/core/runtime/os_specific_freestanding.odin deleted file mode 100644 index a6d04cefb..000000000 --- a/core/runtime/os_specific_freestanding.odin +++ /dev/null @@ -1,7 +0,0 @@ -//+build freestanding -package runtime - -// TODO(bill): reimplement `os.write` -_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { - return 0, -1 -} diff --git a/core/runtime/os_specific_wasi.odin b/core/runtime/os_specific_wasi.odin deleted file mode 100644 index 3f69504ee..000000000 --- a/core/runtime/os_specific_wasi.odin +++ /dev/null @@ -1,10 +0,0 @@ -//+build wasi -package runtime - -import "core:sys/wasm/wasi" - -_os_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { - data := (wasi.ciovec_t)(data) - n, err := wasi.fd_write(1, {data}) - return int(n), _OS_Errno(err) -} diff --git a/core/runtime/os_specific_windows.odin b/core/runtime/os_specific_windows.odin deleted file mode 100644 index 9f001fa5a..000000000 --- a/core/runtime/os_specific_windows.odin +++ /dev/null @@ -1,135 +0,0 @@ -//+build windows -package runtime - -foreign import kernel32 "system:Kernel32.lib" - -@(private="file") -@(default_calling_convention="stdcall") -foreign kernel32 { - // NOTE(bill): The types are not using the standard names (e.g. DWORD and LPVOID) to just minimizing the dependency - - // os_write - GetStdHandle :: proc(which: u32) -> rawptr --- - SetHandleInformation :: proc(hObject: rawptr, dwMask: u32, dwFlags: u32) -> b32 --- - WriteFile :: proc(hFile: rawptr, lpBuffer: rawptr, nNumberOfBytesToWrite: u32, lpNumberOfBytesWritten: ^u32, lpOverlapped: rawptr) -> b32 --- - GetLastError :: proc() -> u32 --- - - // default_allocator - GetProcessHeap :: proc() -> rawptr --- - HeapAlloc :: proc(hHeap: rawptr, dwFlags: u32, dwBytes: uint) -> rawptr --- - HeapReAlloc :: proc(hHeap: rawptr, dwFlags: u32, lpMem: rawptr, dwBytes: uint) -> rawptr --- - HeapFree :: proc(hHeap: rawptr, dwFlags: u32, lpMem: rawptr) -> b32 --- -} - -_os_write :: proc "contextless" (data: []byte) -> (n: int, err: _OS_Errno) #no_bounds_check { - if len(data) == 0 { - return 0, 0 - } - - STD_ERROR_HANDLE :: ~u32(0) -12 + 1 - HANDLE_FLAG_INHERIT :: 0x00000001 - MAX_RW :: 1<<30 - - h := GetStdHandle(STD_ERROR_HANDLE) - when size_of(uintptr) == 8 { - SetHandleInformation(h, HANDLE_FLAG_INHERIT, 0) - } - - single_write_length: u32 - total_write: i64 - length := i64(len(data)) - - for total_write < length { - remaining := length - total_write - to_write := u32(min(i32(remaining), MAX_RW)) - - e := WriteFile(h, &data[total_write], to_write, &single_write_length, nil) - if single_write_length <= 0 || !e { - err = _OS_Errno(GetLastError()) - n = int(total_write) - return - } - total_write += i64(single_write_length) - } - n = int(total_write) - return -} - -heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr { - HEAP_ZERO_MEMORY :: 0x00000008 - return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size)) -} -heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr { - if new_size == 0 { - heap_free(ptr) - return nil - } - if ptr == nil { - return heap_alloc(new_size) - } - - HEAP_ZERO_MEMORY :: 0x00000008 - return HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size)) -} -heap_free :: proc "contextless" (ptr: rawptr) { - if ptr == nil { - return - } - HeapFree(GetProcessHeap(), 0, ptr) -} - - -// -// NOTE(tetra, 2020-01-14): The heap doesn't respect alignment. -// Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert -// padding. We also store the original pointer returned by heap_alloc right before -// the pointer we return to the user. -// - - - -_windows_default_alloc_or_resize :: proc "contextless" (size, alignment: int, old_ptr: rawptr = nil, zero_memory := true) -> ([]byte, Allocator_Error) { - if size == 0 { - _windows_default_free(old_ptr) - return nil, nil - } - - a := max(alignment, align_of(rawptr)) - space := size + a - 1 - - allocated_mem: rawptr - if old_ptr != nil { - original_old_ptr := ([^]rawptr)(old_ptr)[-1] - allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr)) - } else { - allocated_mem = heap_alloc(space+size_of(rawptr), zero_memory) - } - aligned_mem := ([^]u8)(allocated_mem)[size_of(rawptr):] - - ptr := uintptr(aligned_mem) - aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a) - diff := int(aligned_ptr - ptr) - if (size + diff) > space || allocated_mem == nil { - return nil, .Out_Of_Memory - } - - aligned_mem = ([^]byte)(aligned_ptr) - ([^]rawptr)(aligned_mem)[-1] = allocated_mem - - return aligned_mem[:size], nil -} - -_windows_default_alloc :: proc "contextless" (size, alignment: int, zero_memory := true) -> ([]byte, Allocator_Error) { - return _windows_default_alloc_or_resize(size, alignment, nil, zero_memory) -} - - -_windows_default_free :: proc "contextless" (ptr: rawptr) { - if ptr != nil { - heap_free(([^]rawptr)(ptr)[-1]) - } -} - -_windows_default_resize :: proc "contextless" (p: rawptr, old_size: int, new_size: int, new_alignment: int) -> ([]byte, Allocator_Error) { - return _windows_default_alloc_or_resize(new_size, new_alignment, p) -} diff --git a/core/runtime/procs_wasm.odin b/core/runtime/procs_wasm.odin deleted file mode 100644 index 26dcfef77..000000000 --- a/core/runtime/procs_wasm.odin +++ /dev/null @@ -1,40 +0,0 @@ -//+build wasm32, wasm64p32 -package runtime - -@(private="file") -ti_int :: struct #raw_union { - using s: struct { lo, hi: u64 }, - all: i128, -} - -@(link_name="__ashlti3", linkage="strong") -__ashlti3 :: proc "contextless" (a: i128, b_: u32) -> i128 { - bits_in_dword :: size_of(u32)*8 - b := u32(b_) - - input, result: ti_int - input.all = a - if b & bits_in_dword != 0 { - result.lo = 0 - result.hi = input.lo << (b-bits_in_dword) - } else { - if b == 0 { - return a - } - result.lo = input.lo<>(bits_in_dword-b)) - } - return result.all -} - - -@(link_name="__multi3", linkage="strong") -__multi3 :: proc "contextless" (a, b: i128) -> i128 { - x, y, r: ti_int - - x.all = a - y.all = b - r.all = i128(x.lo * y.lo) // TODO this is incorrect - r.hi += x.hi*y.lo + x.lo*y.hi - return r.all -} \ No newline at end of file diff --git a/core/simd/simd.odin b/core/simd/simd.odin index 9d530ec31..01d11dfbe 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -1,7 +1,15 @@ package simd -import "core:builtin" -import "core:intrinsics" +import "base:builtin" +import "base:intrinsics" + +// IS_EMULATED is true iff the compile-time target lacks hardware support +// for at least 128-bit SIMD. +IS_EMULATED :: true when (ODIN_ARCH == .amd64 || ODIN_ARCH == .i386) && !intrinsics.has_target_feature("sse2") else + true when (ODIN_ARCH == .arm64 || ODIN_ARCH == .arm32) && !intrinsics.has_target_feature("neon") else + true when (ODIN_ARCH == .wasm64p32 || ODIN_ARCH == .wasm32) && !intrinsics.has_target_feature("simd128") else + true when (ODIN_ARCH == .riscv64) && !intrinsics.has_target_feature("v") else + false // 128-bit vector aliases u8x16 :: #simd[16]u8 @@ -74,8 +82,8 @@ shl_masked :: intrinsics.simd_shl_masked shr_masked :: intrinsics.simd_shr_masked // Saturation Arithmetic -add_sat :: intrinsics.simd_add_sat -sub_sat :: intrinsics.simd_sub_sat +saturating_add :: intrinsics.simd_saturating_add +saturating_sub :: intrinsics.simd_saturating_sub bit_and :: intrinsics.simd_bit_and bit_or :: intrinsics.simd_bit_or @@ -102,6 +110,15 @@ lanes_le :: intrinsics.simd_lanes_le lanes_gt :: intrinsics.simd_lanes_gt lanes_ge :: intrinsics.simd_lanes_ge + +// Gather and Scatter intrinsics +gather :: intrinsics.simd_gather +scatter :: intrinsics.simd_scatter +masked_load :: intrinsics.simd_masked_load +masked_store :: intrinsics.simd_masked_store +masked_expand_load :: intrinsics.simd_masked_expand_load +masked_compress_store :: intrinsics.simd_masked_compress_store + // extract :: proc(a: #simd[N]T, idx: uint) -> T extract :: intrinsics.simd_extract // replace :: proc(a: #simd[N]T, idx: uint, elem: T) -> #simd[N]T @@ -115,6 +132,9 @@ reduce_and :: intrinsics.simd_reduce_and reduce_or :: intrinsics.simd_reduce_or reduce_xor :: intrinsics.simd_reduce_xor +reduce_any :: intrinsics.simd_reduce_any +reduce_all :: intrinsics.simd_reduce_all + // swizzle :: proc(a: #simd[N]T, indices: ..int) -> #simd[len(indices)]T swizzle :: builtin.swizzle diff --git a/core/simd/x86/abm.odin b/core/simd/x86/abm.odin index 79b806242..4b07086ce 100644 --- a/core/simd/x86/abm.odin +++ b/core/simd/x86/abm.odin @@ -1,7 +1,7 @@ -//+build i386, amd64 +#+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/adx.odin b/core/simd/x86/adx.odin index d03cffcff..9c6ae063a 100644 --- a/core/simd/x86/adx.odin +++ b/core/simd/x86/adx.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 @(require_results) @@ -37,7 +37,7 @@ when ODIN_ARCH == .amd64 { } } -@(private, default_calling_convention="c") +@(private, default_calling_convention="none") foreign _ { @(link_name="llvm.x86.addcarry.32") llvm_addcarry_u32 :: proc(a: u8, b: u32, c: u32) -> (u8, u32) --- diff --git a/core/simd/x86/aes.odin b/core/simd/x86/aes.odin new file mode 100644 index 000000000..338381422 --- /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..78ebd182f 100644 --- a/core/simd/x86/cmpxchg16b.odin +++ b/core/simd/x86/cmpxchg16b.odin @@ -1,7 +1,7 @@ -//+build amd64 +#+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/fxsr.odin b/core/simd/x86/fxsr.odin index cd78de7d4..ab8cdca7d 100644 --- a/core/simd/x86/fxsr.odin +++ b/core/simd/x86/fxsr.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 @(enable_target_feature="fxsr") @@ -21,7 +21,7 @@ when ODIN_ARCH == .amd64 { } } -@(private, default_calling_convention="c") +@(private, default_calling_convention="none") foreign _ { @(link_name="llvm.x86.fxsave") fxsave :: proc(p: rawptr) --- diff --git a/core/simd/x86/pclmulqdq.odin b/core/simd/x86/pclmulqdq.odin index 692fb7ce1..14e633c06 100644 --- a/core/simd/x86/pclmulqdq.odin +++ b/core/simd/x86/pclmulqdq.odin @@ -1,12 +1,12 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 -@(require_results, enable_target_feature="pclmulqdq") +@(require_results, enable_target_feature="pclmul") _mm_clmulepi64_si128 :: #force_inline proc "c" (a, b: __m128i, $IMM8: u8) -> __m128i { return pclmulqdq(a, b, u8(IMM8)) } -@(private, default_calling_convention="c") +@(private, default_calling_convention="none") foreign _ { @(link_name="llvm.x86.pclmulqdq") pclmulqdq :: proc(a, round_key: __m128i, #const imm8: u8) -> __m128i --- diff --git a/core/simd/x86/rdtsc.odin b/core/simd/x86/rdtsc.odin index 54024c3f2..84c762274 100644 --- a/core/simd/x86/rdtsc.odin +++ b/core/simd/x86/rdtsc.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 @(require_results) @@ -11,7 +11,7 @@ __rdtscp :: #force_inline proc "c" (aux: ^u32) -> u64 { return rdtscp(aux) } -@(private, default_calling_convention="c") +@(private, default_calling_convention="none") foreign _ { @(link_name="llvm.x86.rdtsc") rdtsc :: proc() -> u64 --- diff --git a/core/simd/x86/sha.odin b/core/simd/x86/sha.odin index f015f4b8a..8caa3a268 100644 --- a/core/simd/x86/sha.odin +++ b/core/simd/x86/sha.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 @(require_results, enable_target_feature="sha") @@ -30,7 +30,7 @@ _mm_sha256rnds2_epu32 :: #force_inline proc "c" (a, b, k: __m128i) -> __m128i { return transmute(__m128i)sha256rnds2(transmute(i32x4)a, transmute(i32x4)b, transmute(i32x4)k) } -@(private, default_calling_convention="c") +@(private, default_calling_convention="none") foreign _ { @(link_name="llvm.x86.sha1msg1") sha1msg1 :: proc(a, b: i32x4) -> i32x4 --- diff --git a/core/simd/x86/sse.odin b/core/simd/x86/sse.odin index 2b70e954f..1b4a863b6 100644 --- a/core/simd/x86/sse.odin +++ b/core/simd/x86/sse.odin @@ -1,7 +1,7 @@ -//+build i386, amd64 +#+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) @@ -532,7 +532,7 @@ when ODIN_ARCH == .amd64 { } -@(private, default_calling_convention="c") +@(private, default_calling_convention="none") foreign _ { @(link_name="llvm.x86.sse.add.ss") addss :: proc(a, b: __m128) -> __m128 --- diff --git a/core/simd/x86/sse2.odin b/core/simd/x86/sse2.odin index dd292712f..4c231c377 100644 --- a/core/simd/x86/sse2.odin +++ b/core/simd/x86/sse2.odin @@ -1,7 +1,7 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 -import "core:intrinsics" +import "base:intrinsics" import "core:simd" @(enable_target_feature="sse2") @@ -35,23 +35,23 @@ _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 { - return transmute(__m128i)simd.add_sat(transmute(i8x16)a, transmute(i8x16)b) + return transmute(__m128i)simd.saturating_add(transmute(i8x16)a, transmute(i8x16)b) } @(require_results, enable_target_feature="sse2") _mm_adds_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.add_sat(transmute(i16x8)a, transmute(i16x8)b) + return transmute(__m128i)simd.saturating_add(transmute(i16x8)a, transmute(i16x8)b) } @(require_results, enable_target_feature="sse2") _mm_adds_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.add_sat(transmute(u8x16)a, transmute(u8x16)b) + return transmute(__m128i)simd.saturating_add(transmute(u8x16)a, transmute(u8x16)b) } @(require_results, enable_target_feature="sse2") _mm_adds_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.add_sat(transmute(u16x8)a, transmute(u16x8)b) + return transmute(__m128i)simd.saturating_add(transmute(u16x8)a, transmute(u16x8)b) } @(require_results, enable_target_feature="sse2") _mm_avg_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { @@ -118,23 +118,23 @@ _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 { - return transmute(__m128i)simd.sub_sat(transmute(i8x16)a, transmute(i8x16)b) + return transmute(__m128i)simd.saturating_sub(transmute(i8x16)a, transmute(i8x16)b) } @(require_results, enable_target_feature="sse2") _mm_subs_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.sub_sat(transmute(i16x8)a, transmute(i16x8)b) + return transmute(__m128i)simd.saturating_sub(transmute(i16x8)a, transmute(i16x8)b) } @(require_results, enable_target_feature="sse2") _mm_subs_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.sub_sat(transmute(u8x16)a, transmute(u8x16)b) + return transmute(__m128i)simd.saturating_sub(transmute(u8x16)a, transmute(u8x16)b) } @(require_results, enable_target_feature="sse2") _mm_subs_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - return transmute(__m128i)simd.sub_sat(transmute(u16x8)a, transmute(u16x8)b) + return transmute(__m128i)simd.saturating_sub(transmute(u16x8)a, transmute(u16x8)b) } @@ -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,11 +236,11 @@ _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 { - return transmute(__m128i)psraiw(transmute(i16x8)a. IMM8) + return transmute(__m128i)psraiw(transmute(i16x8)a, IMM8) } @(require_results, enable_target_feature="sse2") _mm_sra_epi16 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { @@ -255,7 +262,7 @@ _mm_srli_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { } @(require_results, enable_target_feature="sse2") _mm_srli_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { - return transmute(__m128i)psrliw(transmute(i16x8)a. IMM8) + return transmute(__m128i)psrliw(transmute(i16x8)a, IMM8) } @(require_results, enable_target_feature="sse2") _mm_srl_epi16 :: #force_inline proc "c" (a, count: __m128i) -> __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 { @@ -1040,7 +1047,7 @@ when ODIN_ARCH == .amd64 { } -@(private, default_calling_convention="c") +@(private, default_calling_convention="none") foreign _ { @(link_name="llvm.x86.sse2.pause") pause :: proc() --- @@ -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 7a3073c18..0e074c946 100644 --- a/core/simd/x86/sse3.odin +++ b/core/simd/x86/sse3.odin @@ -1,7 +1,7 @@ -//+build i386, amd64 +#+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) } @@ -49,7 +49,7 @@ _mm_moveldup_ps :: #force_inline proc "c" (a: __m128) -> __m128 { return simd.shuffle(a, a, 0, 0, 2, 2) } -@(private, default_calling_convention="c") +@(private, default_calling_convention="none") foreign _ { @(link_name = "llvm.x86.sse3.addsub.ps") addsubps :: proc(a, b: __m128) -> __m128 --- @@ -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 b35be33f2..81089ed63 100644 --- a/core/simd/x86/sse41.odin +++ b/core/simd/x86/sse41.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 import "core:simd" @@ -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)) } @@ -291,7 +291,7 @@ when ODIN_ARCH == .amd64 { } -@(private, default_calling_convention="c") +@(private, default_calling_convention="none") foreign _ { @(link_name = "llvm.x86.sse41.pblendvb") pblendvb :: proc(a, b: i8x16, mask: i8x16) -> i8x16 --- @@ -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 62b4f0478..1a5cb3f50 100644 --- a/core/simd/x86/sse42.odin +++ b/core/simd/x86/sse42.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 import "core:simd" @@ -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 { @@ -104,7 +104,7 @@ when ODIN_ARCH == .amd64 { } } -@(private, default_calling_convention="c") +@(private, default_calling_convention="none") foreign _ { // SSE 4.2 string and text comparison ops @(link_name="llvm.x86.sse42.pcmpestrm128") diff --git a/core/simd/x86/ssse3.odin b/core/simd/x86/ssse3.odin index f11ef6774..07c846e7b 100644 --- a/core/simd/x86/ssse3.odin +++ b/core/simd/x86/ssse3.odin @@ -1,7 +1,7 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 -import "core:intrinsics" +import "base:intrinsics" import "core:simd" _ :: simd @@ -105,7 +105,7 @@ _mm_sign_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { -@(private, default_calling_convention="c") +@(private, default_calling_convention="none") foreign _ { @(link_name = "llvm.x86.ssse3.pabs.b.128") pabsb128 :: proc(a: i8x16) -> u8x16 --- diff --git a/core/simd/x86/types.odin b/core/simd/x86/types.odin index 06a2cd41e..ea0eff534 100644 --- a/core/simd/x86/types.odin +++ b/core/simd/x86/types.odin @@ -1,4 +1,4 @@ -//+build i386, amd64 +#+build i386, amd64 package simd_x86 import "core:simd" diff --git a/core/slice/heap/heap.odin b/core/slice/heap/heap.odin index 0a3f53efb..7480a1673 100644 --- a/core/slice/heap/heap.odin +++ b/core/slice/heap/heap.odin @@ -26,7 +26,9 @@ package heap make :: proc(data: []$T, less: proc(a, b: T) -> bool) { // amoritize length lookup length := len(data) - if length <= 1 do return + if length <= 1 { + return + } // start from data parent, no need to consider children for start := (length - 2) / 2; start >= 0; start -= 1 { @@ -53,7 +55,9 @@ push :: proc(data: []$T, less: proc(a, b: T) -> bool) { */ pop :: proc(data: []$T, less: proc(a, b: T) -> bool) { length := len(data) - if length <= 1 do return + if length <= 1 { + return + } last := length @@ -206,7 +210,9 @@ sift_up :: proc(data: []$T, less: proc(a, b: T) -> bool) { // amoritize length lookup length := len(data) - if length <= 1 do return + if length <= 1 { + return + } last := length length = (length - 2) / 2 diff --git a/core/slice/map.odin b/core/slice/map.odin index 50d6dbd37..c68488d26 100644 --- a/core/slice/map.odin +++ b/core/slice/map.odin @@ -1,7 +1,7 @@ package slice -import "core:intrinsics" -import "core:runtime" +import "base:intrinsics" +import "base:runtime" _ :: intrinsics _ :: runtime @@ -48,11 +48,11 @@ map_entries :: proc(m: $M/map[$K]$V, allocator := context.allocator) -> (entries return } -map_entry_infos :: proc(m: $M/map[$K]$V, allocator := context.allocator) -> (entries: []Map_Entry_Info(K, V)) #no_bounds_check { +map_entry_infos :: proc(m: $M/map[$K]$V, allocator := context.allocator) -> (entries: []Map_Entry_Info(K, V), err: runtime.Allocator_Error) #no_bounds_check { m := m rm := (^runtime.Raw_Map)(&m) - info := type_info_base(type_info_of(M)).variant.(Type_Info_Map) + info := runtime.type_info_base(type_info_of(M)).variant.(runtime.Type_Info_Map) if info.map_info != nil { entries = make(type_of(entries), len(m), allocator) or_return @@ -61,8 +61,8 @@ map_entry_infos :: proc(m: $M/map[$K]$V, allocator := context.allocator) -> (ent entry_index := 0 for bucket_index in 0.. ( + 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/ptr.odin b/core/slice/ptr.odin index b17a27dc8..99d4157c3 100644 --- a/core/slice/ptr.odin +++ b/core/slice/ptr.odin @@ -1,7 +1,7 @@ package slice -import "core:builtin" -import "core:runtime" +import "base:builtin" +import "base:runtime" ptr_add :: proc(p: $P/^$T, x: int) -> ^T { return ([^]T)(p)[x:] diff --git a/core/slice/slice.odin b/core/slice/slice.odin index 107f48fb2..66166bddb 100644 --- a/core/slice/slice.odin +++ b/core/slice/slice.odin @@ -1,9 +1,9 @@ package slice -import "core:intrinsics" -import "core:builtin" +import "base:intrinsics" +import "base:builtin" import "core:math/bits" -import "core:runtime" +import "base:runtime" _ :: intrinsics _ :: builtin @@ -37,22 +37,52 @@ to_bytes :: proc "contextless" (s: []$T) -> []byte { } /* - Turn a slice of one type, into a slice of another type. + Turns a byte slice into a type. +*/ +@(require_results) +to_type :: proc(buf: []u8, $T: typeid) -> (T, bool) #optional_ok { + if len(buf) < size_of(T) { + return {}, false + } + return intrinsics.unaligned_load((^T)(raw_data(buf))), true +} - Only converts the type and length of the slice itself. - The length is rounded down to the nearest whole number of items. +/* +Turn a slice of one type, into a slice of another type. + +Only converts the type and length of the slice itself. +The length is rounded down to the nearest whole number of items. + +Example: + + import "core:fmt" + import "core:slice" + + i64s_as_i32s :: proc() { + large_items := []i64{1, 2, 3, 4} + small_items := slice.reinterpret([]i32, large_items) + assert(len(small_items) == 8) + fmt.println(large_items, "->", small_items) + } + + bytes_as_i64s :: proc() { + small_items := [12]byte{} + small_items[0] = 1 + small_items[8] = 2 + large_items := slice.reinterpret([]i64, small_items[:]) + assert(len(large_items) == 1) // only enough bytes to make 1 x i64; two would need at least 8 bytes. + fmt.println(small_items, "->", large_items) + } + + reinterpret_example :: proc() { + i64s_as_i32s() + bytes_as_i64s() + } + +Output: + [1, 2, 3, 4] -> [1, 0, 2, 0, 3, 0, 4, 0] + [1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0] -> [1] - ``` - large_items := []i64{1, 2, 3, 4} - small_items := slice.reinterpret([]i32, large_items) - assert(len(small_items) == 8) - ``` - ``` - small_items := []byte{1, 0, 0, 0, 0, 0, 0, 0, - 2, 0, 0, 0} - large_items := slice.reinterpret([]i64, small_items) - assert(len(large_items) == 1) // only enough bytes to make 1 x i64; two would need at least 8 bytes. - ``` */ @(require_results) reinterpret :: proc "contextless" ($T: typeid/[]$U, s: []$V) -> []U { @@ -78,7 +108,7 @@ swap_between :: proc(a, b: $T/[]$E) { n := builtin.min(len(a), len(b)) if n >= 0 { ptr_swap_overlapping(&a[0], &b[0], size_of(E)*n) - } + } } @@ -96,9 +126,37 @@ contains :: proc(array: $T/[]$E, value: E) -> bool where intrinsics.type_is_comp return found } +/* +Searches the given slice for the given element in O(n) time. + +If you need a custom search condition, see `linear_search_proc` + +Inputs: +- array: The slice to search in. +- key: The element to search for. + +Returns: +- index: The index `i`, such that `array[i]` is the first occurrence of `key` in `array`, or -1 if `key` is not present in `array`. + +Example: + index: int + found: bool + + a := []i32{10, 10, 10, 20} + + index, found = linear_search_reverse(a, 10) + assert(index == 0 && found == true) + + index, found = linear_search_reverse(a, 30) + assert(index == -1 && found == false) + + // Note that `index == 1`, since it is relative to `a[2:]` + index, found = linear_search_reverse(a[2:], 20) + assert(index == 1 && found == true) +*/ @(require_results) linear_search :: proc(array: $A/[]$T, key: T) -> (index: int, found: bool) - where intrinsics.type_is_comparable(T) #no_bounds_check { + where intrinsics.type_is_comparable(T) { for x, i in array { if x == key { return i, true @@ -107,8 +165,18 @@ linear_search :: proc(array: $A/[]$T, key: T) -> (index: int, found: bool) return -1, false } +/* +Searches the given slice for the first element satisfying predicate `f` in O(n) time. + +Inputs: +- array: The slice to search in. +- f: The search condition. + +Returns: +- index: The index `i`, such that `array[i]` is the first `x` in `array` for which `f(x) == true`, or -1 if such `x` does not exist. +*/ @(require_results) -linear_search_proc :: proc(array: $A/[]$T, f: proc(T) -> bool) -> (index: int, found: bool) #no_bounds_check { +linear_search_proc :: proc(array: $A/[]$T, f: proc(T) -> bool) -> (index: int, found: bool) { for x, i in array { if f(x) { return i, true @@ -117,53 +185,163 @@ linear_search_proc :: proc(array: $A/[]$T, f: proc(T) -> bool) -> (index: int, f return -1, false } +/* +Searches the given slice for the given element in O(n) time, starting from the +slice end. + +If you need a custom search condition, see `linear_search_reverse_proc` + +Inputs: +- array: The slice to search in. +- key: The element to search for. + +Returns: +- index: The index `i`, such that `array[i]` is the last occurrence of `key` in `array`, or -1 if `key` is not present in `array`. + +Example: + index: int + found: bool + + a := []i32{10, 10, 10, 20} + + index, found = linear_search_reverse(a, 20) + assert(index == 3 && found == true) + + index, found = linear_search_reverse(a, 10) + assert(index == 2 && found == true) + + index, found = linear_search_reverse(a, 30) + assert(index == -1 && found == false) + + // Note that `index == 1`, since it is relative to `a[2:]` + index, found = linear_search_reverse(a[2:], 20) + assert(index == 1 && found == true) +*/ @(require_results) -binary_search :: proc(array: $A/[]$T, key: T) -> (index: int, found: bool) - where intrinsics.type_is_ordered(T) #no_bounds_check { - - n := len(array) - switch n { - case 0: - return -1, false - case 1: - if array[0] == key { - return 0, true +linear_search_reverse :: proc(array: $A/[]$T, key: T) -> (index: int, found: bool) + where intrinsics.type_is_comparable(T) { + #reverse for x, i in array { + if x == key { + return i, true } - return -1, false - } - - lo, hi := 0, n-1 - - for array[hi] != array[lo] && key >= array[lo] && key <= array[hi] { - when intrinsics.type_is_ordered_numeric(T) { - // NOTE(bill): This is technically interpolation search - m := lo + int((key - array[lo]) * T(hi - lo) / (array[hi] - array[lo])) - } else { - m := lo + (hi - lo)/2 - } - switch { - case array[m] < key: - lo = m + 1 - case key < array[m]: - hi = m - 1 - case: - return m, true - } - } - - if key == array[lo] { - return lo, true } return -1, false } +/* +Searches the given slice for the last element satisfying predicate `f` in O(n) +time, starting from the slice end. + +Inputs: +- array: The slice to search in. +- f: The search condition. + +Returns: +- index: The index `i`, such that `array[i]` is the last `x` in `array` for which `f(x) == true`, or -1 if such `x` does not exist. +*/ +@(require_results) +linear_search_reverse_proc :: proc(array: $A/[]$T, f: proc(T) -> bool) -> (index: int, found: bool) { + #reverse for x, i in array { + if f(x) { + return i, true + } + } + return -1, false +} + +/* +Searches the given slice for the given element. +If the slice is not sorted, the returned index is unspecified and meaningless. + +If the value is found then the returned int is the index of the matching element. +If there are multiple matches, then any one of the matches could be returned. + +If the value is not found then the returned int is the index where a matching +element could be inserted while maintaining sorted order. + +For slices of more complex types see: `binary_search_by` + +Example: + /* + Looks up a series of four elements. The first is found, with a + uniquely determined position; the second and third are not + found; the fourth could match any position in `[1, 4]`. + */ + + index: int + found: bool + + s := []i32{0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55} + + index, found = slice.binary_search(s, 13) + assert(index == 9 && found == true) + + index, found = slice.binary_search(s, 4) + assert(index == 7 && found == false) + + index, found = slice.binary_search(s, 100) + assert(index == 13 && found == false) + + index, found = slice.binary_search(s, 1) + assert(index >= 1 && index <= 4 && found == true) +*/ +@(require_results) +binary_search :: proc(array: $A/[]$T, key: T) -> (index: int, found: bool) + where intrinsics.type_is_ordered(T) #no_bounds_check { + return binary_search_by(array, key, cmp_proc(T)) +} + +/* +Searches the given slice for the given element. +If the slice is not sorted, the returned index is unspecified and meaningless. + +If the value is found then the returned int is the index of the matching element. +If there are multiple matches, then any one of the matches could be returned. + +If the value is not found then the returned int is the index where a matching +element could be inserted while maintaining sorted order. + +The array elements and key may be different types. This allows the filter procedure +to compare keys against a slice of structs, one struct value at a time. + +Returns: +- index: int +- found: bool + +*/ +@(require_results) +binary_search_by :: proc(array: $A/[]$T, key: $K, f: proc(T, K) -> Ordering) -> (index: int, found: bool) #no_bounds_check { + n := len(array) + left, right := 0, n + for left < right { + mid := int(uint(left+right) >> 1) + if f(array[mid], key) == .Less { + left = mid+1 + } else { + // .Equal or .Greater + right = mid + } + } + return left, left < n && f(array[left], key) == .Equal +} @(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 } when intrinsics.type_is_simple_compare(E) { + if len(a) == 0 { + // Empty slices are always equivalent to each other. + // + // This check is here in the event that a slice with a `data` of + // nil is compared against a slice with a non-nil `data` but a + // length of zero. + // + // In that case, `memory_compare` would return -1 or +1 because one + // of the pointers is nil. + return true + } return runtime.memory_compare(raw_data(a), raw_data(b), len(a)*size_of(E)) == 0 } else { for i in 0.. (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) @@ -211,7 +389,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 { @@ -312,6 +490,12 @@ is_empty :: proc(a: $T/[]$E) -> bool { return len(a) == 0 } +// Gets the byte size of the backing data +@(require_results) +size :: proc "contextless" (a: $T/[]$E) -> int { + return len(a) * size_of(E) +} + @(require_results) @@ -397,14 +581,35 @@ reduce :: proc(s: $S/[]$U, initializer: $V, f: proc(V, U) -> V) -> V { } @(require_results) -filter :: proc(s: $S/[]$U, f: proc(U) -> bool, allocator := context.allocator) -> S { - r := make([dynamic]U, 0, 0, allocator) +reduce_reverse :: proc(s: $S/[]$U, initializer: $V, f: proc(V, U) -> V) -> V { + r := initializer + for i := len(s)-1; i >= 0; i -= 1 { + #no_bounds_check r = f(r, s[i]) + } + return r +} + +@(require_results) +filter :: proc(s: $S/[]$U, f: proc(U) -> bool, allocator := context.allocator) -> (res: S, err: runtime.Allocator_Error) #optional_allocator_error { + r := make([dynamic]U, 0, 0, allocator) or_return for v in s { if f(v) { append(&r, v) } } - return r[:] + return r[:], nil +} + +@(require_results) +filter_reverse :: proc(s: $S/[]$U, f: proc(U) -> bool, allocator := context.allocator) -> (res: S, err: runtime.Allocator_Error) #optional_allocator_error { + r := make([dynamic]U, 0, 0, allocator) or_return + for i := len(s)-1; i >= 0; i -= 1 { + #no_bounds_check v := s[i] + if f(v) { + append(&r, v) + } + } + return r[:], nil } @(require_results) @@ -427,6 +632,64 @@ scanner :: proc (s: $S/[]$U, initializer: $V, f: proc(V, U) -> V, allocator := c } +@(require_results) +repeat :: proc(s: $S/[]$U, count: int, allocator := context.allocator) -> (b: S, err: runtime.Allocator_Error) #optional_allocator_error { + if count < 0 { + panic("slice: negative repeat count") + } else if count > 0 && (len(s)*count)/count != len(s) { + panic("slice: repeat count will cause an overflow") + } + + b = make(S, len(s)*count, allocator) or_return + i := copy(b, s) + for i < len(b) { // 2^N trick to reduce the need to copy + copy(b[i:], b[:i]) + i *= 2 + } + return +} + +// 'unique' replaces consecutive runs of equal elements with a single copy. +// The procedures modifies the slice in-place and returns the modified slice. +@(require_results) +unique :: proc(s: $S/[]$T) -> S where intrinsics.type_is_comparable(T) #no_bounds_check { + if len(s) < 2 { + return s + } + i := 1 + for j in 1.. bool) -> S #no_bounds_check { + if len(s) < 2 { + return s + } + i := 1 + for j in 1.. (res: T, ok: bool) where intrinsics.type_is_ordered(T) #optional_ok { if len(s) != 0 { @@ -463,6 +726,40 @@ min_max :: proc(s: $S/[]$T) -> (min, max: T, ok: bool) where intrinsics.type_is_ return } +// Find the index of the (first) minimum element in a slice. +@(require_results) +min_index :: proc(s: $S/[]$T) -> (min_index: int, ok: bool) where intrinsics.type_is_ordered(T) #optional_ok { + if len(s) == 0 { + return -1, false + } + min_index = 0 + min_value := s[0] + for v, i in s[1:] { + if v < min_value { + min_value = v + min_index = i+1 + } + } + return min_index, true +} + +// Find the index of the (first) maximum element in a slice. +@(require_results) +max_index :: proc(s: $S/[]$T) -> (max_index: int, ok: bool) where intrinsics.type_is_ordered(T) #optional_ok { + if len(s) == 0 { + return -1, false + } + max_index = 0 + max_value := s[0] + for v, i in s[1:] { + if v > max_value { + max_value = v + max_index = i+1 + } + } + return max_index, true +} + @(require_results) any_of :: proc(s: $S/[]$T, value: T) -> bool where intrinsics.type_is_comparable(T) { for v in s { @@ -571,3 +868,39 @@ enumerated_array :: proc(ptr: ^$T) -> []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/slice/sort.odin b/core/slice/sort.odin index 515eddcc3..3b4119afa 100644 --- a/core/slice/sort.odin +++ b/core/slice/sort.odin @@ -62,7 +62,7 @@ _sort_by_indices :: proc(data, sorted: $T/[]$E, indices: []int) { sort_by_indices_overwrite :: proc(data: $T/[]$E, indices: []int) { assert(len(data) == len(indices)) - temp := make([]int, len(data), context.allocator) + temp := make([]E, len(data), context.allocator) defer delete(temp) for v, i in indices { temp[i] = data[v] diff --git a/core/slice/sort_private.odin b/core/slice/sort_private.odin index 32eb7d417..36637c4cd 100644 --- a/core/slice/sort_private.odin +++ b/core/slice/sort_private.odin @@ -1,7 +1,7 @@ -//+private +#+private package slice -import "core:intrinsics" +import "base:intrinsics" _ :: intrinsics ORD :: intrinsics.type_is_ordered diff --git a/core/sort/sort.odin b/core/sort/sort.odin index ad972ef8e..322613cc4 100644 --- a/core/sort/sort.odin +++ b/core/sort/sort.odin @@ -2,7 +2,7 @@ package sort import "core:mem" import _slice "core:slice" -import "core:intrinsics" +import "base:intrinsics" _ :: intrinsics _ :: _slice diff --git a/core/strconv/decimal/decimal.odin b/core/strconv/decimal/decimal.odin index 4130da306..06503d01a 100644 --- a/core/strconv/decimal/decimal.odin +++ b/core/strconv/decimal/decimal.odin @@ -249,7 +249,7 @@ shift_right :: proc(a: ^Decimal, k: uint) { trim(a) } -import "core:runtime" +import "base:runtime" println :: proc(args: ..any) { for arg, i in args { if i != 0 { diff --git a/core/strconv/generic_float.odin b/core/strconv/generic_float.odin index 70febf832..b049f0fe1 100644 --- a/core/strconv/generic_float.odin +++ b/core/strconv/generic_float.odin @@ -103,8 +103,10 @@ generic_ftoa :: proc(buf: []byte, val: f64, fmt: byte, precision, bit_size: int) } } else { switch fmt { - case 'e', 'E': decimal.round(d, prec+1) - case 'f', 'F': decimal.round(d, d.decimal_point+prec) + case 'e', 'E': + decimal.round(d, prec + 1) + case 'f', 'F': + decimal.round(d, d.decimal_point+prec) case 'g', 'G': if prec == 0 { prec = 1 @@ -373,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 eae9f9504..26a737bd1 100644 --- a/core/strconv/strconv.odin +++ b/core/strconv/strconv.odin @@ -7,11 +7,11 @@ Parses a boolean value from the input string **Inputs** - s: The input string - - true: "1", "t", "T", "true", "TRUE", "True" - - false: "0", "f", "F", "false", "FALSE", "False" + - true: "1", "t", "T", "true", "TRUE", "True" + - false: "0", "f", "F", "false", "FALSE", "False" - n: An optional pointer to an int to store the length of the parsed substring (default: nil) -**Returns** +**Returns** - result: The parsed boolean value (default: false) - ok: A boolean indicating whether the parsing was successful */ @@ -29,7 +29,7 @@ parse_bool :: proc(s: string, n: ^int = nil) -> (result: bool = false, ok: bool) /* Finds the integer value of the given rune -**Inputs** +**Inputs** - r: The input rune to find the integer value of **Returns** The integer value of the given rune @@ -47,7 +47,7 @@ _digit_value :: proc(r: rune) -> int { /* Parses an integer value from the input string in the given base, without a prefix -**Inputs** +**Inputs** - str: The input string to parse the integer value from - base: The base of the integer value to be parsed (must be between 1 and 16) - n: An optional pointer to an int to store the length of the parsed substring (default: nil) @@ -65,7 +65,7 @@ Output: -1234 false -**Returns** +**Returns** - value: Parses an integer value from a string, in the given base, without a prefix. - ok: ok=false if no numeric value of the appropriate base could be found, or if the input string contained more than just the number. */ @@ -117,12 +117,12 @@ parse_i64_of_base :: proc(str: string, base: int, n: ^int = nil) -> (value: i64, /* Parses an integer value from the input string in base 10, unless there's a prefix -**Inputs** +**Inputs** - str: The input string to parse the integer value from - n: An optional pointer to an int to store the length of the parsed substring (default: nil) Example: - + import "core:fmt" import "core:strconv" parse_i64_maybe_prefixed_example :: proc() { @@ -132,13 +132,13 @@ Example: n, ok = strconv.parse_i64_maybe_prefixed("0xeeee") fmt.println(n,ok) } - + Output: 1234 true 61166 true -**Returns** +**Returns** - value: The parsed integer value - ok: ok=false if a valid integer could not be found, or if the input string contained more than just the number. */ @@ -200,14 +200,14 @@ parse_i64 :: proc{parse_i64_maybe_prefixed, parse_i64_of_base} /* Parses an unsigned 64-bit integer value from the input string without a prefix, using the specified base -**Inputs** +**Inputs** - str: The input string to parse - base: The base of the number system to use for parsing - - Must be between 1 and 16 (inclusive) + - Must be between 1 and 16 (inclusive) - n: An optional pointer to an int to store the length of the parsed substring (default: nil) Example: - + import "core:fmt" import "core:strconv" parse_u64_of_base_example :: proc() { @@ -217,13 +217,13 @@ Example: n, ok = strconv.parse_u64_of_base("5678eee",16) fmt.println(n,ok) } - + Output: 1234 false 90672878 true -**Returns** +**Returns** - value: The parsed uint64 value - ok: A boolean indicating whether the parsing was successful */ @@ -261,15 +261,15 @@ parse_u64_of_base :: proc(str: string, base: int, n: ^int = nil) -> (value: u64, /* Parses an unsigned 64-bit integer value from the input string, using the specified base or inferring the base from a prefix -**Inputs** +**Inputs** - str: The input string to parse - base: The base of the number system to use for parsing (default: 0) - - If base is 0, it will be inferred based on the prefix in the input string (e.g. '0x' for hexadecimal) - - If base is not 0, it will be used for parsing regardless of any prefix in the input string + - If base is 0, it will be inferred based on the prefix in the input string (e.g. '0x' for hexadecimal) + - If base is not 0, it will be used for parsing regardless of any prefix in the input string - n: An optional pointer to an int to store the length of the parsed substring (default: nil) Example: - + import "core:fmt" import "core:strconv" parse_u64_maybe_prefixed_example :: proc() { @@ -279,13 +279,13 @@ Example: n, ok = strconv.parse_u64_maybe_prefixed("0xee") fmt.println(n,ok) } - + Output: 1234 true 238 true -**Returns** +**Returns** - value: The parsed uint64 value - ok: ok=false if a valid integer could not be found, if the value was negative, or if the input string contained more than just the number. */ @@ -336,14 +336,14 @@ parse_u64 :: proc{parse_u64_maybe_prefixed, parse_u64_of_base} /* Parses a signed integer value from the input string, using the specified base or inferring the base from a prefix -**Inputs** +**Inputs** - s: The input string to parse - base: The base of the number system to use for parsing (default: 0) - - If base is 0, it will be inferred based on the prefix in the input string (e.g. '0x' for hexadecimal) - - If base is not 0, it will be used for parsing regardless of any prefix in the input string + - If base is 0, it will be inferred based on the prefix in the input string (e.g. '0x' for hexadecimal) + - If base is not 0, it will be used for parsing regardless of any prefix in the input string Example: - + import "core:fmt" import "core:strconv" parse_int_example :: proc() { @@ -356,14 +356,14 @@ Example: n, ok = strconv.parse_int("0xffff") // with prefix and inferred base fmt.println(n,ok) } - + Output: 1234 true 65535 true 65535 true -**Returns** +**Returns** - value: The parsed int value - ok: `false` if no appropriate value could be found, or if the input string contained more than just the number. */ @@ -379,11 +379,11 @@ parse_int :: proc(s: string, base := 0, n: ^int = nil) -> (value: int, ok: bool) /* Parses an unsigned integer value from the input string, using the specified base or inferring the base from a prefix -**Inputs** +**Inputs** - s: The input string to parse - base: The base of the number system to use for parsing (default: 0, inferred) - - If base is 0, it will be inferred based on the prefix in the input string (e.g. '0x' for hexadecimal) - - If base is not 0, it will be used for parsing regardless of any prefix in the input string + - If base is 0, it will be inferred based on the prefix in the input string (e.g. '0x' for hexadecimal) + - If base is not 0, it will be used for parsing regardless of any prefix in the input string Example: @@ -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, @@ -1098,6 +1121,7 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { break trunc_block } f := f64(mantissa) + f_abs := f if neg { f = -f } @@ -1109,7 +1133,7 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { f *= pow10[exp-22] exp = 22 } - if f > 1e15 || f < 1e-15 { + if f_abs > 1e15 || f_abs < 1e-15 { break trunc_block } return f * pow10[exp], nr, true @@ -1124,6 +1148,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 @@ -1213,6 +1506,13 @@ Output: append_int :: proc(buf: []byte, i: i64, base: int) -> string { return append_bits(buf, u64(i), base, true, 8*size_of(int), digits, nil) } + + + +append_u128 :: proc(buf: []byte, u: u128, base: int) -> string { + return append_bits_128(buf, u, base, false, 8*size_of(uint), digits, nil) +} + /* Converts an integer value to a string and stores it in the given buffer @@ -1276,7 +1576,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: @@ -1430,7 +1730,7 @@ quote_rune :: proc(buf: []byte, r: rune) -> string { } } - if buf == nil { + if buf == nil || r < 0 { return "" } diff --git a/core/strings/ascii_set.odin b/core/strings/ascii_set.odin index 247b38527..98fcd3719 100644 --- a/core/strings/ascii_set.odin +++ b/core/strings/ascii_set.odin @@ -1,4 +1,3 @@ -//+private package strings import "core:unicode/utf8" diff --git a/core/strings/builder.odin b/core/strings/builder.odin index d87626d07..97a615990 100644 --- a/core/strings/builder.odin +++ b/core/strings/builder.odin @@ -1,6 +1,6 @@ package strings -import "core:runtime" +import "base:runtime" import "core:unicode/utf8" import "core:strconv" import "core:mem" @@ -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 } @@ -502,7 +516,7 @@ pop_byte :: proc(b: ^Builder) -> (r: byte) { } r = b.buf[len(b.buf)-1] - d := cast(^runtime.Raw_Dynamic_Array)&b.buf + d := (^runtime.Raw_Dynamic_Array)(&b.buf) d.len = max(d.len-1, 0) return } @@ -522,7 +536,7 @@ pop_rune :: proc(b: ^Builder) -> (r: rune, width: int) { } r, width = utf8.decode_last_rune(b.buf[:]) - d := cast(^runtime.Raw_Dynamic_Array)&b.buf + d := (^runtime.Raw_Dynamic_Array)(&b.buf) d.len = max(d.len-width, 0) return } @@ -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 812307b2e..4c270980c 100644 --- a/core/strings/intern.odin +++ b/core/strings/intern.odin @@ -1,6 +1,6 @@ package strings -import "core:runtime" +import "base:runtime" import "core:mem" // Custom string entry struct @@ -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 2f36eddbe..c014d2b2b 100644 --- a/core/strings/strings.odin +++ b/core/strings/strings.odin @@ -1,6 +1,8 @@ // Procedures to manipulate UTF-8 encoded strings package strings +import "base:intrinsics" +import "core:bytes" import "core:io" import "core:mem" import "core:unicode" @@ -91,8 +93,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 "contextless" (ptr: [^]byte, len: int) -> (res: string) { + s := string(ptr[:len]) s = truncate_to_byte(s, 0) return s } @@ -137,7 +139,7 @@ NOTE: Failure to find the byte results in returning the entire string. Returns: - res: The truncated string */ -truncate_to_byte :: proc(str: string, b: byte) -> (res: string) { +truncate_to_byte :: proc "contextless" (str: string, b: byte) -> (res: string) { n := index_byte(str, b) if n < 0 { n = len(str) @@ -259,7 +261,7 @@ Inputs: Returns: - result: `-1` if `lhs` comes first, `1` if `rhs` comes first, or `0` if they are equal */ -compare :: proc(lhs, rhs: string) -> (result: int) { +compare :: proc "contextless" (lhs, rhs: string) -> (result: int) { return mem.compare(transmute([]byte)lhs, transmute([]byte)rhs) } /* @@ -344,6 +346,17 @@ Output: contains_any :: proc(s, chars: string) -> (res: bool) { return index_any(s, chars) >= 0 } + + +contains_space :: proc(s: string) -> (res: bool) { + for c in s { + if is_space(c) { + return true + } + } + return false +} + /* Returns the UTF-8 rune count of the string `s` @@ -531,6 +544,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 +578,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 @@ -691,6 +710,63 @@ The concatenated string, and an error if allocation fails concatenate_safe :: proc(a: []string, allocator := context.allocator) -> (res: string, err: mem.Allocator_Error) { return concatenate(a, allocator) } + +/* +Returns a substring of the input string `s` with the specified rune offset and length + +Inputs: +- s: The input string to cut +- rune_offset: The starting rune index (default is 0). In runes, not bytes. +- rune_length: The number of runes to include in the substring (default is 0, which returns the remainder of the string). In runes, not bytes. + +Returns: +- res: The substring + +Example: + + import "core:fmt" + import "core:strings" + + cut_example :: proc() { + fmt.println(strings.cut("some example text", 0, 4)) // -> "some" + fmt.println(strings.cut("some example text", 2, 2)) // -> "me" + fmt.println(strings.cut("some example text", 5, 7)) // -> "example" + } + +Output: + + some + me + example + +*/ +cut :: proc(s: string, rune_offset := int(0), rune_length := int(0)) -> (res: string) { + s := s; rune_length := rune_length + + count := 0 + for _, offset in s { + if count == rune_offset { + s = s[offset:] + break + } + count += 1 + } + + if rune_length < 1 { + return s + } + + count = 0 + for _, offset in s { + if count == rune_length { + s = s[:offset] + break + } + count += 1 + } + return s +} + /* Returns a substring of the input string `s` with the specified rune offset and length @@ -712,9 +788,9 @@ Example: import "core:strings" cut_example :: proc() { - fmt.println(strings.cut("some example text", 0, 4)) // -> "some" - fmt.println(strings.cut("some example text", 2, 2)) // -> "me" - fmt.println(strings.cut("some example text", 5, 7)) // -> "example" + fmt.println(strings.cut_clone("some example text", 0, 4)) // -> "some" + fmt.println(strings.cut_clone("some example text", 2, 2)) // -> "me" + fmt.println(strings.cut_clone("some example text", 5, 7)) // -> "example" } Output: @@ -724,57 +800,11 @@ Output: example */ -cut :: proc(s: string, rune_offset := int(0), rune_length := int(0), allocator := context.allocator, loc := #caller_location) -> (res: string, err: mem.Allocator_Error) #optional_allocator_error { - s := s; rune_length := rune_length - context.allocator = allocator - - // If we signal that we want the entire remainder (length <= 0) *and* - // the offset is zero, then we can early out by cloning the input - if rune_offset == 0 && rune_length <= 0 { - return clone(s) - } - - // We need to know if we have enough runes to cover offset + length. - rune_count := utf8.rune_count_in_string(s) - - // We're asking for a substring starting after the end of the input string. - // That's just an empty string. - if rune_offset >= rune_count { - return "", nil - } - - // If we don't specify the length of the substring, use the remainder. - if rune_length <= 0 { - rune_length = rune_count - rune_offset - } - - // We don't yet know how many bytes we need exactly. - // But we do know it's bounded by the number of runes * 4 bytes, - // and can be no more than the size of the input string. - bytes_needed := min(rune_length * 4, len(s)) - buf := make([]u8, bytes_needed, allocator, loc) or_return - - byte_offset := 0 - for i := 0; i < rune_count; i += 1 { - _, w := utf8.decode_rune_in_string(s) - - // If the rune is part of the substring, copy it to the output buffer. - if i >= rune_offset { - for j := 0; j < w; j += 1 { - buf[byte_offset+j] = s[j] - } - byte_offset += w - } - - // We're done if we reach the end of the input string, *or* - // if we've reached a specified length in runes. - if rune_length > 0 { - if i == rune_offset + rune_length - 1 { break } - } - s = s[w:] - } - return string(buf[:byte_offset]), nil +cut_clone :: proc(s: string, rune_offset := int(0), rune_length := int(0), allocator := context.allocator, loc := #caller_location) -> (res: string, err: mem.Allocator_Error) #optional_allocator_error { + res = cut(s, rune_offset, rune_length) + return clone(res, allocator, loc) } + /* Splits the input string `s` into a slice of substrings separated by the specified `sep` string @@ -809,7 +839,7 @@ _split :: proc(s_, sep: string, sep_save, n_: int, allocator := context.allocato n = l } - res := make([]string, n, allocator, loc) or_return + res = make([]string, n, allocator, loc) or_return for i := 0; i < n-1; i += 1 { _, w := utf8.decode_rune_in_string(s) res[i] = s[:w] @@ -885,6 +915,7 @@ Splits a string into parts based on a separator. If n < count of seperators, the Inputs: - s: The string to split. - sep: The separator string used to split the input string. +- n: The maximum amount of parts to split the string into. - allocator: (default is context.allocator) Returns: @@ -1000,11 +1031,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 @@ -1421,13 +1447,8 @@ Output: -1 */ -index_byte :: proc(s: string, c: byte) -> (res: int) { - for i := 0; i < len(s); i += 1 { - if s[i] == c { - return i - } - } - return -1 +index_byte :: proc "contextless" (s: string, c: byte) -> (res: int) { + return #force_inline bytes.index_byte(transmute([]u8)s, c) } /* Returns the byte offset of the last byte `c` in the string `s`, -1 when not found. @@ -1461,13 +1482,8 @@ Output: -1 */ -last_index_byte :: proc(s: string, c: byte) -> (res: int) { - for i := len(s)-1; i >= 0; i -= 1 { - if s[i] == c { - return i - } - } - return -1 +last_index_byte :: proc "contextless" (s: string, c: byte) -> (res: int) { + return #force_inline bytes.last_index_byte(transmute([]u8)s, c) } /* Returns the byte offset of the first rune `r` in the string `s` it finds, -1 when not found. @@ -1560,8 +1576,8 @@ Output: -1 */ -index :: proc(s, substr: string) -> (res: int) { - hash_str_rabin_karp :: proc(s: string) -> (hash: u32 = 0, pow: u32 = 1) { +index :: proc "contextless" (s, substr: string) -> (res: int) { + hash_str_rabin_karp :: proc "contextless" (s: string) -> (hash: u32 = 0, pow: u32 = 1) { for i := 0; i < len(s); i += 1 { hash = hash*PRIME_RABIN_KARP + u32(s[i]) } @@ -1791,7 +1807,8 @@ last_index_any :: proc(s, chars: string) -> (res: int) { if r >= utf8.RUNE_SELF { r = utf8.RUNE_ERROR } - return index_rune(chars, r) + i := index_rune(chars, r) + return i if i < 0 else 0 } if len(s) > 8 { @@ -1855,7 +1872,8 @@ index_multi :: proc(s: string, substrs: []string) -> (idx: int, width: int) { lowest_index := len(s) found := false for substr in substrs { - if i := index(s, substr); i >= 0 { + haystack := s[:min(len(s), lowest_index + len(substr))] + if i := index(haystack, substr); i >= 0 { if i < lowest_index { lowest_index = i width = len(substr) @@ -2068,7 +2086,10 @@ replace :: proc(s, old, new: string, n: int, allocator := context.allocator, loc } - t := make([]byte, len(s) + byte_count*(len(new) - len(old)), allocator, loc) + t, err := make([]byte, len(s) + byte_count*(len(new) - len(old)), allocator, loc) + if err != nil { + return + } was_allocation = true w := 0 @@ -2412,9 +2433,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 { @@ -2712,7 +2730,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 } @@ -3310,3 +3328,106 @@ levenshtein_distance :: proc(a, b: string, allocator := context.allocator, loc : return costs[n], nil } + +@(private) +internal_substring :: proc(s: string, rune_start: int, rune_end: int) -> (sub: string, ok: bool) { + sub = s + ok = true + + rune_i: int + + if rune_start > 0 { + ok = false + for _, i in sub { + if rune_start == rune_i { + ok = true + sub = sub[i:] + break + } + rune_i += 1 + } + if !ok { return } + } + + if rune_end >= rune_start { + ok = false + for _, i in sub { + if rune_end == rune_i { + ok = true + sub = sub[:i] + break + } + rune_i += 1 + } + + if rune_end == rune_i { + ok = true + } + } + + return +} + +/* +Returns a substring of `s` that starts at rune index `rune_start` and goes up to `rune_end`. + +Think of it as slicing `s[rune_start:rune_end]` but rune-wise. + +Inputs: +- s: the string to substring +- rune_start: the start (inclusive) rune +- rune_end: the end (exclusive) rune + +Returns: +- sub: the substring +- ok: whether the rune indexes where in bounds of the original string +*/ +substring :: proc(s: string, rune_start: int, rune_end: int) -> (sub: string, ok: bool) { + if rune_start < 0 || rune_end < 0 || rune_end < rune_start { + return + } + + return internal_substring(s, rune_start, rune_end) +} + +/* +Returns a substring of `s` that starts at rune index `rune_start` and goes up to the end of the string. + +Think of it as slicing `s[rune_start:]` but rune-wise. + +Inputs: +- s: the string to substring +- rune_start: the start (inclusive) rune + +Returns: +- sub: the substring +- ok: whether the rune indexes where in bounds of the original string +*/ +substring_from :: proc(s: string, rune_start: int) -> (sub: string, ok: bool) { + if rune_start < 0 { + return + } + + return internal_substring(s, rune_start, -1) +} + +/* +Returns a substring of `s` that goes up to rune index `rune_end`. + +Think of it as slicing `s[:rune_end]` but rune-wise. + +Inputs: +- s: the string to substring +- rune_end: the end (exclusive) rune + +Returns: +- sub: the substring +- ok: whether the rune indexes where in bounds of the original string +*/ +substring_to :: proc(s: string, rune_end: int) -> (sub: string, ok: bool) { + if rune_end < 0 { + return + } + + return internal_substring(s, -1, rune_end) +} diff --git a/core/sync/atomic.odin b/core/sync/atomic.odin index 0900a6544..7e514a6b4 100644 --- a/core/sync/atomic.odin +++ b/core/sync/atomic.odin @@ -1,45 +1,453 @@ package sync -import "core:intrinsics" +import "base:intrinsics" +/* +This procedure may lower CPU consumption or yield to a hyperthreaded twin +processor. It's exact function is architecture specific, but the intent is to +say that you're not doing much on a CPU. +*/ cpu_relax :: intrinsics.cpu_relax /* -Atomic_Memory_Order :: enum { - Relaxed = 0, // Unordered - Consume = 1, // Monotonic - Acquire = 2, - Release = 3, - Acq_Rel = 4, - Seq_Cst = 5, -} +Describes memory ordering for an atomic operation. + +Modern CPU's contain multiple cores and caches specific to those cores. When a +core performs a write to memory, the value is written to cache first. The issue +is that a core doesn't typically see what's inside the caches of other cores. +In order to make operations consistent CPU's implement mechanisms that +synchronize memory operations across cores by asking other cores or by +pushing data about writes to other cores. + +Due to how these algorithms are implemented, the stores and loads performed by +one core may seem to happen in a different order to another core. It also may +happen that a core reorders stores and loads (independent of how compiler put +them into the machine code). This can cause issues when trying to synchronize +multiple memory locations between two cores. Which is why CPU's allow for +stronger memory ordering guarantees if certain instructions or instruction +variants are used. + +In Odin there are 5 different memory ordering guaranties that can be provided +to an atomic operation: + +- `Relaxed`: The memory access (load or store) is unordered with respect to + other memory accesses. This can be used to implement an atomic counter. + Multiple threads access a single variable, but it doesn't matter when + exactly it gets incremented, because it will become eventually consistent. +- `Consume`: No loads or stores dependent on a memory location can be + reordered before a load with consume memory order. If other threads released + the same memory, it becomes visible. +- `Acquire`: No loads or stores on a memory location can be reordered before a + load of that memory location with acquire memory ordering. If other threads + release the same memory, it becomes visible. +- `Release`: No loads or stores on a memory location can be reordered after a + store of that memory location with release memory ordering. All threads that + acquire the same memory location will see all writes done by the current + thread. +- `Acq_Rel`: Acquire-release memory ordering: combines acquire and release + memory orderings in the same operation. +- `Seq_Cst`: Sequential consistency. The strongest memory ordering. A load will + always be an acquire operation, a store will always be a release operation, + and in addition to that all threads observe the same order of writes. + +Non-explicit atomics will always be sequentially consistent. + + Atomic_Memory_Order :: enum { + Relaxed = 0, // Unordered + Consume = 1, // Monotonic + Acquire = 2, + Release = 3, + Acq_Rel = 4, + Seq_Cst = 5, + } + +**Note(i386, x64)**: x86 has a very strong memory model by default. It +guarantees that all writes are ordered, stores and loads aren't reordered. In +a sense, all operations are at least acquire and release operations. If `lock` +prefix is used, all operations are sequentially consistent. If you use explicit +atomics, make sure you have the correct atomic memory order, because bugs likely +will not show up in x86, but may show up on e.g. arm. More on x86 memory +ordering can be found +[[here; https://www.cs.cmu.edu/~410-f10/doc/Intel_Reordering_318147.pdf]] */ Atomic_Memory_Order :: intrinsics.Atomic_Memory_Order +/* +Establish memory ordering. -atomic_thread_fence :: intrinsics.atomic_thread_fence -atomic_signal_fence :: intrinsics.atomic_signal_fence -atomic_store :: intrinsics.atomic_store -atomic_store_explicit :: intrinsics.atomic_store_explicit -atomic_load :: intrinsics.atomic_load -atomic_load_explicit :: intrinsics.atomic_load_explicit -atomic_add :: intrinsics.atomic_add -atomic_add_explicit :: intrinsics.atomic_add_explicit -atomic_sub :: intrinsics.atomic_sub -atomic_sub_explicit :: intrinsics.atomic_sub_explicit -atomic_and :: intrinsics.atomic_and -atomic_and_explicit :: intrinsics.atomic_and_explicit -atomic_nand :: intrinsics.atomic_nand -atomic_nand_explicit :: intrinsics.atomic_nand_explicit -atomic_or :: intrinsics.atomic_or -atomic_or_explicit :: intrinsics.atomic_or_explicit -atomic_xor :: intrinsics.atomic_xor -atomic_xor_explicit :: intrinsics.atomic_xor_explicit -atomic_exchange :: intrinsics.atomic_exchange -atomic_exchange_explicit :: intrinsics.atomic_exchange_explicit +This procedure establishes memory ordering, without an associated atomic +operation. +*/ +atomic_thread_fence :: intrinsics.atomic_thread_fence -// Returns value and optional ok boolean -atomic_compare_exchange_strong :: intrinsics.atomic_compare_exchange_strong +/* +Establish memory ordering between a current thread and a signal handler. + +This procedure establishes memory ordering between a thread and a signal +handler, that run on the same thread, without an associated atomic operation. +This procedure is equivalent to `atomic_thread_fence`, except it doesn't +issue any CPU instructions for memory ordering. +*/ +atomic_signal_fence :: intrinsics.atomic_signal_fence + +/* +Atomically store a value into memory. + +This procedure stores a value to a memory location in such a way that no other +thread is able to see partial reads. This operation is sequentially-consistent. +*/ +atomic_store :: intrinsics.atomic_store + +/* +Atomically store a value into memory with explicit memory ordering. + +This procedure stores a value to a memory location in such a way that no other +thread is able to see partial reads. The memory ordering of this operation is +as specified by the `order` parameter. +*/ +atomic_store_explicit :: intrinsics.atomic_store_explicit + +/* +Atomically load a value from memory. + +This procedure loads a value from a memory location in such a way that the +received value is not a partial read. The memory ordering of this operation is +sequentially-consistent. +*/ +atomic_load :: intrinsics.atomic_load + +/* +Atomically load a value from memory with explicit memory ordering. + +This procedure loads a value from a memory location in such a way that the +received value is not a partial read. The memory ordering of this operation +is as specified by the `order` parameter. +*/ +atomic_load_explicit :: intrinsics.atomic_load_explicit + +/* +Atomically add a value to the value stored in memory. + +This procedure loads a value from memory, adds the specified value to it, and +stores it back as an atomic operation. This operation is an atomic equivalent +of the following: + + dst^ += val + +The memory ordering of this operation is sequentially-consistent. +*/ +atomic_add :: intrinsics.atomic_add + +/* +Atomically add a value to the value stored in memory. + +This procedure loads a value from memory, adds the specified value to it, and +stores it back as an atomic operation. This operation is an atomic equivalent +of the following: + + dst^ += val + +The memory ordering of this operation is as specified by the `order` parameter. +*/ +atomic_add_explicit :: intrinsics.atomic_add_explicit + +/* +Atomically subtract a value from the value stored in memory. + +This procedure loads a value from memory, subtracts the specified value from it, +and stores the result back as an atomic operation. This operation is an atomic +equivalent of the following: + + dst^ -= val + +The memory ordering of this operation is sequentially-consistent. +*/ +atomic_sub :: intrinsics.atomic_sub + +/* +Atomically subtract a value from the value stored in memory. + +This procedure loads a value from memory, subtracts the specified value from it, +and stores the result back as an atomic operation. This operation is an atomic +equivalent of the following: + + dst^ -= val + +The memory ordering of this operation is as specified by the `order` parameter. +*/ +atomic_sub_explicit :: intrinsics.atomic_sub_explicit + +/* +Atomically replace the memory location with the result of AND operation with +the specified value. + +This procedure loads a value from memory, calculates the result of AND operation +between the loaded value and the specified value, and stores it back into the +same memory location as an atomic operation. This operation is an atomic +equivalent of the following: + + dst^ &= val + +The memory ordering of this operation is sequentially-consistent. +*/ +atomic_and :: intrinsics.atomic_and + +/* +Atomically replace the memory location with the result of AND operation with +the specified value. + +This procedure loads a value from memory, calculates the result of AND operation +between the loaded value and the specified value, and stores it back into the +same memory location as an atomic operation. This operation is an atomic +equivalent of the following: + + dst^ &= val + +The memory ordering of this operation is as specified by the `order` parameter. +*/ +atomic_and_explicit :: intrinsics.atomic_and_explicit + +/* +Atomically replace the memory location with the result of NAND operation with +the specified value. + +This procedure loads a value from memory, calculates the result of NAND operation +between the loaded value and the specified value, and stores it back into the +same memory location as an atomic operation. This operation is an atomic +equivalent of the following: + + dst^ = ~(dst^ & val) + +The memory ordering of this operation is sequentially-consistent. +*/ +atomic_nand :: intrinsics.atomic_nand + +/* +Atomically replace the memory location with the result of NAND operation with +the specified value. + +This procedure loads a value from memory, calculates the result of NAND operation +between the loaded value and the specified value, and stores it back into the +same memory location as an atomic operation. This operation is an atomic +equivalent of the following: + + dst^ = ~(dst^ & val) + +The memory ordering of this operation is as specified by the `order` parameter. +*/ +atomic_nand_explicit :: intrinsics.atomic_nand_explicit + +/* +Atomically replace the memory location with the result of OR operation with +the specified value. + +This procedure loads a value from memory, calculates the result of OR operation +between the loaded value and the specified value, and stores it back into the +same memory location as an atomic operation. This operation is an atomic +equivalent of the following: + + dst^ |= val + +The memory ordering of this operation is sequentially-consistent. +*/ +atomic_or :: intrinsics.atomic_or + +/* +Atomically replace the memory location with the result of OR operation with +the specified value. + +This procedure loads a value from memory, calculates the result of OR operation +between the loaded value and the specified value, and stores it back into the +same memory location as an atomic operation. This operation is an atomic +equivalent of the following: + + dst^ |= val + +The memory ordering of this operation is as specified by the `order` parameter. +*/ +atomic_or_explicit :: intrinsics.atomic_or_explicit + +/* +Atomically replace the memory location with the result of XOR operation with +the specified value. + +This procedure loads a value from memory, calculates the result of XOR operation +between the loaded value and the specified value, and stores it back into the +same memory location as an atomic operation. This operation is an atomic +equivalent of the following: + + dst^ ~= val + +The memory ordering of this operation is sequentially-consistent. +*/ +atomic_xor :: intrinsics.atomic_xor + +/* +Atomically replace the memory location with the result of XOR operation with +the specified value. + +This procedure loads a value from memory, calculates the result of XOR operation +between the loaded value and the specified value, and stores it back into the +same memory location as an atomic operation. This operation is an atomic +equivalent of the following: + + dst^ ~= val + +The memory ordering of this operation is as specified by the `order` parameter. +*/ +atomic_xor_explicit :: intrinsics.atomic_xor_explicit + +/* +Atomically exchange the value in a memory location, with the specified value. + +This procedure loads a value from the specified memory location, and stores the +specified value into that memory location. Then the loaded value is returned, +all done in a single atomic operation. This operation is an atomic equivalent +of the following: + + tmp := dst^ + dst^ = val + return tmp + +The memory ordering of this operation is sequentially-consistent. +*/ +atomic_exchange :: intrinsics.atomic_exchange + +/* +Atomically exchange the value in a memory location, with the specified value. + +This procedure loads a value from the specified memory location, and stores the +specified value into that memory location. Then the loaded value is returned, +all done in a single atomic operation. This operation is an atomic equivalent +of the following: + + tmp := dst^ + dst^ = val + return tmp + +The memory ordering of this operation is as specified by the `order` parameter. +*/ +atomic_exchange_explicit :: intrinsics.atomic_exchange_explicit + +/* +Atomically compare and exchange the value with a memory location. + +This procedure checks if the value pointed to by the `dst` parameter is equal +to `old`, and if they are, it stores the value `new` into the memory location, +all done in a single atomic operation. This procedure returns the old value +stored in a memory location and a boolean value signifying whether `old` was +equal to `new`. + +This procedure is an atomic equivalent of the following operation: + + old_dst := dst^ + if old_dst == old { + dst^ = new + return old_dst, true + } else { + return old_dst, false + } + +The strong version of compare exchange always returns true, when the returned +old value stored in location pointed to by `dst` and the `old` parameter are +equal. + +Atomic compare exchange has two memory orderings: One is for the +read-modify-write operation, if the comparison succeeds, and the other is for +the load operation, if the comparison fails. The memory ordering for both of +of these operations is sequentially-consistent. +*/ +atomic_compare_exchange_strong :: intrinsics.atomic_compare_exchange_strong + +/* +Atomically compare and exchange the value with a memory location. + +This procedure checks if the value pointed to by the `dst` parameter is equal +to `old`, and if they are, it stores the value `new` into the memory location, +all done in a single atomic operation. This procedure returns the old value +stored in a memory location and a boolean value signifying whether `old` was +equal to `new`. + +This procedure is an atomic equivalent of the following operation: + + old_dst := dst^ + if old_dst == old { + dst^ = new + return old_dst, true + } else { + return old_dst, false + } + +The strong version of compare exchange always returns true, when the returned +old value stored in location pointed to by `dst` and the `old` parameter are +equal. + +Atomic compare exchange has two memory orderings: One is for the +read-modify-write operation, if the comparison succeeds, and the other is for +the load operation, if the comparison fails. The memory ordering for these +operations is as specified by `success` and `failure` parameters respectively. +*/ atomic_compare_exchange_strong_explicit :: intrinsics.atomic_compare_exchange_strong_explicit -atomic_compare_exchange_weak :: intrinsics.atomic_compare_exchange_weak -atomic_compare_exchange_weak_explicit :: intrinsics.atomic_compare_exchange_weak_explicit \ No newline at end of file + +/* +Atomically compare and exchange the value with a memory location. + +This procedure checks if the value pointed to by the `dst` parameter is equal +to `old`, and if they are, it stores the value `new` into the memory location, +all done in a single atomic operation. This procedure returns the old value +stored in a memory location and a boolean value signifying whether `old` was +equal to `new`. + +This procedure is an atomic equivalent of the following operation: + + old_dst := dst^ + if old_dst == old { + // may return false here + dst^ = new + return old_dst, true + } else { + return old_dst, false + } + +The weak version of compare exchange may return false, even if `dst^ == old`. +On some platforms running weak compare exchange in a loop is faster than a +strong version. + +Atomic compare exchange has two memory orderings: One is for the +read-modify-write operation, if the comparison succeeds, and the other is for +the load operation, if the comparison fails. The memory ordering for both +of these operations is sequentially-consistent. +*/ +atomic_compare_exchange_weak :: intrinsics.atomic_compare_exchange_weak + +/* +Atomically compare and exchange the value with a memory location. + +This procedure checks if the value pointed to by the `dst` parameter is equal +to `old`, and if they are, it stores the value `new` into the memory location, +all done in a single atomic operation. This procedure returns the old value +stored in a memory location and a boolean value signifying whether `old` was +equal to `new`. + +This procedure is an atomic equivalent of the following operation: + + old_dst := dst^ + if old_dst == old { + // may return false here + dst^ = new + return old_dst, true + } else { + return old_dst, false + } + +The weak version of compare exchange may return false, even if `dst^ == old`. +On some platforms running weak compare exchange in a loop is faster than a +strong version. + +Atomic compare exchange has two memory orderings: One is for the +read-modify-write operation, if the comparison succeeds, and the other is for +the load operation, if the comparison fails. The memory ordering for these +operations is as specified by the `success` and `failure` parameters +respectively. +*/ +atomic_compare_exchange_weak_explicit :: intrinsics.atomic_compare_exchange_weak_explicit \ No newline at end of file diff --git a/core/sync/chan/chan.odin b/core/sync/chan/chan.odin new file mode 100644 index 000000000..c470d15f3 --- /dev/null +++ b/core/sync/chan/chan.odin @@ -0,0 +1,488 @@ +package sync_chan + +import "base:builtin" +import "base:intrinsics" +import "base:runtime" +import "core:mem" +import "core:sync" +import "core:math/rand" + +Direction :: enum { + Send = -1, + Both = 0, + Recv = +1, +} + +Chan :: struct($T: typeid, $D: Direction = Direction.Both) { + #subtype impl: ^Raw_Chan `fmt:"-"`, +} + +Raw_Chan :: struct { + // Shared + allocator: runtime.Allocator, + allocation_size: int, + msg_size: u16, + closed: b16, // guarded by `mutex` + mutex: sync.Mutex, + r_cond: sync.Cond, + w_cond: sync.Cond, + r_waiting: int, // guarded by `mutex` + w_waiting: int, // guarded by `mutex` + + // Buffered + queue: ^Raw_Queue, + + // Unbuffered + unbuffered_data: rawptr, +} + + +create :: proc{ + create_unbuffered, + create_buffered, +} + +@(require_results) +create_unbuffered :: proc($C: typeid/Chan($T), allocator: runtime.Allocator) -> (c: C, err: runtime.Allocator_Error) + where size_of(T) <= int(max(u16)) { + c.impl, err = create_raw_unbuffered(size_of(T), align_of(T), allocator) + return +} + +@(require_results) +create_buffered :: proc($C: typeid/Chan($T), #any_int cap: int, allocator: runtime.Allocator) -> (c: C, err: runtime.Allocator_Error) + where size_of(T) <= int(max(u16)) { + c.impl, err = create_raw_buffered(size_of(T), align_of(T), cap, allocator) + return +} + +create_raw :: proc{ + create_raw_unbuffered, + create_raw_buffered, +} + +@(require_results) +create_raw_unbuffered :: proc(#any_int msg_size, msg_alignment: int, allocator: runtime.Allocator) -> (c: ^Raw_Chan, err: runtime.Allocator_Error) { + assert(msg_size <= int(max(u16))) + align := max(align_of(Raw_Chan), msg_alignment) + + size := mem.align_forward_int(size_of(Raw_Chan), align) + offset := size + size += msg_size + size = mem.align_forward_int(size, align) + + 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) + return +} + +@(require_results) +create_raw_buffered :: proc(#any_int msg_size, msg_alignment: int, #any_int cap: int, allocator: runtime.Allocator) -> (c: ^Raw_Chan, err: runtime.Allocator_Error) { + assert(msg_size <= int(max(u16))) + if cap <= 0 { + return create_raw_unbuffered(msg_size, msg_alignment, allocator) + } + + align := max(align_of(Raw_Chan), msg_alignment, align_of(Raw_Queue)) + + size := mem.align_forward_int(size_of(Raw_Chan), align) + q_offset := size + size = mem.align_forward_int(q_offset + size_of(Raw_Queue), msg_alignment) + offset := size + size += msg_size * cap + size = mem.align_forward_int(size, align) + + ptr := mem.alloc(size, align, allocator) or_return + c = (^Raw_Chan)(ptr) + c.allocator = allocator + c.allocation_size = size + + bptr := ([^]byte)(ptr) + + c.queue = (^Raw_Queue)(bptr[q_offset:]) + c.msg_size = u16(msg_size) + + raw_queue_init(c.queue, ([^]byte)(bptr[offset:]), cap, msg_size) + return +} + +destroy :: proc(c: ^Raw_Chan) -> (err: runtime.Allocator_Error) { + if c != nil { + allocator := c.allocator + err = mem.free_with_size(c, c.allocation_size, allocator) + } + return +} + +@(require_results) +as_send :: #force_inline proc "contextless" (c: $C/Chan($T, $D)) -> (s: Chan(T, .Send)) where C.D <= .Both { + return transmute(type_of(s))c +} +@(require_results) +as_recv :: #force_inline proc "contextless" (c: $C/Chan($T, $D)) -> (r: Chan(T, .Recv)) where C.D >= .Both { + return transmute(type_of(r))c +} + + +send :: proc "contextless" (c: $C/Chan($T, $D), data: T) -> (ok: bool) where C.D <= .Both { + data := data + ok = send_raw(c, &data) + return +} + +@(require_results) +try_send :: proc "contextless" (c: $C/Chan($T, $D), data: T) -> (ok: bool) where C.D <= .Both { + data := data + ok = try_send_raw(c, &data) + return +} + +@(require_results) +recv :: proc "contextless" (c: $C/Chan($T)) -> (data: T, ok: bool) where C.D >= .Both { + ok = recv_raw(c, &data) + return +} + + +@(require_results) +try_recv :: proc "contextless" (c: $C/Chan($T)) -> (data: T, ok: bool) where C.D >= .Both { + ok = try_recv_raw(c, &data) + return +} + + +@(require_results) +send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool) { + if c == nil { + return + } + if c.queue != nil { // buffered + sync.guard(&c.mutex) + for !c.closed && c.queue.len == c.queue.cap { + c.w_waiting += 1 + sync.wait(&c.w_cond, &c.mutex) + c.w_waiting -= 1 + } + + if c.closed { + return false + } + + ok = raw_queue_push(c.queue, msg_in) + if c.r_waiting > 0 { + sync.signal(&c.r_cond) + } + } else if c.unbuffered_data != nil { // unbuffered + sync.guard(&c.mutex) + + if c.closed { + return false + } + + mem.copy(c.unbuffered_data, msg_in, int(c.msg_size)) + c.w_waiting += 1 + if c.r_waiting > 0 { + sync.signal(&c.r_cond) + } + sync.wait(&c.w_cond, &c.mutex) + ok = true + } + return +} + +@(require_results) +recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> (ok: bool) { + if c == nil { + return + } + if c.queue != nil { // buffered + sync.guard(&c.mutex) + for c.queue.len == 0 { + if c.closed { + return + } + + c.r_waiting += 1 + sync.wait(&c.r_cond, &c.mutex) + c.r_waiting -= 1 + } + + msg := raw_queue_pop(c.queue) + if msg != nil { + mem.copy(msg_out, msg, int(c.msg_size)) + } + + if c.w_waiting > 0 { + sync.signal(&c.w_cond) + } + ok = true + } else if c.unbuffered_data != nil { // unbuffered + sync.guard(&c.mutex) + + for !c.closed && + c.w_waiting == 0 { + c.r_waiting += 1 + sync.wait(&c.r_cond, &c.mutex) + c.r_waiting -= 1 + } + + if c.closed { + return + } + + mem.copy(msg_out, c.unbuffered_data, int(c.msg_size)) + c.w_waiting -= 1 + + sync.signal(&c.w_cond) + ok = true + } + return +} + + +@(require_results) +try_send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool) { + if c == nil { + return false + } + if c.queue != nil { // buffered + sync.guard(&c.mutex) + if c.queue.len == c.queue.cap { + return false + } + + if c.closed { + return false + } + + ok = raw_queue_push(c.queue, msg_in) + if c.r_waiting > 0 { + sync.signal(&c.r_cond) + } + } else if c.unbuffered_data != nil { // unbuffered + sync.guard(&c.mutex) + + if c.closed { + return false + } + + mem.copy(c.unbuffered_data, msg_in, int(c.msg_size)) + c.w_waiting += 1 + if c.r_waiting > 0 { + sync.signal(&c.r_cond) + } + sync.wait(&c.w_cond, &c.mutex) + ok = true + } + return +} + +@(require_results) +try_recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> bool { + if c == nil { + return false + } + if c.queue != nil { // buffered + sync.guard(&c.mutex) + if c.queue.len == 0 { + return false + } + + msg := raw_queue_pop(c.queue) + if msg != nil { + mem.copy(msg_out, msg, int(c.msg_size)) + } + + if c.w_waiting > 0 { + sync.signal(&c.w_cond) + } + return true + } else if c.unbuffered_data != nil { // unbuffered + sync.guard(&c.mutex) + + if c.closed || c.w_waiting == 0 { + return false + } + + mem.copy(msg_out, c.unbuffered_data, int(c.msg_size)) + c.w_waiting -= 1 + + sync.signal(&c.w_cond) + return true + } + return false +} + + + +@(require_results) +is_buffered :: proc "contextless" (c: ^Raw_Chan) -> bool { + return c != nil && c.queue != nil +} + +@(require_results) +is_unbuffered :: proc "contextless" (c: ^Raw_Chan) -> bool { + return c != nil && c.unbuffered_data != nil +} + +@(require_results) +len :: proc "contextless" (c: ^Raw_Chan) -> int { + if c != nil && c.queue != nil { + sync.guard(&c.mutex) + return c.queue.len + } + return 0 +} + +@(require_results) +cap :: proc "contextless" (c: ^Raw_Chan) -> int { + if c != nil && c.queue != nil { + sync.guard(&c.mutex) + return c.queue.cap + } + return 0 +} + +close :: proc "contextless" (c: ^Raw_Chan) -> bool { + if c == nil { + return false + } + sync.guard(&c.mutex) + if c.closed { + return false + } + c.closed = true + sync.broadcast(&c.r_cond) + sync.broadcast(&c.w_cond) + return true +} + +@(require_results) +is_closed :: proc "contextless" (c: ^Raw_Chan) -> bool { + if c == nil { + return true + } + sync.guard(&c.mutex) + return bool(c.closed) +} + + + + +Raw_Queue :: struct { + data: [^]byte, + len: int, + cap: int, + next: int, + size: int, // element size +} + +raw_queue_init :: proc "contextless" (q: ^Raw_Queue, data: rawptr, cap: int, size: int) { + q.data = ([^]byte)(data) + q.len = 0 + q.cap = cap + q.next = 0 + q.size = size +} + + +@(require_results) +raw_queue_push :: proc "contextless" (q: ^Raw_Queue, data: rawptr) -> bool { + if q.len == q.cap { + return false + } + pos := q.next + q.len + if pos >= q.cap { + pos -= q.cap + } + + val_ptr := q.data[pos*q.size:] + mem.copy(val_ptr, data, q.size) + q.len += 1 + return true +} + +@(require_results) +raw_queue_pop :: proc "contextless" (q: ^Raw_Queue) -> (data: rawptr) { + if q.len > 0 { + data = q.data[q.next*q.size:] + q.next += 1 + q.len -= 1 + if q.next >= q.cap { + q.next -= q.cap + } + } + return +} + + +@(require_results) +can_recv :: proc "contextless" (c: ^Raw_Chan) -> bool { + sync.guard(&c.mutex) + if is_buffered(c) { + return c.queue.len > 0 + } + return c.w_waiting > 0 +} + + +@(require_results) +can_send :: proc "contextless" (c: ^Raw_Chan) -> bool { + sync.guard(&c.mutex) + if is_buffered(c) { + return c.queue.len < c.queue.cap + } + return c.w_waiting == 0 +} + + + +@(require_results) +select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: []rawptr, recv_out: rawptr) -> (select_idx: int, ok: bool) #no_bounds_check { + Select_Op :: struct { + idx: int, // local to the slice that was given + is_recv: bool, + } + + candidate_count := builtin.len(recvs)+builtin.len(sends) + candidates := ([^]Select_Op)(intrinsics.alloca(candidate_count*size_of(Select_Op), align_of(Select_Op))) + count := 0 + + for c, i in recvs { + if can_recv(c) { + candidates[count] = { + is_recv = true, + idx = i, + } + count += 1 + } + } + + for c, i in sends { + if can_send(c) { + candidates[count] = { + is_recv = false, + idx = i, + } + count += 1 + } + } + + if count == 0 { + return + } + + select_idx = rand.int_max(count) if count > 0 else 0 + + sel := candidates[select_idx] + if sel.is_recv { + ok = recv_raw(recvs[sel.idx], recv_out) + } else { + ok = send_raw(sends[sel.idx], send_msgs[sel.idx]) + } + return +} diff --git a/core/sync/doc.odin b/core/sync/doc.odin new file mode 100644 index 000000000..320732ea7 --- /dev/null +++ b/core/sync/doc.odin @@ -0,0 +1,21 @@ +/* +Synchronization primitives + +This package implements various synchronization primitives that can be used to +synchronize threads' access to shared memory. + +To limit or control the threads' access to shared memory typically the +following approaches are used: + +- Locks +- Lock-free + +When using locks, sections of the code that access shared memory (also known as +**critical sections**) are guarded by locks, allowing limited access to threads +and blocking the execution of any other threads. + +In lock-free programming the data itself is organized in such a way that threads +don't intervene much. It can be done via segmenting the data between threads, +and/or by using atomic operations. +*/ +package sync diff --git a/core/sync/extended.odin b/core/sync/extended.odin index c76ab504b..30b1b2770 100644 --- a/core/sync/extended.odin +++ b/core/sync/extended.odin @@ -4,15 +4,41 @@ import "core:time" import vg "core:sys/valgrind" _ :: vg -// A Wait_Group waits for a collection of threads to finish -// -// A Wait_Group must not be copied after first use +/* +Wait group. + +Wait group is a synchronization primitive used by the waiting thread to wait, +until all working threads finish work. + +The waiting thread first sets the number of working threads it will expect to +wait for using `wait_group_add` call, and start waiting using `wait_group_wait` +call. When worker threads complete their work, each of them will call +`wait_group_done`, and after all working threads have called this procedure, +the waiting thread will resume execution. + +For the purpose of keeping track whether all working threads have finished their +work, the wait group keeps an internal atomic counter. Initially, the waiting +thread might set it to a certain non-zero amount. When each working thread +completes the work, the internal counter is atomically decremented until it +reaches zero. When it reaches zero, the waiting thread is unblocked. The counter +is not allowed to become negative. + +**Note**: Just like any synchronization primitives, a wait group cannot be +copied after first use. See documentation for `Mutex` or `Cond`. +*/ Wait_Group :: struct #no_copy { counter: int, mutex: Mutex, cond: Cond, } +/* +Increment an internal counter of a wait group. + +This procedure atomically increments a number to the specified wait group's +internal counter by a specified amount. This operation can be done on any +thread. +*/ wait_group_add :: proc "contextless" (wg: ^Wait_Group, delta: int) { if delta == 0 { return @@ -22,31 +48,52 @@ wait_group_add :: proc "contextless" (wg: ^Wait_Group, delta: int) { atomic_add(&wg.counter, delta) if wg.counter < 0 { - _panic("sync.Wait_Group negative counter") + panic_contextless("sync.Wait_Group negative counter") } if wg.counter == 0 { cond_broadcast(&wg.cond) if wg.counter != 0 { - _panic("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait") + panic_contextless("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait") } } } +/* +Signal work done by a thread in a wait group. + +This procedure decrements the internal counter of the specified wait group and +wakes up the waiting thread. Once the internal counter reaches zero, the waiting +thread resumes execution. +*/ wait_group_done :: proc "contextless" (wg: ^Wait_Group) { wait_group_add(wg, -1) } +/* +Wait for all worker threads in the wait group. + +This procedure blocks the execution of the current thread, until the specified +wait group's internal counter reaches zero. +*/ wait_group_wait :: proc "contextless" (wg: ^Wait_Group) { guard(&wg.mutex) if wg.counter != 0 { cond_wait(&wg.cond, &wg.mutex) if wg.counter != 0 { - _panic("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait") + panic_contextless("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait") } } } +/* +Wait for all worker threads in the wait group, or until timeout is reached. + +This procedure blocks the execution of the current thread, until the specified +wait group's internal counter reaches zero, or until the timeout is reached. + +This procedure returns `false`, if the timeout was reached, `true` otherwise. +*/ wait_group_wait_with_timeout :: proc "contextless" (wg: ^Wait_Group, duration: time.Duration) -> bool { if duration <= 0 { return false @@ -58,47 +105,49 @@ wait_group_wait_with_timeout :: proc "contextless" (wg: ^Wait_Group, duration: t return false } if wg.counter != 0 { - _panic("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait") + panic_contextless("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait") } } return true } - - /* -A barrier enabling multiple threads to synchronize the beginning of some computation +Barrier. -Example: - package example +A barrier is a synchronization primitive enabling multiple threads to +synchronize the beginning of some computation. - import "core:fmt" - import "core:sync" - import "core:thread" +When `barrier_wait` procedure is called by any thread, that thread will block +the execution, until all threads associated with the barrier reach the same +point of execution and also call `barrier_wait`. - barrier := &sync.Barrier{} +When a barrier is initialized, a `thread_count` parameter is passed, signifying +the amount of participant threads of the barrier. The barrier also keeps track +of an internal atomic counter. When a thread calls `barrier_wait`, the internal +counter is incremented. When the internal counter reaches `thread_count`, it is +reset and all threads waiting on the barrier are unblocked. - main :: proc "contextless" () { - fmt.println("Start") +This type of synchronization primitive can be used to synchronize "staged" +workloads, where the workload is split into stages, and until all threads have +completed the previous threads, no thread is allowed to start work on the next +stage. In this case, after each stage, a `barrier_wait` shall be inserted in the +thread procedure. - THREAD_COUNT :: 4 - threads: [THREAD_COUNT]^thread.Thread +**Example**: - sync.barrier_init(barrier, THREAD_COUNT) - - for _, i in threads { - threads[i] = thread.create_and_start(proc(t: ^thread.Thread) { - // Same messages will be printed together but without any interleaving - fmt.println("Getting ready!") - sync.barrier_wait(barrier) - fmt.println("Off their marks they go!") - }) - } - - for t in threads { - thread.destroy(t) // join and free thread - } - fmt.println("Finished") + THREAD_COUNT :: 4 + threads: [THREAD_COUNT]^thread.Thread + sync.barrier_init(barrier, THREAD_COUNT) + for _, i in threads { + threads[i] = thread.create_and_start(proc(t: ^thread.Thread) { + // Same messages will be printed together but without any interleaving + fmt.println("Getting ready!") + sync.barrier_wait(barrier) + fmt.println("Off their marks they go!") + }) + } + for t in threads { + thread.destroy(t) } */ Barrier :: struct #no_copy { @@ -109,6 +158,13 @@ Barrier :: struct #no_copy { thread_count: int, } +/* +Initialize a barrier. + + +This procedure initializes the barrier for the specified amount of participant +threads. +*/ barrier_init :: proc "contextless" (b: ^Barrier, thread_count: int) { when ODIN_VALGRIND_SUPPORT { vg.helgrind_barrier_resize_pre(b, uint(thread_count)) @@ -118,8 +174,13 @@ barrier_init :: proc "contextless" (b: ^Barrier, thread_count: int) { b.thread_count = thread_count } -// Block the current thread until all threads have rendezvoused -// Barrier can be reused after all threads rendezvoused once, and can be used continuously +/* +Block the current thread until all threads have rendezvoused. + +This procedure blocks the execution of the current thread, until all threads +have reached the same point in the execution of the thread proc. Multiple calls +to `barrier_wait` are allowed within the thread procedure. +*/ barrier_wait :: proc "contextless" (b: ^Barrier) -> (is_leader: bool) { when ODIN_VALGRIND_SUPPORT { vg.helgrind_barrier_wait_pre(b) @@ -140,29 +201,51 @@ barrier_wait :: proc "contextless" (b: ^Barrier) -> (is_leader: bool) { return true } +/* +Auto-reset event. +Represents a thread synchronization primitive that, when signalled, releases one +single waiting thread and then resets automatically to a state where it can be +signalled again. + +When a thread calls `auto_reset_event_wait`, its execution will be blocked, +until the event is signalled by another thread. The call to +`auto_reset_event_signal` wakes up exactly one thread waiting for the event. +*/ Auto_Reset_Event :: struct #no_copy { // status == 0: Event is reset and no threads are waiting - // status == 1: Event is signaled + // status == 1: Event is signalled // status == -N: Event is reset and N threads are waiting status: i32, sema: Sema, } +/* +Signal an auto-reset event. + +This procedure signals an auto-reset event, waking up exactly one waiting +thread. +*/ auto_reset_event_signal :: proc "contextless" (e: ^Auto_Reset_Event) { old_status := atomic_load_explicit(&e.status, .Relaxed) + new_status := old_status + 1 if old_status < 1 else 1 for { - new_status := old_status + 1 if old_status < 1 else 1 if _, ok := atomic_compare_exchange_weak_explicit(&e.status, old_status, new_status, .Release, .Relaxed); ok { break } - - if old_status < 0 { - sema_post(&e.sema) - } + cpu_relax() + } + if old_status < 0 { + sema_post(&e.sema) } } +/* +Wait on an auto-reset event. + +This procedure blocks the execution of the current thread, until the event is +signalled by another thread. +*/ auto_reset_event_wait :: proc "contextless" (e: ^Auto_Reset_Event) { old_status := atomic_sub_explicit(&e.status, 1, .Acquire) if old_status < 1 { @@ -170,13 +253,35 @@ auto_reset_event_wait :: proc "contextless" (e: ^Auto_Reset_Event) { } } +/* +Ticket lock. +A ticket lock is a mutual exclusion lock that uses "tickets" to control which +thread is allowed into a critical section. +This synchronization primitive works just like spinlock, except that it implements +a "fairness" guarantee, making sure that each thread gets a roughly equal amount +of entries into the critical section. + +This type of synchronization primitive is applicable for short critical sections +in low-contention systems, as it uses a spinlock under the hood. +*/ Ticket_Mutex :: struct #no_copy { ticket: uint, serving: uint, } +/* +Acquire a lock on a ticket mutex. + +This procedure acquires a lock on a ticket mutex. If the ticket mutex is held +by another thread, this procedure also blocks the execution until the lock +can be acquired. + +Once the lock is acquired, any thread calling `ticket_mutex_lock` will be +blocked from entering any critical sections associated with the same ticket +mutex, until the lock is released. +*/ ticket_mutex_lock :: #force_inline proc "contextless" (m: ^Ticket_Mutex) { ticket := atomic_add_explicit(&m.ticket, 1, .Relaxed) for ticket != atomic_load_explicit(&m.serving, .Acquire) { @@ -184,44 +289,147 @@ ticket_mutex_lock :: #force_inline proc "contextless" (m: ^Ticket_Mutex) { } } +/* +Release a lock on a ticket mutex. + +This procedure releases the lock on a ticket mutex. If any of the threads are +waiting to acquire the lock, exactly one of those threads is unblocked and +allowed into the critical section. +*/ ticket_mutex_unlock :: #force_inline proc "contextless" (m: ^Ticket_Mutex) { - atomic_add_explicit(&m.serving, 1, .Relaxed) + atomic_add_explicit(&m.serving, 1, .Release) } + +/* +Guard the current scope with a lock on a ticket mutex. + +This procedure acquires a lock on a ticket mutex. The lock is automatically +released at the end of callee's scope. If the mutex was already locked, this +procedure also blocks until the lock can be acquired. + +When a lock has been acquired, all threads attempting to acquire a lock will be +blocked from entering any critical sections associated with the ticket mutex, +until the lock is released. + +This procedure always returns `true`. This makes it easy to define a critical +section by putting the function inside the `if` statement. + +**Example**: + + if ticket_mutex_guard(&m) { + ... + } +*/ @(deferred_in=ticket_mutex_unlock) ticket_mutex_guard :: proc "contextless" (m: ^Ticket_Mutex) -> bool { ticket_mutex_lock(m) return true } +/* +Benaphore. +A benaphore is a combination of an atomic variable and a semaphore that can +improve locking efficiency in a no-contention system. Acquiring a benaphore +lock doesn't call into an internal semaphore, if no other thread is in the +middle of a critical section. + +Once a lock on a benaphore is acquired by a thread, no other thread is allowed +into any critical sections, associted with the same benaphore, until the lock +is released. +*/ Benaphore :: struct #no_copy { counter: i32, sema: Sema, } +/* +Acquire a lock on a benaphore. + +This procedure acquires a lock on the specified benaphore. If the lock on a +benaphore is already held, this procedure also blocks the execution of the +current thread, until the lock could be acquired. + +Once a lock is acquired, all threads attempting to take a lock will be blocked +from entering any critical sections associated with the same benaphore, until +until the lock is released. +*/ benaphore_lock :: proc "contextless" (b: ^Benaphore) { - if atomic_add_explicit(&b.counter, 1, .Acquire) > 1 { + if atomic_add_explicit(&b.counter, 1, .Acquire) > 0 { sema_wait(&b.sema) } } +/* +Try to acquire a lock on a benaphore. + +This procedure tries to acquire a lock on the specified benaphore. If it was +already locked, then the returned value is `false`, otherwise the lock is +acquired and the procedure returns `true`. + +If the lock is acquired, all threads that attempt to acquire a lock will be +blocked from entering any critical sections associated with the same benaphore, +until the lock is released. +*/ benaphore_try_lock :: proc "contextless" (b: ^Benaphore) -> bool { v, _ := atomic_compare_exchange_strong_explicit(&b.counter, 0, 1, .Acquire, .Acquire) return v == 0 } +/* +Release a lock on a benaphore. + +This procedure releases a lock on the specified benaphore. If any of the threads +are waiting on the lock, exactly one thread is allowed into a critical section +associated with the same benaphore. +*/ benaphore_unlock :: proc "contextless" (b: ^Benaphore) { - if atomic_sub_explicit(&b.counter, 1, .Release) > 0 { + if atomic_sub_explicit(&b.counter, 1, .Release) > 1 { sema_post(&b.sema) } } +/* +Guard the current scope with a lock on a benaphore. + +This procedure acquires a lock on a benaphore. The lock is automatically +released at the end of callee's scope. If the benaphore was already locked, this +procedure also blocks until the lock can be acquired. + +When a lock has been acquired, all threads attempting to acquire a lock will be +blocked from entering any critical sections associated with the same benaphore, +until the lock is released. + +This procedure always returns `true`. This makes it easy to define a critical +section by putting the function inside the `if` statement. + +**Example**: + + if benaphore_guard(&m) { + ... + } +*/ @(deferred_in=benaphore_unlock) benaphore_guard :: proc "contextless" (m: ^Benaphore) -> bool { benaphore_lock(m) return true } +/* +Recursive benaphore. + +A recursive benaphore is just like a plain benaphore, except it allows +reentrancy into the critical section. + +When a lock is acquired on a benaphore, all other threads attempting to +acquire a lock on the same benaphore will be blocked from any critical sections, +associated with the same benaphore. + +When a lock is acquired on a benaphore by a thread, that thread is allowed +to acquire another lock on the same benaphore. When a thread has acquired the +lock on a benaphore, the benaphore will stay locked until the thread releases +the lock as many times as it has been locked by the thread. +*/ Recursive_Benaphore :: struct #no_copy { counter: int, owner: int, @@ -229,67 +437,122 @@ Recursive_Benaphore :: struct #no_copy { sema: Sema, } +/* +Acquire a lock on a recursive benaphore. + +This procedure acquires a lock on a recursive benaphore. If the benaphore is +held by another thread, this function blocks until the lock can be acquired. + +Once a lock is acquired, all other threads attempting to acquire a lock will +be blocked from entering any critical sections associated with the same +recursive benaphore, until the lock is released. +*/ recursive_benaphore_lock :: proc "contextless" (b: ^Recursive_Benaphore) { tid := current_thread_id() - if atomic_add_explicit(&b.counter, 1, .Acquire) > 1 { - if tid != b.owner { - sema_wait(&b.sema) + check_owner: if tid != atomic_load_explicit(&b.owner, .Acquire) { + atomic_add_explicit(&b.counter, 1, .Relaxed) + if _, ok := atomic_compare_exchange_strong_explicit(&b.owner, 0, tid, .Release, .Relaxed); ok { + break check_owner } + sema_wait(&b.sema) + atomic_store_explicit(&b.owner, tid, .Release) } // inside the lock - b.owner = tid b.recursion += 1 } +/* +Try to acquire a lock on a recursive benaphore. + +This procedure attempts to acquire a lock on recursive benaphore. If the +benaphore is already held by a different thread, this procedure returns `false`. +Otherwise the lock is acquired and the procedure returns `true`. + +If the lock is acquired, all other threads attempting to acquire a lock will +be blocked from entering any critical sections assciated with the same recursive +benaphore, until the lock is released. +*/ recursive_benaphore_try_lock :: proc "contextless" (b: ^Recursive_Benaphore) -> bool { tid := current_thread_id() - if b.owner == tid { - atomic_add_explicit(&b.counter, 1, .Acquire) - } - - if v, _ := atomic_compare_exchange_strong_explicit(&b.counter, 0, 1, .Acquire, .Acquire); v != 0 { + check_owner: if tid != atomic_load_explicit(&b.owner, .Acquire) { + if _, ok := atomic_compare_exchange_strong_explicit(&b.owner, 0, tid, .Release, .Relaxed); ok { + atomic_add_explicit(&b.counter, 1, .Relaxed) + break check_owner + } return false } // inside the lock - b.owner = tid b.recursion += 1 return true } +/* +Release a lock on a recursive benaphore. + +This procedure releases a lock on the specified recursive benaphore. It also +causes the critical sections associated with the same benaphore, to become open +for other threads for entering. +*/ recursive_benaphore_unlock :: proc "contextless" (b: ^Recursive_Benaphore) { tid := current_thread_id() - _assert(tid == b.owner, "tid != b.owner") + assert_contextless(tid == atomic_load_explicit(&b.owner, .Relaxed), "tid != b.owner") b.recursion -= 1 recursion := b.recursion + if recursion == 0 { - b.owner = 0 - } - if atomic_sub_explicit(&b.counter, 1, .Release) > 0 { - if recursion == 0 { + if atomic_sub_explicit(&b.counter, 1, .Relaxed) == 1 { + atomic_store_explicit(&b.owner, 0, .Release) + } else { sema_post(&b.sema) } } // outside the lock } +/* +Guard the current scope with a recursive benaphore. + +This procedure acquires a lock on the specified recursive benaphores and +automatically releases it at the end of the callee's scope. If the recursive +benaphore was already held by a another thread, this procedure also blocks until +the lock can be acquired. + +When the lock is acquired all other threads attempting to take a lock will be +blocked from entering any critical sections associated with the same benaphore, +until the lock is released. + +This procedure always returns `true`, which makes it easy to define a critical +section by calling this procedure inside an `if` statement. + +**Example**: + + if recursive_benaphore_guard(&m) { + ... + } +*/ @(deferred_in=recursive_benaphore_unlock) recursive_benaphore_guard :: proc "contextless" (m: ^Recursive_Benaphore) -> bool { recursive_benaphore_lock(m) return true } +/* +Once action. - - -// Once is a data value that will perform exactly on action. -// -// A Once must not be copied after first use. +`Once` a synchronization primitive, that only allows a single entry into a +critical section from a single thread. +*/ Once :: struct #no_copy { m: Mutex, done: bool, } -// once_do calls the procedure fn if and only if once_do is being called for the first for this instance of Once. +/* +Call a function once. + +The `once_do` procedure group calls a specified function, if it wasn't already +called from the perspective of a specific `Once` struct. +*/ once_do :: proc{ once_do_without_data, once_do_without_data_contextless, @@ -297,7 +560,9 @@ once_do :: proc{ once_do_with_data_contextless, } -// once_do_without_data calls the procedure fn if and only if once_do_without_data is being called for the first for this instance of Once. +/* +Call a function with no data once. +*/ once_do_without_data :: proc(o: ^Once, fn: proc()) { @(cold) do_slow :: proc(o: ^Once, fn: proc()) { @@ -313,7 +578,9 @@ once_do_without_data :: proc(o: ^Once, fn: proc()) { } } -// once_do_without_data calls the procedure fn if and only if once_do_without_data is being called for the first for this instance of Once. +/* +Call a contextless function with no data once. +*/ once_do_without_data_contextless :: proc(o: ^Once, fn: proc "contextless" ()) { @(cold) do_slow :: proc(o: ^Once, fn: proc "contextless" ()) { @@ -329,7 +596,9 @@ once_do_without_data_contextless :: proc(o: ^Once, fn: proc "contextless" ()) { } } -// once_do_with_data calls the procedure fn if and only if once_do_with_data is being called for the first for this instance of Once. +/* +Call a function with data once. +*/ once_do_with_data :: proc(o: ^Once, fn: proc(data: rawptr), data: rawptr) { @(cold) do_slow :: proc(o: ^Once, fn: proc(data: rawptr), data: rawptr) { @@ -345,7 +614,9 @@ once_do_with_data :: proc(o: ^Once, fn: proc(data: rawptr), data: rawptr) { } } -// once_do_with_data_contextless calls the procedure fn if and only if once_do_with_data_contextless is being called for the first for this instance of Once. +/* +Call a contextless function with data once. +*/ once_do_with_data_contextless :: proc "contextless" (o: ^Once, fn: proc "contextless" (data: rawptr), data: rawptr) { @(cold) do_slow :: proc "contextless" (o: ^Once, fn: proc "contextless" (data: rawptr), data: rawptr) { @@ -361,60 +632,113 @@ once_do_with_data_contextless :: proc "contextless" (o: ^Once, fn: proc "context } } +/* +A Parker is an associated token which is initially not present: - - - -// A Parker is an associated token which is initially not present: -// * The `park` procedure blocks the current thread unless or until the token -// is available, at which point the token is consumed. -// * The `park_with_timeout` procedures works the same as `park` but only -// blocks for the specified duration. -// * The `unpark` procedure automatically makes the token available if it -// was not already. +* The `park` procedure blocks the current thread unless or until the token + is available, at which point the token is consumed. +* The `park_with_timeout` procedures works the same as `park` but only + blocks for the specified duration. +* The `unpark` procedure automatically makes the token available if it + was not already. +*/ Parker :: struct #no_copy { state: Futex, } -// Blocks the current thread until the token is made available. -// -// Assumes this is only called by the thread that owns the Parker. +@(private="file") PARKER_EMPTY :: 0 +@(private="file") PARKER_NOTIFIED :: 1 +@(private="file") PARKER_PARKED :: max(u32) + +/* +Blocks until the token is available. + +This procedure blocks the execution of the current thread, until a token is +made available. + +**Note**: This procedure assumes this is only called by the thread that owns +the Parker. +*/ park :: proc "contextless" (p: ^Parker) { - EMPTY :: 0 - NOTIFIED :: 1 - PARKED :: max(u32) - if atomic_sub_explicit(&p.state, 1, .Acquire) == NOTIFIED { + if atomic_sub_explicit(&p.state, 1, .Acquire) == PARKER_NOTIFIED { return } for { - futex_wait(&p.state, PARKED) - if _, ok := atomic_compare_exchange_strong_explicit(&p.state, NOTIFIED, EMPTY, .Acquire, .Acquire); ok { + futex_wait(&p.state, PARKER_PARKED) + if _, ok := atomic_compare_exchange_strong_explicit(&p.state, PARKER_NOTIFIED, PARKER_EMPTY, .Acquire, .Acquire); ok { return } } } -// Blocks the current thread until the token is made available, but only -// for a limited duration. -// -// Assumes this is only called by the thread that owns the Parker +/* +Blocks until the token is available with timeout. + +This procedure blocks the execution of the current thread until a token is made +available, or until the timeout has expired, whatever happens first. + +**Note**: This procedure assumes this is only called by the thread that owns +the Parker. +*/ park_with_timeout :: proc "contextless" (p: ^Parker, duration: time.Duration) { - EMPTY :: 0 - NOTIFIED :: 1 - PARKED :: max(u32) - if atomic_sub_explicit(&p.state, 1, .Acquire) == NOTIFIED { + start_tick := time.tick_now() + remaining_duration := duration + if atomic_sub_explicit(&p.state, 1, .Acquire) == PARKER_NOTIFIED { return } - futex_wait_with_timeout(&p.state, PARKED, duration) - atomic_exchange_explicit(&p.state, EMPTY, .Acquire) + for { + if !futex_wait_with_timeout(&p.state, PARKER_PARKED, remaining_duration) { + return + } + old, ok := atomic_compare_exchange_weak_explicit((^u32)(&p.state), PARKER_PARKED, PARKER_EMPTY, .Acquire, .Relaxed) + if ok || old == PARKER_PARKED { + return + } + end_tick := time.tick_now() + remaining_duration -= time.tick_diff(start_tick, end_tick) + start_tick = end_tick + } } -// Automatically makes thee token available if it was not already. +/* +Make the token available. +*/ unpark :: proc "contextless" (p: ^Parker) { - EMPTY :: 0 - NOTIFIED :: 1 - PARKED :: max(Futex) - if atomic_exchange_explicit(&p.state, NOTIFIED, .Release) == PARKED { + if atomic_exchange_explicit((^u32)(&p.state), PARKER_NOTIFIED, .Release) == PARKER_PARKED { futex_signal(&p.state) } -} \ No newline at end of file +} + +/* +One-shot event. + +A one-shot event is an associated token which is initially not present: + +* The `one_shot_event_wait` blocks the current thread until the event + is made available +* The `one_shot_event_signal` procedure automatically makes the token + available if its was not already. +*/ +One_Shot_Event :: struct #no_copy { + state: Futex, +} + +/* +Block until the event is made available. + +This procedure blocks the execution of the current thread, until the event is +made available. +*/ +one_shot_event_wait :: proc "contextless" (e: ^One_Shot_Event) { + for atomic_load_explicit(&e.state, .Acquire) == 0 { + futex_wait(&e.state, 0) + } +} + +/* +Make event available. +*/ +one_shot_event_signal :: proc "contextless" (e: ^One_Shot_Event) { + atomic_store_explicit(&e.state, 1, .Release) + futex_broadcast(&e.state) +} diff --git a/core/sync/futex_darwin.odin b/core/sync/futex_darwin.odin index b85b15782..10ff7bfbb 100644 --- a/core/sync/futex_darwin.odin +++ b/core/sync/futex_darwin.odin @@ -1,16 +1,19 @@ -//+private -//+build darwin +#+private +#+build darwin package sync import "core:c" +import "core:sys/darwin" import "core:time" -foreign import System "System.framework" +foreign import System "system:System.framework" foreign System { // __ulock_wait is not available on 10.15 // See https://github.com/odin-lang/Odin/issues/1959 __ulock_wait :: proc "c" (operation: u32, addr: rawptr, value: u64, timeout_us: u32) -> c.int --- + // >= MacOS 11. + __ulock_wait2 :: proc "c" (operation: u32, addr: rawptr, value: u64, timeout_ns: u64, value2: u64) -> c.int --- __ulock_wake :: proc "c" (operation: u32, addr: rawptr, wake_value: u64) -> c.int --- } @@ -29,25 +32,71 @@ _futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool { } _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, duration: time.Duration) -> bool { - timeout_ns := u32(duration) * 1000 - - s := __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_ns) + when darwin.WAIT_ON_ADDRESS_AVAILABLE { + s: i32 + if duration > 0 { + s = darwin.os_sync_wait_on_address_with_timeout(f, u64(expected), size_of(Futex), {}, .MACH_ABSOLUTE_TIME, u64(duration)) + } else { + s = darwin.os_sync_wait_on_address(f, u64(expected), size_of(Futex), {}) + } + + if s >= 0 { + return true + } + + switch darwin.errno() { + case -EINTR, -EFAULT: + return true + case -ETIMEDOUT: + return false + case: + panic_contextless("darwin.os_sync_wait_on_address_with_timeout failure") + } + } else { + + when darwin.ULOCK_WAIT_2_AVAILABLE { + timeout_ns := u64(duration) + s := __ulock_wait2(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_ns, 0) + } else { + timeout_us := u32(duration / time.Microsecond) + s := __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_us) + } + if s >= 0 { return true } + switch s { case EINTR, EFAULT: return true case ETIMEDOUT: return false case: - _panic("futex_wait failure") + panic_contextless("futex_wait failure") } return true + } } _futex_signal :: proc "contextless" (f: ^Futex) { + when darwin.WAIT_ON_ADDRESS_AVAILABLE { + loop: for { + s := darwin.os_sync_wake_by_address_any(f, size_of(Futex), {}) + if s >= 0 { + return + } + switch darwin.errno() { + case -EINTR, -EFAULT: + continue loop + case -ENOENT: + return + case: + panic_contextless("darwin.os_sync_wake_by_address_any failure") + } + } + } else { + loop: for { s := __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, 0) if s >= 0 { @@ -59,12 +108,31 @@ _futex_signal :: proc "contextless" (f: ^Futex) { case ENOENT: return case: - _panic("futex_wake_single failure") + panic_contextless("futex_wake_single failure") } } + + } } _futex_broadcast :: proc "contextless" (f: ^Futex) { + when darwin.WAIT_ON_ADDRESS_AVAILABLE { + loop: for { + s := darwin.os_sync_wake_by_address_all(f, size_of(Futex), {}) + if s >= 0 { + return + } + switch darwin.errno() { + case -EINTR, -EFAULT: + continue loop + case -ENOENT: + return + case: + panic_contextless("darwin.os_sync_wake_by_address_all failure") + } + } + } else { + loop: for { s := __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO | ULF_WAKE_ALL, f, 0) if s >= 0 { @@ -76,8 +144,9 @@ _futex_broadcast :: proc "contextless" (f: ^Futex) { case ENOENT: return case: - _panic("futex_wake_all failure") + panic_contextless("futex_wake_all failure") } } -} + } +} diff --git a/core/sync/futex_freebsd.odin b/core/sync/futex_freebsd.odin index 60b1d6e0d..e3f95b146 100644 --- a/core/sync/futex_freebsd.odin +++ b/core/sync/futex_freebsd.odin @@ -1,37 +1,29 @@ -//+private -//+build freebsd +#+private +#+build freebsd package sync import "core:c" +import "core:sys/freebsd" import "core:time" -UMTX_OP_WAIT :: 2 -UMTX_OP_WAKE :: 3 - -ETIMEDOUT :: 60 - -foreign import libc "system:c" - -foreign libc { - _umtx_op :: proc "c" (obj: rawptr, op: c.int, val: c.ulong, uaddr: rawptr, uaddr2: rawptr) -> c.int --- - __error :: proc "c" () -> ^c.int --- -} - _futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool { - timeout := [2]i64{14400, 0} // 4 hours - for { - res := _umtx_op(f, UMTX_OP_WAIT, c.ulong(expected), nil, &timeout) + timeout := freebsd.timespec {14400, 0} // 4 hours + timeout_size := cast(rawptr)cast(uintptr)size_of(timeout) - if res != -1 { + for { + errno := freebsd._umtx_op(f, .WAIT_UINT, cast(c.ulong)expected, timeout_size, &timeout) + + if errno == nil { return true } - if __error()^ == ETIMEDOUT { + if errno == .ETIMEDOUT { continue } - _panic("_futex_wait failure") + panic_contextless("_futex_wait failure") } + unreachable() } @@ -40,32 +32,33 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati return false } - timeout := [2]i64{i64(duration/1e9), i64(duration%1e9)} + timeout := freebsd.timespec {cast(freebsd.time_t)duration / 1e9, cast(c.long)duration % 1e9} + timeout_size := cast(rawptr)cast(uintptr)size_of(timeout) - res := _umtx_op(f, UMTX_OP_WAIT, c.ulong(expected), nil, &timeout) - if res != -1 { + errno := freebsd._umtx_op(f, .WAIT_UINT, cast(c.ulong)expected, timeout_size, &timeout) + if errno == nil { return true } - if __error()^ == ETIMEDOUT { + if errno == .ETIMEDOUT { return false } - _panic("_futex_wait_with_timeout failure") + panic_contextless("_futex_wait_with_timeout failure") } _futex_signal :: proc "contextless" (f: ^Futex) { - res := _umtx_op(f, UMTX_OP_WAKE, 1, nil, nil) + errno := freebsd._umtx_op(f, .WAKE, 1, nil, nil) - if res == -1 { - _panic("_futex_signal failure") + if errno != nil { + panic_contextless("_futex_signal failure") } } _futex_broadcast :: proc "contextless" (f: ^Futex) { - res := _umtx_op(f, UMTX_OP_WAKE, c.ulong(max(i32)), nil, nil) + errno := freebsd._umtx_op(f, .WAKE, cast(c.ulong)max(i32), nil, nil) - if res == -1 { - _panic("_futex_broadcast failure") + if errno != nil { + panic_contextless("_futex_broadcast failure") } } diff --git a/core/sync/futex_haiku.odin b/core/sync/futex_haiku.odin new file mode 100644 index 000000000..21d07b801 --- /dev/null +++ b/core/sync/futex_haiku.odin @@ -0,0 +1,166 @@ +#+private +package sync + +import "core:c" +import "core:sys/haiku" +import "core:sys/unix" +import "core:time" + +@(private="file") +Wait_Node :: struct { + thread: unix.pthread_t, + futex: ^Futex, + prev, next: ^Wait_Node, +} +@(private="file") +atomic_flag :: distinct bool +@(private="file") +Wait_Queue :: struct { + lock: atomic_flag, + list: Wait_Node, +} +@(private="file") +waitq_lock :: proc "contextless" (waitq: ^Wait_Queue) { + for cast(bool)atomic_exchange_explicit(&waitq.lock, atomic_flag(true), .Acquire) { + cpu_relax() // spin... + } +} +@(private="file") +waitq_unlock :: proc "contextless" (waitq: ^Wait_Queue) { + atomic_store_explicit(&waitq.lock, atomic_flag(false), .Release) +} + +// FIXME: This approach may scale badly in the future, +// possible solution - hash map (leads to deadlocks now). +@(private="file") +g_waitq: Wait_Queue + +@(init, private="file") +g_waitq_init :: proc() { + g_waitq = { + list = { + prev = &g_waitq.list, + next = &g_waitq.list, + }, + } +} + +@(private="file") +get_waitq :: #force_inline proc "contextless" (f: ^Futex) -> ^Wait_Queue { + _ = f + return &g_waitq +} + +_futex_wait :: proc "contextless" (f: ^Futex, expect: u32) -> (ok: bool) { + waitq := get_waitq(f) + waitq_lock(waitq) + defer waitq_unlock(waitq) + + head := &waitq.list + waiter := Wait_Node{ + thread = unix.pthread_self(), + futex = f, + prev = head, + next = head.next, + } + + waiter.prev.next = &waiter + waiter.next.prev = &waiter + + old_mask, mask: haiku.sigset_t + haiku.sigemptyset(&mask) + haiku.sigaddset(&mask, haiku.SIGCONT) + unix.pthread_sigmask(haiku.SIG_BLOCK, &mask, &old_mask) + + if u32(atomic_load_explicit(f, .Acquire)) == expect { + waitq_unlock(waitq) + defer waitq_lock(waitq) + + sig: c.int + haiku.sigwait(&mask, &sig) + errno := haiku.errno() + ok = errno == .OK + } + + waiter.prev.next = waiter.next + waiter.next.prev = waiter.prev + + _ = unix.pthread_sigmask(haiku.SIG_SETMASK, &old_mask, nil) + + // FIXME: Add error handling! + return +} + +_futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expect: u32, duration: time.Duration) -> (ok: bool) { + if duration <= 0 { + return false + } + waitq := get_waitq(f) + waitq_lock(waitq) + defer waitq_unlock(waitq) + + head := &waitq.list + waiter := Wait_Node{ + thread = unix.pthread_self(), + futex = f, + prev = head, + next = head.next, + } + + waiter.prev.next = &waiter + waiter.next.prev = &waiter + + old_mask, mask: haiku.sigset_t + haiku.sigemptyset(&mask) + haiku.sigaddset(&mask, haiku.SIGCONT) + unix.pthread_sigmask(haiku.SIG_BLOCK, &mask, &old_mask) + + if u32(atomic_load_explicit(f, .Acquire)) == expect { + waitq_unlock(waitq) + defer waitq_lock(waitq) + + info: haiku.siginfo_t + ts := unix.timespec{ + tv_sec = i64(duration / 1e9), + tv_nsec = i64(duration % 1e9), + } + haiku.sigtimedwait(&mask, &info, &ts) + errno := haiku.errno() + ok = errno == .EAGAIN || errno == .OK + } + + waiter.prev.next = waiter.next + waiter.next.prev = waiter.prev + + unix.pthread_sigmask(haiku.SIG_SETMASK, &old_mask, nil) + + // FIXME: Add error handling! + return +} + +_futex_signal :: proc "contextless" (f: ^Futex) { + waitq := get_waitq(f) + waitq_lock(waitq) + defer waitq_unlock(waitq) + + head := &waitq.list + for waiter := head.next; waiter != head; waiter = waiter.next { + if waiter.futex == f { + unix.pthread_kill(waiter.thread, haiku.SIGCONT) + break + } + } +} + +_futex_broadcast :: proc "contextless" (f: ^Futex) { + waitq := get_waitq(f) + waitq_lock(waitq) + defer waitq_unlock(waitq) + + head := &waitq.list + for waiter := head.next; waiter != head; waiter = waiter.next { + if waiter.futex == f { + unix.pthread_kill(waiter.thread, haiku.SIGCONT) + } + } +} diff --git a/core/sync/futex_linux.odin b/core/sync/futex_linux.odin index fe57c12ed..52143880b 100644 --- a/core/sync/futex_linux.odin +++ b/core/sync/futex_linux.odin @@ -1,5 +1,5 @@ -//+private -//+build linux +#+private +#+build linux package sync import "core:time" @@ -15,7 +15,7 @@ _futex_wait :: proc "contextless" (futex: ^Futex, expected: u32) -> bool { return true case: // TODO(flysand): More descriptive panic messages based on the vlaue of `errno` - _panic("futex_wait failure") + panic_contextless("futex_wait failure") } } @@ -34,7 +34,7 @@ _futex_wait_with_timeout :: proc "contextless" (futex: ^Futex, expected: u32, du case .NONE, .EINTR, .EAGAIN: return true case: - _panic("futex_wait_with_timeout failure") + panic_contextless("futex_wait_with_timeout failure") } } @@ -44,7 +44,7 @@ _futex_signal :: proc "contextless" (futex: ^Futex) { case .NONE: return case: - _panic("futex_wake_single failure") + panic_contextless("futex_wake_single failure") } } @@ -57,6 +57,6 @@ _futex_broadcast :: proc "contextless" (futex: ^Futex) { case .NONE: return case: - _panic("_futex_wake_all failure") + panic_contextless("_futex_wake_all failure") } } diff --git a/core/sync/futex_netbsd.odin b/core/sync/futex_netbsd.odin new file mode 100644 index 000000000..e49b25b02 --- /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_contextless("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_contextless("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_contextless("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_contextless("_futex_wake_all failure") + } +} diff --git a/core/sync/futex_openbsd.odin b/core/sync/futex_openbsd.odin index 4883a0841..7d3cc8578 100644 --- a/core/sync/futex_openbsd.odin +++ b/core/sync/futex_openbsd.odin @@ -1,5 +1,5 @@ -//+private -//+build openbsd +#+private +#+build openbsd package sync import "core:c" @@ -36,7 +36,7 @@ _futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool { return false } - _panic("futex_wait failure") + panic_contextless("futex_wait failure") } _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, duration: time.Duration) -> bool { @@ -62,14 +62,14 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati return false } - _panic("futex_wait_with_timeout failure") + panic_contextless("futex_wait_with_timeout failure") } _futex_signal :: proc "contextless" (f: ^Futex) { res := _unix_futex(f, FUTEX_WAKE_PRIVATE, 1, nil) if res == -1 { - _panic("futex_wake_single failure") + panic_contextless("futex_wake_single failure") } } @@ -77,6 +77,6 @@ _futex_broadcast :: proc "contextless" (f: ^Futex) { res := _unix_futex(f, FUTEX_WAKE_PRIVATE, u32(max(i32)), nil) if res == -1 { - _panic("_futex_wake_all failure") + panic_contextless("_futex_wake_all failure") } } diff --git a/core/sync/futex_wasm.odin b/core/sync/futex_wasm.odin index 248542836..16e69ca74 100644 --- a/core/sync/futex_wasm.odin +++ b/core/sync/futex_wasm.odin @@ -1,36 +1,44 @@ -//+private -//+build wasm32, wasm64p32 +#+private +#+build wasm32, wasm64p32 package sync -import "core:intrinsics" +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_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") + } else { + _ = intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, -1) + return true + } } _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_contextless("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 != 2 + } } _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_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") + } else { + _ = intrinsics.wasm_memory_atomic_notify32((^u32)(f), 1) } } _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_contextless("usage of `core:sync` requires the `-target-feature:\"atomics\"` or a `-microarch` that supports it") + } else { + _ = intrinsics.wasm_memory_atomic_notify32((^u32)(f), max(u32)) } } diff --git a/core/sync/futex_windows.odin b/core/sync/futex_windows.odin index 8ddbef3ed..bb9686a1a 100644 --- a/core/sync/futex_windows.odin +++ b/core/sync/futex_windows.odin @@ -1,18 +1,18 @@ -//+private -//+build windows +#+private +#+build windows package sync import "core:time" foreign import Synchronization "system:Synchronization.lib" -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign Synchronization { WakeByAddressSingle :: proc(Address: rawptr) --- WakeByAddressAll :: proc(Address: rawptr) --- } foreign import Ntdll "system:Ntdll.lib" -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign Ntdll { RtlWaitOnAddress :: proc(Address: rawptr, CompareAddress: rawptr, AddressSize: uint, Timeout: ^i64) -> i32 --- RtlNtStatusToDosError :: proc(status: i32) -> u32 --- @@ -30,7 +30,7 @@ foreign Ntdll { GODDAMN MICROSOFT! */ -CustomWaitOnAddress :: proc "stdcall" (Address: rawptr, CompareAddress: rawptr, AddressSize: uint, Timeout: ^i64) -> bool { +CustomWaitOnAddress :: proc "system" (Address: rawptr, CompareAddress: rawptr, AddressSize: uint, Timeout: ^i64) -> bool { status := RtlWaitOnAddress(Address, CompareAddress, AddressSize, Timeout) if status != 0 { SetLastError(RtlNtStatusToDosError(status)) diff --git a/core/sync/primitives.odin b/core/sync/primitives.odin index 5e71f6336..f091de045 100644 --- a/core/sync/primitives.odin +++ b/core/sync/primitives.odin @@ -1,48 +1,109 @@ package sync -import "core:runtime" import "core:time" +/* +Obtain the current thread ID. +*/ current_thread_id :: proc "contextless" () -> int { return _current_thread_id() } -// A Mutex is a [[mutual exclusion lock; https://en.wikipedia.org/wiki/Mutual_exclusion]] -// It can be used to prevent more than one thread from executing the same piece of code, -// and thus prevent access to same piece of memory by multiple threads, at the same time. -// -// A Mutex's zero value represents an initial, *unlocked* state. -// -// If another thread tries to take the lock while another thread holds it, it will pause -// until the lock is released. Code or memory that is "surrounded" by a mutex lock is said -// to be "guarded by a mutex". -// -// A Mutex must not be copied after first use (e.g., after locking it the first time). -// This is because, in order to coordinate with other threads, all threads must watch -// the same memory address to know when the lock has been released. Trying to use a -// copy of the lock at a different memory address will result in broken and unsafe -// behavior. For this reason, Mutexes are marked as `#no_copy`. +/* +Mutual exclusion lock. + +A Mutex is a [[mutual exclusion lock; https://en.wikipedia.org/wiki/Mutual_exclusion]] +It can be used to prevent more than one thread from entering the critical +section, and thus prevent access to same piece of memory by multiple threads, at +the same time. + +Mutex's zero-initializzed value represents an initial, *unlocked* state. + +If another thread tries to acquire the lock, while it's already held (typically +by another thread), the thread's execution will be blocked, until the lock is +released. Code or memory that is "surrounded" by a mutex lock and unlock +operations is said to be "guarded by a mutex". + +**Note**: A Mutex must not be copied after first use (e.g., after locking it the +first time). This is because, in order to coordinate with other threads, all +threads must watch the same memory address to know when the lock has been +released. Trying to use a copy of the lock at a different memory address will +result in broken and unsafe behavior. For this reason, Mutexes are marked as +`#no_copy`. + +**Note**: If the current thread attempts to lock a mutex, while it's already +holding another lock, that will cause a trivial case of deadlock. Do not use +`Mutex` in recursive functions. In case multiple locks by the same thread are +desired, use `Recursive_Mutex`. +*/ Mutex :: struct #no_copy { impl: _Mutex, } -// mutex_lock locks m +/* +Acquire a lock on a mutex. + +This procedure acquires a lock with the specified mutex. If the mutex has been +already locked by any thread, this procedure also blocks until the lock can be +acquired. + +Once the lock is acquired, all other threads that attempt to acquire a lock will +be blocked from entering any critical sections associated with the same mutex, +until the the lock is released. + +**Note**: If the mutex is already locked by the current thread, a call to this +procedure will block indefinately. Do not use this in recursive procedures. +*/ mutex_lock :: proc "contextless" (m: ^Mutex) { _mutex_lock(m) } -// mutex_unlock unlocks m +/* +Release a lock on a mutex. + +This procedure releases the lock associated with the specified mutex. If the +mutex was not locked, this operation is a no-op. + +When the current thread, that holds a lock to the mutex calls `mutex_unlock`, +this allows one other thread waiting on the mutex to enter any critical sections +associated with the mutex. If there are no threads waiting on the mutex, the +critical sections will remain open. +*/ mutex_unlock :: proc "contextless" (m: ^Mutex) { _mutex_unlock(m) } -// mutex_try_lock tries to lock m, will return true on success, and false on failure +/* +Try to acquire a lock on a mutex. + +This procedure tries to acquire a lock on the specified mutex. If it was already +locked, then the returned value is `false`, otherwise the lock is acquired and +the procedure returns `true`. + +If the lock is acquired, all threads that attempt to acquire a lock will be +blocked from entering any critical sections associated with the same mutex, +until the lock is released. +*/ mutex_try_lock :: proc "contextless" (m: ^Mutex) -> bool { return _mutex_try_lock(m) } /* -Example: +Guard the current scope with a lock on a mutex. + +This procedure acquires a mutex lock. The lock is automatically released +at the end of callee's scope. If the mutex was already locked, this procedure +also blocks until the lock can be acquired. + +When a lock has been acquired, all threads attempting to acquire a lock will be +blocked from entering any critical sections associated with the mutex, until +the lock is released. + +This procedure always returns `true`. This makes it easy to define a critical +section by putting the function inside the `if` statement. + +**Example**: + if mutex_guard(&m) { ... } @@ -53,47 +114,145 @@ mutex_guard :: proc "contextless" (m: ^Mutex) -> bool { return true } -// A RW_Mutex is a reader/writer mutual exclusion lock -// The lock can be held by any arbitrary number of readers or a single writer -// The zero value for a RW_Mutex is an unlocked mutex -// -// A RW_Mutex must not be copied after first use +/* +Read-write mutual exclusion lock. + +An `RW_Mutex` is a reader/writer mutual exclusion lock. The lock can be held by +any number of readers or a single writer. + +This type of synchronization primitive supports two kinds of lock operations: + +- Exclusive lock (write lock) +- Shared lock (read lock) + +When an exclusive lock is acquired by any thread, all other threads, attempting +to acquire either an exclusive or shared lock, will be blocked from entering the +critical sections associated with the read-write mutex, until the exclusive +owner of the lock releases the lock. + +When a shared lock is acquired by any thread, any other thread attempting to +acquire a shared lock will also be able to enter all the critical sections +associated with the read-write mutex. However threads attempting to acquire +an exclusive lock will be blocked from entering those critical sections, until +all shared locks are released. + +**Note**: A read-write mutex must not be copied after first use (e.g., after +acquiring a lock). This is because, in order to coordinate with other threads, +all threads must watch the same memory address to know when the lock has been +released. Trying to use a copy of the lock at a different memory address will +result in broken and unsafe behavior. For this reason, mutexes are marked as +`#no_copy`. + +**Note**: A read-write mutex is not recursive. Do not attempt to acquire an +exclusive lock more than once from the same thread, or an exclusive and shared +lock on the same thread. Taking a shared lock multiple times is acceptable. +*/ RW_Mutex :: struct #no_copy { impl: _RW_Mutex, } -// rw_mutex_lock locks rw for writing (with a single writer) -// If the mutex is already locked for reading or writing, the mutex blocks until the mutex is available. +/* +Acquire an exclusive lock. + +This procedure acquires an exclusive lock on the specified read-write mutex. If +the lock is already held by any thread, this procedure also blocks until the +lock can be acquired. + +After a lock has been acquired, any thread attempting to acquire any lock +will be blocked from entering any critical sections associated with the same +read-write mutex, until the exclusive lock is released. +*/ rw_mutex_lock :: proc "contextless" (rw: ^RW_Mutex) { _rw_mutex_lock(rw) } -// rw_mutex_unlock unlocks rw for writing (with a single writer) +/* +Release an exclusive lock. + +This procedure releases an exclusive lock associated with the specified +read-write mutex. + +When the exclusive lock is released, all critical sections, associated with the +same read-write mutex, become open to other threads. +*/ rw_mutex_unlock :: proc "contextless" (rw: ^RW_Mutex) { _rw_mutex_unlock(rw) } -// rw_mutex_try_lock tries to lock rw for writing (with a single writer) +/* +Try to acquire an exclusive lock on a read-write mutex. + +This procedure tries to acquire an exclusive lock on the specified read-write +mutex. If the mutex was already locked, the procedure returns `false`. Otherwise +it acquires the exclusive lock and returns `true`. + +If the lock has been acquired, all threads attempting to acquire any lock +will be blocked from entering any critical sections associated with the same +read-write mutex, until the exclusive locked is released. +*/ rw_mutex_try_lock :: proc "contextless" (rw: ^RW_Mutex) -> bool { return _rw_mutex_try_lock(rw) } -// rw_mutex_shared_lock locks rw for reading (with arbitrary number of readers) +/* +Acquire a shared lock on a read-write mutex. + +This procedure acquires a shared lock on the specified read-write mutex. If the +mutex already has an exclusive lock held, this procedure also blocks until the +lock can be acquired. + +After the shared lock is obtained, all threads attempting to acquire an +exclusive lock will be blocked from entering any critical sections associated +with the same read-write mutex, until all shared locks associated with the +specified read-write mutex are released. +*/ rw_mutex_shared_lock :: proc "contextless" (rw: ^RW_Mutex) { _rw_mutex_shared_lock(rw) } -// rw_mutex_shared_unlock unlocks rw for reading (with arbitrary number of readers) +/* +Release the shared lock on a read-write mutex. + +This procedure releases shared lock on the specified read-write mutex. When all +shared locks are released, all critical sections associated with the same +read-write mutex become open to other threads. +*/ rw_mutex_shared_unlock :: proc "contextless" (rw: ^RW_Mutex) { _rw_mutex_shared_unlock(rw) } -// rw_mutex_try_shared_lock tries to lock rw for reading (with arbitrary number of readers) +/* +Try to acquire a shared lock on a read-write mutex. + +This procedure attempts to acquire a lock on the specified read-write mutex. If +the mutex already has an exclusive lock held, this procedure returns `false`. +Otherwise, it acquires the lock on the mutex and returns `true`. + +If the shared lock has been acquired, it causes all threads attempting to +acquire the exclusive lock to be blocked from entering any critical sections +associated with the same read-write mutex, until all shared locks are released. +*/ rw_mutex_try_shared_lock :: proc "contextless" (rw: ^RW_Mutex) -> bool { return _rw_mutex_try_shared_lock(rw) } + /* -Example: +Guard the current scope with an exclusive lock on a read-write mutex. + +This procedure acquires an exclusive lock on the specified read-write mutex. +This procedure automatically releases the lock at the end of the callee's scope. +If the mutex was already locked by readers or a writer, this procedure blocks, +until a lock can be acquired. + +When an exclusive lock is acquired, all other threads attempting to acquire an +exclusive lock will be blocked from entering any critical sections associated +with the same read-write mutex, until the exclusive lock is released. + +This procedure always returns `true`, which makes it easy to define a critical +section by running this procedure inside an `if` statement. + +**Example**: + if rw_mutex_guard(&m) { ... } @@ -105,8 +264,23 @@ rw_mutex_guard :: proc "contextless" (m: ^RW_Mutex) -> bool { } /* -Example: - if rw_mutex_shared_guard(&m) { +Guard the current scope with a shared lock on a read-write mutex. + +This procedure acquires a shared lock on the specified read-write mutex. This +procedure automatically releases the lock at the end of the callee's scope. If +the mutex already has an associated exclusive lock, this procedure blocks, until +a lock can be acquired. + +When a shared lock is obtained, all other threads attempting to obtain an +exclusive lock will be blocked from any critical sections, associated with the +same read-write mutex, until all shared locks are released. + +This procedure always returns `true`, which makes it easy to define a critical +section by running this procedure inside an `if` statement. + +**Example**: + + if rw_mutex_guard(&m) { ... } */ @@ -116,30 +290,91 @@ rw_mutex_shared_guard :: proc "contextless" (m: ^RW_Mutex) -> bool { return true } +/* +Recursive mutual exclusion lock. +Recurisve mutex is just like a plain mutex, except it allows reentrancy. In +order for a thread to release the mutex for other threads, the mutex needs to +be unlocked as many times, as it was locked. -// A Recursive_Mutex is a recursive mutual exclusion lock -// The zero value for a Recursive_Mutex is an unlocked mutex -// -// A Recursive_Mutex must not be copied after first use +When a lock is acquired on a recursive mutex, all other threads attempting to +acquire a lock on the same mutex will be blocked from any critical sections, +associated with the same recrusive mutex. + +When a lock is acquired on a recursive mutex by a thread, that thread is allowed +to acquire another lock on the same mutex. When a thread has acquired the lock +on a recursive mutex, the recursive mutex will stay locked until the thread +releases the lock as many times as it has been locked by the thread. + +**Note**: A recursive mutex must not be copied after first use (e.g., after +acquiring a lock). This is because, in order to coordinate with other threads, +all threads must watch the same memory address to know when the lock has been +released. Trying to use a copy of the lock at a different memory address will +result in broken and unsafe behavior. For this reason, mutexes are marked as +`#no_copy`. +*/ Recursive_Mutex :: struct #no_copy { impl: _Recursive_Mutex, } +/* +Acquire a lock on a recursive mutex. + +This procedure acquires a lock on the specified recursive mutex. If the lock is +acquired by a different thread, this procedure also blocks until the lock can be +acquired. + +When the lock is acquired, all other threads attempting to acquire a lock will +be blocked from entering any critical sections associated with the same mutex, +until the lock is released. +*/ recursive_mutex_lock :: proc "contextless" (m: ^Recursive_Mutex) { _recursive_mutex_lock(m) } +/* +Release a lock on a recursive mutex. + +This procedure releases a lock on the specified recursive mutex. It also causes +the critical sections associated with the same mutex, to become open for other +threads for entering. +*/ recursive_mutex_unlock :: proc "contextless" (m: ^Recursive_Mutex) { _recursive_mutex_unlock(m) } +/* +Try to acquire a lock on a recursive mutex. + +This procedure attempts to acquire a lock on the specified recursive mutex. If +the recursive mutex is locked by other threads, this procedure returns `false`. +Otherwise it locks the mutex and returns `true`. + +If the lock is acquired, all other threads attempting to obtain a lock will be +blocked from entering any critical sections associated with the same mutex, +until the lock is released. +*/ recursive_mutex_try_lock :: proc "contextless" (m: ^Recursive_Mutex) -> bool { return _recursive_mutex_try_lock(m) } /* -Example: +Guard the scope with a recursive mutex lock. + +This procedure acquires a lock on the specified recursive mutex and +automatically releases it at the end of the callee's scope. If the recursive +mutex was already held by a another thread, this procedure also blocks until the +lock can be acquired. + +When the lock is acquired all other threads attempting to take a lock will be +blocked from entering any critical sections associated with the same mutex, +until the lock is released. + +This procedure always returns `true`, which makes it easy to define a critical +section by calling this procedure inside an `if` statement. + +**Example**: + if recursive_mutex_guard(&m) { ... } @@ -150,19 +385,69 @@ recursive_mutex_guard :: proc "contextless" (m: ^Recursive_Mutex) -> bool { return true } +/* +A condition variable. -// Cond implements a condition variable, a rendezvous point for threads -// waiting for signalling the occurence of an event -// -// A Cond must not be copied after first use +`Cond` implements a condition variable, a rendezvous point for threads waiting +for signalling the occurence of an event. Condition variables are used in +conjuction with mutexes to provide a shared access to one or more shared +variable. + +A typical usage of condition variable is as follows. A thread that intends to +modify a shared variable shall: + +1. Acquire a lock on a mutex. +2. Modify the shared memory. +3. Release the lock. +3. Call `cond_signal` or `cond_broadcast`. + +A thread that intends to wait on a shared variable shall: + +1. Acquire a lock on a mutex. +2. Call `cond_wait` or `cond_wait_with_timeout` (will release the mutex). +3. Check the condition and keep waiting in a loop if not satisfied with result. + +**Note**: A condition variable must not be copied after first use (e.g., after +waiting on it the first time). This is because, in order to coordinate with +other threads, all threads must watch the same memory address to know when the +lock has been released. Trying to use a copy of the lock at a different memory +address will result in broken and unsafe behavior. For this reason, condition +variables are marked as `#no_copy`. +*/ Cond :: struct #no_copy { impl: _Cond, } +/* +Wait until the condition variable is signalled and release the associated mutex. + +This procedure blocks the current thread until the specified condition variable +is signalled, or until a spurious wakeup occurs. In addition, if the condition +has been signalled, this procedure releases the lock on the specified mutex. + +The mutex must be held by the calling thread, before calling the procedure. + +**Note**: This procedure can return on a spurious wake-up, even if the condition +variable was not signalled by a thread. +*/ cond_wait :: proc "contextless" (c: ^Cond, m: ^Mutex) { _cond_wait(c, m) } +/* +Wait until the condition variable is signalled or timeout is reached and release +the associated mutex. + +This procedure blocks the current thread until the specified condition variable +is signalled, a timeout is reached, or until a spurious wakeup occurs. In +addition, if the condition has been signalled, this procedure releases the +lock on the specified mutex. + +If the timeout was reached, this procedure returns `false`. Otherwise it returns +`true`. + +Before this procedure is called the mutex must be held by the calling thread. +*/ cond_wait_with_timeout :: proc "contextless" (c: ^Cond, m: ^Mutex, duration: time.Duration) -> bool { if duration <= 0 { return false @@ -170,51 +455,123 @@ cond_wait_with_timeout :: proc "contextless" (c: ^Cond, m: ^Mutex, duration: tim return _cond_wait_with_timeout(c, m, duration) } +/* +Wake up one thread that waits on a condition variable. + +This procedure causes exactly one thread waiting on the condition variable to +wake up. +*/ cond_signal :: proc "contextless" (c: ^Cond) { _cond_signal(c) } +/* +Wake up all threads that wait on a condition variable. + +This procedure causes all threads waiting on the condition variable to wake up. +*/ cond_broadcast :: proc "contextless" (c: ^Cond) { _cond_broadcast(c) } +/* +Semaphore. -// When waited upon, blocks until the internal count is greater than zero, then subtracts one. -// Posting to the semaphore increases the count by one, or the provided amount. -// -// A Sema must not be copied after first use +When waited upon, semaphore blocks until the internal count is greater than +zero, then decrements the internal counter by one. Posting to the semaphore +increases the count by one, or the provided amount. + +This type of synchronization primitives can be useful for implementing queues. +The internal counter of the semaphore can be thought of as the amount of items +in the queue. After a data has been pushed to the queue, the thread shall call +`sema_post()` procedure, increasing the counter. When a thread takes an item +from the queue to do the job, it shall call `sema_wait()`, waiting on the +semaphore counter to become non-zero and decreasing it, if necessary. + +**Note**: A semaphore must not be copied after first use (e.g., after posting +to it). This is because, in order to coordinate with other threads, all threads +must watch the same memory address to know when the lock has been released. +Trying to use a copy of the lock at a different memory address will result in +broken and unsafe behavior. For this reason, semaphores are marked as `#no_copy`. +*/ Sema :: struct #no_copy { impl: _Sema, } +/* +Increment the internal counter on a semaphore by the specified amount. + +This procedure increments the internal counter of the semaphore. If any of the +threads were waiting on the semaphore, up to `count` of threads will continue +the execution and enter the critical section. +*/ sema_post :: proc "contextless" (s: ^Sema, count := 1) { _sema_post(s, count) } +/* +Wait on a semaphore until the internal counter is non-zero. + +This procedure blocks the execution of the current thread, until the semaphore +counter is non-zero, and atomically decrements it by one, once the wait has +ended. +*/ sema_wait :: proc "contextless" (s: ^Sema) { _sema_wait(s) } +/* +Wait on a semaphore until the internal counter is non-zero or a timeout is reached. + +This procedure blocks the execution of the current thread, until the semaphore +counter is non-zero, and if so atomically decrements it by one, once the wait +has ended. If the specified timeout is reached, the function returns `false`, +otherwise it returns `true`. +*/ sema_wait_with_timeout :: proc "contextless" (s: ^Sema, duration: time.Duration) -> bool { return _sema_wait_with_timeout(s, duration) } +/* +Fast userspace mutual exclusion lock. +Futex is a fast userspace mutual exclusion lock, that uses a pointer to a 32-bit +value as an identifier of the queue of waiting threads. The value pointed to +by that pointer can be used to store extra data. -// Futex is a fast userspace mutual exclusion lock, using a 32-bit memory address as a hint -// -// An Futex must not be copied after first use +**IMPORTANT**: A futex must not be copied after first use (e.g., after waiting +on it the first time, or signalling it). This is because, in order to coordinate +with other threads, all threads must watch the same memory address. Trying to +use a copy of the lock at a different memory address will result in broken and +unsafe behavior. +*/ Futex :: distinct u32 +/* +Sleep if the futex contains the expected value until it's signalled. + +If the value of the futex is `expected`, this procedure blocks the execution of +the current thread, until the futex is woken up, or until a spurious wakeup +occurs. +*/ futex_wait :: proc "contextless" (f: ^Futex, expected: u32) { if u32(atomic_load_explicit(f, .Acquire)) != expected { return } - - _assert(_futex_wait(f, expected), "futex_wait failure") + ok := _futex_wait(f, expected) + assert_contextless(ok, "futex_wait failure") } -// returns true if the wait happened within the duration, false if it exceeded the time duration +/* +Sleep if the futex contains the expected value until it's signalled or the +timeout is reached. + +If the value of the futex is `expected`, this procedure blocks the execution of +the current thread, until the futex is signalled, a timeout is reached, or +until a spurious wakeup occurs. + +This procedure returns `false` if the timeout was reached, `true` otherwise. +*/ futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, duration: time.Duration) -> bool { if u32(atomic_load_explicit(f, .Acquire)) != expected { return true @@ -226,25 +583,16 @@ futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, duratio return _futex_wait_with_timeout(f, expected, duration) } +/* +Wake up a single thread waiting on a futex. +*/ futex_signal :: proc "contextless" (f: ^Futex) { _futex_signal(f) } +/* +Wake up multiple threads waiting on a futex. +*/ futex_broadcast :: proc "contextless" (f: ^Futex) { _futex_broadcast(f) } - - -@(private) -_assert :: proc "contextless" (cond: bool, msg: string) { - if !cond { - _panic(msg) - } -} - -@(private) -_panic :: proc "contextless" (msg: string) -> ! { - runtime.print_string(msg) - runtime.print_byte('\n') - runtime.trap() -} diff --git a/core/sync/primitives_atomic.odin b/core/sync/primitives_atomic.odin index 1b7cdfe35..a8a84b2bc 100644 --- a/core/sync/primitives_atomic.odin +++ b/core/sync/primitives_atomic.odin @@ -67,7 +67,7 @@ atomic_mutex_unlock :: proc "contextless" (m: ^Atomic_Mutex) { switch atomic_exchange_explicit(&m.state, .Unlocked, .Release) { case .Unlocked: - unreachable() + // Kind of okay - unlocking while already unlocked. case .Locked: // Okay case .Waiting: @@ -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) } } @@ -240,7 +240,7 @@ atomic_recursive_mutex_lock :: proc "contextless" (m: ^Atomic_Recursive_Mutex) { atomic_recursive_mutex_unlock :: proc "contextless" (m: ^Atomic_Recursive_Mutex) { tid := current_thread_id() - _assert(tid == m.owner, "tid != m.owner") + assert_contextless(tid == m.owner, "tid != m.owner") m.recursion -= 1 recursion := m.recursion if recursion == 0 { @@ -338,7 +338,7 @@ atomic_sema_wait :: proc "contextless" (s: ^Atomic_Sema) { original_count := atomic_load_explicit(&s.count, .Relaxed) for original_count == 0 { futex_wait(&s.count, u32(original_count)) - original_count = s.count + original_count = atomic_load_explicit(&s.count, .Relaxed) } if original_count == atomic_compare_exchange_strong_explicit(&s.count, original_count, original_count-1, .Acquire, .Acquire) { return @@ -361,7 +361,7 @@ atomic_sema_wait_with_timeout :: proc "contextless" (s: ^Atomic_Sema, duration: if !futex_wait_with_timeout(&s.count, u32(original_count), remaining) { return false } - original_count = s.count + original_count = atomic_load_explicit(&s.count, .Relaxed) } if original_count == atomic_compare_exchange_strong_explicit(&s.count, original_count, original_count-1, .Acquire, .Acquire) { return true diff --git a/core/sync/primitives_darwin.odin b/core/sync/primitives_darwin.odin index 726113ae7..141cea744 100644 --- a/core/sync/primitives_darwin.odin +++ b/core/sync/primitives_darwin.odin @@ -1,11 +1,11 @@ -//+build darwin -//+private +#+build darwin +#+private package sync import "core:c" -import "core:intrinsics" +import "base:intrinsics" -foreign import pthread "System.framework" +foreign import pthread "system:System.framework" _current_thread_id :: proc "contextless" () -> int { tid: u64 diff --git a/core/sync/primitives_freebsd.odin b/core/sync/primitives_freebsd.odin index 2d7cbf18d..fe6b11e72 100644 --- a/core/sync/primitives_freebsd.odin +++ b/core/sync/primitives_freebsd.odin @@ -1,5 +1,5 @@ -//+build freebsd -//+private +#+build freebsd +#+private package sync import "core:c" diff --git a/core/sync/primitives_haiku.odin b/core/sync/primitives_haiku.odin new file mode 100644 index 000000000..69d005206 --- /dev/null +++ b/core/sync/primitives_haiku.odin @@ -0,0 +1,8 @@ +#+private +package sync + +import "core:sys/haiku" + +_current_thread_id :: proc "contextless" () -> int { + return int(haiku.find_thread(nil)) +} diff --git a/core/sync/primitives_internal.odin b/core/sync/primitives_internal.odin index 23483aef5..4478a77d2 100644 --- a/core/sync/primitives_internal.odin +++ b/core/sync/primitives_internal.odin @@ -1,4 +1,4 @@ -//+private +#+private package sync import "core:time" diff --git a/core/sync/primitives_linux.odin b/core/sync/primitives_linux.odin index aa7a8b4b2..bf04f8d99 100644 --- a/core/sync/primitives_linux.odin +++ b/core/sync/primitives_linux.odin @@ -1,5 +1,5 @@ -//+build linux -//+private +#+build linux +#+private package sync import "core:sys/linux" diff --git a/core/sync/primitives_netbsd.odin b/core/sync/primitives_netbsd.odin new file mode 100644 index 000000000..66da0745a --- /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/sync/primitives_openbsd.odin b/core/sync/primitives_openbsd.odin index ff3ff837f..1f6efd8f7 100644 --- a/core/sync/primitives_openbsd.odin +++ b/core/sync/primitives_openbsd.odin @@ -1,5 +1,5 @@ -//+build openbsd -//+private +#+build openbsd +#+private package sync foreign import libc "system:c" diff --git a/core/sync/primitives_wasm.odin b/core/sync/primitives_wasm.odin index f8d9ab657..8906d96be 100644 --- a/core/sync/primitives_wasm.odin +++ b/core/sync/primitives_wasm.odin @@ -1,5 +1,5 @@ -//+private -//+build wasm32, wasm64p32 +#+private +#+build wasm32, wasm64p32 package sync _current_thread_id :: proc "contextless" () -> int { diff --git a/core/sync/primitives_windows.odin b/core/sync/primitives_windows.odin index 9f5bfc280..744bc248b 100644 --- a/core/sync/primitives_windows.odin +++ b/core/sync/primitives_windows.odin @@ -1,5 +1,5 @@ -//+build windows -//+private +#+build windows +#+private package sync import "core:time" diff --git a/core/sync/sync_util.odin b/core/sync/sync_util.odin index 0c948eb9e..07b970a82 100644 --- a/core/sync/sync_util.odin +++ b/core/sync/sync_util.odin @@ -9,6 +9,7 @@ Example: guard :: proc{ mutex_guard, rw_mutex_guard, + recursive_mutex_guard, ticket_mutex_guard, benaphore_guard, recursive_benaphore_guard, @@ -31,6 +32,7 @@ shared_guard :: proc{ lock :: proc{ mutex_lock, rw_mutex_lock, + recursive_mutex_lock, ticket_mutex_lock, benaphore_lock, recursive_benaphore_lock, @@ -43,6 +45,7 @@ lock :: proc{ unlock :: proc{ mutex_unlock, rw_mutex_unlock, + recursive_mutex_unlock, ticket_mutex_unlock, benaphore_unlock, recursive_benaphore_unlock, @@ -55,6 +58,7 @@ unlock :: proc{ try_lock :: proc{ mutex_try_lock, rw_mutex_try_lock, + recursive_mutex_try_lock, benaphore_try_lock, recursive_benaphore_try_lock, atomic_mutex_try_lock, diff --git a/core/sys/darwin/CoreFoundation/CFBase.odin b/core/sys/darwin/CoreFoundation/CFBase.odin new file mode 100644 index 000000000..7335f087b --- /dev/null +++ b/core/sys/darwin/CoreFoundation/CFBase.odin @@ -0,0 +1,34 @@ +package CoreFoundation + +foreign import CoreFoundation "system:CoreFoundation.framework" + +TypeID :: distinct uint +OptionFlags :: distinct uint +HashCode :: distinct uint +Index :: distinct int +TypeRef :: distinct rawptr + +Range :: struct { + location: Index, + length: Index, +} + +foreign CoreFoundation { + // Releases a Core Foundation object. + CFRelease :: proc(cf: TypeRef) --- +} + +// Releases a Core Foundation object. +Release :: proc { + ReleaseObject, + ReleaseString, +} + +ReleaseObject :: #force_inline proc(cf: TypeRef) { + CFRelease(cf) +} + +// Releases a Core Foundation string. +ReleaseString :: #force_inline proc(theString: String) { + CFRelease(TypeRef(theString)) +} diff --git a/core/sys/darwin/CoreFoundation/CFString.odin b/core/sys/darwin/CoreFoundation/CFString.odin new file mode 100644 index 000000000..24485a494 --- /dev/null +++ b/core/sys/darwin/CoreFoundation/CFString.odin @@ -0,0 +1,194 @@ +package CoreFoundation + +foreign import CoreFoundation "system:CoreFoundation.framework" + +String :: distinct TypeRef // same as CFStringRef + +StringEncoding :: distinct u32 + +StringBuiltInEncodings :: enum StringEncoding { + MacRoman = 0, + WindowsLatin1 = 0x0500, + ISOLatin1 = 0x0201, + NextStepLatin = 0x0B01, + ASCII = 0x0600, + Unicode = 0x0100, + UTF8 = 0x08000100, + NonLossyASCII = 0x0BFF, + + UTF16 = 0x0100, + UTF16BE = 0x10000100, + UTF16LE = 0x14000100, + + UTF32 = 0x0c000100, + UTF32BE = 0x18000100, + UTF32LE = 0x1c000100, +} + +StringEncodings :: enum Index { + MacJapanese = 1, + MacChineseTrad = 2, + MacKorean = 3, + MacArabic = 4, + MacHebrew = 5, + MacGreek = 6, + MacCyrillic = 7, + MacDevanagari = 9, + MacGurmukhi = 10, + MacGujarati = 11, + MacOriya = 12, + MacBengali = 13, + MacTamil = 14, + MacTelugu = 15, + MacKannada = 16, + MacMalayalam = 17, + MacSinhalese = 18, + MacBurmese = 19, + MacKhmer = 20, + MacThai = 21, + MacLaotian = 22, + MacGeorgian = 23, + MacArmenian = 24, + MacChineseSimp = 25, + MacTibetan = 26, + MacMongolian = 27, + MacEthiopic = 28, + MacCentralEurRoman = 29, + MacVietnamese = 30, + MacExtArabic = 31, + MacSymbol = 33, + MacDingbats = 34, + MacTurkish = 35, + MacCroatian = 36, + MacIcelandic = 37, + MacRomanian = 38, + MacCeltic = 39, + MacGaelic = 40, + MacFarsi = 0x8C, + MacUkrainian = 0x98, + MacInuit = 0xEC, + MacVT100 = 0xFC, + MacHFS = 0xFF, + ISOLatin2 = 0x0202, + ISOLatin3 = 0x0203, + ISOLatin4 = 0x0204, + ISOLatinCyrillic = 0x0205, + ISOLatinArabic = 0x0206, + ISOLatinGreek = 0x0207, + ISOLatinHebrew = 0x0208, + ISOLatin5 = 0x0209, + ISOLatin6 = 0x020A, + ISOLatinThai = 0x020B, + ISOLatin7 = 0x020D, + ISOLatin8 = 0x020E, + ISOLatin9 = 0x020F, + ISOLatin10 = 0x0210, + DOSLatinUS = 0x0400, + DOSGreek = 0x0405, + DOSBalticRim = 0x0406, + DOSLatin1 = 0x0410, + DOSGreek1 = 0x0411, + DOSLatin2 = 0x0412, + DOSCyrillic = 0x0413, + DOSTurkish = 0x0414, + DOSPortuguese = 0x0415, + DOSIcelandic = 0x0416, + DOSHebrew = 0x0417, + DOSCanadianFrench = 0x0418, + DOSArabic = 0x0419, + DOSNordic = 0x041A, + DOSRussian = 0x041B, + DOSGreek2 = 0x041C, + DOSThai = 0x041D, + DOSJapanese = 0x0420, + DOSChineseSimplif = 0x0421, + DOSKorean = 0x0422, + DOSChineseTrad = 0x0423, + WindowsLatin2 = 0x0501, + WindowsCyrillic = 0x0502, + WindowsGreek = 0x0503, + WindowsLatin5 = 0x0504, + WindowsHebrew = 0x0505, + WindowsArabic = 0x0506, + WindowsBalticRim = 0x0507, + WindowsVietnamese = 0x0508, + WindowsKoreanJohab = 0x0510, + ANSEL = 0x0601, + JIS_X0201_76 = 0x0620, + JIS_X0208_83 = 0x0621, + JIS_X0208_90 = 0x0622, + JIS_X0212_90 = 0x0623, + JIS_C6226_78 = 0x0624, + ShiftJIS_X0213 = 0x0628, + ShiftJIS_X0213_MenKuTen = 0x0629, + GB_2312_80 = 0x0630, + GBK_95 = 0x0631, + GB_18030_2000 = 0x0632, + KSC_5601_87 = 0x0640, + KSC_5601_92_Johab = 0x0641, + CNS_11643_92_P1 = 0x0651, + CNS_11643_92_P2 = 0x0652, + CNS_11643_92_P3 = 0x0653, + ISO_2022_JP = 0x0820, + ISO_2022_JP_2 = 0x0821, + ISO_2022_JP_1 = 0x0822, + ISO_2022_JP_3 = 0x0823, + ISO_2022_CN = 0x0830, + ISO_2022_CN_EXT = 0x0831, + ISO_2022_KR = 0x0840, + EUC_JP = 0x0920, + EUC_CN = 0x0930, + EUC_TW = 0x0931, + EUC_KR = 0x0940, + ShiftJIS = 0x0A01, + KOI8_R = 0x0A02, + Big5 = 0x0A03, + MacRomanLatin1 = 0x0A04, + HZ_GB_2312 = 0x0A05, + Big5_HKSCS_1999 = 0x0A06, + VISCII = 0x0A07, + KOI8_U = 0x0A08, + Big5_E = 0x0A09, + NextStepJapanese = 0x0B02, + EBCDIC_US = 0x0C01, + EBCDIC_CP037 = 0x0C02, + UTF7 = 0x04000100, + UTF7_IMAP = 0x0A10, + ShiftJIS_X0213_00 = 0x0628, // Deprecated. Use `ShiftJIS_X0213` instead. +} + +@(link_prefix="CF", default_calling_convention="c") +foreign CoreFoundation { + // Copies the character contents of a string to a local C string buffer after converting the characters to a given encoding. + StringGetCString :: proc(theString: String, buffer: [^]byte, bufferSize: Index, encoding: StringEncoding) -> b8 --- + + // Returns the number (in terms of UTF-16 code pairs) of Unicode characters in a string. + StringGetLength :: proc(theString: String) -> Index --- + + // Returns the maximum number of bytes a string of a specified length (in Unicode characters) will take up if encoded in a specified encoding. + StringGetMaximumSizeForEncoding :: proc(length: Index, encoding: StringEncoding) -> Index --- + + // Fetches a range of the characters from a string into a byte buffer after converting the characters to a specified encoding. + StringGetBytes :: proc(thestring: String, range: Range, encoding: StringEncoding, lossByte: u8, isExternalRepresentation: b8, buffer: [^]byte, maxBufLen: Index, usedBufLen: ^Index) -> Index --- + + StringIsEncodingAvailable :: proc(encoding: StringEncoding) -> bool --- + + @(link_name = "__CFStringMakeConstantString") + StringMakeConstantString :: proc "c" (#const c: cstring) -> String --- +} + +STR :: StringMakeConstantString + +StringCopyToOdinString :: proc(theString: String, allocator := context.allocator) -> (str: string, ok: bool) #optional_ok { + length := StringGetLength(theString) + max := StringGetMaximumSizeForEncoding(length, StringEncoding(StringBuiltInEncodings.UTF8)) + + buf, err := make([]byte, max, allocator) + if err != nil { + return + } + + n: Index + StringGetBytes(theString, {0, length}, StringEncoding(StringBuiltInEncodings.UTF8), 0, false, raw_data(buf), Index(len(buf)), &n) + return string(buf[:n]), true +} diff --git a/vendor/darwin/Foundation/NSApplication.odin b/core/sys/darwin/Foundation/NSApplication.odin similarity index 96% rename from vendor/darwin/Foundation/NSApplication.odin rename to core/sys/darwin/Foundation/NSApplication.odin index 7b89d84b0..254da75ad 100644 --- a/vendor/darwin/Foundation/NSApplication.odin +++ b/core/sys/darwin/Foundation/NSApplication.odin @@ -1,10 +1,9 @@ -//+build darwin package objc_Foundation foreign import "system:Foundation.framework" -import "core:intrinsics" -import "core:runtime" +import "base:intrinsics" +import "base:runtime" import "core:strings" RunLoopMode :: ^String @@ -80,16 +79,45 @@ Application_setActivationPolicy :: proc "c" (self: ^Application, activationPolic return msgSend(BOOL, self, "setActivationPolicy:", activationPolicy) } +// NOTE: this is technically deprecated but still actively used (Sokol, glfw, SDL, etc.) +// and has no clear alternative although `activate` is what Apple tells you to use, +// that does not work the same way. +// @(deprecated="Use NSApplication method activate instead.") @(objc_type=Application, objc_name="activateIgnoringOtherApps") Application_activateIgnoringOtherApps :: proc "c" (self: ^Application, ignoreOtherApps: BOOL) { msgSend(nil, self, "activateIgnoringOtherApps:", ignoreOtherApps) } +@(objc_type=Application, objc_name="activate") +Application_activate :: proc "c" (self: ^Application) { + msgSend(nil, self, "activate") +} + +@(objc_type=Application, objc_name="setTitle") +Application_setTitle :: proc "c" (self: ^Application, title: ^String) { + msgSend(nil, self, "setTitle", title) +} + +@(objc_type=Application, objc_name="mainMenu") +Window_mainMenu :: proc "c" (self: ^Application) -> ^Menu { + return msgSend(^Menu, self, "mainMenu") +} + @(objc_type=Application, objc_name="setMainMenu") Application_setMainMenu :: proc "c" (self: ^Application, menu: ^Menu) { msgSend(nil, self, "setMainMenu:", menu) } +@(objc_type=Application, objc_name="mainWindow") +Application_mainWindow :: proc "c" (self: ^Application) -> ^Window { + return msgSend(^Window, self, "mainWindow") +} + +@(objc_type=Application, objc_name="keyWindow") +Application_keyWindow :: proc "c" (self: ^Application) -> ^Window { + return msgSend(^Window, self, "keyWindow") +} + @(objc_type=Application, objc_name="windows") Application_windows :: proc "c" (self: ^Application) -> ^Array { return msgSend(^Array, self, "windows") @@ -100,6 +128,11 @@ Application_run :: proc "c" (self: ^Application) { msgSend(nil, self, "run") } +@(objc_type=Application, objc_name="finishLaunching") +Application_finishLaunching :: proc "c" (self: ^Application) { + msgSend(nil, self, "finishLaunching") +} + @(objc_type=Application, objc_name="terminate") Application_terminate :: proc "c" (self: ^Application, sender: ^Object) { msgSend(nil, self, "terminate:", sender) @@ -122,7 +155,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/vendor/darwin/Foundation/NSArray.odin b/core/sys/darwin/Foundation/NSArray.odin similarity index 96% rename from vendor/darwin/Foundation/NSArray.odin rename to core/sys/darwin/Foundation/NSArray.odin index ac4aa6181..b238f63f8 100644 --- a/vendor/darwin/Foundation/NSArray.odin +++ b/core/sys/darwin/Foundation/NSArray.odin @@ -1,7 +1,6 @@ -//+build darwin package objc_Foundation -import "core:intrinsics" +import "base:intrinsics" @(objc_class="NSArray") Array :: struct { diff --git a/vendor/darwin/Foundation/NSAutoreleasePool.odin b/core/sys/darwin/Foundation/NSAutoreleasePool.odin similarity index 98% rename from vendor/darwin/Foundation/NSAutoreleasePool.odin rename to core/sys/darwin/Foundation/NSAutoreleasePool.odin index d3a6f490f..8eb3657b6 100644 --- a/vendor/darwin/Foundation/NSAutoreleasePool.odin +++ b/core/sys/darwin/Foundation/NSAutoreleasePool.odin @@ -1,4 +1,3 @@ -//+build darwin package objc_Foundation @(objc_class="NSAutoreleasePool") diff --git a/vendor/darwin/Foundation/NSBlock.odin b/core/sys/darwin/Foundation/NSBlock.odin similarity index 98% rename from vendor/darwin/Foundation/NSBlock.odin rename to core/sys/darwin/Foundation/NSBlock.odin index 29bd210de..b9d94bfee 100644 --- a/vendor/darwin/Foundation/NSBlock.odin +++ b/core/sys/darwin/Foundation/NSBlock.odin @@ -1,8 +1,7 @@ -//+build darwin package objc_Foundation -import "core:intrinsics" -import "core:builtin" +import "base:intrinsics" +import "base:builtin" import "core:mem" @(objc_class="NSBlock") diff --git a/vendor/darwin/Foundation/NSBundle.odin b/core/sys/darwin/Foundation/NSBundle.odin similarity index 99% rename from vendor/darwin/Foundation/NSBundle.odin rename to core/sys/darwin/Foundation/NSBundle.odin index 450a6d951..25fc8df32 100644 --- a/vendor/darwin/Foundation/NSBundle.odin +++ b/core/sys/darwin/Foundation/NSBundle.odin @@ -1,4 +1,3 @@ -//+build darwin package objc_Foundation @(objc_class="NSBundle") diff --git a/vendor/darwin/Foundation/NSColor.odin b/core/sys/darwin/Foundation/NSColor.odin similarity index 99% rename from vendor/darwin/Foundation/NSColor.odin rename to core/sys/darwin/Foundation/NSColor.odin index 4c0325c1a..453b33144 100644 --- a/vendor/darwin/Foundation/NSColor.odin +++ b/core/sys/darwin/Foundation/NSColor.odin @@ -1,4 +1,3 @@ -//+build darwin package objc_Foundation @(objc_class="NSColorSpace") diff --git a/vendor/darwin/Foundation/NSData.odin b/core/sys/darwin/Foundation/NSData.odin similarity index 97% rename from vendor/darwin/Foundation/NSData.odin rename to core/sys/darwin/Foundation/NSData.odin index 069a59b64..04c1ce25d 100644 --- a/vendor/darwin/Foundation/NSData.odin +++ b/core/sys/darwin/Foundation/NSData.odin @@ -1,4 +1,3 @@ -//+build darwin package objc_Foundation @(objc_class="NSData") diff --git a/vendor/darwin/Foundation/NSDate.odin b/core/sys/darwin/Foundation/NSDate.odin similarity index 63% rename from vendor/darwin/Foundation/NSDate.odin rename to core/sys/darwin/Foundation/NSDate.odin index e4564e06b..41efb0cf5 100644 --- a/vendor/darwin/Foundation/NSDate.odin +++ b/core/sys/darwin/Foundation/NSDate.odin @@ -1,4 +1,3 @@ -//+build darwin package objc_Foundation @(objc_class="NSDate") @@ -17,4 +16,14 @@ Date_init :: proc "c" (self: ^Date) -> ^Date { @(objc_type=Date, objc_name="dateWithTimeIntervalSinceNow") Date_dateWithTimeIntervalSinceNow :: proc "c" (secs: TimeInterval) -> ^Date { return msgSend(^Date, Date, "dateWithTimeIntervalSinceNow:", secs) -} \ No newline at end of file +} + +@(objc_type=Date, objc_name="distantFuture", objc_is_class_method=true) +Date_distantFuture :: proc "c" () -> ^Date { + return msgSend(^Date, Date, "distantFuture") +} + +@(objc_type=Date, objc_name="distantPast", objc_is_class_method=true) +Date_distantPast :: proc "c" () -> ^Date { + return msgSend(^Date, Date, "distantPast") +} diff --git a/vendor/darwin/Foundation/NSDictionary.odin b/core/sys/darwin/Foundation/NSDictionary.odin similarity index 99% rename from vendor/darwin/Foundation/NSDictionary.odin rename to core/sys/darwin/Foundation/NSDictionary.odin index f84954656..8af58cf62 100644 --- a/vendor/darwin/Foundation/NSDictionary.odin +++ b/core/sys/darwin/Foundation/NSDictionary.odin @@ -1,4 +1,3 @@ -//+build darwin package objc_Foundation @(objc_class="NSDictionary") diff --git a/vendor/darwin/Foundation/NSEnumerator.odin b/core/sys/darwin/Foundation/NSEnumerator.odin similarity index 97% rename from vendor/darwin/Foundation/NSEnumerator.odin rename to core/sys/darwin/Foundation/NSEnumerator.odin index 717e58e4c..52f3f49d7 100644 --- a/vendor/darwin/Foundation/NSEnumerator.odin +++ b/core/sys/darwin/Foundation/NSEnumerator.odin @@ -1,8 +1,7 @@ -//+build darwin package objc_Foundation import "core:c" -import "core:intrinsics" +import "base:intrinsics" FastEnumerationState :: struct #packed { state: c.ulong, diff --git a/vendor/darwin/Foundation/NSError.odin b/core/sys/darwin/Foundation/NSError.odin similarity index 99% rename from vendor/darwin/Foundation/NSError.odin rename to core/sys/darwin/Foundation/NSError.odin index 77b556e91..1657befe2 100644 --- a/vendor/darwin/Foundation/NSError.odin +++ b/core/sys/darwin/Foundation/NSError.odin @@ -1,4 +1,3 @@ -//+build darwin package objc_Foundation foreign import "system:Foundation.framework" diff --git a/vendor/darwin/Foundation/NSEvent.odin b/core/sys/darwin/Foundation/NSEvent.odin similarity index 92% rename from vendor/darwin/Foundation/NSEvent.odin rename to core/sys/darwin/Foundation/NSEvent.odin index fa66c7fa6..548c5c172 100644 --- a/vendor/darwin/Foundation/NSEvent.odin +++ b/core/sys/darwin/Foundation/NSEvent.odin @@ -1,4 +1,3 @@ -//+build darwin package objc_Foundation @(objc_class="NSEvent") @@ -6,8 +5,8 @@ Event :: struct {using _: Object} -EventMask :: distinct bit_set[EventType; UInteger] -EventMaskAny :: ~EventMask{} +EventMask :: distinct bit_set[EventType; UInteger] +EventMaskAny :: transmute(EventMask)(max(UInteger)) when size_of(UInteger) == 4 { // We don't support a 32-bit darwin system but this is mostly to shut up the type checker for the time being @@ -106,6 +105,28 @@ PointingDeviceType :: enum UInteger { Eraser = 3, } +EventModifierFlag :: enum UInteger { + CapsLock = 16, + Shift = 17, + Control = 18, + Option = 19, + Command = 20, + NumericPad = 21, + Help = 22, + Function = 23, +} + +EventModifierFlags :: distinct bit_set[EventModifierFlag; UInteger] +EventModifierFlagCapsLock :: EventModifierFlags{.CapsLock} +EventModifierFlagShift :: EventModifierFlags{.Shift} +EventModifierFlagControl :: EventModifierFlags{.Control} +EventModifierFlagOption :: EventModifierFlags{.Option} +EventModifierFlagCommand :: EventModifierFlags{.Command} +EventModifierFlagNumericPad :: EventModifierFlags{.NumericPad} +EventModifierFlagHelp :: EventModifierFlags{.Help} +EventModifierFlagFunction :: EventModifierFlags{.Function} +EventModifierFlagDeviceIndependentFlagsMask : UInteger : 0xffff0000 + // Defined in Carbon.framework Events.h kVK :: enum { ANSI_A = 0x00, @@ -237,8 +258,8 @@ Event_type :: proc "c" (self: ^Event) -> EventType { return msgSend(EventType, self, "type") } @(objc_type=Event, objc_name="modifierFlags") -Event_modifierFlags :: proc "c" (self: ^Event) -> UInteger { - return msgSend(UInteger, self, "modifierFlags") +Event_modifierFlags :: proc "c" (self: ^Event) -> EventModifierFlags { + return msgSend(EventModifierFlags, self, "modifierFlags") } @(objc_type=Event, objc_name="timestamp") Event_timestamp :: proc "c" (self: ^Event) -> TimeInterval { diff --git a/vendor/darwin/Foundation/NSLock.odin b/core/sys/darwin/Foundation/NSLock.odin similarity index 98% rename from vendor/darwin/Foundation/NSLock.odin rename to core/sys/darwin/Foundation/NSLock.odin index d9662968d..168380669 100644 --- a/vendor/darwin/Foundation/NSLock.odin +++ b/core/sys/darwin/Foundation/NSLock.odin @@ -1,4 +1,3 @@ -//+build darwin package objc_Foundation Locking :: struct($T: typeid) {using _: Object} diff --git a/vendor/darwin/Foundation/NSMenu.odin b/core/sys/darwin/Foundation/NSMenu.odin similarity index 89% rename from vendor/darwin/Foundation/NSMenu.odin rename to core/sys/darwin/Foundation/NSMenu.odin index 5a4f7b1f5..e49162a7f 100644 --- a/vendor/darwin/Foundation/NSMenu.odin +++ b/core/sys/darwin/Foundation/NSMenu.odin @@ -1,8 +1,7 @@ -//+build darwin package objc_Foundation -import "core:builtin" -import "core:intrinsics" +import "base:builtin" +import "base:intrinsics" KeyEquivalentModifierFlag :: enum UInteger { CapsLock = 16, // Set if Caps Lock key is pressed. @@ -25,7 +24,7 @@ MenuItemCallback :: proc "c" (unused: rawptr, name: SEL, sender: ^Object) @(objc_class="NSMenuItem") -MenuItem :: struct {using _: Object} +MenuItem :: struct {using _: Object} @(objc_type=MenuItem, objc_name="alloc", objc_is_class_method=true) MenuItem_alloc :: proc "c" () -> ^MenuItem { @@ -71,11 +70,15 @@ MenuItem_setSubmenu :: proc "c" (self: ^MenuItem, submenu: ^Menu) { msgSend(nil, self, "setSubmenu:", submenu) } +@(objc_type=MenuItem, objc_name="title") +MenuItem_title :: proc "c" (self: ^MenuItem) -> ^String { + return msgSend(^String, self, "title") +} @(objc_class="NSMenu") -Menu :: struct {using _: Object} +Menu :: struct {using _: Object} @(objc_type=Menu, objc_name="alloc", objc_is_class_method=true) Menu_alloc :: proc "c" () -> ^Menu { @@ -101,4 +104,9 @@ Menu_addItem :: proc "c" (self: ^Menu, item: ^MenuItem) { @(objc_type=Menu, objc_name="addItemWithTitle") Menu_addItemWithTitle :: proc "c" (self: ^Menu, title: ^String, selector: SEL, keyEquivalent: ^String) -> ^MenuItem { return msgSend(^MenuItem, self, "addItemWithTitle:action:keyEquivalent:", title, selector, keyEquivalent) +} + +@(objc_type=Menu, objc_name="itemArray") +Menu_itemArray :: proc "c" (self: ^Menu) -> ^Array { + return msgSend(^Array, self, "itemArray") } \ No newline at end of file diff --git a/vendor/darwin/Foundation/NSNotification.odin b/core/sys/darwin/Foundation/NSNotification.odin similarity index 99% rename from vendor/darwin/Foundation/NSNotification.odin rename to core/sys/darwin/Foundation/NSNotification.odin index d319395a5..f766d0cab 100644 --- a/vendor/darwin/Foundation/NSNotification.odin +++ b/core/sys/darwin/Foundation/NSNotification.odin @@ -1,4 +1,3 @@ -//+build darwin package objc_Foundation @(objc_class="NSNotification") diff --git a/vendor/darwin/Foundation/NSNumber.odin b/core/sys/darwin/Foundation/NSNumber.odin similarity index 99% rename from vendor/darwin/Foundation/NSNumber.odin rename to core/sys/darwin/Foundation/NSNumber.odin index a2d3c2d16..b3124885f 100644 --- a/vendor/darwin/Foundation/NSNumber.odin +++ b/core/sys/darwin/Foundation/NSNumber.odin @@ -1,4 +1,3 @@ -//+build darwin package objc_Foundation import "core:c" diff --git a/vendor/darwin/Foundation/NSObject.odin b/core/sys/darwin/Foundation/NSObject.odin similarity index 98% rename from vendor/darwin/Foundation/NSObject.odin rename to core/sys/darwin/Foundation/NSObject.odin index 7c529aa09..31ece47a1 100644 --- a/vendor/darwin/Foundation/NSObject.odin +++ b/core/sys/darwin/Foundation/NSObject.odin @@ -1,7 +1,6 @@ -//+build darwin package objc_Foundation -import "core:intrinsics" +import "base:intrinsics" methodSignatureForSelector :: proc "c" (obj: ^Object, selector: SEL) -> rawptr { return msgSend(rawptr, obj, "methodSignatureForSelector:", selector) diff --git a/core/sys/darwin/Foundation/NSObjectProtocol.odin b/core/sys/darwin/Foundation/NSObjectProtocol.odin new file mode 100644 index 000000000..99d942579 --- /dev/null +++ b/core/sys/darwin/Foundation/NSObjectProtocol.odin @@ -0,0 +1,5 @@ +package objc_Foundation + +@(objc_class="NSObjectProtocol") +ObjectProtocol :: struct {using _: Object} +// TODO: implement NSObjectProtocol diff --git a/vendor/darwin/Foundation/NSOpenPanel.odin b/core/sys/darwin/Foundation/NSOpenPanel.odin similarity index 86% rename from vendor/darwin/Foundation/NSOpenPanel.odin rename to core/sys/darwin/Foundation/NSOpenPanel.odin index 6b5dc0b6b..482347daa 100644 --- a/vendor/darwin/Foundation/NSOpenPanel.odin +++ b/core/sys/darwin/Foundation/NSOpenPanel.odin @@ -1,4 +1,3 @@ -//+build darwin package objc_Foundation @(objc_class="NSOpenPanel") @@ -30,3 +29,7 @@ OpenPanel_setResolvesAliases :: proc "c" (self: ^OpenPanel, setting: BOOL) { OpenPanel_setAllowsMultipleSelection :: proc "c" (self: ^OpenPanel, setting: BOOL) { msgSend(nil, self, "setAllowsMultipleSelection:", setting) } +@(objc_type=OpenPanel, objc_name="setAllowedFileTypes") +OpenPanel_setAllowedFileTypes :: proc "c" (self: ^OpenPanel, types: ^Array) { + msgSend(nil, self, "setAllowedFileTypes:", types) +} diff --git a/vendor/darwin/Foundation/NSPanel.odin b/core/sys/darwin/Foundation/NSPanel.odin similarity index 89% rename from vendor/darwin/Foundation/NSPanel.odin rename to core/sys/darwin/Foundation/NSPanel.odin index b18ebc81e..4bdf08cdb 100644 --- a/vendor/darwin/Foundation/NSPanel.odin +++ b/core/sys/darwin/Foundation/NSPanel.odin @@ -1,4 +1,3 @@ -//+build darwin package objc_Foundation ModalResponse :: enum UInteger { diff --git a/vendor/darwin/Foundation/NSPasteboard.odin b/core/sys/darwin/Foundation/NSPasteboard.odin similarity index 88% rename from vendor/darwin/Foundation/NSPasteboard.odin rename to core/sys/darwin/Foundation/NSPasteboard.odin index a1e23d977..74cf7d172 100644 --- a/vendor/darwin/Foundation/NSPasteboard.odin +++ b/core/sys/darwin/Foundation/NSPasteboard.odin @@ -1,4 +1,3 @@ -//+build darwin package objc_Foundation @(objc_class="NSPasteboard") diff --git a/core/sys/darwin/Foundation/NSProcessInfo.odin b/core/sys/darwin/Foundation/NSProcessInfo.odin new file mode 100644 index 000000000..e070bf8e2 --- /dev/null +++ b/core/sys/darwin/Foundation/NSProcessInfo.odin @@ -0,0 +1,203 @@ +package objc_Foundation + +import "base:intrinsics" + +import "core:c" + +@(objc_class="NSProcessInfo") +ProcessInfo :: struct {using _: Object} + +// Getting the Process Information Agent + +@(objc_type=ProcessInfo, objc_name="processInfo", objc_is_class_method=true) +ProcessInfo_processInfo :: proc "c" () -> ^ProcessInfo { + return msgSend(^ProcessInfo, ProcessInfo, "processInfo") +} + +// Accessing Process Information + +@(objc_type=ProcessInfo, objc_name="arguments") +ProcessInfo_arguments :: proc "c" (self: ^ProcessInfo) -> ^Array { + return msgSend(^Array, self, "arguments") +} + +@(objc_type=ProcessInfo, objc_name="environment") +ProcessInfo_environment :: proc "c" (self: ^ProcessInfo) -> ^Dictionary { + return msgSend(^Dictionary, self, "environment") +} + +@(objc_type=ProcessInfo, objc_name="globallyUniqueString") +ProcessInfo_globallyUniqueString :: proc "c" (self: ^ProcessInfo) -> ^String { + return msgSend(^String, self, "globallyUniqueString") +} + +@(objc_type=ProcessInfo, objc_name="isMacCatalystApp") +ProcessInfo_isMacCatalystApp :: proc "c" (self: ^ProcessInfo) -> bool { + return msgSend(bool, self, "isMacCatalystApp") +} + +@(objc_type=ProcessInfo, objc_name="isiOSAppOnMac") +ProcessInfo_isiOSAppOnMac :: proc "c" (self: ^ProcessInfo) -> bool { + return msgSend(bool, self, "isiOSAppOnMac") +} + +@(objc_type=ProcessInfo, objc_name="processIdentifier") +ProcessInfo_processIdentifier :: proc "c" (self: ^ProcessInfo) -> c.int { + return msgSend(c.int, self, "processIdentifier") +} + +@(objc_type=ProcessInfo, objc_name="processName") +ProcessInfo_processName :: proc "c" (self: ^ProcessInfo) -> ^String { + return msgSend(^String, self, "processName") +} + +// Accessing User Information + +@(objc_type=ProcessInfo, objc_name="userName") +ProcessInfo_userName :: proc "c" (self: ^ProcessInfo) -> ^String { + return msgSend(^String, self, "userName") +} + +@(objc_type=ProcessInfo, objc_name="fullUserName") +ProcessInfo_fullUserName :: proc "c" (self: ^ProcessInfo) -> ^String { + return msgSend(^String, self, "fullUserName") +} + +// Sudden Application Termination + +@(objc_type=ProcessInfo, objc_name="disableSuddenTermination") +ProcessInfo_disableSuddenTermination :: proc "c" (self: ^ProcessInfo) { + msgSend(nil, self, "disableSuddenTermination") +} + +@(objc_type=ProcessInfo, objc_name="enableSuddenTermination") +ProcessInfo_enableSuddenTermination :: proc "c" (self: ^ProcessInfo) { + msgSend(nil, self, "enableSuddenTermination") +} + +// Controlling Automatic Termination + +@(objc_type=ProcessInfo, objc_name="disableAutomaticTermination") +ProcessInfo_disableAutomaticTermination :: proc "c" (self: ^ProcessInfo, reason: ^String) { + msgSend(nil, self, "disableAutomaticTermination:", reason) +} + +@(objc_type=ProcessInfo, objc_name="enableAutomaticTermination") +ProcessInfo_enableAutomaticTermination :: proc "c" (self: ^ProcessInfo, reason: ^String) { + msgSend(nil, self, "enableAutomaticTermination:", reason) +} + +@(objc_type=ProcessInfo, objc_name="automaticTerminationSupportEnabled") +ProcessInfo_automaticTerminationSupportEnabled :: proc "c" (self: ^ProcessInfo) -> bool { + return msgSend(bool, self, "automaticTerminationSupportEnabled") +} + +@(objc_type=ProcessInfo, objc_name="setAutomaticTerminationSupportEnabled") +ProcessInfo_setAutomaticTerminationSupportEnabled :: proc "c" (self: ^ProcessInfo, automaticTerminationSupportEnabled: bool) { + msgSend(nil, self, "setAutomaticTerminationSupportEnabled:", automaticTerminationSupportEnabled) +} + +// Getting Host Information + +@(objc_type=ProcessInfo, objc_name="hostName") +ProcessInfo_hostName :: proc "c" (self: ^ProcessInfo) -> ^String { + return msgSend(^String, self, "hostName") +} + +@(objc_type=ProcessInfo, objc_name="operatingSystemVersionString") +ProcessInfo_operatingSystemVersionString :: proc "c" (self: ^ProcessInfo) -> ^String { + return msgSend(^String, self, "operatingSystemVersionString") +} + +@(objc_type=ProcessInfo, objc_name="operatingSystemVersion") +ProcessInfo_operatingSystemVersion :: proc "c" (self: ^ProcessInfo) -> OperatingSystemVersion { + return msgSend(OperatingSystemVersion, self, "operatingSystemVersion") +} + +@(objc_type=ProcessInfo, objc_name="isOperatingSystemAtLeastVersion") +ProcessInfo_isOperatingSystemAtLeastVersion :: proc "c" (self: ^ProcessInfo, version: OperatingSystemVersion) -> bool { + return msgSend(bool, self, "isOperatingSystemAtLeastVersion:", version) +} + +// Getting Computer Information + +@(objc_type=ProcessInfo, objc_name="processorCount") +ProcessInfo_processorCount :: proc "c" (self: ^ProcessInfo) -> UInteger { + return msgSend(UInteger, self, "processorCount") +} + +@(objc_type=ProcessInfo, objc_name="activeProcessorCount") +ProcessInfo_activeProcessorCount :: proc "c" (self: ^ProcessInfo) -> UInteger { + return msgSend(UInteger, self, "activeProcessorCount") +} + +@(objc_type=ProcessInfo, objc_name="physicalMemory") +ProcessInfo_physicalMemory :: proc "c" (self: ^ProcessInfo) -> c.ulonglong { + return msgSend(c.ulonglong, self, "physicalMemory") +} + +@(objc_type=ProcessInfo, objc_name="systemUptime") +ProcessInfo_systemUptime :: proc "c" (self: ^ProcessInfo) -> TimeInterval { + return msgSend(TimeInterval, self, "systemUptime") +} + +// Managing Activities + +@(private) +log2 :: intrinsics.constant_log2 + +ActivityOptionsBits :: enum u64 { + IdleDisplaySleepDisabled = log2(1099511627776), // Require the screen to stay powered on. + IdleSystemSleepDisabled = log2(1048576), // Prevent idle sleep. + SuddenTerminationDisabled = log2(16384), // Prevent sudden termination. + AutomaticTerminationDisabled = log2(32768), // Prevent automatic termination. + AnimationTrackingEnabled = log2(35184372088832), // Track activity with an animation signpost interval. + TrackingEnabled = log2(70368744177664), // Track activity with a signpost interval. + UserInitiated = log2(16777215), // Performing a user-requested action. + UserInitiatedAllowingIdleSystemSleep = log2(15728639), // Performing a user-requested action, but the system can sleep on idle. + Background = log2(255), // Initiated some kind of work, but not as the direct result of a user request. + LatencyCritical = log2(1095216660480), // Requires the highest amount of timer and I/O precision available. + UserInteractive = log2(1095233437695), // Responding to user interaction. +} +ActivityOptions :: bit_set[ActivityOptionsBits; u64] + +@(objc_type=ProcessInfo, objc_name="beginActivityWithOptions") +ProcessInfo_beginActivityWithOptions :: proc "c" (self: ^ProcessInfo, options: ActivityOptions, reason: ^String) -> ^ObjectProtocol { + return msgSend(^ObjectProtocol, self, "beginActivityWithOptions:reason:", options, reason) +} + +@(objc_type=ProcessInfo, objc_name="endActivity") +ProcessInfo_endActivity :: proc "c" (self: ^ProcessInfo, activity: ^ObjectProtocol) { + msgSend(nil, self, "endActivity:", activity) +} + +@(objc_type=ProcessInfo, objc_name="performActivityWithOptions") +ProcessInfo_performActivityWithOptions :: proc "c" (self: ^ProcessInfo, options: ActivityOptions, reason: ^String, block: proc "c" ()) { + msgSend(nil, self, "performActivityWithOptions:reason:usingBlock:", options, reason, block) +} + +@(objc_type=ProcessInfo, objc_name="performExpiringActivityWithReason") +ProcessInfo_performExpiringActivityWithReason :: proc "c" (self: ^ProcessInfo, reason: ^String, block: proc "c" (expired: bool)) { + msgSend(nil, self, "performExpiringActivityWithReason:usingBlock:", reason, block) +} + +// Getting the Thermal State + +ProcessInfoThermalState :: enum c.long { + Nominal, + Fair, + Serious, + Critical, +} + +@(objc_type=ProcessInfo, objc_name="thermalState") +ProcessInfo_thermalState :: proc "c" (self: ^ProcessInfo) -> ProcessInfoThermalState { + return msgSend(ProcessInfoThermalState, self, "thermalState") +} + +// Determining Whether Low Power Mode is Enabled + +@(objc_type=ProcessInfo, objc_name="isLowPowerModeEnabled") +ProcessInfo_isLowPowerModeEnabled :: proc "c" (self: ^ProcessInfo) -> bool { + return msgSend(bool, self, "isLowPowerModeEnabled") +} diff --git a/vendor/darwin/Foundation/NSRange.odin b/core/sys/darwin/Foundation/NSRange.odin similarity index 96% rename from vendor/darwin/Foundation/NSRange.odin rename to core/sys/darwin/Foundation/NSRange.odin index b23b170ed..dcb100e91 100644 --- a/vendor/darwin/Foundation/NSRange.odin +++ b/core/sys/darwin/Foundation/NSRange.odin @@ -1,4 +1,3 @@ -//+build darwin package objc_Foundation Range :: struct { diff --git a/vendor/darwin/Foundation/NSSavePanel.odin b/core/sys/darwin/Foundation/NSSavePanel.odin similarity index 94% rename from vendor/darwin/Foundation/NSSavePanel.odin rename to core/sys/darwin/Foundation/NSSavePanel.odin index b749cde53..8e4d7a07b 100644 --- a/vendor/darwin/Foundation/NSSavePanel.odin +++ b/core/sys/darwin/Foundation/NSSavePanel.odin @@ -1,4 +1,3 @@ -//+build darwin package objc_Foundation @(objc_class="NSSavePanel") diff --git a/vendor/darwin/Foundation/NSScreen.odin b/core/sys/darwin/Foundation/NSScreen.odin similarity index 86% rename from vendor/darwin/Foundation/NSScreen.odin rename to core/sys/darwin/Foundation/NSScreen.odin index 70270f680..79ab00fbe 100644 --- a/vendor/darwin/Foundation/NSScreen.odin +++ b/core/sys/darwin/Foundation/NSScreen.odin @@ -1,4 +1,3 @@ -//+build darwin package objc_Foundation @(objc_class="NSScreen") @@ -32,3 +31,7 @@ Screen_visibleFrame :: proc "c" (self: ^Screen) -> Rect { Screen_colorSpace :: proc "c" (self: ^Screen) -> ^ColorSpace { return msgSend(^ColorSpace, self, "colorSpace") } +@(objc_type=Screen, objc_name="backingScaleFactor") +Screen_backingScaleFactor :: proc "c" (self: ^Screen) -> Float { + return msgSend(Float, self, "backingScaleFactor") +} \ No newline at end of file diff --git a/vendor/darwin/Foundation/NSSet.odin b/core/sys/darwin/Foundation/NSSet.odin similarity index 97% rename from vendor/darwin/Foundation/NSSet.odin rename to core/sys/darwin/Foundation/NSSet.odin index 173c11f04..7fb8db6c2 100644 --- a/vendor/darwin/Foundation/NSSet.odin +++ b/core/sys/darwin/Foundation/NSSet.odin @@ -1,4 +1,3 @@ -//+build darwin package objc_Foundation @(objc_class="NSSet") diff --git a/vendor/darwin/Foundation/NSString.odin b/core/sys/darwin/Foundation/NSString.odin similarity index 93% rename from vendor/darwin/Foundation/NSString.odin rename to core/sys/darwin/Foundation/NSString.odin index ba8f57129..a10b33fc0 100644 --- a/vendor/darwin/Foundation/NSString.odin +++ b/core/sys/darwin/Foundation/NSString.odin @@ -1,4 +1,3 @@ -//+build darwin package objc_Foundation foreign import "system:Foundation.framework" @@ -24,12 +23,9 @@ StringEncoding :: enum UInteger { WindowsCP1250 = 15, ISO2022JP = 21, MacOSRoman = 30, - UTF16 = Unicode, - UTF16BigEndian = 0x90000100, UTF16LittleEndian = 0x94000100, - UTF32 = 0x8c000100, UTF32BigEndian = 0x98000100, UTF32LittleEndian = 0x9c000100, @@ -50,12 +46,9 @@ StringCompareOption :: enum UInteger { unichar :: distinct u16 -@(link_prefix="NS", default_calling_convention="c") -foreign Foundation { - StringFromClass :: proc(cls: Class) -> ^String --- -} - AT :: MakeConstantString + +// CFString is 'toll-free bridged' with its Cocoa Foundation counterpart, NSString. MakeConstantString :: proc "c" (#const c: cstring) -> ^String { foreign Foundation { __CFStringMakeConstantString :: proc "c" (c: cstring) -> ^String --- @@ -63,6 +56,13 @@ MakeConstantString :: proc "c" (#const c: cstring) -> ^String { return __CFStringMakeConstantString(c) } +@(link_prefix="NS", default_calling_convention="c") +foreign Foundation { + StringFromClass :: proc(cls: Class) -> ^String --- + ClassFromString :: proc(str: ^String) -> Class --- + StringFromSelector :: proc(selector: SEL) -> ^String --- + SelectorFromString :: proc(str: ^String) -> SEL --- +} @(objc_type=String, objc_name="alloc", objc_is_class_method=true) String_alloc :: proc "c" () -> ^String { @@ -74,7 +74,6 @@ String_init :: proc "c" (self: ^String) -> ^String { return msgSend(^String, self, "init") } - @(objc_type=String, objc_name="initWithString") String_initWithString :: proc "c" (self: ^String, other: ^String) -> ^String { return msgSend(^String, self, "initWithString:", other) diff --git a/vendor/darwin/Foundation/NSTypes.odin b/core/sys/darwin/Foundation/NSTypes.odin similarity index 92% rename from vendor/darwin/Foundation/NSTypes.odin rename to core/sys/darwin/Foundation/NSTypes.odin index 78c62e6c1..822a07ab1 100644 --- a/vendor/darwin/Foundation/NSTypes.odin +++ b/core/sys/darwin/Foundation/NSTypes.odin @@ -1,7 +1,6 @@ -//+build darwin package objc_Foundation -import "core:intrinsics" +import "base:intrinsics" @(private) msgSend :: intrinsics.objc_send @@ -21,7 +20,7 @@ BOOL :: bool // TODO(bill): should this be `distinct`? YES :: true NO :: false -OperatingSystemVersion :: struct #packed { +OperatingSystemVersion :: struct #align(8) { majorVersion: Integer, minorVersion: Integer, patchVersion: Integer, @@ -59,4 +58,4 @@ when size_of(Float) == 8 { } else { _POINT_ENCODING :: "{NSPoint=ff}" _SIZE_ENCODING :: "{NSSize=ff}" -} \ No newline at end of file +} diff --git a/vendor/darwin/Foundation/NSURL.odin b/core/sys/darwin/Foundation/NSURL.odin similarity index 98% rename from vendor/darwin/Foundation/NSURL.odin rename to core/sys/darwin/Foundation/NSURL.odin index ab8454f56..9e9081219 100644 --- a/vendor/darwin/Foundation/NSURL.odin +++ b/core/sys/darwin/Foundation/NSURL.odin @@ -1,4 +1,3 @@ -//+build darwin package objc_Foundation @(objc_class="NSURL") diff --git a/vendor/darwin/Foundation/NSUndoManager.odin b/core/sys/darwin/Foundation/NSUndoManager.odin similarity index 88% rename from vendor/darwin/Foundation/NSUndoManager.odin rename to core/sys/darwin/Foundation/NSUndoManager.odin index 4a19ac254..16411dcb4 100644 --- a/vendor/darwin/Foundation/NSUndoManager.odin +++ b/core/sys/darwin/Foundation/NSUndoManager.odin @@ -1,4 +1,3 @@ -//+build darwin package objc_Foundation @(objc_class="NSUndoManager") diff --git a/vendor/darwin/Foundation/NSUserActivity.odin b/core/sys/darwin/Foundation/NSUserActivity.odin similarity index 89% rename from vendor/darwin/Foundation/NSUserActivity.odin rename to core/sys/darwin/Foundation/NSUserActivity.odin index 2540d0deb..3b2f956ee 100644 --- a/vendor/darwin/Foundation/NSUserActivity.odin +++ b/core/sys/darwin/Foundation/NSUserActivity.odin @@ -1,4 +1,3 @@ -//+build darwin package objc_Foundation @(objc_class="NSUserActivity") diff --git a/vendor/darwin/Foundation/NSUserDefaults.odin b/core/sys/darwin/Foundation/NSUserDefaults.odin similarity index 97% rename from vendor/darwin/Foundation/NSUserDefaults.odin rename to core/sys/darwin/Foundation/NSUserDefaults.odin index df7bb487a..a8a6d7545 100644 --- a/vendor/darwin/Foundation/NSUserDefaults.odin +++ b/core/sys/darwin/Foundation/NSUserDefaults.odin @@ -1,4 +1,3 @@ -//+build darwin package objc_Foundation @(objc_class="NSUserDefaults") diff --git a/vendor/darwin/Foundation/NSWindow.odin b/core/sys/darwin/Foundation/NSWindow.odin similarity index 94% rename from vendor/darwin/Foundation/NSWindow.odin rename to core/sys/darwin/Foundation/NSWindow.odin index 48c972d8e..0fe334207 100644 --- a/vendor/darwin/Foundation/NSWindow.odin +++ b/core/sys/darwin/Foundation/NSWindow.odin @@ -1,9 +1,8 @@ -//+build darwin package objc_Foundation import "core:strings" -import "core:runtime" -import "core:intrinsics" +import "base:runtime" +import "base:intrinsics" Rect :: struct { using origin: Point, @@ -628,18 +627,7 @@ Window_alloc :: proc "c" () -> ^Window { @(objc_type=Window, objc_name="initWithContentRect") Window_initWithContentRect :: proc (self: ^Window, contentRect: Rect, styleMask: WindowStyleMask, backing: BackingStoreType, doDefer: BOOL) -> ^Window { - self := self - // HACK: due to a compiler bug, the generated calling code does not - // currently work for this message. Has to do with passing a struct along - // with other parameters, so we don't send the rect here. - // Omiting the rect argument here actually works, because of how the C - // calling conventions are defined. - self = msgSend(^Window, self, "initWithContentRect:styleMask:backing:defer:", styleMask, backing, doDefer) - - // apply the contentRect now, since we did not pass it to the init call - msgSend(nil, self, "setContentSize:", contentRect.size) - msgSend(nil, self, "setFrameOrigin:", contentRect.origin) - return self + return msgSend(^Window, self, "initWithContentRect:styleMask:backing:defer:", contentRect, styleMask, backing, doDefer) } @(objc_type=Window, objc_name="contentView") Window_contentView :: proc "c" (self: ^Window) -> ^View { @@ -713,3 +701,51 @@ 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) +} +@(objc_type=Window, objc_name="setIsMiniaturized") +Window_setIsMiniaturized :: proc "c" (self: ^Window, ok: BOOL) { + msgSend(nil, self, "setIsMiniaturized:", ok) +} +@(objc_type=Window, objc_name="setIsVisible") +Window_setIsVisible :: proc "c" (self: ^Window, ok: BOOL) { + msgSend(nil, self, "setIsVisible:", ok) +} +@(objc_type=Window, objc_name="setIsZoomed") +Window_setIsZoomed :: proc "c" (self: ^Window, ok: BOOL) { + msgSend(nil, self, "setIsZoomed:", ok) +} +@(objc_type=Window, objc_name="isZoomable") +Window_isZoomable :: proc "c" (self: ^Window) -> BOOL { + return msgSend(BOOL, self, "isZoomable") +} +@(objc_type=Window, objc_name="isResizable") +Window_isResizable :: proc "c" (self: ^Window) -> BOOL { + return msgSend(BOOL, self, "isResizable") +} +@(objc_type=Window, objc_name="isModalPanel") +Window_isModalPanel :: proc "c" (self: ^Window) -> BOOL { + return msgSend(BOOL, self, "isModalPanel") +} +@(objc_type=Window, objc_name="isMiniaturizable") +Window_isMiniaturizable :: proc "c" (self: ^Window) -> BOOL { + return msgSend(BOOL, self, "isMiniaturizable") +} +@(objc_type=Window, objc_name="isFloatingPanel") +Window_isFloatingPanel :: proc "c" (self: ^Window) -> BOOL { + return msgSend(BOOL, self, "isFloatingPanel") +} +@(objc_type=Window, objc_name="hasCloseBox") +Window_hasCloseBox :: proc "c" (self: ^Window) -> BOOL { + return msgSend(BOOL, self, "hasCloseBox") +} +@(objc_type=Window, objc_name="hasTitleBar") +Window_hasTitleBar :: proc "c" (self: ^Window) -> BOOL { + return msgSend(BOOL, self, "hasTitleBar") +} +@(objc_type=Window, objc_name="orderedIndex") +Window_orderedIndex :: proc "c" (self: ^Window) -> Integer { + return msgSend(Integer, self, "orderedIndex") +} \ No newline at end of file diff --git a/core/sys/darwin/Foundation/objc.odin b/core/sys/darwin/Foundation/objc.odin new file mode 100644 index 000000000..82d6199ce --- /dev/null +++ b/core/sys/darwin/Foundation/objc.odin @@ -0,0 +1,166 @@ +package objc_Foundation + +foreign import "system:Foundation.framework" +// NOTE: Most of our bindings are reliant on Cocoa (everything under appkit) so just unconditionally import it +@(require) foreign import "system:Cocoa.framework" + +import "base:intrinsics" +import "core:c" + +IMP :: proc "c" (object: id, sel: SEL, #c_vararg args: ..any) -> id + +@(default_calling_convention="c") +foreign Foundation { + objc_getMetaClass :: proc(name: cstring) -> id --- + objc_lookUpClass :: proc(name: cstring) -> Class --- + objc_allocateClassPair :: proc(superclass: Class, name: cstring, extraBytes: c.size_t) -> Class --- + objc_registerClassPair :: proc(cls: Class) --- + objc_disposeClassPair :: proc(cls: Class) --- + objc_duplicateClass :: proc(original: Class, name: cstring, extraBytes: c.size_t) -> Class --- + objc_getProtocol :: proc(name: cstring) -> ^Protocol --- + objc_copyProtocolList :: proc(outCount: ^uint) -> [^]^Protocol --- + objc_constructInstance :: proc(cls: Class, bytes: rawptr) -> id --- + objc_destructInstance :: proc(obj: id) -> rawptr --- + objc_getClassList :: proc(buffer: [^]Class, bufferCount: int) -> int --- + objc_copyClassList :: proc(outCount: ^uint) -> [^]Class --- + objc_getRequiredClass :: proc(name: cstring) -> Class --- + objc_setAssociatedObject :: proc(object: id, key: rawptr, value: id, policy: objc_AssociationPolicy) --- + objc_getAssociatedObject :: proc(object: id, key: rawptr) -> id --- + objc_removeAssociatedObjects :: proc(object: id) --- + + sel_registerName :: proc(name: cstring) -> SEL --- + sel_getName :: proc(sel: SEL) -> cstring --- + sel_isEqual :: proc(lhs, rhs: SEL) -> BOOL --- + + class_addMethod :: proc(cls: Class, name: SEL, imp: IMP, types: cstring) -> BOOL --- + class_getInstanceMethod :: proc(cls: Class, name: SEL) -> Method --- + class_getClassMethod :: proc(cls: Class, name: SEL) -> Method --- + class_copyMethodList :: proc(cls: Class, outCount: ^uint) -> [^]Method --- + class_createInstance :: proc(cls: Class, extraBytes: c.size_t) -> id --- + class_replaceMethod :: proc(cls: Class, name: SEL, imp: IMP, types: cstring) -> IMP --- + class_getMethodImplementation :: proc(cls: Class, name: SEL) -> IMP --- + class_getSuperclass :: proc(cls: Class) -> Class --- + class_getName :: proc(cls: Class) -> cstring --- + class_isMetaClass :: proc(cls: Class) -> BOOL --- + class_addProtocol :: proc(cls: Class, protocol: ^Protocol) -> BOOL --- + class_getVersion :: proc(cls: Class) -> c.int --- + class_setVersion :: proc(cls: Class, version: c.int) --- + class_getProperty :: proc(cls: Class, name: cstring) -> objc_property_t --- + class_addProperty :: proc(cls: Class, name: cstring, attributes: [^]objc_property_attribute_t, attributeCount: uint) -> BOOL --- + class_replaceProperty :: proc(cls: Class, name: cstring, attributes: [^]objc_property_attribute_t, attributeCount: uint) --- + class_copyPropertyList :: proc(cls: Class, outCount: ^uint) -> [^]objc_property_t --- + class_conformsToProtocol :: proc(cls: Class, protocol: ^Protocol) -> BOOL --- + class_copyProtocolList :: proc(cls: Class, outCount: ^uint) -> [^]^Protocol --- + class_respondsToSelector :: proc(cls: Class, sel: SEL) -> BOOL --- + class_getClassVariable :: proc(cls: Class, name: cstring) -> Ivar --- + class_getInstanceVariable :: proc(cls: Class, name: cstring) -> Ivar --- + class_addIvar :: proc(cls: Class, name: cstring, size: c.size_t, alignment: u8, types: cstring) -> BOOL --- + class_copyIvarList :: proc(cls: Class, outCount: ^uint) -> [^]Ivar --- + class_getInstanceSize :: proc(cls: Class) -> c.size_t --- + + property_getName :: proc(property: objc_property_t) -> cstring --- + property_getAttributes :: proc(property: objc_property_t) -> cstring --- + property_copyAttributeList :: proc(property: objc_property_t, outCount: ^uint) -> [^]objc_property_attribute_t --- + property_copyAttributeValue :: proc(property: objc_property_t, attributeName: cstring) -> cstring --- + + protocol_conformsToProtocol :: proc(proto: ^Protocol, other: ^Protocol) -> BOOL --- + protocol_isEqual :: proc(proto: ^Protocol, other: ^Protocol) -> BOOL --- + protocol_getName :: proc(proto: ^Protocol) -> cstring --- + + method_getImplementation :: proc(m: Method) -> IMP --- + method_setImplementation :: proc(m: Method, imp: IMP) --- + method_copyArgumentType :: proc(m: Method, index: uint) -> cstring --- + method_getReturnType :: proc(m: Method, dst: cstring, dst_len: c.size_t) --- + method_getNumberOfArguments :: proc(m: Method) -> uint --- + method_getArgumentType :: proc(m: Method, index: uint, dst: cstring, dst_len: c.size_t) --- + + object_getClass :: proc(obj: id) -> Class --- + object_setClass :: proc(obj: id, cls: Class) -> Class --- + object_copy :: proc(obj: id, size: c.size_t) -> id --- + object_dispose :: proc(obj: id) -> id --- + object_getClassName :: proc(obj: id) -> cstring --- + object_getIndexedIvars :: proc(obj: id) -> rawptr --- + object_getInstanceVariable :: proc(obj: id, name: cstring, outValue: rawptr) -> Ivar --- + object_setInstanceVariable :: proc(obj: id, name: cstring, value: rawptr) -> Ivar --- + object_getIvar :: proc(obj: id, ivar: Ivar) -> id --- + object_setIvar :: proc(obj: id, ivar: Ivar, value: id) --- + + ivar_getName :: proc(v: Ivar) -> cstring --- + ivar_getTypeEncoding :: proc(v: Ivar) -> cstring --- + ivar_getOffset :: proc(v: Ivar) -> c.ptrdiff_t --- +} + + +@(objc_class="NSZone") +Zone :: struct {using _: Object} + +@(link_prefix="NS") +foreign Foundation { + AllocateObject :: proc "c" (aClass: Class, extraBytes: UInteger, zone: ^Zone) -> id --- + DeallocateObject :: proc "c" (object: id) --- +} + +Method :: ^objc_method +objc_method :: struct { + method_name: SEL, + method_types: cstring, + method_imp: IMP, +} +objc_method_list :: struct {} + +objc_property :: struct{} +objc_property_t :: ^objc_property + +objc_property_attribute_t :: struct { + name: cstring, + value: cstring, +} + +objc_ivar :: struct {} +Ivar :: ^objc_ivar + +objc_ivar_list :: struct {} + +objc_cache :: struct { + mask: u32, + occupied: u32, + buckets: [1]Method, +} + +objc_protocol_list :: struct { + next: ^objc_protocol_list, + count: c.int, + list: [1]^Protocol, +} + +@(objc_class="Protocol") +Protocol :: struct{using _: intrinsics.objc_object} + +objc_object_internals :: struct { + isa: ^objc_class_internals, +} + + +objc_class_internals :: struct { + isa: Class, + super_class: Class, + name: cstring, + version: c.long, + info: c.long, + instance_size: c.long, + ivars: ^objc_ivar_list, + + methodLists: ^^objc_method_list, + + cache: rawptr, + protocols: rawptr, + +} + +objc_AssociationPolicy :: enum c.uintptr_t { + Assign = 0, + Retain_Nonatomic = 1, + Copy_Nonatomic = 3, + Retain = 01401, + Copy = 01403, +} diff --git a/core/sys/darwin/Security/SecBase.odin b/core/sys/darwin/Security/SecBase.odin new file mode 100644 index 000000000..9cc82d6f5 --- /dev/null +++ b/core/sys/darwin/Security/SecBase.odin @@ -0,0 +1,386 @@ +package Security + +OSStatus :: distinct i32 + +errSec :: enum OSStatus { + Success = 0, // No error. + Unimplemented = -4, // Function or operation not implemented. + DiskFull = -34, // The disk is full. + IO = -36, // I/O error. + OpWr = -49, // File already open with with write permission. + Param = -50, // One or more parameters passed to a function were not valid. + WrPerm = -61, // Write permissions error. + Allocate = -108, // Failed to allocate memory. + UserCanceled = -128, // User canceled the operation. + BadReq = -909, // Bad parameter or invalid state for operation. + InternalComponent = -2070, + CoreFoundationUnknown = -4960, + MissingEntitlement, // A required entitlement isn't present. + RestrictedAPI, // Client is restricted and is not permitted to perform this operation. + NotAvailable = -25291, // No keychain is available. You may need to restart your computer. + ReadOnly = -25292, // This keychain cannot be modified. + AuthFailed = -25293, // The user name or passphrase you entered is not correct. + NoSuchKeychain = -25294, // The specified keychain could not be found. + InvalidKeychain = -25295, // The specified keychain is not a valid keychain file. + DuplicateKeychain = -25296, // A keychain with the same name already exists. + DuplicateCallback = -25297, // The specified callback function is already installed. + InvalidCallback = -25298, // The specified callback function is not valid. + DuplicateItem = -25299, // The specified item already exists in the keychain. + ItemNotFound = -25300, // The specified item could not be found in the keychain. + BufferTooSmall = -25301, // There is not enough memory available to use the specified item. + DataTooLarge = -25302, // This item contains information which is too large or in a format that cannot be displayed. + NoSuchAttr = -25303, // The specified attribute does not exist. + InvalidItemRef = -25304, // The specified item is no longer valid. It may have been deleted from the keychain. + InvalidSearchRef = -25305, // Unable to search the current keychain. + NoSuchClass = -25306, // The specified item does not appear to be a valid keychain item. + NoDefaultKeychain = -25307, // A default keychain could not be found. + InteractionNotAllowed = -25308, // User interaction is not allowed. + ReadOnlyAttr = -25309, // The specified attribute could not be modified. + WrongSecVersion = -25310, // This keychain was created by a different version of the system software and cannot be opened. + KeySizeNotAllowed = -25311, // This item specifies a key size which is too large or too small. + NoStorageModule = -25312, // A required component (data storage module) could not be loaded. You may need to restart your computer. + NoCertificateModule = -25313, // A required component (certificate module) could not be loaded. You may need to restart your computer. + NoPolicyModule = -25314, // A required component (policy module) could not be loaded. You may need to restart your computer. + InteractionRequired = -25315, // User interaction is required, but is currently not allowed. + DataNotAvailable = -25316, // The contents of this item cannot be retrieved. + DataNotModifiable = -25317, // The contents of this item cannot be modified. + CreateChainFailed = -25318, // One or more certificates required to validate this certificate cannot be found. + InvalidPrefsDomain = -25319, // The specified preferences domain is not valid. + InDarkWake = -25320, // In dark wake, no UI possible + ACLNotSimple = -25240, // The specified access control list is not in standard (simple) form. + PolicyNotFound = -25241, // The specified policy cannot be found. + InvalidTrustSetting = -25242, // The specified trust setting is invalid. + NoAccessForItem = -25243, // The specified item has no access control. + InvalidOwnerEdit = -25244, // Invalid attempt to change the owner of this item. + TrustNotAvailable = -25245, // No trust results are available. + UnsupportedFormat = -25256, // Import/Export format unsupported. + UnknownFormat = -25257, // Unknown format in import. + KeyIsSensitive = -25258, // Key material must be wrapped for export. + MultiplePrivKeys = -25259, // An attempt was made to import multiple private keys. + PassphraseRequired = -25260, // Passphrase is required for import/export. + InvalidPasswordRef = -25261, // The password reference was invalid. + InvalidTrustSettings = -25262, // The Trust Settings Record was corrupted. + NoTrustSettings = -25263, // No Trust Settings were found. + Pkcs12VerifyFailure = -25264, // MAC verification failed during PKCS12 import (wrong password?) + NotSigner = -26267, // A certificate was not signed by its proposed parent. + Decode = -26275, // Unable to decode the provided data. + ServiceNotAvailable = -67585, // The required service is not available. + InsufficientClientID = -67586, // The client ID is not correct. + DeviceReset = -67587, // A device reset has occurred. + DeviceFailed = -67588, // A device failure has occurred. + AppleAddAppACLSubject = -67589, // Adding an application ACL subject failed. + ApplePublicKeyIncomplete = -67590, // The public key is incomplete. + AppleSignatureMismatch = -67591, // A signature mismatch has occurred. + AppleInvalidKeyStartDate = -67592, // The specified key has an invalid start date. + AppleInvalidKeyEndDate = -67593, // The specified key has an invalid end date. + ConversionError = -67594, // A conversion error has occurred. + AppleSSLv2Rollback = -67595, // A SSLv2 rollback error has occurred. + QuotaExceeded = -67596, // The quota was exceeded. + FileTooBig = -67597, // The file is too big. + InvalidDatabaseBlob = -67598, // The specified database has an invalid blob. + InvalidKeyBlob = -67599, // The specified database has an invalid key blob. + IncompatibleDatabaseBlob = -67600, // The specified database has an incompatible blob. + IncompatibleKeyBlob = -67601, // The specified database has an incompatible key blob. + HostNameMismatch = -67602, // A host name mismatch has occurred. + UnknownCriticalExtensionFlag = -67603, // There is an unknown critical extension flag. + NoBasicConstraints = -67604, // No basic constraints were found. + NoBasicConstraintsCA = -67605, // No basic CA constraints were found. + InvalidAuthorityKeyID = -67606, // The authority key ID is not valid. + InvalidSubjectKeyID = -67607, // The subject key ID is not valid. + InvalidKeyUsageForPolicy = -67608, // The key usage is not valid for the specified policy. + InvalidExtendedKeyUsage = -67609, // The extended key usage is not valid. + InvalidIDLinkage = -67610, // The ID linkage is not valid. + PathLengthConstraintExceeded = -67611, // The path length constraint was exceeded. + InvalidRoot = -67612, // The root or anchor certificate is not valid. + CRLExpired = -67613, // The CRL has expired. + CRLNotValidYet = -67614, // The CRL is not yet valid. + CRLNotFound = -67615, // The CRL was not found. + CRLServerDown = -67616, // The CRL server is down. + CRLBadURI = -67617, // The CRL has a bad Uniform Resource Identifier. + UnknownCertExtension = -67618, // An unknown certificate extension was encountered. + UnknownCRLExtension = -67619, // An unknown CRL extension was encountered. + CRLNotTrusted = -67620, // The CRL is not trusted. + CRLPolicyFailed = -67621, // The CRL policy failed. + IDPFailure = -67622, // The issuing distribution point was not valid. + SMIMEEmailAddressesNotFound = -67623, // An email address mismatch was encountered. + SMIMEBadExtendedKeyUsage = -67624, // The appropriate extended key usage for SMIME was not found. + SMIMEBadKeyUsage = -67625, // The key usage is not compatible with SMIME. + SMIMEKeyUsageNotCritical = -67626, // The key usage extension is not marked as critical. + SMIMENoEmailAddress = -67627, // No email address was found in the certificate. + SMIMESubjAltNameNotCritical = -67628, // The subject alternative name extension is not marked as critical. + SSLBadExtendedKeyUsage = -67629, // The appropriate extended key usage for SSL was not found. + OCSPBadResponse = -67630, // The OCSP response was incorrect or could not be parsed. + OCSPBadRequest = -67631, // The OCSP request was incorrect or could not be parsed. + OCSPUnavailable = -67632, // OCSP service is unavailable. + OCSPStatusUnrecognized = -67633, // The OCSP server did not recognize this certificate. + EndOfData = -67634, // An end-of-data was detected. + IncompleteCertRevocationCheck = -67635, // An incomplete certificate revocation check occurred. + NetworkFailure = -67636, // A network failure occurred. + OCSPNotTrustedToAnchor = -67637, // The OCSP response was not trusted to a root or anchor certificate. + RecordModified = -67638, // The record was modified. + OCSPSignatureError = -67639, // The OCSP response had an invalid signature. + OCSPNoSigner = -67640, // The OCSP response had no signer. + OCSPResponderMalformedReq = -67641, // The OCSP responder was given a malformed request. + OCSPResponderInternalError = -67642, // The OCSP responder encountered an internal error. + OCSPResponderTryLater = -67643, // The OCSP responder is busy, try again later. + OCSPResponderSignatureRequired = -67644, // The OCSP responder requires a signature. + OCSPResponderUnauthorized = -67645, // The OCSP responder rejected this request as unauthorized. + OCSPResponseNonceMismatch = -67646, // The OCSP response nonce did not match the request. + CodeSigningBadCertChainLength = -67647, // Code signing encountered an incorrect certificate chain length. + CodeSigningNoBasicConstraints = -67648, // Code signing found no basic constraints. + CodeSigningBadPathLengthConstraint = -67649, // Code signing encountered an incorrect path length constraint. + CodeSigningNoExtendedKeyUsage = -67650, // Code signing found no extended key usage. + CodeSigningDevelopment = -67651, // Code signing indicated use of a development-only certificate. + ResourceSignBadCertChainLength = -67652, // Resource signing has encountered an incorrect certificate chain length. + ResourceSignBadExtKeyUsage = -67653, // Resource signing has encountered an error in the extended key usage. + TrustSettingDeny = -67654, // The trust setting for this policy was set to Deny. + InvalidSubjectName = -67655, // An invalid certificate subject name was encountered. + UnknownQualifiedCertStatement = -67656, // An unknown qualified certificate statement was encountered. + MobileMeRequestQueued = -67657, + MobileMeRequestRedirected = -67658, + MobileMeServerError = -67659, + MobileMeServerNotAvailable = -67660, + MobileMeServerAlreadyExists = -67661, + MobileMeServerServiceErr = -67662, + MobileMeRequestAlreadyPending = -67663, + MobileMeNoRequestPending = -67664, + MobileMeCSRVerifyFailure = -67665, + MobileMeFailedConsistencyCheck = -67666, + NotInitialized = -67667, // A function was called without initializing CSSM. + InvalidHandleUsage = -67668, // The CSSM handle does not match with the service type. + PVCReferentNotFound = -67669, // A reference to the calling module was not found in the list of authorized callers. + FunctionIntegrityFail = -67670, // A function address was not within the verified module. + InternalError = -67671, // An internal error has occurred. + MemoryError = -67672, // A memory error has occurred. + InvalidData = -67673, // Invalid data was encountered. + MDSError = -67674, // A Module Directory Service error has occurred. + InvalidPointer = -67675, // An invalid pointer was encountered. + SelfCheckFailed = -67676, // Self-check has failed. + FunctionFailed = -67677, // A function has failed. + ModuleManifestVerifyFailed = -67678, // A module manifest verification failure has occurred. + InvalidGUID = -67679, // An invalid GUID was encountered. + InvalidHandle = -67680, // An invalid handle was encountered. + InvalidDBList = -67681, // An invalid DB list was encountered. + InvalidPassthroughID = -67682, // An invalid passthrough ID was encountered. + InvalidNetworkAddress = -67683, // An invalid network address was encountered. + CRLAlreadySigned = -67684, // The certificate revocation list is already signed. + InvalidNumberOfFields = -67685, // An invalid number of fields were encountered. + VerificationFailure = -67686, // A verification failure occurred. + UnknownTag = -67687, // An unknown tag was encountered. + InvalidSignature = -67688, // An invalid signature was encountered. + InvalidName = -67689, // An invalid name was encountered. + InvalidCertificateRef = -67690, // An invalid certificate reference was encountered. + InvalidCertificateGroup = -67691, // An invalid certificate group was encountered. + TagNotFound = -67692, // The specified tag was not found. + InvalidQuery = -67693, // The specified query was not valid. + InvalidValue = -67694, // An invalid value was detected. + CallbackFailed = -67695, // A callback has failed. + ACLDeleteFailed = -67696, // An ACL delete operation has failed. + ACLReplaceFailed = -67697, // An ACL replace operation has failed. + ACLAddFailed = -67698, // An ACL add operation has failed. + ACLChangeFailed = -67699, // An ACL change operation has failed. + InvalidAccessCredentials = -67700, // Invalid access credentials were encountered. + InvalidRecord = -67701, // An invalid record was encountered. + InvalidACL = -67702, // An invalid ACL was encountered. + InvalidSampleValue = -67703, // An invalid sample value was encountered. + IncompatibleVersion = -67704, // An incompatible version was encountered. + PrivilegeNotGranted = -67705, // The privilege was not granted. + InvalidScope = -67706, // An invalid scope was encountered. + PVCAlreadyConfigured = -67707, // The PVC is already configured. + InvalidPVC = -67708, // An invalid PVC was encountered. + EMMLoadFailed = -67709, // The EMM load has failed. + EMMUnloadFailed = -67710, // The EMM unload has failed. + AddinLoadFailed = -67711, // The add-in load operation has failed. + InvalidKeyRef = -67712, // An invalid key was encountered. + InvalidKeyHierarchy = -67713, // An invalid key hierarchy was encountered. + AddinUnloadFailed = -67714, // The add-in unload operation has failed. + LibraryReferenceNotFound = -67715, // A library reference was not found. + InvalidAddinFunctionTable = -67716, // An invalid add-in function table was encountered. + InvalidServiceMask = -67717, // An invalid service mask was encountered. + ModuleNotLoaded = -67718, // A module was not loaded. + InvalidSubServiceID = -67719, // An invalid subservice ID was encountered. + AttributeNotInContext = -67720, // An attribute was not in the context. + ModuleManagerInitializeFailed = -67721, // A module failed to initialize. + ModuleManagerNotFound = -67722, // A module was not found. + EventNotificationCallbackNotFound = -67723, // An event notification callback was not found. + InputLengthError = -67724, // An input length error was encountered. + OutputLengthError = -67725, // An output length error was encountered. + PrivilegeNotSupported = -67726, // The privilege is not supported. + DeviceError = -67727, // A device error was encountered. + AttachHandleBusy = -67728, // The CSP handle was busy. + NotLoggedIn = -67729, // You are not logged in. + AlgorithmMismatch = -67730, // An algorithm mismatch was encountered. + KeyUsageIncorrect = -67731, // The key usage is incorrect. + KeyBlobTypeIncorrect = -67732, // The key blob type is incorrect. + KeyHeaderInconsistent = -67733, // The key header is inconsistent. + UnsupportedKeyFormat = -67734, // The key header format is not supported. + UnsupportedKeySize = -67735, // The key size is not supported. + InvalidKeyUsageMask = -67736, // The key usage mask is not valid. + UnsupportedKeyUsageMask = -67737, // The key usage mask is not supported. + InvalidKeyAttributeMask = -67738, // The key attribute mask is not valid. + UnsupportedKeyAttributeMask = -67739, // The key attribute mask is not supported. + InvalidKeyLabel = -67740, // The key label is not valid. + UnsupportedKeyLabel = -67741, // The key label is not supported. + InvalidKeyFormat = -67742, // The key format is not valid. + UnsupportedVectorOfBuffers = -67743, // The vector of buffers is not supported. + InvalidInputVector = -67744, // The input vector is not valid. + InvalidOutputVector = -67745, // The output vector is not valid. + InvalidContext = -67746, // An invalid context was encountered. + InvalidAlgorithm = -67747, // An invalid algorithm was encountered. + InvalidAttributeKey = -67748, // A key attribute was not valid. + MissingAttributeKey = -67749, // A key attribute was missing. + InvalidAttributeInitVector = -67750, // An init vector attribute was not valid. + MissingAttributeInitVector = -67751, // An init vector attribute was missing. + InvalidAttributeSalt = -67752, // A salt attribute was not valid. + MissingAttributeSalt = -67753, // A salt attribute was missing. + InvalidAttributePadding = -67754, // A padding attribute was not valid. + MissingAttributePadding = -67755, // A padding attribute was missing. + InvalidAttributeRandom = -67756, // A random number attribute was not valid. + MissingAttributeRandom = -67757, // A random number attribute was missing. + InvalidAttributeSeed = -67758, // A seed attribute was not valid. + MissingAttributeSeed = -67759, // A seed attribute was missing. + InvalidAttributePassphrase = -67760, // A passphrase attribute was not valid. + MissingAttributePassphrase = -67761, // A passphrase attribute was missing. + InvalidAttributeKeyLength = -67762, // A key length attribute was not valid. + MissingAttributeKeyLength = -67763, // A key length attribute was missing. + InvalidAttributeBlockSize = -67764, // A block size attribute was not valid. + MissingAttributeBlockSize = -67765, // A block size attribute was missing. + InvalidAttributeOutputSize = -67766, // An output size attribute was not valid. + MissingAttributeOutputSize = -67767, // An output size attribute was missing. + InvalidAttributeRounds = -67768, // The number of rounds attribute was not valid. + MissingAttributeRounds = -67769, // The number of rounds attribute was missing. + InvalidAlgorithmParms = -67770, // An algorithm parameters attribute was not valid. + MissingAlgorithmParms = -67771, // An algorithm parameters attribute was missing. + InvalidAttributeLabel = -67772, // A label attribute was not valid. + MissingAttributeLabel = -67773, // A label attribute was missing. + InvalidAttributeKeyType = -67774, // A key type attribute was not valid. + MissingAttributeKeyType = -67775, // A key type attribute was missing. + InvalidAttributeMode = -67776, // A mode attribute was not valid. + MissingAttributeMode = -67777, // A mode attribute was missing. + InvalidAttributeEffectiveBits = -67778, // An effective bits attribute was not valid. + MissingAttributeEffectiveBits = -67779, // An effective bits attribute was missing. + InvalidAttributeStartDate = -67780, // A start date attribute was not valid. + MissingAttributeStartDate = -67781, // A start date attribute was missing. + InvalidAttributeEndDate = -67782, // An end date attribute was not valid. + MissingAttributeEndDate = -67783, // An end date attribute was missing. + InvalidAttributeVersion = -67784, // A version attribute was not valid. + MissingAttributeVersion = -67785, // A version attribute was missing. + InvalidAttributePrime = -67786, // A prime attribute was not valid. + MissingAttributePrime = -67787, // A prime attribute was missing. + InvalidAttributeBase = -67788, // A base attribute was not valid. + MissingAttributeBase = -67789, // A base attribute was missing. + InvalidAttributeSubprime = -67790, // A subprime attribute was not valid. + MissingAttributeSubprime = -67791, // A subprime attribute was missing. + InvalidAttributeIterationCount = -67792, // An iteration count attribute was not valid. + MissingAttributeIterationCount = -67793, // An iteration count attribute was missing. + InvalidAttributeDLDBHandle = -67794, // A database handle attribute was not valid. + MissingAttributeDLDBHandle = -67795, // A database handle attribute was missing. + InvalidAttributeAccessCredentials = -67796, // An access credentials attribute was not valid. + MissingAttributeAccessCredentials = -67797, // An access credentials attribute was missing. + InvalidAttributePublicKeyFormat = -67798, // A public key format attribute was not valid. + MissingAttributePublicKeyFormat = -67799, // A public key format attribute was missing. + InvalidAttributePrivateKeyFormat = -67800, // A private key format attribute was not valid. + MissingAttributePrivateKeyFormat = -67801, // A private key format attribute was missing. + InvalidAttributeSymmetricKeyFormat = -67802, // A symmetric key format attribute was not valid. + MissingAttributeSymmetricKeyFormat = -67803, // A symmetric key format attribute was missing. + InvalidAttributeWrappedKeyFormat = -67804, // A wrapped key format attribute was not valid. + MissingAttributeWrappedKeyFormat = -67805, // A wrapped key format attribute was missing. + StagedOperationInProgress = -67806, // A staged operation is in progress. + StagedOperationNotStarted = -67807, // A staged operation was not started. + VerifyFailed = -67808, // A cryptographic verification failure has occurred. + QuerySizeUnknown = -67809, // The query size is unknown. + BlockSizeMismatch = -67810, // A block size mismatch occurred. + PublicKeyInconsistent = -67811, // The public key was inconsistent. + DeviceVerifyFailed = -67812, // A device verification failure has occurred. + InvalidLoginName = -67813, // An invalid login name was detected. + AlreadyLoggedIn = -67814, // The user is already logged in. + InvalidDigestAlgorithm = -67815, // An invalid digest algorithm was detected. + InvalidCRLGroup = -67816, // An invalid CRL group was detected. + CertificateCannotOperate = -67817, // The certificate cannot operate. + CertificateExpired = -67818, // An expired certificate was detected. + CertificateNotValidYet = -67819, // The certificate is not yet valid. + CertificateRevoked = -67820, // The certificate was revoked. + CertificateSuspended = -67821, // The certificate was suspended. + InsufficientCredentials = -67822, // Insufficient credentials were detected. + InvalidAction = -67823, // The action was not valid. + InvalidAuthority = -67824, // The authority was not valid. + VerifyActionFailed = -67825, // A verify action has failed. + InvalidCertAuthority = -67826, // The certificate authority was not valid. + InvalidCRLAuthority = -67827, // The CRL authority was not valid. + InvalidCRLEncoding = -67828, // The CRL encoding was not valid. + InvalidCRLType = -67829, // The CRL type was not valid. + InvalidCRL = -67830, // The CRL was not valid. + InvalidFormType = -67831, // The form type was not valid. + InvalidID = -67832, // The ID was not valid. + InvalidIdentifier = -67833, // The identifier was not valid. + InvalidIndex = -67834, // The index was not valid. + InvalidPolicyIdentifiers = -67835, // The policy identifiers are not valid. + InvalidTimeString = -67836, // The time specified was not valid. + InvalidReason = -67837, // The trust policy reason was not valid. + InvalidRequestInputs = -67838, // The request inputs are not valid. + InvalidResponseVector = -67839, // The response vector was not valid. + InvalidStopOnPolicy = -67840, // The stop-on policy was not valid. + InvalidTuple = -67841, // The tuple was not valid. + MultipleValuesUnsupported = -67842, // Multiple values are not supported. + NotTrusted = -67843, // The certificate was not trusted. + NoDefaultAuthority = -67844, // No default authority was detected. + RejectedForm = -67845, // The trust policy had a rejected form. + RequestLost = -67846, // The request was lost. + RequestRejected = -67847, // The request was rejected. + UnsupportedAddressType = -67848, // The address type is not supported. + UnsupportedService = -67849, // The service is not supported. + InvalidTupleGroup = -67850, // The tuple group was not valid. + InvalidBaseACLs = -67851, // The base ACLs are not valid. + InvalidTupleCredentials = -67852, // The tuple credentials are not valid. + InvalidEncoding = -67853, // The encoding was not valid. + InvalidValidityPeriod = -67854, // The validity period was not valid. + InvalidRequestor = -67855, // The requestor was not valid. + RequestDescriptor = -67856, // The request descriptor was not valid. + InvalidBundleInfo = -67857, // The bundle information was not valid. + InvalidCRLIndex = -67858, // The CRL index was not valid. + NoFieldValues = -67859, // No field values were detected. + UnsupportedFieldFormat = -67860, // The field format is not supported. + UnsupportedIndexInfo = -67861, // The index information is not supported. + UnsupportedLocality = -67862, // The locality is not supported. + UnsupportedNumAttributes = -67863, // The number of attributes is not supported. + UnsupportedNumIndexes = -67864, // The number of indexes is not supported. + UnsupportedNumRecordTypes = -67865, // The number of record types is not supported. + FieldSpecifiedMultiple = -67866, // Too many fields were specified. + IncompatibleFieldFormat = -67867, // The field format was incompatible. + InvalidParsingModule = -67868, // The parsing module was not valid. + DatabaseLocked = -67869, // The database is locked. + DatastoreIsOpen = -67870, // The data store is open. + MissingValue = -67871, // A missing value was detected. + UnsupportedQueryLimits = -67872, // The query limits are not supported. + UnsupportedNumSelectionPreds = -67873, // The number of selection predicates is not supported. + UnsupportedOperator = -67874, // The operator is not supported. + InvalidDBLocation = -67875, // The database location is not valid. + InvalidAccessRequest = -67876, // The access request is not valid. + InvalidIndexInfo = -67877, // The index information is not valid. + InvalidNewOwner = -67878, // The new owner is not valid. + InvalidModifyMode = -67879, // The modify mode is not valid. + MissingRequiredExtension = -67880, // A required certificate extension is missing. + ExtendedKeyUsageNotCritical = -67881, // The extended key usage extension was not marked critical. + TimestampMissing = -67882, // A timestamp was expected but was not found. + TimestampInvalid = -67883, // The timestamp was not valid. + TimestampNotTrusted = -67884, // The timestamp was not trusted. + TimestampServiceNotAvailable = -67885, // The timestamp service is not available. + TimestampBadAlg = -67886, // An unrecognized or unsupported Algorithm Identifier in timestamp. + TimestampBadRequest = -67887, // The timestamp transaction is not permitted or supported. + TimestampBadDataFormat = -67888, // The timestamp data submitted has the wrong format. + TimestampTimeNotAvailable = -67889, // The time source for the Timestamp Authority is not available. + TimestampUnacceptedPolicy = -67890, // The requested policy is not supported by the Timestamp Authority. + TimestampUnacceptedExtension = -67891, // The requested extension is not supported by the Timestamp Authority. + TimestampAddInfoNotAvailable = -67892, // The additional information requested is not available. + TimestampSystemFailure = -67893, // The timestamp request cannot be handled due to system failure. + SigningTimeMissing = -67894, // A signing time was expected but was not found. + TimestampRejection = -67895, // A timestamp transaction was rejected. + TimestampWaiting = -67896, // A timestamp transaction is waiting. + TimestampRevocationWarning = -67897, // A timestamp authority revocation warning was issued. + TimestampRevocationNotification = -67898, // A timestamp authority revocation notification was issued. + CertificatePolicyNotAllowed = -67899, // The requested policy is not allowed for this certificate. + CertificateNameNotAllowed = -67900, // The requested name is not allowed for this certificate. + CertificateValidityPeriodTooLong = -67901, // The validity period in the certificate exceeds the maximum allowed. + CertificateIsCA = -67902, // The verified certificate is a CA rather than an end-entity. + CertificateDuplicateExtension = -67903, // The certificate contains multiple extensions with the same extension ID. +} diff --git a/core/sys/darwin/Security/SecRandom.odin b/core/sys/darwin/Security/SecRandom.odin new file mode 100644 index 000000000..0527baca1 --- /dev/null +++ b/core/sys/darwin/Security/SecRandom.odin @@ -0,0 +1,19 @@ +package Security + +import CF "core:sys/darwin/CoreFoundation" + +foreign import Security "system:Security.framework" + +// A reference to a random number generator. +RandomRef :: distinct rawptr + +@(link_prefix="Sec", default_calling_convention="c") +foreign Security { + // Default random ref for /dev/random. Synonym for nil. + @(link_name="kSecRandomDefault") kSecRandomDefault: RandomRef + + // Generates an array of cryptographically secure random bytes. + RandomCopyBytes :: proc(rnd: RandomRef = kSecRandomDefault, count: uint, bytes: [^]byte) -> errSec --- + + CopyErrorMessageString :: proc(status: errSec, reserved: rawptr = nil) -> CF.String --- +} \ No newline at end of file diff --git a/core/sys/darwin/darwin.odin b/core/sys/darwin/darwin.odin new file mode 100644 index 000000000..d109f5544 --- /dev/null +++ b/core/sys/darwin/darwin.odin @@ -0,0 +1,35 @@ +#+build darwin +package darwin + +import "core:c" + +foreign import system "system:System.framework" + +Bool :: b8 + +RUsage :: struct { + ru_utime: timeval, + ru_stime: timeval, + ru_maxrss: c.long, + ru_ixrss: c.long, + ru_idrss: c.long, + ru_isrss: c.long, + ru_minflt: c.long, + ru_majflt: c.long, + ru_nswap: c.long, + ru_inblock: c.long, + ru_oublock: c.long, + ru_msgsnd: c.long, + ru_msgrcv: c.long, + ru_nsignals: c.long, + ru_nvcsw: c.long, + ru_nivcsw: c.long, +} + +foreign system { + __error :: proc() -> ^i32 --- +} + +errno :: #force_inline proc "contextless" () -> i32 { + return __error()^ +} diff --git a/core/sys/darwin/mach_darwin.odin b/core/sys/darwin/mach_darwin.odin index e6272b9aa..2cc823e69 100644 --- a/core/sys/darwin/mach_darwin.odin +++ b/core/sys/darwin/mach_darwin.odin @@ -1,29 +1,524 @@ package darwin -foreign import pthread "System.framework" +foreign import mach "system:System.framework" import "core:c" +import "base:intrinsics" -// NOTE(tetra): Unclear whether these should be aligned 16 or not. -// However all other sync primitives are aligned for robustness. -// I cannot currently align these though. -// See core/sys/unix/pthread_linux.odin/pthread_t. -task_t :: distinct u64 -semaphore_t :: distinct u64 +kern_return_t :: distinct c.int -kern_return_t :: distinct u64 -thread_act_t :: distinct u64 +mach_port_t :: distinct c.uint +vm_map_t :: mach_port_t +mem_entry_name_port_t :: mach_port_t +ipc_space_t :: mach_port_t +thread_t :: mach_port_t +task_t :: mach_port_t +semaphore_t :: mach_port_t + +vm_size_t :: distinct c.uintptr_t + +vm_address_t :: vm_offset_t +vm_offset_t :: distinct c.uintptr_t + +// NOTE(beau): typedefed to int in the original headers +boolean_t :: b32 + +vm_prot_t :: distinct c.int + +vm_inherit_t :: distinct c.uint + +mach_port_name_t :: distinct c.uint + +sync_policy_t :: distinct c.int @(default_calling_convention="c") -foreign pthread { - mach_task_self :: proc() -> task_t --- +foreign mach { + mach_task_self :: proc() -> mach_port_t --- - semaphore_create :: proc(task: task_t, semaphore: ^semaphore_t, policy, value: c.int) -> kern_return_t --- - semaphore_destroy :: proc(task: task_t, semaphore: semaphore_t) -> kern_return_t --- + semaphore_create :: proc(task: task_t, semaphore: ^semaphore_t, policy: Sync_Policy, value: c.int) -> Kern_Return --- + semaphore_destroy :: proc(task: task_t, semaphore: semaphore_t) -> Kern_Return --- - semaphore_signal :: proc(semaphore: semaphore_t) -> kern_return_t --- - semaphore_signal_all :: proc(semaphore: semaphore_t) -> kern_return_t --- - semaphore_signal_thread :: proc(semaphore: semaphore_t, thread: thread_act_t) -> kern_return_t --- - - semaphore_wait :: proc(semaphore: semaphore_t) -> kern_return_t --- + semaphore_signal :: proc(semaphore: semaphore_t) -> Kern_Return --- + semaphore_signal_all :: proc(semaphore: semaphore_t) -> Kern_Return --- + semaphore_signal_thread :: proc(semaphore: semaphore_t, thread: thread_t) -> Kern_Return --- + + semaphore_wait :: proc(semaphore: semaphore_t) -> Kern_Return --- + + vm_allocate :: proc (target_task : vm_map_t, address: ^vm_address_t, size: vm_size_t, flags: VM_Flags) -> Kern_Return --- + + vm_deallocate :: proc(target_task: vm_map_t, address: vm_address_t, size: vm_size_t) -> Kern_Return --- + + vm_map :: proc( + target_task: vm_map_t, + address: ^vm_address_t, + size: vm_size_t, + mask: vm_address_t, + flags: VM_Flags, + object: mem_entry_name_port_t, + offset: vm_offset_t, + copy: boolean_t, + cur_protection, + max_protection: VM_Prot_Flags, + inheritance: VM_Inherit, + ) -> Kern_Return --- + + mach_make_memory_entry :: proc( + target_task: vm_map_t, + size: ^vm_size_t, + offset: vm_offset_t, + permission: VM_Prot_Flags, + object_handle: ^mem_entry_name_port_t, + parent_entry: mem_entry_name_port_t, + ) -> Kern_Return --- + + mach_port_deallocate :: proc( + task: ipc_space_t, + name: mach_port_name_t, + ) -> Kern_Return --- + + vm_page_size: vm_size_t +} + +Kern_Return :: enum kern_return_t { + Success, + + /* Specified address is not currently valid. + */ + Invalid_Address, + + /* Specified memory is valid, but does not permit the + * required forms of access. + */ + Protection_Failure, + + /* The address range specified is already in use, or + * no address range of the size specified could be + * found. + */ + No_Space, + + /* The function requested was not applicable to this + * type of argument, or an argument is invalid + */ + Invalid_Argument, + + /* The function could not be performed. A catch-all. + */ + Failure, + + /* A system resource could not be allocated to fulfill + * this request. This failure may not be permanent. + */ + Resource_Shortage, + + /* The task in question does not hold receive rights + * for the port argument. + */ + Not_Receiver, + + /* Bogus access restriction. + */ + No_Access, + + /* During a page fault, the target address refers to a + * memory object that has been destroyed. This + * failure is permanent. + */ + Memory_Failure, + + /* During a page fault, the memory object indicated + * that the data could not be returned. This failure + * may be temporary; future attempts to access this + * same data may succeed, as defined by the memory + * object. + */ + Memory_Error, + + /* The receive right is already a member of the portset. + */ + Already_In_Set, + + /* The receive right is not a member of a port set. + */ + Not_In_Set, + + /* The name already denotes a right in the task. + */ + Name_Exists, + + /* The operation was aborted. Ipc code will + * catch this and reflect it as a message error. + */ + Aborted, + + /* The name doesn't denote a right in the task. + */ + Invalid_Name, + + /* Target task isn't an active task. + */ + Invalid_Task, + + /* The name denotes a right, but not an appropriate right. + */ + Invalid_Right, + + /* A blatant range error. + */ + Invalid_Value, + + /* Operation would overflow limit on user-references. + */ + URefs_Overflow, + + /* The supplied (port) capability is improper. + */ + Invalid_Capability, + + /* The task already has send or receive rights + * for the port under another name. + */ + Right_Exists, + + /* Target host isn't actually a host. + */ + Invalid_Host, + + /* An attempt was made to supply "precious" data + * for memory that is already present in a + * memory object. + */ + Memory_Present, + + /* A page was requested of a memory manager via + * memory_object_data_request for an object using + * a MEMORY_OBJECT_COPY_CALL strategy, with the + * VM_PROT_WANTS_COPY flag being used to specify + * that the page desired is for a copy of the + * object, and the memory manager has detected + * the page was pushed into a copy of the object + * while the kernel was walking the shadow chain + * from the copy to the object. This error code + * is delivered via memory_object_data_error + * and is handled by the kernel (it forces the + * kernel to restart the fault). It will not be + * seen by users. + */ + Memory_Data_Moved, + + /* A strategic copy was attempted of an object + * upon which a quicker copy is now possible. + * The caller should retry the copy using + * vm_object_copy_quickly. This error code + * is seen only by the kernel. + */ + Memory_Restart_Copy, + + /* An argument applied to assert processor set privilege + * was not a processor set control port. + */ + Invalid_Processor_Set, + + /* The specified scheduling attributes exceed the thread's + * limits. + */ + Policy_Limit, + + /* The specified scheduling policy is not currently + * enabled for the processor set. + */ + Invalid_Policy, + + /* The external memory manager failed to initialize the + * memory object. + */ + Invalid_Object, + + /* A thread is attempting to wait for an event for which + * there is already a waiting thread. + */ + Already_Waiting, + + /* An attempt was made to destroy the default processor + * set. + */ + Default_Set, + + /* An attempt was made to fetch an exception port that is + * protected, or to abort a thread while processing a + * protected exception. + */ + Exception_Protected, + + /* A ledger was required but not supplied. + */ + Invalid_Ledger, + + /* The port was not a memory cache control port. + */ + Invalid_Memory_Control, + + /* An argument supplied to assert security privilege + * was not a host security port. + */ + Invalid_Security, + + /* thread_depress_abort was called on a thread which + * was not currently depressed. + */ + Not_Depressed, + + /* Object has been terminated and is no longer available + */ + Terminated, + + /* Lock set has been destroyed and is no longer available. + */ + Lock_Set_Destroyed, + + /* The thread holding the lock terminated before releasing + * the lock + */ + Lock_Unstable, + + /* The lock is already owned by another thread + */ + Lock_Owned, + + /* The lock is already owned by the calling thread + */ + Lock_Owned_Self, + + /* Semaphore has been destroyed and is no longer available. + */ + Semaphore_Destroyed, + + /* Return from RPC indicating the target server was + * terminated before it successfully replied + */ + Rpc_Server_Terminated, + + /* Terminate an orphaned activation. + */ + RPC_Terminate_Orphan, + + /* Allow an orphaned activation to continue executing. + */ + RPC_Continue_Orphan, + + /* Empty thread activation (No thread linked to it) + */ + Not_Supported, + + /* Remote node down or inaccessible. + */ + Node_Down, + + /* A signalled thread was not actually waiting. */ + Not_Waiting, + + /* Some thread-oriented operation (semaphore_wait) timed out + */ + Operation_Timed_Out, + + /* During a page fault, indicates that the page was rejected + * as a result of a signature check. + */ + Codesign_Error, + + /* The requested property cannot be changed at this time. + */ + Policy_Static, + + /* The provided buffer is of insufficient size for the requested data. + */ + Insufficient_Buffer_Size, + + /* Denied by security policy + */ + Denied, + + /* The KC on which the function is operating is missing + */ + Missing_KC, + + /* The KC on which the function is operating is invalid + */ + Invalid_KC, + + /* A search or query operation did not return a result + */ + Not_Found, + + /* Maximum return value allowable + */ + Return_Max = 0x100, +} + +/* + * VM allocation flags: + * + * VM_FLAGS_FIXED + * (really the absence of VM_FLAGS_ANYWHERE) + * Allocate new VM region at the specified virtual address, if possible. + * + * VM_FLAGS_ANYWHERE + * Allocate new VM region anywhere it would fit in the address space. + * + * VM_FLAGS_PURGABLE + * Create a purgable VM object for that new VM region. + * + * VM_FLAGS_4GB_CHUNK + * The new VM region will be chunked up into 4GB sized pieces. + * + * VM_FLAGS_NO_PMAP_CHECK + * (for DEBUG kernel config only, ignored for other configs) + * Do not check that there is no stale pmap mapping for the new VM region. + * This is useful for kernel memory allocations at bootstrap when building + * the initial kernel address space while some memory is already in use. + * + * VM_FLAGS_OVERWRITE + * The new VM region can replace existing VM regions if necessary + * (to be used in combination with VM_FLAGS_FIXED). + * + * VM_FLAGS_NO_CACHE + * Pages brought in to this VM region are placed on the speculative + * queue instead of the active queue. In other words, they are not + * cached so that they will be stolen first if memory runs low. + */ + +@(private="file") +LOG2 :: intrinsics.constant_log2 + +VM_Flag :: enum c.int { + Anywhere, + Purgable, + _4GB_Chunk, + Random_Addr, + No_Cache, + Resilient_Codesign, + Resilient_Media, + Permanent, + + // NOTE(beau): log 2 of the bit we want in the bit set so we get that bit in + // the bit set + + TPRO = LOG2(0x1000), + Overwrite = LOG2(0x4000),/* delete any existing mappings first */ + + Superpage_Size_Any = LOG2(0x10000), + Superpage_Size_2MB = LOG2(0x20000), + __Superpage3 = LOG2(0x40000), + + Return_Data_Addr = LOG2(0x100000), + Return_4K_Data_Addr = LOG2(0x800000), + + Alias_Mask1 = 24, + Alias_Mask2, + Alias_Mask3, + Alias_Mask4, + Alias_Mask5, + Alias_Mask6, + Alias_Mask7, + Alias_Mask8, + + HW = TPRO, +} + +VM_Flags :: distinct bit_set[VM_Flag; c.int] +VM_FLAGS_FIXED :: VM_Flags{} + +/* + * VM_FLAGS_SUPERPAGE_MASK + * 3 bits that specify whether large pages should be used instead of + * base pages (!=0), as well as the requested page size. + */ +VM_FLAGS_SUPERPAGE_MASK :: VM_Flags { + .Superpage_Size_Any, + .Superpage_Size_2MB, + .__Superpage3, +} + +// 0xFF000000 +VM_FLAGS_ALIAS_MASK :: VM_Flags { + .Alias_Mask1, + .Alias_Mask2, + .Alias_Mask3, + .Alias_Mask4, + .Alias_Mask5, + .Alias_Mask6, + .Alias_Mask7, + .Alias_Mask8, +} + +VM_GET_FLAGS_ALIAS :: proc(flags: VM_Flags) -> c.int { + return transmute(c.int)(flags & VM_FLAGS_ALIAS_MASK) >> 24 +} +// NOTE(beau): no need for VM_SET_FLAGS_ALIAS, just mask in things from +// VM_Flag.Alias_Mask* + +/* These are the flags that we accept from user-space */ +VM_FLAGS_USER_ALLOCATE :: VM_Flags { + .Anywhere, + .Purgable, + ._4GB_Chunk, + .Random_Addr, + .No_Cache, + .Permanent, + .Overwrite, +} | VM_FLAGS_FIXED | VM_FLAGS_SUPERPAGE_MASK | VM_FLAGS_ALIAS_MASK + +VM_FLAGS_USER_MAP :: VM_Flags { + .Return_4K_Data_Addr, + .Return_Data_Addr, +} | VM_FLAGS_USER_ALLOCATE + +VM_FLAGS_USER_REMAP :: VM_Flags { + .Anywhere, + .Random_Addr, + .Overwrite, + .Return_Data_Addr, + .Resilient_Codesign, + .Resilient_Media, +} | VM_FLAGS_FIXED + +VM_FLAGS_SUPERPAGE_NONE :: VM_Flags{} /* no superpages, if all bits are 0 */ + +/* + * Protection values, defined as bits within the vm_prot_t type + */ + +VM_Prot :: enum vm_prot_t { + Read, + Write, + Execute, +} + +VM_Prot_Flags :: distinct bit_set[VM_Prot; vm_prot_t] + +VM_PROT_NONE :: VM_Prot_Flags{} +VM_PROT_DEFAULT :: VM_Prot_Flags{.Read, .Write} +VM_PROT_ALL :: VM_Prot_Flags{.Read, .Write, .Execute} + +/* + * Enumeration of valid values for vm_inherit_t. + */ + +VM_Inherit :: enum vm_inherit_t { + Share, + Copy, + None, + Donate_Copy, + + Default = Copy, + Last_Valid = None, +} + +Sync_Policy :: enum sync_policy_t { + Fifo, + Fixed_Priority, + Reversed, + Order_Mask, + + Lifo = Fifo | Reversed, } diff --git a/core/sys/darwin/proc.odin b/core/sys/darwin/proc.odin new file mode 100644 index 000000000..fa5391f6f --- /dev/null +++ b/core/sys/darwin/proc.odin @@ -0,0 +1,192 @@ +package darwin + +import "base:intrinsics" + +import "core:sys/posix" + +foreign import lib "system:System.framework" + +// Incomplete bindings to the proc API on MacOS, add to when needed. + +foreign lib { + proc_pidinfo :: proc(pid: posix.pid_t, flavor: PID_Info_Flavor, arg: i64, buffer: rawptr, buffersize: i32) -> i32 --- + proc_pidpath :: proc(pid: posix.pid_t, buffer: [^]byte, buffersize: u32) -> i32 --- + proc_listallpids :: proc(buffer: [^]i32, buffersize: i32) -> i32 --- + proc_pid_rusage :: proc(pid: posix.pid_t, flavor: Pid_Rusage_Flavor, buffer: rawptr) -> i32 --- +} + +MAXCOMLEN :: 16 + +proc_bsdinfo :: struct { + pbi_flags: PBI_Flags, + pbi_status: u32, + pbi_xstatus: u32, + pbi_pid: u32, + pbi_ppid: u32, + pbi_uid: posix.uid_t, + pbi_gid: posix.gid_t, + pbi_ruid: posix.uid_t, + pbi_rgid: posix.gid_t, + pbi_svuid: posix.uid_t, + pbi_svgid: posix.gid_t, + rfu_1: u32, + pbi_comm: [MAXCOMLEN]byte `fmt:"s,0"`, + pbi_name: [2 * MAXCOMLEN]byte `fmt:"s,0"`, + pbi_nfiles: u32, + pbi_pgid: u32, + pbi_pjobc: u32, + e_tdev: u32, + e_tpgid: u32, + pbi_nice: i32, + pbi_start_tvsec: u64, + pbi_start_tvusec: u64, +} + +proc_bsdshortinfo :: struct { + pbsi_pid: u32, + pbsi_ppid: u32, + pbsi_pgid: u32, + pbsi_status: u32, + pbsi_comm: [MAXCOMLEN]byte `fmt:"s,0"`, + pbsi_flags: PBI_Flags, + pbsi_uid: posix.uid_t, + pbsi_gid: posix.gid_t, + pbsi_ruid: posix.uid_t, + pbsi_rgid: posix.gid_t, + pbsi_svuid: posix.uid_t, + pbsi_svgid: posix.gid_t, + pbsi_rfu: u32, +} + +proc_vnodepathinfo :: struct { + pvi_cdir: vnode_info_path, + pvi_rdir: vnode_info_path, +} + +vnode_info_path :: struct { + vip_vi: vnode_info, + vip_path: [posix.PATH_MAX]byte, +} + +vnode_info :: struct { + vi_stat: vinfo_stat, + vi_type: i32, + vi_pad: i32, + vi_fsid: fsid_t, +} + +vinfo_stat :: struct { + vst_dev: u32, + vst_mode: u16, + vst_nlink: u16, + vst_ino: u64, + vst_uid: posix.uid_t, + vst_gid: posix.gid_t, + vst_atime: i64, + vst_atimensec: i64, + vst_mtime: i64, + vst_mtimensec: i64, + vst_ctime: i64, + vst_ctimensec: i64, + vst_birthtime: i64, + vst_birthtimensec: i64, + vst_size: posix.off_t, + vst_blocks: i64, + vst_blksize: i32, + vst_flags: u32, + vst_gen: u32, + vst_rdev: u32, + vst_qspare: [2]i64, +} + +proc_taskinfo :: struct { + pti_virtual_size: u64 `fmt:"M"`, + pti_resident_size: u64 `fmt:"M"`, + pti_total_user: u64, + pti_total_system: u64, + pti_threads_user: u64, + pti_threads_system: u64, + pti_policy: i32, + pti_faults: i32, + pti_pageins: i32, + pti_cow_faults: i32, + pti_messages_sent: i32, + pti_messages_received: i32, + pti_syscalls_mach: i32, + pti_syscalls_unix: i32, + pti_csw: i32, + pti_threadnum: i32, + pti_numrunning: i32, + pti_priority: i32, +} + +proc_taskallinfo :: struct { + pbsd: proc_bsdinfo, + ptinfo: proc_taskinfo, +} + +fsid_t :: distinct [2]i32 + +PBI_Flag_Bits :: enum u32 { + SYSTEM = intrinsics.constant_log2(0x0001), + TRACED = intrinsics.constant_log2(0x0002), + INEXIT = intrinsics.constant_log2(0x0004), + PWAIT = intrinsics.constant_log2(0x0008), + LP64 = intrinsics.constant_log2(0x0010), + SLEADER = intrinsics.constant_log2(0x0020), + CTTY = intrinsics.constant_log2(0x0040), + CONTROLT = intrinsics.constant_log2(0x0080), + THCWD = intrinsics.constant_log2(0x0100), + PC_THROTTLE = intrinsics.constant_log2(0x0200), + PC_SUSP = intrinsics.constant_log2(0x0400), + PC_KILL = intrinsics.constant_log2(0x0600), + PA_THROTTLE = intrinsics.constant_log2(0x0800), + PA_SUSP = intrinsics.constant_log2(0x1000), + PA_PSUGID = intrinsics.constant_log2(0x2000), + EXEC = intrinsics.constant_log2(0x4000), +} +PBI_Flags :: bit_set[PBI_Flag_Bits; u32] + +PID_Info_Flavor :: enum i32 { + LISTFDS = 1, + TASKALLINFO, + BSDINFO, + TASKINFO, + THREADINFO, + LISTTHREADS, + REGIONINFO, + REGIONPATHINFO, + VNODEPATHINFO, + THREADPATHINFO, + PATHINFO, + WORKQUEUEINFO, + SHORTBSDINFO, + LISTFILEPORTS, + THREADID64INFO, + RUSAGE, +} + +PIDPATHINFO_MAXSIZE :: 4*posix.PATH_MAX + +Pid_Rusage_Flavor :: enum i32 { + V0, + V1, + V2, + V3, + V4, + V5, +} + +rusage_info_v0 :: struct { + ri_uuid: [16]u8, + ri_user_time: u64, + ri_system_time: u64, + ri_pkg_idle_wkups: u64, + ri_interrupt_wkups: u64, + ri_pageins: u64, + ri_wired_size: u64, + ri_resident_size: u64, + ri_phys_footprint: u64, + ri_proc_start_abstime: u64, + ri_proc_exit_abstime: u64, +} diff --git a/core/sys/darwin/sync.odin b/core/sys/darwin/sync.odin new file mode 100644 index 000000000..58fc7c9e4 --- /dev/null +++ b/core/sys/darwin/sync.odin @@ -0,0 +1,314 @@ +package darwin + +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 { + WAIT_ON_ADDRESS_AVAILABLE :: true + } else when ODIN_MINIMUM_OS_VERSION >= 14_04_00 { + WAIT_ON_ADDRESS_AVAILABLE :: true + } else { + WAIT_ON_ADDRESS_AVAILABLE :: false + } + + when ODIN_PLATFORM_SUBTARGET == .iOS && ODIN_MINIMUM_OS_VERSION >= 14_00_00 { + ULOCK_WAIT_2_AVAILABLE :: true + } else when ODIN_MINIMUM_OS_VERSION >= 11_00_00 { + ULOCK_WAIT_2_AVAILABLE :: true + } else { + ULOCK_WAIT_2_AVAILABLE :: false + } + +} else { + WAIT_ON_ADDRESS_AVAILABLE :: false + ULOCK_WAIT_2_AVAILABLE :: false +} + +os_sync_wait_on_address_flag :: enum u32 { + // 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. + // + // When using this flag, it is important to pass OS_SYNC_WAKE_BY_ADDRESS_SHARED + // flag along with the exact same @addr to os_sync_wake_by_address_any and + // its variants to correctly find and wake up blocked waiters on the @addr. + // + // 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 = 0, +} + +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 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. + // + // When using this flag, it is important to pass OS_SYNC_WAIT_ON_ADDRESS_SHARED + // flag along with the exact same @addr to os_sync_wait_on_address and + // its variants to correctly find and wake up blocked waiters on the @addr. + // + // 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 = 0, +} + +os_sync_wake_by_address_flags :: distinct bit_set[os_sync_wake_by_address_flag; u32] + +os_clockid :: enum u32 { + MACH_ABSOLUTE_TIME = 32, +} + +foreign system { + // This function provides an atomic compare-and-wait functionality that + // can be used to implement other higher level synchronization primitives. + // + // It reads a value from @addr, compares it to expected @value and blocks + // the calling thread if they are equal. This sequence of operations is + // done atomically with respect to other concurrent operations that can + // be performed on this @addr by other threads using this same function + // or os_sync_wake_by_addr variants. At this point, the blocked calling + // thread is considered to be a waiter on this @addr, waiting to be woken + // up by a call to os_sync_wake_by_addr variants. If the value at @addr + // turns out to be different than expected, the calling thread returns + // immediately without blocking. + // + // This function is expected to be used for implementing synchronization + // primitives that do not have a sense of ownership (e.g. condition + // variables, semaphores) as it does not provide priority inversion avoidance. + // For locking primitives, it is recommended that you use existing OS + // primitives such as os_unfair_lock API family / pthread mutex or + // std::mutex. + // + // @param addr + // The userspace address to be used for atomic compare-and-wait. + // This address must be aligned to @size. + // + // @param value + // The value expected at @addr. + // + // @param size + // The size of @value, in bytes. This can be either 4 or 8 today. + // For @value of @size 4 bytes, the upper 4 bytes of @value are ignored. + // + // @param flags + // Flags to alter behavior of os_sync_wait_on_address. + // See os_sync_wait_on_address_flags_t. + // + // @return + // If the calling thread is woken up by a call to os_sync_wake_by_addr + // variants or the value at @addr is different than expected, this function + // returns successfully and the return value indicates the number + // of outstanding waiters blocked on this address. + // In the event of an error, returns -1 with errno set to indicate the error. + // + // EINVAL : Invalid flags or size. + // EINVAL : The @addr passed is NULL or misaligned. + // EINVAL : The operation associated with existing kernel state + // at this @addr is inconsistent with what the caller + // has requested. + // It is important to make sure consistent values are + // passed across wait and wake APIs for @addr, @size + // and the shared memory specification + // (See os_sync_wait_on_address_flags_t). + // + // It is possible for the os_sync_wait_on_address and its variants to perform + // an early return in the event of following errors where user may want to + // re-try the wait operation. E.g. low memory conditions could cause such early + // return. + // It is important to read the current value at the @addr before re-trying + // to ensure that the new value still requires waiting on @addr. + // + // ENOMEM : Unable to allocate memory for kernel internal data + // structures. + // EINTR : The syscall was interrupted / spurious wake up. + // EFAULT : Unable to read value from the @addr. Kernel copyin failed. + // It is possible to receive EFAULT error in following cases: + // 1. The @addr is an invalid address. This is a programmer error. + // 2. The @addr is valid; but, this is a transient error such as + // due to low memory conditions. User may want to re-try the wait + // operation. + // Following code snippet illustrates a possible re-try loop. + // + // retry: + // current = atomic_load_explicit(addr, memory_order_relaxed); + // if (current != expected) { + // int ret = os_sync_wait_on_address(addr, current, size, flags); + // if ((ret < 0) && ((errno == EINTR) || (errno == EFAULT))) { + // goto retry; + // } + // } + // + os_sync_wait_on_address :: proc( + addr: rawptr, + value: u64, + size: uint, + flags: os_sync_wait_on_address_flags, + ) -> i32 --- + + // This function is a variant of os_sync_wait_on_address that + // allows the calling thread to specify a deadline + // until which it is willing to block. + // + // @param addr + // The userspace address to be used for atomic compare-and-wait. + // This address must be aligned to @size. + // + // @param value + // The value expected at @addr. + // + // @param size + // The size of @value, in bytes. This can be either 4 or 8 today. + // For @value of @size 4 bytes, the upper 4 bytes of @value are ignored. + // + // @param flags + // Flags to alter behavior of os_sync_wait_on_address_with_deadline. + // See os_sync_wait_on_address_flags_t. + // + // @param clockid + // This value anchors @deadline argument to a specific clock id. + // See os_clockid_t. + // + // @param deadline + // This value is used to specify a deadline until which the calling + // thread is willing to block. + // Passing zero for the @deadline results in an error being returned. + // It is recommended to use os_sync_wait_on_address API to block + // indefinitely until woken up by a call to os_sync_wake_by_address_any + // or os_sync_wake_by_address_all APIs. + // + // @return + // If the calling thread is woken up by a call to os_sync_wake_by_addr + // variants or the value at @addr is different than expected, this function + // returns successfully and the return value indicates the number + // of outstanding waiters blocked on this address. + // In the event of an error, returns -1 with errno set to indicate the error. + // + // In addition to errors returned by os_sync_wait_on_address, this function + // can return the following additional error codes. + // + // EINVAL : Invalid clock id. + // EINVAL : The @deadline passed is 0. + // ETIMEDOUT : Deadline expired. + os_sync_wait_on_address_with_deadline :: proc( + addr: rawptr, + value: u64, + size: uint, + flags: os_sync_wait_on_address_flags, + clockid: os_clockid, + deadline: u64, + ) -> i32 --- + + // This function is a variant of os_sync_wait_on_address that + // allows the calling thread to specify a timeout + // until which it is willing to block. + // + // @param addr + // The userspace address to be used for atomic compare-and-wait. + // This address must be aligned to @size. + // + // @param value + // The value expected at @addr. + // + // @param size + // The size of @value, in bytes. This can be either 4 or 8 today. + // For @value of @size 4 bytes, the upper 4 bytes of @value are ignored. + // + // @param flags + // Flags to alter behavior of os_sync_wait_on_address_with_timeout. + // See os_sync_wait_on_address_flags_t. + // + // @param clockid + // This value anchors @timeout_ns argument to a specific clock id. + // See os_clockid_t. + // + // @param timeout_ns + // This value is used to specify a timeout in nanoseconds until which + // the calling thread is willing to block. + // Passing zero for the @timeout_ns results in an error being returned. + // It is recommended to use os_sync_wait_on_address API to block + // indefinitely until woken up by a call to os_sync_wake_by_address_any + // or os_sync_wake_by_address_all APIs. + // + // @return + // If the calling thread is woken up by a call to os_sync_wake_by_address + // variants or the value at @addr is different than expected, this function + // returns successfully and the return value indicates the number + // of outstanding waiters blocked on this address. + // In the event of an error, returns -1 with errno set to indicate the error. + // + // In addition to errors returned by os_sync_wait_on_address, this function + // can return the following additional error codes. + // + // EINVAL : Invalid clock id. + // EINVAL : The @timeout_ns passed is 0. + // ETIMEDOUT : Timeout expired. + os_sync_wait_on_address_with_timeout :: proc( + addr: rawptr, + value: u64, + size: uint, + flags: os_sync_wait_on_address_flags, + clockid: os_clockid, + timeout_ns: u64, + ) -> i32 --- + + // This function wakes up one waiter out of all those blocked in os_sync_wait_on_address + // or its variants on the @addr. No guarantee is provided about which + // specific waiter is woken up. + // + // @param addr + // The userspace address to be used for waking up the blocked waiter. + // It should be same as what is passed to os_sync_wait_on_address or its variants. + // + // @param size + // The size of lock value, in bytes. This can be either 4 or 8 today. + // It should be same as what is passed to os_sync_wait_on_address or its variants. + // + // @param flags + // Flags to alter behavior of os_sync_wake_by_address_any. + // See os_sync_wake_by_address_flags_t. + // + // @return + // Returns 0 on success. + // In the event of an error, returns -1 with errno set to indicate the error. + // + // EINVAL : Invalid flags or size. + // EINVAL : The @addr passed is NULL. + // EINVAL : The operation associated with existing kernel state + // at this @addr is inconsistent with what caller + // has requested. + // It is important to make sure consistent values are + // passed across wait and wake APIs for @addr, @size + // 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_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. + // + // @param addr + // The userspace address to be used for waking up the blocked waiters. + // It should be same as what is passed to os_sync_wait_on_address or its variants. + // + // @param size + // The size of lock value, in bytes. This can be either 4 or 8 today. + // It should be same as what is passed to os_sync_wait_on_address or its variants. + // + // @param flags + // Flags to alter behavior of os_sync_wake_by_address_all. + // See os_sync_wake_by_address_flags_t. + // + // @return + // Returns 0 on success. + // 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_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 c225c77fb..ae8373f99 100644 --- a/core/sys/darwin/xnu_system_call_helpers.odin +++ b/core/sys/darwin/xnu_system_call_helpers.odin @@ -1,7 +1,11 @@ package darwin import "core:c" -import "core:runtime" +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 { @@ -11,9 +15,9 @@ sys_write_string :: proc (fd: c.int, message: string) -> bool { Offset_From :: enum c.int { SEEK_SET = 0, // the offset is set to offset bytes. SEEK_CUR = 1, // the offset is set to its current location plus offset bytes. - SEEK_END = 2, // the offset is set to the size of the file plus offset bytes. - SEEK_HOLE = 3, // the offset is set to the start of the next hole greater than or equal to the supplied offset. - SEEK_DATA = 4, // the offset is set to the start of the next non-hole file region greater than or equal to the supplied offset. + SEEK_END = 2, // the offset is set to the size of the file plus offset bytes. + SEEK_HOLE = 3, // the offset is set to the start of the next hole greater than or equal to the supplied offset. + SEEK_DATA = 4, // the offset is set to the start of the next non-hole file region greater than or equal to the supplied offset. } Open_Flags_Enum :: enum u8 { @@ -87,6 +91,29 @@ _sys_permission_mode :: #force_inline proc (mode: Permission) -> u32 { return cflags } +_sys_open_mode :: #force_inline proc(mode: Open_Flags) -> u32 { + cflags : u32 = 0 + + cflags |= OPEN_FLAG_RDONLY * u32(Open_Flags.RDONLY in mode) + cflags |= OPEN_FLAG_WRONLY * u32(Open_Flags.WRONLY in mode) + cflags |= OPEN_FLAG_RDWR * u32(Open_Flags.RDWR in mode) + cflags |= OPEN_FLAG_NONBLOCK * u32(Open_Flags.NONBLOCK in mode) + cflags |= OPEN_FLAG_CREAT * u32(Open_Flags.CREAT in mode) + cflags |= OPEN_FLAG_APPEND * u32(Open_Flags.APPEND in mode) + cflags |= OPEN_FLAG_TRUNC * u32(Open_Flags.TRUNC in mode) + cflags |= OPEN_FLAG_EXCL * u32(Open_Flags.EXCL in mode) + cflags |= OPEN_FLAG_SHLOCK * u32(Open_Flags.SHLOCK in mode) + cflags |= OPEN_FLAG_EXLOCK * u32(Open_Flags.EXLOCK in mode) + cflags |= OPEN_FLAG_DIRECTORY * u32(Open_Flags.DIRECTORY in mode) + cflags |= OPEN_FLAG_NOFOLLOW * u32(Open_Flags.NOFOLLOW in mode) + cflags |= OPEN_FLAG_SYMLINK * u32(Open_Flags.SYMLINK in mode) + cflags |= OPEN_FLAG_EVTONLY * u32(Open_Flags.EVTONLY in mode) + cflags |= OPEN_FLAG_CLOEXEC * u32(Open_Flags.CLOEXEC in mode) + cflags |= OPEN_FLAG_NOFOLLOW_ANY * u32(Open_Flags.NOFOLLOW_ANY in mode) + + return cflags +} + @(private) clone_to_cstring :: proc(s: string, allocator: runtime.Allocator, loc := #caller_location) -> cstring { c := make([]byte, len(s)+1, allocator, loc) @@ -105,22 +132,7 @@ sys_open :: proc(path: string, oflag: Open_Flags, mode: Permission) -> (c.int, b cflags = _sys_permission_mode(mode) - cmode |= OPEN_FLAG_RDONLY * u32(Open_Flags.RDONLY in oflag) - cmode |= OPEN_FLAG_WRONLY * u32(Open_Flags.WRONLY in oflag) - cmode |= OPEN_FLAG_RDWR * u32(Open_Flags.RDWR in oflag) - cmode |= OPEN_FLAG_NONBLOCK * u32(Open_Flags.NONBLOCK in oflag) - cmode |= OPEN_FLAG_CREAT * u32(Open_Flags.CREAT in oflag) - cmode |= OPEN_FLAG_APPEND * u32(Open_Flags.APPEND in oflag) - cmode |= OPEN_FLAG_TRUNC * u32(Open_Flags.TRUNC in oflag) - cmode |= OPEN_FLAG_EXCL * u32(Open_Flags.EXCL in oflag) - cmode |= OPEN_FLAG_SHLOCK * u32(Open_Flags.SHLOCK in oflag) - cmode |= OPEN_FLAG_EXLOCK * u32(Open_Flags.EXLOCK in oflag) - cmode |= OPEN_FLAG_DIRECTORY * u32(Open_Flags.DIRECTORY in oflag) - cmode |= OPEN_FLAG_NOFOLLOW * u32(Open_Flags.NOFOLLOW in oflag) - cmode |= OPEN_FLAG_SYMLINK * u32(Open_Flags.SYMLINK in oflag) - cmode |= OPEN_FLAG_EVTONLY * u32(Open_Flags.EVTONLY in oflag) - cmode |= OPEN_FLAG_CLOEXEC * u32(Open_Flags.CLOEXEC in oflag) - cmode |= OPEN_FLAG_NOFOLLOW_ANY * u32(Open_Flags.NOFOLLOW_ANY in oflag) + cmode = _sys_open_mode(oflag) result := syscall_open(cpath, cmode, cflags) state := result != -1 @@ -183,3 +195,29 @@ sys_lstat :: proc(path: string, status: ^stat) -> bool { cpath: cstring = clone_to_cstring(path, context.temp_allocator) return syscall_lstat(cpath, status) != -1 } + +sys_shm_open :: proc(name: string, oflag: Open_Flags, mode: Permission) -> (c.int, bool) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + cmode: u32 = 0 + cflags: u32 = 0 + cname: cstring = clone_to_cstring(name, context.temp_allocator) + + cflags = _sys_permission_mode(mode) + + cmode = _sys_open_mode(oflag) + + result := syscall_shm_open(cname, cmode, cflags) + state := result != -1 + + // NOTE(beau): Presently fstat doesn't report any changed permissions + // on the file descriptor even with this fchmod (which fails with a + // non-zero return). I can also reproduce this with the syscalls in c + // so I suspect it's not odin's bug. I've left the fchmod in case the + // underlying issue is fixed. + if state && cflags != 0 { + state = (syscall_fchmod(result, cflags) != -1) + } + + return result * cast(c.int)state, state +} diff --git a/core/sys/darwin/xnu_system_call_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 c7a6d6bc4..1188091a9 100644 --- a/core/sys/darwin/xnu_system_call_wrappers.odin +++ b/core/sys/darwin/xnu_system_call_wrappers.odin @@ -1,7 +1,11 @@ package darwin import "core:c" -import "core:intrinsics" +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 */ @@ -125,7 +129,7 @@ DARWIN_MAXCOMLEN :: 16 /*--==========================================================================--*/ __darwin_ino64_t :: u64 -__darwin_time_t :: u32 +__darwin_time_t :: c.long __darwin_dev_t :: i32 __darwin_mode_t :: u16 __darwin_off_t :: i64 @@ -188,43 +192,43 @@ _STRUCT_TIMEVAL :: struct { /* pwd.h */ _Password_Entry :: struct { - pw_name: cstring, /* username */ - pw_passwd: cstring, /* user password */ - pw_uid: i32, /* user ID */ - pw_gid: i32, /* group ID */ + pw_name: cstring, /* username */ + pw_passwd: cstring, /* user password */ + pw_uid: i32, /* user ID */ + pw_gid: i32, /* group ID */ pw_change: u64, /* password change time */ pw_class: cstring, /* user access class */ - pw_gecos: cstring, /* full user name */ - pw_dir: cstring, /* home directory */ - pw_shell: cstring, /* shell program */ + pw_gecos: cstring, /* full user name */ + pw_dir: cstring, /* home directory */ + pw_shell: cstring, /* shell program */ pw_expire: u64, /* account expiration */ pw_fields: i32, /* filled fields */ } /* processinfo.h */ _Proc_Bsdinfo :: struct { - pbi_flags: u32, /* if is 64bit; emulated etc */ - pbi_status: u32, - pbi_xstatus: u32, - pbi_pid: u32, - pbi_ppid: u32, - pbi_uid: u32, - pbi_gid: u32, - pbi_ruid: u32, - pbi_rgid: u32, - pbi_svuid: u32, - pbi_svgid: u32, - res: u32, - pbi_comm: [DARWIN_MAXCOMLEN]u8, - pbi_name: [2 * DARWIN_MAXCOMLEN]u8, /* empty if no name is registered */ - pbi_nfiles: u32, - pbi_pgid: u32, - pbi_pjobc: u32, - e_tdev: u32, /* controlling tty dev */ - e_tpgid: u32, /* tty process group id */ - pbi_nice: i32, - pbi_start_tvsec: u64, - pbi_start_tvusec: u64, + pbi_flags: u32, /* if is 64bit; emulated etc */ + pbi_status: u32, + pbi_xstatus: u32, + pbi_pid: u32, + pbi_ppid: u32, + pbi_uid: u32, + pbi_gid: u32, + pbi_ruid: u32, + pbi_rgid: u32, + pbi_svuid: u32, + pbi_svgid: u32, + res: u32, + pbi_comm: [DARWIN_MAXCOMLEN]u8, + pbi_name: [2 * DARWIN_MAXCOMLEN]u8, /* empty if no name is registered */ + pbi_nfiles: u32, + pbi_pgid: u32, + pbi_pjobc: u32, + e_tdev: u32, /* controlling tty dev */ + e_tpgid: u32, /* tty process group id */ + pbi_nice: i32, + pbi_start_tvsec: u64, + pbi_start_tvusec: u64, } /*--==========================================================================--*/ @@ -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)) } @@ -367,7 +371,7 @@ syscall_execve :: #force_inline proc "contextless" (path: cstring, argv: [^]cstr } syscall_munmap :: #force_inline proc "contextless" (addr: rawptr, len: u64) -> c.int { - return cast(c.int)intrinsics.syscall(unix_offset_syscall(.mmap), uintptr(addr), uintptr(len)) + return cast(c.int)intrinsics.syscall(unix_offset_syscall(.munmap), uintptr(addr), uintptr(len)) } syscall_mmap :: #force_inline proc "contextless" (addr: ^u8, len: u64, port: c.int, flags: c.int, fd: int, offset: off_t) -> ^u8 { @@ -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 { @@ -417,3 +421,15 @@ syscall_chdir :: #force_inline proc "contextless" (path: cstring) -> c.int { syscall_fchdir :: #force_inline proc "contextless" (fd: c.int, path: cstring) -> c.int { return cast(c.int)intrinsics.syscall(unix_offset_syscall(.getentropy), uintptr(fd), transmute(uintptr)path) } + +syscall_getrusage :: #force_inline proc "contextless" (who: c.int, rusage: ^RUsage) -> c.int { + return cast(c.int) intrinsics.syscall(unix_offset_syscall(.getrusage), uintptr(who), uintptr(rusage)) +} + +syscall_shm_open :: #force_inline proc "contextless" (name: cstring, oflag: u32, mode: u32) -> c.int { + return cast(c.int)intrinsics.syscall(unix_offset_syscall(.shm_open), transmute(uintptr)name, uintptr(oflag), uintptr(mode)) +} + +syscall_shm_unlink :: #force_inline proc "contextless" (name: cstring) -> c.int { + return cast(c.int)intrinsics.syscall(unix_offset_syscall(.shm_unlink), transmute(uintptr)name) +} diff --git a/core/sys/freebsd/syscalls.odin b/core/sys/freebsd/syscalls.odin new file mode 100644 index 000000000..83b51138a --- /dev/null +++ b/core/sys/freebsd/syscalls.odin @@ -0,0 +1,623 @@ +package sys_freebsd + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +import "base:intrinsics" +import "core:c" + +// FreeBSD 15 syscall numbers +// See: https://alfonsosiciliano.gitlab.io/posts/2023-08-28-freebsd-15-system-calls.html + +SYS_read : uintptr : 3 +SYS_write : uintptr : 4 +SYS_open : uintptr : 5 +SYS_close : uintptr : 6 +SYS_getpid : uintptr : 20 +SYS_recvfrom : uintptr : 29 +SYS_accept : uintptr : 30 +SYS_getsockname: uintptr : 32 +SYS_fcntl : uintptr : 92 +SYS_fsync : uintptr : 95 +SYS_socket : uintptr : 97 +SYS_connect : uintptr : 98 +SYS_bind : uintptr : 104 +SYS_listen : uintptr : 106 +SYS_sendto : uintptr : 133 +SYS_shutdown : uintptr : 134 +SYS_setsockopt : uintptr : 105 +SYS_sysctl : uintptr : 202 +SYS__umtx_op : uintptr : 454 +SYS_pread : uintptr : 475 +SYS_pwrite : uintptr : 476 +SYS_accept4 : uintptr : 541 + +// +// Odin syscall wrappers +// + +// Read input. +// +// The read() function appeared in Version 1 AT&T UNIX. +read :: proc "contextless" (fd: Fd, buf: []u8) -> (int, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_read, + cast(uintptr)fd, + cast(uintptr)raw_data(buf), + cast(uintptr)len(buf)) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + +// Write output. +// +// The write() function appeared in Version 1 AT&T UNIX. +write :: proc "contextless" (fd: Fd, buf: []u8) -> (int, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_pwrite, + cast(uintptr)fd, + cast(uintptr)raw_data(buf), + cast(uintptr)len(buf)) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + +// Open or create a file for reading, writing or executing. +// +// The open() function appeared in Version 1 AT&T UNIX. +// The openat() function was introduced in FreeBSD 8.0. +open :: proc "contextless" (path: string, flags: File_Status_Flags, mode: int = 0o000) -> (Fd, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_open, + cast(uintptr)raw_data(path), + cast(uintptr)transmute(c.int)flags, + cast(uintptr)mode) + + if !ok { + return 0, cast(Errno)result + } + + return cast(Fd)result, nil +} + +// Delete a descriptor. +// +// The open() function appeared in Version 1 AT&T UNIX. +close :: proc "contextless" (fd: Fd) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_close, + cast(uintptr)fd) + + return cast(Errno)result +} + +// Get parent or calling process identification. +// +// The getpid() function appeared in Version 7 AT&T UNIX. +getpid :: proc "contextless" () -> pid_t { + // This always succeeds. + result, _ := intrinsics.syscall_bsd(SYS_getpid) + return cast(pid_t)result +} + +// Receive message(s) from a socket. +// +// The recv() function appeared in 4.2BSD. +// The recvmmsg() function appeared in FreeBSD 11.0. +recvfrom :: proc "contextless" (s: Fd, buf: []u8, flags: Recv_Flags, from: ^$T) -> (int, Errno) +where + intrinsics.type_is_subtype_of(T, Socket_Address_Header) +{ + fromlen: socklen_t = size_of(T) + + result, ok := intrinsics.syscall_bsd(SYS_recvfrom, + cast(uintptr)s, + cast(uintptr)raw_data(buf), + cast(uintptr)len(buf), + cast(uintptr)flags, + cast(uintptr)from, + cast(uintptr)&fromlen) + + // `from.len` will be modified by the syscall, so we shouldn't need to pass + // `fromlen` back from this API. + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + +// Receive message(s) from a socket. +// +// The recv() function appeared in 4.2BSD. +// The recvmmsg() function appeared in FreeBSD 11.0. +recv :: proc "contextless" (s: Fd, buf: []u8, flags: Recv_Flags) -> (int, Errno) { + // This is a wrapper over recvfrom(). + result, ok := intrinsics.syscall_bsd(SYS_recvfrom, + cast(uintptr)s, + cast(uintptr)raw_data(buf), + cast(uintptr)len(buf), + cast(uintptr)flags, + 0, + 0) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + +// Accept a connection on a socket. +// +// The accept() system call appeared in 4.2BSD. +accept_T :: proc "contextless" (s: Fd, sockaddr: ^$T) -> (Fd, Errno) +where + intrinsics.type_is_subtype_of(T, Socket_Address_Header) +{ + // sockaddr must contain a valid pointer, or this will segfault because + // we're telling the syscall that there's memory available to write to. + addrlen: socklen_t = size_of(T) + + result, ok := intrinsics.syscall_bsd(SYS_accept, + cast(uintptr)s, + cast(uintptr)sockaddr, + cast(uintptr)&addrlen) + + if !ok { + return 0, cast(Errno)result + } + + sockaddr.len = cast(u8)addrlen + + return cast(Fd)result, nil +} + + +// Accept a connection on a socket. +// +// The accept() system call appeared in 4.2BSD. +accept_nil :: proc "contextless" (s: Fd) -> (Fd, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_accept, + cast(uintptr)s, + cast(uintptr)0, + cast(uintptr)0) + + if !ok { + return 0, cast(Errno)result + } + + return cast(Fd)result, nil +} + +accept :: proc { accept_T, accept_nil } + +// Get socket name. +// +// The getsockname() system call appeared in 4.2BSD. +getsockname :: proc "contextless" (s: Fd, sockaddr: ^$T) -> Errno { + // sockaddr must contain a valid pointer, or this will segfault because + // we're telling the syscall that there's memory available to write to. + addrlen: socklen_t = size_of(T) + + result, ok := intrinsics.syscall_bsd(SYS_getsockname, + cast(uintptr)s, + cast(uintptr)sockaddr, + cast(uintptr)&addrlen) + + if !ok { + return cast(Errno)result + } + + return nil +} + +// Synchronize changes to a file. +// +// The fsync() system call appeared in 4.2BSD. +fsync :: proc "contextless" (fd: Fd) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_fsync, + cast(uintptr)fd) + + return cast(Errno)result +} + +// File control. +// +// The fcntl() system call appeared in 4.2BSD. +// The F_DUP2FD constant first appeared in FreeBSD 7.1. +// +// NOTE: If you know at compile-time what command you're calling, use one of the +// `fcntl_*` procedures instead to preserve type safety. +fcntl :: proc "contextless" (fd: Fd, cmd: File_Control_Command, arg: c.int) -> (int, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)cmd, + cast(uintptr)arg) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + +// TODO: Implement more type-safe fcntl commands. + +fcntl_dupfd :: proc "contextless" (fd: Fd, newfd: Fd) -> (Fd, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)File_Control_Command.DUPFD, + cast(uintptr)newfd) + + if !ok { + return 0, cast(Errno)result + } + + return cast(Fd)result, nil +} + +fcntl_getfd :: proc "contextless" (fd: Fd) -> (bool, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)File_Control_Command.GETFD) + + if !ok { + return false, cast(Errno)result + } + + return result & FD_CLOEXEC > 0, nil +} + +fcntl_setfd :: proc "contextless" (fd: Fd, close_on_exec: bool) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)File_Control_Command.SETFD, + (close_on_exec ? FD_CLOEXEC : 0)) + + return cast(Errno)result +} + +fcntl_getfl :: proc "contextless" (fd: Fd) -> (File_Status_Flags, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)File_Control_Command.GETFL) + + if !ok { + return nil, cast(Errno)result + } + + return transmute(File_Status_Flags)cast(c.int)result, nil +} + +fcntl_setfl :: proc "contextless" (fd: Fd, flags: File_Status_Flags) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)File_Control_Command.SETFL, + cast(uintptr)transmute(c.int)flags) + + return cast(Errno)result +} + +fcntl_getown :: proc "contextless" (fd: Fd) -> (pid_t, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)File_Control_Command.GETOWN) + + if !ok { + return 0, cast(Errno)result + } + + return cast(pid_t)result, nil +} + +fcntl_setown :: proc "contextless" (fd: Fd, pid: pid_t) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)File_Control_Command.SETOWN, + cast(uintptr)pid) + + return cast(Errno)result +} + +fcntl_getlk :: proc "contextless" (fd: Fd, flock: ^File_Lock) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)File_Control_Command.GETLK, + cast(uintptr)flock) + + return cast(Errno)result +} + +fcntl_setlk :: proc "contextless" (fd: Fd, flock: ^File_Lock) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)File_Control_Command.SETLK, + cast(uintptr)flock) + + return cast(Errno)result +} + +fcntl_add_seals :: proc "contextless" (fd: Fd, seals: File_Seals) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)File_Control_Command.ADD_SEALS, + cast(uintptr)transmute(c.int)seals) + + return cast(Errno)result +} + +fcntl_get_seals :: proc "contextless" (fd: Fd) -> (File_Seals, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_fcntl, + cast(uintptr)fd, + cast(uintptr)File_Control_Command.GET_SEALS) + + if !ok { + return nil, cast(Errno)result + } + + return transmute(File_Seals)cast(c.int)result, nil +} + +// +// End type-safe fcntl commands. +// + +// Create an endpoint for communication. +// +// The socket() system call appeared in 4.2BSD. +socket :: proc "contextless" (domain: Protocol_Family, type: Socket_Type, protocol: Protocol) -> (Fd, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_socket, + cast(uintptr)domain, + cast(uintptr)type, + cast(uintptr)protocol) + + if !ok { + return 0, cast(Errno)result + } + + return cast(Fd)result, nil +} + +// Initiate a connection on a socket. +// +// The connect() system call appeared in 4.2BSD. +connect :: proc "contextless" (fd: Fd, sockaddr: ^$T, addrlen: socklen_t) -> Errno +where + intrinsics.type_is_subtype_of(T, Socket_Address_Header) +{ + result, _ := intrinsics.syscall_bsd(SYS_connect, + cast(uintptr)fd, + cast(uintptr)sockaddr, + cast(uintptr)addrlen) + + return cast(Errno)result +} + + +// Assign a local protocol address to a socket. +// +// The bind() system call appeared in 4.2BSD. +bind :: proc "contextless" (s: Fd, sockaddr: ^$T, addrlen: socklen_t) -> Errno +where + intrinsics.type_is_subtype_of(T, Socket_Address_Header) +{ + result, _ := intrinsics.syscall_bsd(SYS_bind, + cast(uintptr)s, + cast(uintptr)sockaddr, + cast(uintptr)addrlen) + + return cast(Errno)result +} + +// Listen for connections on a socket. +// +// The listen() system call appeared in 4.2BSD. +listen :: proc "contextless" (s: Fd, backlog: int) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_listen, + cast(uintptr)s, + cast(uintptr)backlog) + + return cast(Errno)result +} + +// Send message(s) from a socket. +// +// The send() function appeared in 4.2BSD. +// The sendmmsg() function appeared in FreeBSD 11.0. +sendto :: proc "contextless" (s: Fd, msg: []u8, flags: Send_Flags, to: ^$T) -> (int, Errno) +where + intrinsics.type_is_subtype_of(T, Socket_Address_Header) +{ + result, ok := intrinsics.syscall_bsd(SYS_sendto, + cast(uintptr)s, + cast(uintptr)raw_data(msg), + cast(uintptr)len(msg), + cast(uintptr)flags, + cast(uintptr)to, + cast(uintptr)to.len) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + +// Send message(s) from a socket. +// +// The send() function appeared in 4.2BSD. +// The sendmmsg() function appeared in FreeBSD 11.0. +send :: proc "contextless" (s: Fd, msg: []u8, flags: Send_Flags) -> (int, Errno) { + // This is a wrapper over sendto(). + result, ok := intrinsics.syscall_bsd(SYS_sendto, + cast(uintptr)s, + cast(uintptr)raw_data(msg), + cast(uintptr)len(msg), + cast(uintptr)flags, + 0, + 0) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + +// Disable sends and/or receives on a socket. +// +// The shutdown() system call appeared in 4.2BSD. +shutdown :: proc "contextless" (s: Fd, how: Shutdown_Method) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_shutdown, + cast(uintptr)s, + cast(uintptr)how) + + return cast(Errno)result +} + +// Get and set options on sockets. +// +// The getsockopt() and setsockopt() system calls appeared in 4.2BSD. +setsockopt :: proc "contextless" (s: Fd, level: Valid_Socket_Option_Level, optname: Socket_Option, optval: rawptr, optlen: socklen_t) -> Errno { + real_level: uintptr + switch which in level { + case Protocol_Family: real_level = cast(uintptr)which + case Socket_Option_Level: real_level = cast(uintptr)which + } + + result, _ := intrinsics.syscall_bsd(SYS_setsockopt, + cast(uintptr)s, + real_level, + cast(uintptr)optname, + cast(uintptr)optval, + cast(uintptr)optlen) + + return cast(Errno)result +} + +// Get or set system information. +// +// The sysctl() function first appeared in 4.4BSD. +sysctl :: proc "contextless" (mib: []MIB_Identifier, oldp: rawptr, oldlenp: ^c.size_t, newp: rawptr, newlen: c.size_t) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS_sysctl, + cast(uintptr)raw_data(mib), + cast(uintptr)len(mib), + cast(uintptr)oldp, + cast(uintptr)oldlenp, + cast(uintptr)newp, + cast(uintptr)newlen) + + return cast(Errno)result +} + +// Interface for implementation of userspace threading synchronization primitives. +// +// The _umtx_op() system call is non-standard and is used by the 1:1 Threading +// Library (libthr, -lthr) to implement IEEE Std 1003.1-2001 (“POSIX.1”) +// pthread(3) functionality. +_umtx_op :: proc "contextless" (obj: rawptr, op: Userland_Mutex_Operation, val: c.ulong, uaddr, uaddr2: rawptr) -> Errno { + result, _ := intrinsics.syscall_bsd(SYS__umtx_op, + cast(uintptr)obj, + cast(uintptr)op, + cast(uintptr)val, + cast(uintptr)uaddr, + cast(uintptr)uaddr2) + + return cast(Errno)result +} + +// Read input without modifying the file pointer. +// +// The pread() function appeared in AT&T System V Release 4 UNIX. +pread :: proc "contextless" (fd: Fd, buf: []u8, offset: off_t) -> (int, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_pread, + cast(uintptr)fd, + cast(uintptr)raw_data(buf), + cast(uintptr)len(buf), + cast(uintptr)offset) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + +// Write output without modifying the file pointer. +// +// The pwrite() function appeared in AT&T System V Release 4 UNIX. +// +// BUGS +// +// The pwrite() system call appends the file without changing the file +// offset if O_APPEND is set, contrary to IEEE Std 1003.1-2008 (“POSIX.1”) +// where pwrite() writes into offset regardless of whether O_APPEND is set. +pwrite :: proc "contextless" (fd: Fd, buf: []u8, offset: off_t) -> (int, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_pwrite, + cast(uintptr)fd, + cast(uintptr)raw_data(buf), + cast(uintptr)len(buf), + cast(uintptr)offset) + + if !ok { + return 0, cast(Errno)result + } + + return cast(int)result, nil +} + +// Accept a connection on a socket. +// +// The accept4() system call appeared in FreeBSD 10.0. +accept4_T :: proc "contextless" (s: Fd, sockaddr: ^$T, flags: Socket_Flags = {}) -> (Fd, Errno) +where + intrinsics.type_is_subtype_of(T, Socket_Address_Header) +{ + // `sockaddr` must contain a valid pointer, or this will segfault because + // we're telling the syscall that there's memory available to write to. + addrlen: u32 = size_of(T) + + result, ok := intrinsics.syscall_bsd(SYS_accept4, + cast(uintptr)s, + cast(uintptr)sockaddr, + cast(uintptr)&addrlen, + cast(uintptr)transmute(c.int)flags) + + if !ok { + return 0, cast(Errno)result + } + + sockaddr.len = cast(u8)addrlen + + return cast(Fd)result, nil +} + +// Accept a connection on a socket. +// +// The accept4() system call appeared in FreeBSD 10.0. +accept4_nil :: proc "contextless" (s: Fd, flags: Socket_Flags = {}) -> (Fd, Errno) { + result, ok := intrinsics.syscall_bsd(SYS_accept4, + cast(uintptr)s, + cast(uintptr)0, + cast(uintptr)0, + cast(uintptr)transmute(c.int)flags) + + if !ok { + return 0, cast(Errno)result + } + + return cast(Fd)result, nil +} + +accept4 :: proc { accept4_nil, accept4_T } diff --git a/core/sys/freebsd/types.odin b/core/sys/freebsd/types.odin new file mode 100644 index 000000000..37e8abf68 --- /dev/null +++ b/core/sys/freebsd/types.odin @@ -0,0 +1,1587 @@ +package sys_freebsd + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +import "core:c" + +// These definitions have been extracted from a system running FreeBSD 14.0-RELEASE. +// Most comments come from system header files. +// +// Where applicable, original C struct and define names are indicated in line +// comments above Odin declarations. +// +// This data is separated into blocks by original header file. If you happen to +// add or change something in this file, mind the organizational structure. + +Fd :: distinct c.int + +// +// #include +// & +// #include +// + +time_t :: distinct i64 + +// +// #include +// + +off_t :: distinct i64 +pid_t :: distinct i32 +sa_family_t :: distinct u8 +socklen_t :: distinct u32 +suseconds_t :: distinct c.long /* microseconds (signed) */ + +// +// #include +// + +in_port_t :: distinct u16be + +// +// #include +// + +timespec :: struct { + sec: time_t, /* seconds */ + nsec: c.long, /* and nanoseconds */ +} + +// +// #include +// + +timeval :: struct { + sec: time_t, /* seconds */ + usec: suseconds_t, /* and microseconds */ +} + +// +// #include +// + +Errno :: enum c.int { + NONE = 0, + EPERM = 1, + ENOENT = 2, + ESRCH = 3, + EINTR = 4, + EIO = 5, + ENXIO = 6, + E2BIG = 7, + ENOEXEC = 8, + EBADF = 9, + ECHILD = 10, + EDEADLK = 11, + ENOMEM = 12, + EACCES = 13, + EFAULT = 14, + ENOTBLK = 15, + EBUSY = 16, + EEXIST = 17, + EXDEV = 18, + ENODEV = 19, + ENOTDIR = 20, + EISDIR = 21, + EINVAL = 22, + ENFILE = 23, + EMFILE = 24, + ENOTTY = 25, + ETXTBSY = 26, + EFBIG = 27, + ENOSPC = 28, + ESPIPE = 29, + EROFS = 30, + EMLINK = 31, + EPIPE = 32, + EDOM = 33, + ERANGE = 34, + EAGAIN = 35, + EWOULDBLOCK = EAGAIN, + EINPROGRESS = 36, + EALREADY = 37, + ENOTSOCK = 38, + EDESTADDRREQ = 39, + EMSGSIZE = 40, + EPROTOTYPE = 41, + ENOPROTOOPT = 42, + EPROTONOSUPPORT = 43, + ESOCKTNOSUPPORT = 44, + EOPNOTSUPP = 45, + ENOTSUP = EOPNOTSUPP, + EPFNOSUPPORT = 46, + EAFNOSUPPORT = 47, + EADDRINUSE = 48, + EADDRNOTAVAIL = 49, + ENETDOWN = 50, + ENETUNREACH = 51, + ENETRESET = 52, + ECONNABORTED = 53, + ECONNRESET = 54, + ENOBUFS = 55, + EISCONN = 56, + ENOTCONN = 57, + ESHUTDOWN = 58, + ETOOMANYREFS = 59, + ETIMEDOUT = 60, + ECONNREFUSED = 61, + ELOOP = 62, + ENAMETOOLONG = 63, + EHOSTDOWN = 64, + EHOSTUNREACH = 65, + ENOTEMPTY = 66, + EPROCLIM = 67, + EUSERS = 68, + EDQUOT = 69, + ESTALE = 70, + EREMOTE = 71, + EBADRPC = 72, + ERPCMISMATCH = 73, + EPROGUNAVAIL = 74, + EPROGMISMATCH = 75, + EPROCUNAVAIL = 76, + ENOLCK = 77, + ENOSYS = 78, + EFTYPE = 79, + EAUTH = 80, + ENEEDAUTH = 81, + EIDRM = 82, + ENOMSG = 83, + EOVERFLOW = 84, + ECANCELED = 85, + EILSEQ = 86, + ENOATTR = 87, + EDOOFUS = 88, + EBADMSG = 89, + EMULTIHOP = 90, + ENOLINK = 91, + EPROTO = 92, + ENOTCAPABLE = 93, + ECAPMODE = 94, + ENOTRECOVERABLE = 95, + EOWNERDEAD = 96, + EINTEGRITY = 97, +} + +// +// #include +// + +/* + * Types + */ +// #define SOCK_* +Socket_Type :: enum c.int { + STREAM = 1, /* stream socket */ + DGRAM = 2, /* datagram socket */ + RAW = 3, /* raw-protocol interface */ + RDM = 4, /* reliably-delivered message */ + SEQPACKET = 5, /* sequenced packet stream */ + + /* + * Creation flags, OR'ed into socket() and socketpair() type argument. + */ + CLOEXEC = 0x10000000, + NONBLOCK = 0x20000000, +} + +Socket_Flag_Index :: enum c.int { + CLOEXEC = 28, // 0x10000000 + NONBLOCK = 29, // 0x20000000 +} + +Socket_Flags :: bit_set[Socket_Flag_Index; c.int] + +/* + * Option flags per-socket. + */ +// #define SO_* +Socket_Option :: enum c.int { + DEBUG = 0x00000001, /* turn on debugging info recording */ + ACCEPTCONN = 0x00000002, /* socket has had listen() */ + REUSEADDR = 0x00000004, /* allow local address reuse */ + KEEPALIVE = 0x00000008, /* keep connections alive */ + DONTROUTE = 0x00000010, /* just use interface addresses */ + BROADCAST = 0x00000020, /* permit sending of broadcast msgs */ + USELOOPBACK = 0x00000040, /* bypass hardware when possible */ + LINGER = 0x00000080, /* linger on close if data present */ + OOBINLINE = 0x00000100, /* leave received OOB data in line */ + REUSEPORT = 0x00000200, /* allow local address & port reuse */ + TIMESTAMP = 0x00000400, /* timestamp received dgram traffic */ + NOSIGPIPE = 0x00000800, /* no SIGPIPE from EPIPE */ + ACCEPTFILTER = 0x00001000, /* there is an accept filter */ + BINTIME = 0x00002000, /* timestamp received dgram traffic */ + NO_OFFLOAD = 0x00004000, /* socket cannot be offloaded */ + NO_DDP = 0x00008000, /* disable direct data placement */ + REUSEPORT_LB = 0x00010000, /* reuse with load balancing */ + RERROR = 0x00020000, /* keep track of receive errors */ + + /* + * Additional options, not kept in so_options. + */ + SNDBUF = 0x1001, /* send buffer size */ + RCVBUF = 0x1002, /* receive buffer size */ + SNDLOWAT = 0x1003, /* send low-water mark */ + RCVLOWAT = 0x1004, /* receive low-water mark */ + SNDTIMEO = 0x1005, /* send timeout */ + RCVTIMEO = 0x1006, /* receive timeout */ + ERROR = 0x1007, /* get error status and clear */ + TYPE = 0x1008, /* get socket type */ + LABEL = 0x1009, /* socket's MAC label */ + PEERLABEL = 0x1010, /* socket's peer's MAC label */ + LISTENQLIMIT = 0x1011, /* socket's backlog limit */ + LISTENQLEN = 0x1012, /* socket's complete queue length */ + LISTENINCQLEN = 0x1013, /* socket's incomplete queue length */ + SETFIB = 0x1014, /* use this FIB to route */ + USER_COOKIE = 0x1015, /* user cookie (dummynet etc.) */ + PROTOCOL = 0x1016, /* get socket protocol (Linux name) */ + PROTOTYPE = PROTOCOL, /* alias for SO_PROTOCOL (SunOS name) */ + TS_CLOCK = 0x1017, /* clock type used for SO_TIMESTAMP */ + MAX_PACING_RATE = 0x1018, /* socket's max TX pacing rate (Linux name) */ + DOMAIN = 0x1019, /* get socket domain */ + + TS_REALTIME_MICRO = 0, /* microsecond resolution, realtime */ + TS_BINTIME = 1, /* sub-nanosecond resolution, realtime */ + TS_REALTIME = 2, /* nanosecond resolution, realtime */ + TS_MONOTONIC = 3, /* nanosecond resolution, monotonic */ + TS_DEFAULT = TS_REALTIME_MICRO, + TS_CLOCK_MAX = TS_MONOTONIC, +} + +Valid_Socket_Option_Level :: union #no_nil { + Protocol_Family, + Socket_Option_Level, +} + +/* + * Level number for (get/set)sockopt() to apply to socket itself. + */ +// #define SOL_* +Socket_Option_Level :: enum c.int { + SOCKET = 0xffff, /* options for socket level */ +} + +// #define MSG_* +Message_Flag :: enum c.int { + OOB = 0x00000001, /* process out-of-band data */ + PEEK = 0x00000002, /* peek at incoming message */ + DONTROUTE = 0x00000004, /* send without using routing tables */ + EOR = 0x00000008, /* data completes record */ + TRUNC = 0x00000010, /* data discarded before delivery */ + CTRUNC = 0x00000020, /* control data lost before delivery */ + WAITALL = 0x00000040, /* wait for full request or error */ + DONTWAIT = 0x00000080, /* this message should be nonblocking */ + EOF = 0x00000100, /* data completes connection */ + /* 0x00000200 unused */ + /* 0x00000400 unused */ + /* 0x00000800 unused */ + /* 0x00001000 unused */ + NOTIFICATION = 0x00002000, /* SCTP notification */ + NBIO = 0x00004000, /* FIONBIO mode, used by fifofs */ + COMPAT = 0x00008000, /* used in sendit() */ + SOCALLBCK = 0x00010000, /* for use by socket callbacks - soreceive (TCP) */ + NOSIGNAL = 0x00020000, /* do not generate SIGPIPE on EOF */ + CMSG_CLOEXEC = 0x00040000, /* make received fds close-on-exec */ + WAITFORONE = 0x00080000, /* for recvmmsg() */ +} + +// Specific subset of `MSG_*` defines that are only for `recv*`. +Recv_Flags :: enum c.int { + NONE = 0, + OOB = cast(c.int)Message_Flag.OOB, /* process out-of-band data */ + PEEK = cast(c.int)Message_Flag.PEEK, /* peek at incoming message */ + TRUNC = cast(c.int)Message_Flag.TRUNC, /* return real packet or datagram length */ + WAITALL = cast(c.int)Message_Flag.WAITALL, /* wait for full request or error */ + DONTWAIT = cast(c.int)Message_Flag.DONTWAIT, /* do not block */ + CMSG_CLOEXEC = cast(c.int)Message_Flag.CMSG_CLOEXEC, /* set received fds close-on-exec */ + WAITFORONE = cast(c.int)Message_Flag.WAITFORONE, /* do not block after receiving the first message */ +} + +// Specific subset of `MSG_*` defines that are only for `send*`. +Send_Flags :: enum c.int { + NONE = 0, + OOB = cast(c.int)Message_Flag.OOB, /* process out-of-band data */ + DONTROUTE = cast(c.int)Message_Flag.DONTROUTE, /* bypass routing, use direct interface */ + EOR = cast(c.int)Message_Flag.EOR, /* data completes record */ + DONTWAIT = cast(c.int)Message_Flag.DONTWAIT, /* do not block */ + EOF = cast(c.int)Message_Flag.EOF, /* data completes transaction */ + NOSIGNAL = cast(c.int)Message_Flag.NOSIGNAL, /* do not generate SIGPIPE on EOF */ +} + +// Socket address struct header without protocol-specific data. +// +// Inherit from this if you want a custom socket address datatype for use with +// bind(), listen(), et cetera. +Socket_Address_Header :: struct #packed { + len: c.uchar, /* address length */ + family: Address_Family, /* address family */ +} + +// struct sockaddr +Socket_Address_Basic :: struct #packed { + using _: Socket_Address_Header, + data: [14]c.char, +} + +/* + * howto arguments for shutdown(2), specified by Posix.1g. + */ +// #define SHUT_* +Shutdown_Method :: enum c.int { + RD = 0, /* shut down the reading side */ + WR = 1, /* shut down the writing side */ + RDWR = 2, /* shut down both sides */ +} + +// #define AF_* +Address_Family :: enum sa_family_t { + UNSPEC = 0, + LOCAL = 1, + UNIX = LOCAL, + INET = 2, + IMPLINK = 3, + PUP = 4, + CHAOS = 5, + NETBIOS = 6, + ISO = 7, + OSI = ISO, + ECMA = 8, + DATAKIT = 9, + CCITT = 10, + SNA = 11, + DECnet = 12, + DLI = 13, + LAT = 14, + HYLINK = 15, + APPLETALK = 16, + ROUTE = 17, + LINK = 18, + PSEUDO_XTP = 19, + COIP = 20, + CNT = 21, + PSEUDO_RTIP = 22, + IPX = 23, + SIP = 24, + PSEUDO_PIP = 25, + ISDN = 26, + E164 = ISDN, + PSEUDO_KEY = 27, + INET6 = 28, + NATM = 29, + ATM = 30, + NETGRAPH = 32, + SLOW = 33, + SCLUSTER = 34, + ARP = 35, + BLUETOOTH = 36, + IEEE80211 = 37, + NETLINK = 38, + INET_SDP = 40, + INET6_SDP = 42, + HYPERV = 43, + DIVERT = 44, + MAX = 44, + VENDOR00 = 39, + VENDOR01 = 41, + VENDOR03 = 45, + VENDOR04 = 47, + VENDOR05 = 49, + VENDOR06 = 51, + VENDOR07 = 53, + VENDOR08 = 55, + VENDOR09 = 57, + VENDOR10 = 59, + VENDOR11 = 61, + VENDOR12 = 63, + VENDOR13 = 65, + VENDOR14 = 67, + VENDOR15 = 69, + VENDOR16 = 71, + VENDOR17 = 73, + VENDOR18 = 75, + VENDOR19 = 77, + VENDOR20 = 79, + VENDOR21 = 81, + VENDOR22 = 83, + VENDOR23 = 85, + VENDOR24 = 87, + VENDOR25 = 89, + VENDOR26 = 91, + VENDOR27 = 93, + VENDOR28 = 95, + VENDOR29 = 97, + VENDOR30 = 99, + VENDOR31 = 101, + VENDOR32 = 103, + VENDOR33 = 105, + VENDOR34 = 107, + VENDOR35 = 109, + VENDOR36 = 111, + VENDOR37 = 113, + VENDOR38 = 115, + VENDOR39 = 117, + VENDOR40 = 119, + VENDOR41 = 121, + VENDOR42 = 123, + VENDOR43 = 125, + VENDOR44 = 127, + VENDOR45 = 129, + VENDOR46 = 131, + VENDOR47 = 133, +} + +// #define PF_* +Protocol_Family :: enum sa_family_t { + UNSPEC = cast(sa_family_t)Address_Family.UNSPEC, + LOCAL = cast(sa_family_t)Address_Family.LOCAL, + UNIX = LOCAL, + INET = cast(sa_family_t)Address_Family.INET, + IMPLINK = cast(sa_family_t)Address_Family.IMPLINK, + PUP = cast(sa_family_t)Address_Family.PUP, + CHAOS = cast(sa_family_t)Address_Family.CHAOS, + NETBIOS = cast(sa_family_t)Address_Family.NETBIOS, + ISO = cast(sa_family_t)Address_Family.ISO, + OSI = cast(sa_family_t)Address_Family.ISO, + ECMA = cast(sa_family_t)Address_Family.ECMA, + DATAKIT = cast(sa_family_t)Address_Family.DATAKIT, + CCITT = cast(sa_family_t)Address_Family.CCITT, + SNA = cast(sa_family_t)Address_Family.SNA, + DECnet = cast(sa_family_t)Address_Family.DECnet, + DLI = cast(sa_family_t)Address_Family.DLI, + LAT = cast(sa_family_t)Address_Family.LAT, + HYLINK = cast(sa_family_t)Address_Family.HYLINK, + APPLETALK = cast(sa_family_t)Address_Family.APPLETALK, + ROUTE = cast(sa_family_t)Address_Family.ROUTE, + LINK = cast(sa_family_t)Address_Family.LINK, + XTP = cast(sa_family_t)Address_Family.PSEUDO_XTP, + COIP = cast(sa_family_t)Address_Family.COIP, + CNT = cast(sa_family_t)Address_Family.CNT, + SIP = cast(sa_family_t)Address_Family.SIP, + IPX = cast(sa_family_t)Address_Family.IPX, + RTIP = cast(sa_family_t)Address_Family.PSEUDO_RTIP, + PIP = cast(sa_family_t)Address_Family.PSEUDO_PIP, + ISDN = cast(sa_family_t)Address_Family.ISDN, + KEY = cast(sa_family_t)Address_Family.PSEUDO_KEY, + INET6 = cast(sa_family_t)Address_Family.INET6, + NATM = cast(sa_family_t)Address_Family.NATM, + ATM = cast(sa_family_t)Address_Family.ATM, + NETGRAPH = cast(sa_family_t)Address_Family.NETGRAPH, + SLOW = cast(sa_family_t)Address_Family.SLOW, + SCLUSTER = cast(sa_family_t)Address_Family.SCLUSTER, + ARP = cast(sa_family_t)Address_Family.ARP, + BLUETOOTH = cast(sa_family_t)Address_Family.BLUETOOTH, + IEEE80211 = cast(sa_family_t)Address_Family.IEEE80211, + NETLINK = cast(sa_family_t)Address_Family.NETLINK, + INET_SDP = cast(sa_family_t)Address_Family.INET_SDP, + INET6_SDP = cast(sa_family_t)Address_Family.INET6_SDP, + DIVERT = cast(sa_family_t)Address_Family.DIVERT, + MAX = cast(sa_family_t)Address_Family.MAX, +} + +// +// /etc/protocols +// + +Protocol :: enum c.int { + IP = 0, + ICMP = 1, + IGMP = 2, + GGP = 3, + IP_ENCAP = 4, + ST2 = 5, + TCP = 6, + CBT = 7, + EGP = 8, + IGP = 9, + BBN_RCC_MON = 10, + NVP_II = 11, + PUP = 12, + ARGUS = 13, + EMCON = 14, + XNET = 15, + CHAOS = 16, + UDP = 17, + MUX = 18, + DCN_MEAS = 19, + HMP = 20, + PRM = 21, + XNS_IDP = 22, + TRUNK_1 = 23, + TRUNK_2 = 24, + LEAF_1 = 25, + LEAF_2 = 26, + RDP = 27, + IRTP = 28, + ISO_TP4 = 29, + NETBLT = 30, + MFE_NSP = 31, + MERIT_INP = 32, + DCCP = 33, + THREE_PC = 34, + IDPR = 35, + XTP = 36, + DDP = 37, + IDPR_CMTP = 38, + TP_PlusPlus = 39, + IL = 40, + IPV6 = 41, + SDRP = 42, + IPV6_ROUTE = 43, + IPV6_FRAG = 44, + IDRP = 45, + RSVP = 46, + GRE = 47, + DSR = 48, + BNA = 49, + ESP = 50, + AH = 51, + I_NLSP = 52, + SWIPE = 53, + NARP = 54, + MOBILE = 55, + TLSP = 56, + SKIP = 57, + IPV6_ICMP = 58, + IPV6_NONXT = 59, + IPV6_OPTS = 60, + CFTP = 62, + SAT_EXPAK = 64, + KRYPTOLAN = 65, + RVD = 66, + IPPC = 67, + SAT_MON = 69, + VISA = 70, + IPCV = 71, + CPNX = 72, + CPHB = 73, + WSN = 74, + PVP = 75, + BR_SAT_MON = 76, + SUN_ND = 77, + WB_MON = 78, + WB_EXPAK = 79, + ISO_IP = 80, + VMTP = 81, + SECURE_VMTP = 82, + VINES = 83, + TTP = 84, + IPTM = 84, + NSFNET_IGP = 85, + DGP = 86, + TCF = 87, + EIGRP = 88, + OSPFIGP = 89, + Sprite_RPC = 90, + LARP = 91, + MTP = 92, + AX_25 = 93, + IPIP = 94, + MICP = 95, + SCC_SP = 96, + ETHERIP = 97, + ENCAP = 98, + GMTP = 100, + IFMP = 101, + PNNI = 102, + PIM = 103, + ARIS = 104, + SCPS = 105, + QNX = 106, + A_N = 107, + IPComp = 108, + SNP = 109, + Compaq_Peer = 110, + IPX_in_IP = 111, + CARP = 112, + PGM = 113, + L2TP = 115, + DDX = 116, + IATP = 117, + STP = 118, + SRP = 119, + UTI = 120, + SMP = 121, + SM = 122, + PTP = 123, + ISIS = 124, + FIRE = 125, + CRTP = 126, + CRUDP = 127, + SSCOPMCE = 128, + IPLT = 129, + SPS = 130, + PIPE = 131, + SCTP = 132, + FC = 133, + RSVP_E2E_IGNORE = 134, + Mobility_Header = 135, + UDPLite = 136, + MPLS_IN_IP = 137, + MANET = 138, + HIP = 139, + SHIM6 = 140, + WESP = 141, + ROHC = 142, + PFSYNC = 240, + DIVERT = 258, +} + +// +// #include +// + +/* + * Constants used for fcntl(2) + */ + +/* command values */ +// #define F_* +File_Control_Command :: enum c.int { + DUPFD = 0, /* duplicate file descriptor */ + GETFD = 1, /* get file descriptor flags */ + SETFD = 2, /* set file descriptor flags */ + GETFL = 3, /* get file status flags */ + SETFL = 4, /* set file status flags */ + GETOWN = 5, /* get SIGIO/SIGURG proc/pgrp */ + SETOWN = 6, /* set SIGIO/SIGURG proc/pgrp */ + OGETLK = 7, /* get record locking information */ + OSETLK = 8, /* set record locking information */ + OSETLKW = 9, /* F_SETLK; wait if blocked */ + DUP2FD = 10, /* duplicate file descriptor to arg */ + GETLK = 11, /* get record locking information */ + SETLK = 12, /* set record locking information */ + SETLKW = 13, /* F_SETLK; wait if blocked */ + SETLK_REMOTE = 14, /* debugging support for remote locks */ + READAHEAD = 15, /* read ahead */ + RDAHEAD = 16, /* Darwin compatible read ahead */ + DUPFD_CLOEXEC = 17, /* Like F_DUPFD, but FD_CLOEXEC is set */ + DUP2FD_CLOEXEC = 18, /* Like F_DUP2FD, but FD_CLOEXEC is set */ + ADD_SEALS = 19, + GET_SEALS = 20, + ISUNIONSTACK = 21, /* Kludge for libc, don't use it. */ + KINFO = 22, /* Return kinfo_file for this fd */ +} + +/* Seals (F_ADD_SEALS, F_GET_SEALS). */ +// #define F_SEAL_* +File_Seal_Index :: enum c.int { + SEAL = 0, // 0x0001, /* Prevent adding sealings */ + SHRINK = 1, // 0x0002, /* May not shrink */ + GROW = 2, // 0x0004, /* May not grow */ + WRITE = 3, // 0x0008, /* May not write */ +} + +File_Seals :: bit_set[File_Seal_Index; c.int] + +/* file descriptor flags (F_GETFD, F_SETFD) */ +FD_CLOEXEC :: 1 /* close-on-exec flag */ + +/* record locking flags (F_GETLK, F_SETLK, F_SETLKW) */ +// #define F_* +Record_Lock_Flag :: enum c.int { + RDLCK = 1, /* shared or read lock */ + UNLCK = 2, /* unlock */ + WRLCK = 3, /* exclusive or write lock */ + UNLCKSYS = 4, /* purge locks for a given system ID */ + CANCEL = 5, /* cancel an async lock request */ +} + +// struct flock +File_Lock :: struct { + start: off_t, /* starting offset */ + len: off_t, /* len = 0 means until end of file */ + pid: pid_t, /* lock owner */ + type: Record_Lock_Flag, /* lock type: read/write, etc. */ + whence: c.short, /* type of l_start */ + sysid: c.int, /* remote system id or zero for local */ +} + +/* + * File status flags: these are used by open(2), fcntl(2). + * They are also used (indirectly) in the kernel file structure f_flags, + * which is a superset of the open/fcntl flags. Open flags and f_flags + * are inter-convertible using OFLAGS(fflags) and FFLAGS(oflags). + * Open/fcntl flags begin with O_; kernel-internal flags begin with F. + */ +File_Status_Flag :: enum c.int { + /* open-only flags */ + RDONLY = 0x0000, /* open for reading only */ + WRONLY = 0x0001, /* open for writing only */ + RDWR = 0x0002, /* open for reading and writing */ + ACCMODE = 0x0003, /* mask for above modes */ + + /**/ + NONBLOCK = 0x0004, /* no delay */ + APPEND = 0x0008, /* set append mode */ + SHLOCK = 0x0010, /* open with shared file lock */ + EXLOCK = 0x0020, /* open with exclusive file lock */ + ASYNC = 0x0040, /* signal pgrp when data ready */ + FSYNC = 0x0080, /* synchronous writes */ + SYNC = 0x0080, /* POSIX synonym for O_FSYNC */ + NOFOLLOW = 0x0100, /* don't follow symlinks */ + CREAT = 0x0200, /* create if nonexistent */ + TRUNC = 0x0400, /* truncate to zero length */ + EXCL = 0x0800, /* error if already exists */ + + /* Defined by POSIX 1003.1; BSD default, but must be distinct from O_RDONLY. */ + NOCTTY = 0x8000, /* don't assign controlling terminal */ + + /* Attempt to bypass buffer cache */ + DIRECT = 0x00010000, + + DIRECTORY = 0x00020000, /* Fail if not directory */ + EXEC = 0x00040000, /* Open for execute only */ + SEARCH = EXEC, + + /* Defined by POSIX 1003.1-2008; BSD default, but reserve for future use. */ + TTY_INIT = 0x00080000, /* Restore default termios attributes */ + + CLOEXEC = 0x00100000, + VERIFY = 0x00200000, /* open only after verification */ + PATH = 0x00400000, /* fd is only a path */ + RESOLVE_BENEATH = 0x00800000, /* Do not allow name resolution to walk out of cwd */ + DSYNC = 0x01000000, /* POSIX data sync */ + EMPTY_PATH = 0x02000000, +} + +File_Status_Index :: enum c.int { + // No RDONLY (0x00), as that is implied and also impossible to express in a bit_set. + + // The comments below come from the documentation for `fcntl`. + WRONLY = 0, + RDWR = 1, + + /* Non-blocking I/O; if no data is available to a read(2) + system call, or if a write(2) operation would block, the + read or write call returns -1 with the error EAGAIN. */ + NONBLOCK = 2, + + /* Force each write to append at the end of file; corresponds + to the O_APPEND flag of open(2). */ + APPEND = 3, + + SHLOCK = 4, + EXLOCK = 5, + + /* Enable the SIGIO signal to be sent to the process group when + I/O is possible, e.g., upon availability of data to be read. */ + ASYNC = 6, + + /* Enable synchronous writes. Corresponds to the O_SYNC flag + of open(2). O_FSYNC is an historical synonym for O_SYNC. */ + SYNC = 7, + + FSYNC = 7, + NOFOLLOW = 8, + CREAT = 9, + TRUNC = 10, + EXCL = 11, + + NOCTTY = 15, + + /* Minimize or eliminate the cache effects of reading and + writing. The system will attempt to avoid caching the data + you read or write. If it cannot avoid caching the data, it + will minimize the impact the data has on the cache. Use of + this flag can drastically reduce performance if not used + with care. */ + DIRECT = 16, + + DIRECTORY = 17, + EXEC = 18, + TTY_INIT = 19, + CLOEXEC = 20, + VERIFY = 21, + PATH = 22, + RESOLVE_BENEATH = 23, + + /* Enable synchronous data writes. Corresponds to the O_DSYNC + flag of open(2). */ + DSYNC = 24, + + EMPTY_PATH = 25, +} + +File_Status_Flags :: bit_set[File_Status_Index; c.int] + +// +// #include +// + +@private _SS_MAXSIZE :: 128 +@private _SS_ALIGNSIZE :: size_of(i64) +@private _SS_PAD1SIZE :: _SS_ALIGNSIZE - size_of(c.uchar) - size_of(Address_Family) +@private _SS_PAD2SIZE :: _SS_MAXSIZE - size_of(c.uchar) - size_of(Address_Family) - _SS_PAD1SIZE - _SS_ALIGNSIZE + +/* + * RFC 2553: protocol-independent placeholder for socket addresses + */ +// struct sockaddr_storage +Socket_Address_Storage :: struct { + using _: Socket_Address_Header, + _pad1: [_SS_PAD1SIZE]c.char, + _align: i64, /* force desired struct alignment */ + _pad2: [_SS_PAD2SIZE]c.char, +} + +// +// #include +// + +// MIB, or Management Information Base. Used in sysctl(). +MIB_Identifier :: enum c.int { + /* + * Top-level identifiers + */ + CTL_SYSCTL = 0, /* "magic" numbers */ + CTL_KERN = 1, /* "high kernel": proc, limits */ + CTL_VM = 2, /* virtual memory */ + CTL_VFS = 3, /* filesystem, mount type is next */ + CTL_NET = 4, /* network, see socket.h */ + CTL_DEBUG = 5, /* debugging parameters */ + CTL_HW = 6, /* generic cpu/io */ + CTL_MACHDEP = 7, /* machine dependent */ + CTL_USER = 8, /* user-level */ + CTL_P1003_1B = 9, /* POSIX 1003.1B */ + + /* + * CTL_SYSCTL identifiers + */ + CTL_SYSCTL_DEBUG = 0, /* printf all nodes */ + CTL_SYSCTL_NAME = 1, /* string name of OID */ + CTL_SYSCTL_NEXT = 2, /* next OID, honoring CTLFLAG_SKIP */ + CTL_SYSCTL_NAME2OID = 3, /* int array of name */ + CTL_SYSCTL_OIDFMT = 4, /* OID's kind and format */ + CTL_SYSCTL_OIDDESCR = 5, /* OID's description */ + CTL_SYSCTL_OIDLABEL = 6, /* aggregation label */ + CTL_SYSCTL_NEXTNOSKIP = 7, /* next OID, ignoring CTLFLAG_SKIP */ + + /* + * CTL_KERN identifiers + */ + KERN_OSTYPE = 1, /* string: system version */ + KERN_OSRELEASE = 2, /* string: system release */ + KERN_OSREV = 3, /* int: system revision */ + KERN_VERSION = 4, /* string: compile time info */ + KERN_MAXVNODES = 5, /* int: max vnodes */ + KERN_MAXPROC = 6, /* int: max processes */ + KERN_MAXFILES = 7, /* int: max open files */ + KERN_ARGMAX = 8, /* int: max arguments to exec */ + KERN_SECURELVL = 9, /* int: system security level */ + KERN_HOSTNAME = 10, /* string: hostname */ + KERN_HOSTID = 11, /* int: host identifier */ + KERN_CLOCKRATE = 12, /* struct: struct clockrate */ + /* was: #define KERN_VNODE13; disabled in 2003 and removed in 2023 */ + KERN_PROC = 14, /* struct: process entries */ + KERN_FILE = 15, /* struct: file entries */ + KERN_PROF = 16, /* node: kernel profiling info */ + KERN_POSIX1 = 17, /* int: POSIX.1 version */ + KERN_NGROUPS = 18, /* int: # of supplemental group ids */ + KERN_JOB_CONTROL = 19, /* int: is job control available */ + KERN_SAVED_IDS = 20, /* int: saved set-user/group-ID */ + KERN_BOOTTIME = 21, /* struct: time kernel was booted */ + KERN_NISDOMAINNAME = 22, /* string: YP domain name */ + KERN_UPDATEINTERVAL = 23, /* int: update process sleep time */ + KERN_OSRELDATE = 24, /* int: kernel release date */ + KERN_NTP_PLL = 25, /* node: NTP PLL control */ + KERN_BOOTFILE = 26, /* string: name of booted kernel */ + KERN_MAXFILESPERPROC = 27, /* int: max open files per proc */ + KERN_MAXPROCPERUID = 28, /* int: max processes per uid */ + KERN_DUMPDEV = 29, /* struct cdev *: device to dump on */ + KERN_IPC = 30, /* node: anything related to IPC */ + KERN_DUMMY = 31, /* unused */ + KERN_PS_STRINGS = 32, /* int: address of PS_STRINGS */ + KERN_USRSTACK = 33, /* int: address of USRSTACK */ + KERN_LOGSIGEXIT = 34, /* int: do we log sigexit procs? */ + KERN_IOV_MAX = 35, /* int: value of UIO_MAXIOV */ + KERN_HOSTUUID = 36, /* string: host UUID identifier */ + KERN_ARND = 37, /* int: from arc4rand() */ + KERN_MAXPHYS = 38, /* int: MAXPHYS value */ + KERN_LOCKF = 39, /* struct: lockf reports */ + /* + * KERN_PROC subtypes + */ + KERN_PROC_ALL = 0, /* everything */ + KERN_PROC_PID = 1, /* by process id */ + KERN_PROC_PGRP = 2, /* by process group id */ + KERN_PROC_SESSION = 3, /* by session of pid */ + KERN_PROC_TTY = 4, /* by controlling tty */ + KERN_PROC_UID = 5, /* by effective uid */ + KERN_PROC_RUID = 6, /* by real uid */ + KERN_PROC_ARGS = 7, /* get/set arguments/proctitle */ + KERN_PROC_PROC = 8, /* only return procs */ + KERN_PROC_SV_NAME = 9, /* get syscall vector name */ + KERN_PROC_RGID = 10, /* by real group id */ + KERN_PROC_GID = 11, /* by effective group id */ + KERN_PROC_PATHNAME = 12, /* path to executable */ + KERN_PROC_OVMMAP = 13, /* Old VM map entries for process */ + KERN_PROC_OFILEDESC = 14, /* Old file descriptors for process */ + KERN_PROC_KSTACK = 15, /* Kernel stacks for process */ + KERN_PROC_INC_THREAD = 0x10, /* modifier for pid, pgrp, tty, uid, ruid, gid, rgid and proc. This effectively uses 16-31 */ + KERN_PROC_VMMAP = 32, /* VM map entries for process */ + KERN_PROC_FILEDESC = 33, /* File descriptors for process */ + KERN_PROC_GROUPS = 34, /* process groups */ + KERN_PROC_ENV = 35, /* get environment */ + KERN_PROC_AUXV = 36, /* get ELF auxiliary vector */ + KERN_PROC_RLIMIT = 37, /* process resource limits */ + KERN_PROC_PS_STRINGS = 38, /* get ps_strings location */ + KERN_PROC_UMASK = 39, /* process umask */ + KERN_PROC_OSREL = 40, /* osreldate for process binary */ + KERN_PROC_SIGTRAMP = 41, /* signal trampoline location */ + KERN_PROC_CWD = 42, /* process current working directory */ + KERN_PROC_NFDS = 43, /* number of open file descriptors */ + KERN_PROC_SIGFASTBLK = 44, /* address of fastsigblk magic word */ + KERN_PROC_VM_LAYOUT = 45, /* virtual address space layout info */ + + /* + * KERN_IPC identifiers + */ + KIPC_MAXSOCKBUF = 1, /* int: max size of a socket buffer */ + KIPC_SOCKBUF_WASTE = 2, /* int: wastage factor in sockbuf */ + KIPC_SOMAXCONN = 3, /* int: max length of connection q */ + KIPC_MAX_LINKHDR = 4, /* int: max length of link header */ + KIPC_MAX_PROTOHDR = 5, /* int: max length of network header */ + KIPC_MAX_HDR = 6, /* int: max total length of headers */ + KIPC_MAX_DATALEN = 7, /* int: max length of data? */ + + /* + * Definitions for network related sysctl, CTL_NET. + * + * Second level is protocol family. + * Third level is protocol number. + * + * Further levels are defined by the individual families. + */ + + /* + * PF_ROUTE - Routing table + * + * Three additional levels are defined: + * Fourth: address family, 0 is wildcard + * Fifth: type of info, defined below + * Sixth: flag(s) to mask with for NET_RT_FLAGS + */ + NET_RT_DUMP = 1, /* dump; may limit to a.f. */ + NET_RT_FLAGS = 2, /* by flags, e.g. RESOLVING */ + NET_RT_IFLIST = 3, /* survey interface list */ + NET_RT_IFMALIST = 4, /* return multicast address list */ + NET_RT_IFLISTL = 5, /* Survey interface list, using 'l'en versions of msghdr structs. */ + NET_RT_NHOP = 6, /* dump routing nexthops */ + NET_RT_NHGRP = 7, /* dump routing nexthop groups */ + + /* + * CTL_HW identifiers + */ + HW_MACHINE = 1, /* string: machine class */ + HW_MODEL = 2, /* string: specific machine model */ + HW_NCPU = 3, /* int: number of cpus */ + HW_BYTEORDER = 4, /* int: machine byte order */ + HW_PHYSMEM = 5, /* int: total memory */ + HW_USERMEM = 6, /* int: non-kernel memory */ + HW_PAGESIZE = 7, /* int: software page size */ + HW_DISKNAMES = 8, /* strings: disk drive names */ + HW_DISKSTATS = 9, /* struct: diskstats[] */ + HW_FLOATINGPT = 10, /* int: has HW floating point? */ + HW_MACHINE_ARCH = 11, /* string: machine architecture */ + HW_REALMEM = 12, /* int: 'real' memory */ + + /* + * CTL_USER definitions + */ + USER_CS_PATH = 1, /* string: _CS_PATH */ + USER_BC_BASE_MAX = 2, /* int: BC_BASE_MAX */ + USER_BC_DIM_MAX = 3, /* int: BC_DIM_MAX */ + USER_BC_SCALE_MAX = 4, /* int: BC_SCALE_MAX */ + USER_BC_STRING_MAX = 5, /* int: BC_STRING_MAX */ + USER_COLL_WEIGHTS_MAX = 6, /* int: COLL_WEIGHTS_MAX */ + USER_EXPR_NEST_MAX = 7, /* int: EXPR_NEST_MAX */ + USER_LINE_MAX = 8, /* int: LINE_MAX */ + USER_RE_DUP_MAX = 9, /* int: RE_DUP_MAX */ + USER_POSIX2_VERSION = 10, /* int: POSIX2_VERSION */ + USER_POSIX2_C_BIND = 11, /* int: POSIX2_C_BIND */ + USER_POSIX2_C_DEV = 12, /* int: POSIX2_C_DEV */ + USER_POSIX2_CHAR_TERM = 13, /* int: POSIX2_CHAR_TERM */ + USER_POSIX2_FORT_DEV = 14, /* int: POSIX2_FORT_DEV */ + USER_POSIX2_FORT_RUN = 15, /* int: POSIX2_FORT_RUN */ + USER_POSIX2_LOCALEDEF = 16, /* int: POSIX2_LOCALEDEF */ + USER_POSIX2_SW_DEV = 17, /* int: POSIX2_SW_DEV */ + USER_POSIX2_UPE = 18, /* int: POSIX2_UPE */ + USER_STREAM_MAX = 19, /* int: POSIX2_STREAM_MAX */ + USER_TZNAME_MAX = 20, /* int: POSIX2_TZNAME_MAX */ + USER_LOCALBASE = 21, /* string: _PATH_LOCALBASE */ + + CTL_P1003_1B_ASYNCHRONOUS_IO = 1, /* boolean */ + CTL_P1003_1B_MAPPED_FILES = 2, /* boolean */ + CTL_P1003_1B_MEMLOCK = 3, /* boolean */ + CTL_P1003_1B_MEMLOCK_RANGE = 4, /* boolean */ + CTL_P1003_1B_MEMORY_PROTECTION = 5, /* boolean */ + CTL_P1003_1B_MESSAGE_PASSING = 6, /* boolean */ + CTL_P1003_1B_PRIORITIZED_IO = 7, /* boolean */ + CTL_P1003_1B_PRIORITY_SCHEDULING = 8, /* boolean */ + CTL_P1003_1B_REALTIME_SIGNALS = 9, /* boolean */ + CTL_P1003_1B_SEMAPHORES = 10, /* boolean */ + CTL_P1003_1B_FSYNC = 11, /* boolean */ + CTL_P1003_1B_SHARED_MEMORY_OBJECTS = 12, /* boolean */ + CTL_P1003_1B_SYNCHRONIZED_IO = 13, /* boolean */ + CTL_P1003_1B_TIMERS = 14, /* boolean */ + CTL_P1003_1B_AIO_LISTIO_MAX = 15, /* int */ + CTL_P1003_1B_AIO_MAX = 16, /* int */ + CTL_P1003_1B_AIO_PRIO_DELTA_MAX = 17, /* int */ + CTL_P1003_1B_DELAYTIMER_MAX = 18, /* int */ + CTL_P1003_1B_MQ_OPEN_MAX = 19, /* int */ + CTL_P1003_1B_PAGESIZE = 20, /* int */ + CTL_P1003_1B_RTSIG_MAX = 21, /* int */ + CTL_P1003_1B_SEM_NSEMS_MAX = 22, /* int */ + CTL_P1003_1B_SEM_VALUE_MAX = 23, /* int */ + CTL_P1003_1B_SIGQUEUE_MAX = 24, /* int */ + CTL_P1003_1B_TIMER_MAX = 25, /* int */ +} + +// +// #include +// + +// struct rt_metrics +Route_Metrics :: struct { + locks: c.ulong, /* Kernel must leave these values alone */ + mtu: c.ulong, /* MTU for this path */ + hopcount: c.ulong, /* max hops expected */ + expire: c.ulong, /* lifetime for route, e.g. redirect */ + recvpipe: c.ulong, /* inbound delay-bandwidth product */ + sendpipe: c.ulong, /* outbound delay-bandwidth product */ + ssthresh: c.ulong, /* outbound gateway buffer limit */ + rtt: c.ulong, /* estimated round trip time */ + rttvar: c.ulong, /* estimated rtt variance */ + pksent: c.ulong, /* packets sent using this route */ + weight: c.ulong, /* route weight */ + nhidx: c.ulong, /* route nexhop index */ + filler: [2]c.ulong, /* will be used for T/TCP later */ +} + +// struct rt_msghdr +Route_Message_Header :: struct { + msglen: c.ushort, /* to skip over non-understood messages */ + version: c.uchar, /* future binary compatibility */ + type: Route_Message_Type, /* message type */ + index: c.ushort, /* index for associated ifp */ + _spare1: c.ushort, + flags: c.int, /* flags, incl. kern & message, e.g. DONE */ + addrs: c.int, /* bitmask identifying sockaddrs in msg */ + pid: pid_t, /* identify sender */ + seq: c.int, /* for sender to identify action */ + errno: c.int, /* why failed */ + fmask: c.int, /* bitmask used in RTM_CHANGE message */ + inits: c.ulong, /* which metrics we are initializing */ + rmx: Route_Metrics, /* metrics themselves */ +} + +RTM_VERSION :: 5 /* Up the ante and ignore older versions */ + +/* + * Message types. + * + * The format for each message is annotated below using the following + * identifiers: + * + * (1) struct rt_msghdr + * (2) struct ifa_msghdr + * (3) struct if_msghdr + * (4) struct ifma_msghdr + * (5) struct if_announcemsghdr + * + */ +// #define RTM_* +Route_Message_Type :: enum c.uchar { + ADD = 0x1, /* (1) Add Route */ + DELETE = 0x2, /* (1) Delete Route */ + CHANGE = 0x3, /* (1) Change Metrics or flags */ + GET = 0x4, /* (1) Report Metrics */ + LOSING = 0x5, /* (1) Kernel Suspects Partitioning */ + REDIRECT = 0x6, /* (1) Told to use different route */ + MISS = 0x7, /* (1) Lookup failed on this address */ + LOCK = 0x8, /* (1) fix specified metrics */ + /* = 0x9 */ + /* = 0xa */ + RESOLVE = 0xb, /* (1) req to resolve dst to LL addr */ + NEWADDR = 0xc, /* (2) address being added to iface */ + DELADDR = 0xd, /* (2) address being removed from iface */ + IFINFO = 0xe, /* (3) iface going up/down etc. */ + NEWMADDR = 0xf, /* (4) mcast group membership being added to if */ + DELMADDR = 0x10, /* (4) mcast group membership being deleted */ + IFANNOUNCE = 0x11, /* (5) iface arrival/departure */ + IEEE80211 = 0x12, /* (5) IEEE80211 wireless event */ +} + +/* + * Bitmask values for rtm_addrs. + */ +// #define RTA_* +Route_Address_Flag :: enum c.int { + DST = 0x1, /* destination sockaddr present */ + GATEWAY = 0x2, /* gateway sockaddr present */ + NETMASK = 0x4, /* netmask sockaddr present */ + GENMASK = 0x8, /* cloning mask sockaddr present */ + IFP = 0x10, /* interface name sockaddr present */ + IFA = 0x20, /* interface addr sockaddr present */ + AUTHOR = 0x40, /* sockaddr for author of redirect */ + BRD = 0x80, /* for NEWADDR, broadcast or p-p dest addr */ +} + +/* + * Index offsets for sockaddr array for alternate internal encoding. + */ +// #define RTAX_* +Route_Address_Index :: enum c.int { + DST = 0, /* destination sockaddr present */ + GATEWAY = 1, /* gateway sockaddr present */ + NETMASK = 2, /* netmask sockaddr present */ + GENMASK = 3, /* cloning mask sockaddr present */ + IFP = 4, /* interface name sockaddr present */ + IFA = 5, /* interface addr sockaddr present */ + AUTHOR = 6, /* sockaddr for author of redirect */ + BRD = 7, /* for NEWADDR, broadcast or p-p dest addr */ + MAX = 8, /* size of array to allocate */ +} + +// The value stored in rtm_addrs and similar (ifm_addrs, etc.) +Route_Address_Flags :: bit_set[Route_Address_Index; c.int] + +// +// #include +// + +/* + * Values for if_link_state. + */ +// #define LINK_STATE_* +Link_State :: enum u8 { + UNKNOWN = 0, /* link invalid/unknown */ + DOWN = 1, /* link is down */ + UP = 2, /* link is up */ +} + +/* + * Structure describing information about an interface + * which may be of interest to management entities. + */ +// struct if_data +Interface_Data :: struct { + /* generic interface information */ + type: u8, /* ethernet, tokenring, etc */ + physical: u8, /* e.g., AUI, Thinnet, 10base-T, etc */ + addrlen: u8, /* media address length */ + hdrlen: u8, /* media header length */ + link_state: Link_State, /* current link state */ + vhid: u8, /* carp vhid */ + datalen: u16, /* length of this data struct */ + mtu: u32, /* maximum transmission unit */ + metric: u32, /* routing metric (external only) */ + baudrate: u64, /* linespeed */ + /* volatile statistics */ + ipackets: u64, /* packets received on interface */ + ierrors: u64, /* input errors on interface */ + opackets: u64, /* packets sent on interface */ + oerrors: u64, /* output errors on interface */ + collisions: u64, /* collisions on csma interfaces */ + ibytes: u64, /* total number of octets received */ + obytes: u64, /* total number of octets sent */ + imcasts: u64, /* packets received via multicast */ + omcasts: u64, /* packets sent via multicast */ + iqdrops: u64, /* dropped on input */ + oqdrops: u64, /* dropped on output */ + noproto: u64, /* destined for unsupported protocol */ + hwassist: u64, /* HW offload capabilities, see IFCAP */ + + /* Unions are here to make sizes MI. */ + _epoch: struct #raw_union { /* uptime at attach or stat reset */ + tt: time_t, + ph: u64, + }, + + _lastchange: struct #raw_union { /* time of last administrative change */ + tv: timeval, + ph: struct { + ph1: u64, + ph2: u64, + }, + }, +} + +/* + * The 'l' version shall be used by new interfaces, like NET_RT_IFLISTL. It is + * extensible after ifm_data_off or within ifm_data. Both the if_msghdr and + * if_data now have a member field detailing the struct length in addition to + * the routing message length. Macros are provided to find the start of + * ifm_data and the start of the socket address strucutres immediately following + * struct if_msghdrl given a pointer to struct if_msghdrl. + */ +// struct if_msghdrl +Interface_Message_Header_Len :: struct { + msglen: c.ushort, /* to skip over non-understood messages */ + version: c.uchar, /* future binary compatibility */ + type: c.uchar, /* message type */ + addrs: Route_Address_Flags, /* like rtm_addrs */ + flags: c.int, /* value of if_flags */ + index: c.ushort, /* index for associated ifp */ + _spare1: c.ushort, /* spare space to grow if_index, see if_var.h */ + len: c.ushort, /* length of if_msghdrl incl. if_data */ + data_off: c.ushort, /* offset of if_data from beginning */ + _spare2: c.int, + data: Interface_Data, /* statistics and other data about if */ +} + +/* + * The 'l' version shall be used by new interfaces, like NET_RT_IFLISTL. It is + * extensible after ifam_metric or within ifam_data. Both the ifa_msghdrl and + * if_data now have a member field detailing the struct length in addition to + * the routing message length. Macros are provided to find the start of + * ifm_data and the start of the socket address strucutres immediately following + * struct ifa_msghdrl given a pointer to struct ifa_msghdrl. + */ +// struct ifa_msghdrl +Interface_Address_Message_Header_Len :: struct { + msglen: c.ushort, /* to skip over non-understood messages */ + version: c.uchar, /* future binary compatibility */ + type: c.uchar, /* message type */ + addrs: Route_Address_Flags, /* like rtm_addrs */ + flags: c.int, /* value of ifa_flags */ + index: c.ushort, /* index for associated ifp */ + _spare1: c.ushort, /* spare space to grow if_index, see if_var.h */ + len: c.ushort, /* length of ifa_msghdrl incl. if_data */ + data_off: c.ushort, /* offset of if_data from beginning */ + metric: c.int, /* value of ifa_ifp->if_metric */ + data: Interface_Data, /* statistics and other data about if or address */ +} + +// +// #include +// + +// enum ifType +Interface_Type :: enum c.uchar { + OTHER = 0x1, /* none of the following */ + ARPA_1822 = 0x2, /* old-style arpanet imp */ + HDH1822 = 0x3, /* HDH arpanet imp */ + X25DDN = 0x4, /* x25 to imp */ + X25 = 0x5, /* PDN X25 interface (RFC877) */ + ETHER = 0x6, /* Ethernet CSMA/CD */ + ISO88023 = 0x7, /* CMSA/CD */ + ISO88024 = 0x8, /* Token Bus */ + ISO88025 = 0x9, /* Token Ring */ + ISO88026 = 0xa, /* MAN */ + STARLAN = 0xb, + P10 = 0xc, /* Proteon 10MBit ring */ + P80 = 0xd, /* Proteon 80MBit ring */ + HY = 0xe, /* Hyperchannel */ + FDDI = 0xf, + LAPB = 0x10, + SDLC = 0x11, + T1 = 0x12, + CEPT = 0x13, /* E1 - european T1 */ + ISDNBASIC = 0x14, + ISDNPRIMARY = 0x15, + PTPSERIAL = 0x16, /* Proprietary PTP serial */ + PPP = 0x17, /* RFC 1331 */ + LOOP = 0x18, /* loopback */ + EON = 0x19, /* ISO over IP */ + XETHER = 0x1a, /* obsolete 3MB experimental ethernet */ + NSIP = 0x1b, /* XNS over IP */ + SLIP = 0x1c, /* IP over generic TTY */ + ULTRA = 0x1d, /* Ultra Technologies */ + DS3 = 0x1e, /* Generic T3 */ + SIP = 0x1f, /* SMDS */ + FRELAY = 0x20, /* Frame Relay DTE only */ + RS232 = 0x21, + PARA = 0x22, /* parallel-port */ + ARCNET = 0x23, + ARCNETPLUS = 0x24, + ATM = 0x25, /* ATM cells */ + MIOX25 = 0x26, + SONET = 0x27, /* SONET or SDH */ + X25PLE = 0x28, + ISO88022LLC = 0x29, + LOCALTALK = 0x2a, + SMDSDXI = 0x2b, + FRELAYDCE = 0x2c, /* Frame Relay DCE */ + V35 = 0x2d, + HSSI = 0x2e, + HIPPI = 0x2f, + MODEM = 0x30, /* Generic Modem */ + AAL5 = 0x31, /* AAL5 over ATM */ + SONETPATH = 0x32, + SONETVT = 0x33, + SMDSICIP = 0x34, /* SMDS InterCarrier Interface */ + PROPVIRTUAL = 0x35, /* Proprietary Virtual/internal */ + PROPMUX = 0x36, /* Proprietary Multiplexing */ + IEEE80212 = 0x37, /* 100BaseVG */ + FIBRECHANNEL = 0x38, /* Fibre Channel */ + HIPPIINTERFACE = 0x39, /* HIPPI interfaces */ + FRAMERELAYINTERCONNECT = 0x3a, /* Obsolete, use 0x20 either 0x2c */ + AFLANE8023 = 0x3b, /* ATM Emulated LAN for 802.3 */ + AFLANE8025 = 0x3c, /* ATM Emulated LAN for 802.5 */ + CCTEMUL = 0x3d, /* ATM Emulated circuit */ + FASTETHER = 0x3e, /* Fast Ethernet (100BaseT) */ + ISDN = 0x3f, /* ISDN and X.25 */ + V11 = 0x40, /* CCITT V.11/X.21 */ + V36 = 0x41, /* CCITT V.36 */ + G703AT64K = 0x42, /* CCITT G703 at 64Kbps */ + G703AT2MB = 0x43, /* Obsolete see DS1-MIB */ + QLLC = 0x44, /* SNA QLLC */ + FASTETHERFX = 0x45, /* Fast Ethernet (100BaseFX) */ + CHANNEL = 0x46, /* channel */ + IEEE80211 = 0x47, /* radio spread spectrum (unused) */ + IBM370PARCHAN = 0x48, /* IBM System 360/370 OEMI Channel */ + ESCON = 0x49, /* IBM Enterprise Systems Connection */ + DLSW = 0x4a, /* Data Link Switching */ + ISDNS = 0x4b, /* ISDN S/T interface */ + ISDNU = 0x4c, /* ISDN U interface */ + LAPD = 0x4d, /* Link Access Protocol D */ + IPSWITCH = 0x4e, /* IP Switching Objects */ + RSRB = 0x4f, /* Remote Source Route Bridging */ + ATMLOGICAL = 0x50, /* ATM Logical Port */ + DS0 = 0x51, /* Digital Signal Level 0 */ + DS0BUNDLE = 0x52, /* group of ds0s on the same ds1 */ + BSC = 0x53, /* Bisynchronous Protocol */ + ASYNC = 0x54, /* Asynchronous Protocol */ + CNR = 0x55, /* Combat Net Radio */ + ISO88025DTR = 0x56, /* ISO 802.5r DTR */ + EPLRS = 0x57, /* Ext Pos Loc Report Sys */ + ARAP = 0x58, /* Appletalk Remote Access Protocol */ + PROPCNLS = 0x59, /* Proprietary Connectionless Protocol*/ + HOSTPAD = 0x5a, /* CCITT-ITU X.29 PAD Protocol */ + TERMPAD = 0x5b, /* CCITT-ITU X.3 PAD Facility */ + FRAMERELAYMPI = 0x5c, /* Multiproto Interconnect over FR */ + X213 = 0x5d, /* CCITT-ITU X213 */ + ADSL = 0x5e, /* Asymmetric Digital Subscriber Loop */ + RADSL = 0x5f, /* Rate-Adapt. Digital Subscriber Loop*/ + SDSL = 0x60, /* Symmetric Digital Subscriber Loop */ + VDSL = 0x61, /* Very H-Speed Digital Subscrib. Loop*/ + ISO88025CRFPINT = 0x62, /* ISO 802.5 CRFP */ + MYRINET = 0x63, /* Myricom Myrinet */ + VOICEEM = 0x64, /* voice recEive and transMit */ + VOICEFXO = 0x65, /* voice Foreign Exchange Office */ + VOICEFXS = 0x66, /* voice Foreign Exchange Station */ + VOICEENCAP = 0x67, /* voice encapsulation */ + VOICEOVERIP = 0x68, /* voice over IP encapsulation */ + ATMDXI = 0x69, /* ATM DXI */ + ATMFUNI = 0x6a, /* ATM FUNI */ + ATMIMA = 0x6b, /* ATM IMA */ + PPPMULTILINKBUNDLE = 0x6c, /* PPP Multilink Bundle */ + IPOVERCDLC = 0x6d, /* IBM ipOverCdlc */ + IPOVERCLAW = 0x6e, /* IBM Common Link Access to Workstn */ + STACKTOSTACK = 0x6f, /* IBM stackToStack */ + VIRTUALIPADDRESS = 0x70, /* IBM VIPA */ + MPC = 0x71, /* IBM multi-protocol channel support */ + IPOVERATM = 0x72, /* IBM ipOverAtm */ + ISO88025FIBER = 0x73, /* ISO 802.5j Fiber Token Ring */ + TDLC = 0x74, /* IBM twinaxial data link control */ + GIGABITETHERNET = 0x75, /* Gigabit Ethernet */ + HDLC = 0x76, /* HDLC */ + LAPF = 0x77, /* LAP F */ + V37 = 0x78, /* V.37 */ + X25MLP = 0x79, /* Multi-Link Protocol */ + X25HUNTGROUP = 0x7a, /* X25 Hunt Group */ + TRANSPHDLC = 0x7b, /* Transp HDLC */ + INTERLEAVE = 0x7c, /* Interleave channel */ + FAST = 0x7d, /* Fast channel */ + IP = 0x7e, /* IP (for APPN HPR in IP networks) */ + DOCSCABLEMACLAYER = 0x7f, /* CATV Mac Layer */ + DOCSCABLEDOWNSTREAM = 0x80, /* CATV Downstream interface */ + DOCSCABLEUPSTREAM = 0x81, /* CATV Upstream interface */ + A12MPPSWITCH = 0x82, /* Avalon Parallel Processor */ + TUNNEL = 0x83, /* Encapsulation interface */ + COFFEE = 0x84, /* coffee pot */ + CES = 0x85, /* Circiut Emulation Service */ + ATMSUBINTERFACE = 0x86, /* (x) ATM Sub Interface */ + L2VLAN = 0x87, /* Layer 2 Virtual LAN using 802.1Q */ + L3IPVLAN = 0x88, /* Layer 3 Virtual LAN - IP Protocol */ + L3IPXVLAN = 0x89, /* Layer 3 Virtual LAN - IPX Prot. */ + DIGITALPOWERLINE = 0x8a, /* IP over Power Lines */ + MEDIAMAILOVERIP = 0x8b, /* (xxx) Multimedia Mail over IP */ + DTM = 0x8c, /* Dynamic synchronous Transfer Mode */ + DCN = 0x8d, /* Data Communications Network */ + IPFORWARD = 0x8e, /* IP Forwarding Interface */ + MSDSL = 0x8f, /* Multi-rate Symmetric DSL */ + IEEE1394 = 0x90, /* IEEE1394 High Performance SerialBus*/ + IFGSN = 0x91, /* HIPPI-6400 */ + DVBRCCMACLAYER = 0x92, /* DVB-RCC MAC Layer */ + DVBRCCDOWNSTREAM = 0x93, /* DVB-RCC Downstream Channel */ + DVBRCCUPSTREAM = 0x94, /* DVB-RCC Upstream Channel */ + ATMVIRTUAL = 0x95, /* ATM Virtual Interface */ + MPLSTUNNEL = 0x96, /* MPLS Tunnel Virtual Interface */ + SRP = 0x97, /* Spatial Reuse Protocol */ + VOICEOVERATM = 0x98, /* Voice over ATM */ + VOICEOVERFRAMERELAY = 0x99, /* Voice Over Frame Relay */ + IDSL = 0x9a, /* Digital Subscriber Loop over ISDN */ + COMPOSITELINK = 0x9b, /* Avici Composite Link Interface */ + SS7SIGLINK = 0x9c, /* SS7 Signaling Link */ + PROPWIRELESSP2P = 0x9d, /* Prop. P2P wireless interface */ + FRFORWARD = 0x9e, /* Frame forward Interface */ + RFC1483 = 0x9f, /* Multiprotocol over ATM AAL5 */ + USB = 0xa0, /* USB Interface */ + IEEE8023ADLAG = 0xa1, /* IEEE 802.3ad Link Aggregate*/ + BGPPOLICYACCOUNTING = 0xa2, /* BGP Policy Accounting */ + FRF16MFRBUNDLE = 0xa3, /* FRF.16 Multilink Frame Relay*/ + H323GATEKEEPER = 0xa4, /* H323 Gatekeeper */ + H323PROXY = 0xa5, /* H323 Voice and Video Proxy */ + MPLS = 0xa6, /* MPLS */ + MFSIGLINK = 0xa7, /* Multi-frequency signaling link */ + HDSL2 = 0xa8, /* High Bit-Rate DSL, 2nd gen. */ + SHDSL = 0xa9, /* Multirate HDSL2 */ + DS1FDL = 0xaa, /* Facility Data Link (4Kbps) on a DS1*/ + POS = 0xab, /* Packet over SONET/SDH Interface */ + DVBASILN = 0xac, /* DVB-ASI Input */ + DVBASIOUT = 0xad, /* DVB-ASI Output */ + PLC = 0xae, /* Power Line Communications */ + NFAS = 0xaf, /* Non-Facility Associated Signaling */ + TR008 = 0xb0, /* TROO8 */ + GR303RDT = 0xb1, /* Remote Digital Terminal */ + GR303IDT = 0xb2, /* Integrated Digital Terminal */ + ISUP = 0xb3, /* ISUP */ + PROPDOCSWIRELESSMACLAYER = 0xb4, /* prop/Wireless MAC Layer */ + PROPDOCSWIRELESSDOWNSTREAM = 0xb5, /* prop/Wireless Downstream */ + PROPDOCSWIRELESSUPSTREAM = 0xb6, /* prop/Wireless Upstream */ + HIPERLAN2 = 0xb7, /* HIPERLAN Type 2 Radio Interface */ + PROPBWAP2MP = 0xb8, /* PropBroadbandWirelessAccess P2MP*/ + SONETOVERHEADCHANNEL = 0xb9, /* SONET Overhead Channel */ + DIGITALWRAPPEROVERHEADCHANNEL = 0xba, /* Digital Wrapper Overhead */ + AAL2 = 0xbb, /* ATM adaptation layer 2 */ + RADIOMAC = 0xbc, /* MAC layer over radio links */ + ATMRADIO = 0xbd, /* ATM over radio links */ + IMT = 0xbe, /* Inter-Machine Trunks */ + MVL = 0xbf, /* Multiple Virtual Lines DSL */ + REACHDSL = 0xc0, /* Long Reach DSL */ + FRDLCIENDPT = 0xc1, /* Frame Relay DLCI End Point */ + ATMVCIENDPT = 0xc2, /* ATM VCI End Point */ + OPTICALCHANNEL = 0xc3, /* Optical Channel */ + OPTICALTRANSPORT = 0xc4, /* Optical Transport */ + INFINIBAND = 0xc7, /* Infiniband */ + INFINIBANDLAG = 0xc8, /* Infiniband Link Aggregate */ + BRIDGE = 0xd1, /* Transparent bridge interface */ + STF = 0xd7, /* 6to4 interface */ + + /* + * Not based on IANA assignments. Conflicting with IANA assignments. + * We should make them negative probably. + * This requires changes to struct if_data. + */ + GIF = 0xf0, /* Generic tunnel interface */ + PVC = 0xf1, /* Unused */ + ENC = 0xf4, /* Encapsulating interface */ + PFLOG = 0xf6, /* PF packet filter logging */ + PFSYNC = 0xf7, /* PF packet filter synchronization */ + WIREGUARD = 0xf8, /* WireGuard tunnel */ +} + +// +// #include +// + +/* + * Structure of a Link-Level sockaddr: + */ +// struct sockaddr_dl +Socket_Address_Data_Link :: struct { + using _: Socket_Address_Header, + index: c.ushort, /* if != 0, system given index for interface */ + type: Interface_Type, /* interface type */ + nlen: c.uchar, /* interface name length, no trailing 0 reqd. */ + alen: c.uchar, /* link level address length */ + slen: c.uchar, /* link layer selector length */ + data: [46]c.char, /* minimum work area, can be larger; contains both if name and ll address */ +} + +// +// #include +// + +in_addr_t :: distinct u32be + +/* Internet address (a structure for historical reasons). */ +// struct in_addr +IP4_Address :: struct #raw_union { + // NOTE(Feoramund): I have modified this struct from its C definition by + // introducing the byte variant to make it easier to work with. + addr8: [4]u8, + addr32: in_addr_t, +} + +/* Socket address, internet style. */ +// struct sockaddr_in +Socket_Address_Internet :: struct #packed { + using _: Socket_Address_Header, + port: in_port_t, + addr: IP4_Address, + zero: [8]c.char, +} + +// +// #include +// + +/* + * IPv6 address + */ +// struct in6_addr +IP6_Address :: struct #raw_union { + addr8: [16]u8, + addr16: [8]u16be, + addr32: [4]u32be, +} + +/* + * Socket address for IPv6 + */ +// struct sockaddr_in6 +Socket_Address_Internet6 :: struct #packed { + using _: Socket_Address_Header, + port: in_port_t, /* Transport layer port # */ + flowinfo: u32, /* IP6 flow information */ + addr: IP6_Address, /* IP6 address */ + scope_id: u32, /* scope zone index */ +} + +// +// #include +// + +/* op code for _umtx_op */ +// #define UMTX_OP_* +Userland_Mutex_Operation :: enum c.int { + LOCK = 0, /* COMPAT10 */ + UNLOCK = 1, /* COMPAT10 */ + WAIT = 2, + WAKE = 3, + MUTEX_TRYLOCK = 4, + MUTEX_LOCK = 5, + MUTEX_UNLOCK = 6, + SET_CEILING = 7, + CV_WAIT = 8, + CV_SIGNAL = 9, + CV_BROADCAST = 10, + WAIT_UINT = 11, + RW_RDLOCK = 12, + RW_WRLOCK = 13, + RW_UNLOCK = 14, + WAIT_UINT_PRIVATE = 15, + WAKE_PRIVATE = 16, + MUTEX_WAIT = 17, + MUTEX_WAKE = 18, /* deprecated */ + SEM_WAIT = 19, /* deprecated */ + SEM_WAKE = 20, /* deprecated */ + NWAKE_PRIVATE = 21, + MUTEX_WAKE2 = 22, + SEM2_WAIT = 23, + SEM2_WAKE = 24, + SHM = 25, + ROBUST_LISTS = 26, + GET_MIN_TIMEOUT = 27, + SET_MIN_TIMEOUT = 28, +} diff --git a/core/sys/haiku/errors.odin b/core/sys/haiku/errors.odin new file mode 100644 index 000000000..febe647ea --- /dev/null +++ b/core/sys/haiku/errors.odin @@ -0,0 +1,239 @@ +#+build haiku +package sys_haiku + +import "core:c" + +Errno :: enum c.int { + // Error baselines + GENERAL_ERROR_BASE = min(c.int), + OS_ERROR_BASE = GENERAL_ERROR_BASE + 0x1000, + APP_ERROR_BASE = GENERAL_ERROR_BASE + 0x2000, + INTERFACE_ERROR_BASE = GENERAL_ERROR_BASE + 0x3000, + MEDIA_ERROR_BASE = GENERAL_ERROR_BASE + 0x4000, + TRANSLATION_ERROR_BASE = GENERAL_ERROR_BASE + 0x4800, + MIDI_ERROR_BASE = GENERAL_ERROR_BASE + 0x5000, + STORAGE_ERROR_BASE = GENERAL_ERROR_BASE + 0x6000, + POSIX_ERROR_BASE = GENERAL_ERROR_BASE + 0x7000, + MAIL_ERROR_BASE = GENERAL_ERROR_BASE + 0x8000, + PRINT_ERROR_BASE = GENERAL_ERROR_BASE + 0x9000, + DEVICE_ERROR_BASE = GENERAL_ERROR_BASE + 0xa000, + + // Developer-defined errors start at (ERRORS_END+1) + ERRORS_END = GENERAL_ERROR_BASE + 0xffff, + + // General Errors + NO_MEMORY = GENERAL_ERROR_BASE + 0, + IO_ERROR = GENERAL_ERROR_BASE + 1, + PERMISSION_DENIED = GENERAL_ERROR_BASE + 2, + BAD_INDEX = GENERAL_ERROR_BASE + 3, + BAD_TYPE = GENERAL_ERROR_BASE + 4, + BAD_VALUE = GENERAL_ERROR_BASE + 5, + MISMATCHED_VALUES = GENERAL_ERROR_BASE + 6, + NAME_NOT_FOUND = GENERAL_ERROR_BASE + 7, + NAME_IN_USE = GENERAL_ERROR_BASE + 8, + TIMED_OUT = GENERAL_ERROR_BASE + 9, + INTERRUPTED = GENERAL_ERROR_BASE + 10, + WOULD_BLOCK = GENERAL_ERROR_BASE + 11, + CANCELED = GENERAL_ERROR_BASE + 12, + NO_INIT = GENERAL_ERROR_BASE + 13, + NOT_INITIALIZED = GENERAL_ERROR_BASE + 13, + BUSY = GENERAL_ERROR_BASE + 14, + NOT_ALLOWED = GENERAL_ERROR_BASE + 15, + BAD_DATA = GENERAL_ERROR_BASE + 16, + DONT_DO_THAT = GENERAL_ERROR_BASE + 17, + + ERROR = -1, + OK = 0, + NO_ERROR = 0, + + // Kernel Kit Errors + BAD_SEM_ID = OS_ERROR_BASE + 0, + NO_MORE_SEMS = OS_ERROR_BASE + 1, + BAD_THREAD_ID = OS_ERROR_BASE + 0x100, + NO_MORE_THREADS = OS_ERROR_BASE + 0x101, + BAD_THREAD_STATE = OS_ERROR_BASE + 0x102, + BAD_TEAM_ID = OS_ERROR_BASE + 0x103, + NO_MORE_TEAMS = OS_ERROR_BASE + 0x104, + BAD_PORT_ID = OS_ERROR_BASE + 0x200, + NO_MORE_PORTS = OS_ERROR_BASE + 0x201, + BAD_IMAGE_ID = OS_ERROR_BASE + 0x300, + BAD_ADDRESS = OS_ERROR_BASE + 0x301, + NOT_AN_EXECUTABLE = OS_ERROR_BASE + 0x302, + MISSING_LIBRARY = OS_ERROR_BASE + 0x303, + MISSING_SYMBOL = OS_ERROR_BASE + 0x304, + UNKNOWN_EXECUTABLE = OS_ERROR_BASE + 0x305, + LEGACY_EXECUTABLE = OS_ERROR_BASE + 0x306, + + DEBUGGER_ALREADY_INSTALLED = OS_ERROR_BASE + 0x400, + + // Application Kit Errors + BAD_REPLY = APP_ERROR_BASE + 0, + DUPLICATE_REPLY = APP_ERROR_BASE + 1, + MESSAGE_TO_SELF = APP_ERROR_BASE + 2, + BAD_HANDLER = APP_ERROR_BASE + 3, + ALREADY_RUNNING = APP_ERROR_BASE + 4, + LAUNCH_FAILED = APP_ERROR_BASE + 5, + AMBIGUOUS_APP_LAUNCH = APP_ERROR_BASE + 6, + UNKNOWN_MIME_TYPE = APP_ERROR_BASE + 7, + BAD_SCRIPT_SYNTAX = APP_ERROR_BASE + 8, + LAUNCH_FAILED_NO_RESOLVE_LINK = APP_ERROR_BASE + 9, + LAUNCH_FAILED_EXECUTABLE = APP_ERROR_BASE + 10, + LAUNCH_FAILED_APP_NOT_FOUND = APP_ERROR_BASE + 11, + LAUNCH_FAILED_APP_IN_TRASH = APP_ERROR_BASE + 12, + LAUNCH_FAILED_NO_PREFERRED_APP = APP_ERROR_BASE + 13, + LAUNCH_FAILED_FILES_APP_NOT_FOUND = APP_ERROR_BASE + 14, + BAD_MIME_SNIFFER_RULE = APP_ERROR_BASE + 15, + NOT_A_MESSAGE = APP_ERROR_BASE + 16, + SHUTDOWN_CANCELLED = APP_ERROR_BASE + 17, + SHUTTING_DOWN = APP_ERROR_BASE + 18, + + // Storage Kit/File System Errors + FILE_ERROR = STORAGE_ERROR_BASE + 0, + // 1 was B_FILE_NOT_FOUND (deprecated) + FILE_EXISTS = STORAGE_ERROR_BASE + 2, + ENTRY_NOT_FOUND = STORAGE_ERROR_BASE + 3, + NAME_TOO_LONG = STORAGE_ERROR_BASE + 4, + NOT_A_DIRECTORY = STORAGE_ERROR_BASE + 5, + DIRECTORY_NOT_EMPTY = STORAGE_ERROR_BASE + 6, + DEVICE_FULL = STORAGE_ERROR_BASE + 7, + READ_ONLY_DEVICE = STORAGE_ERROR_BASE + 8, + IS_A_DIRECTORY = STORAGE_ERROR_BASE + 9, + NO_MORE_FDS = STORAGE_ERROR_BASE + 10, + CROSS_DEVICE_LINK = STORAGE_ERROR_BASE + 11, + LINK_LIMIT = STORAGE_ERROR_BASE + 12, + BUSTED_PIPE = STORAGE_ERROR_BASE + 13, + UNSUPPORTED = STORAGE_ERROR_BASE + 14, + PARTITION_TOO_SMALL = STORAGE_ERROR_BASE + 15, + PARTIAL_READ = STORAGE_ERROR_BASE + 16, + PARTIAL_WRITE = STORAGE_ERROR_BASE + 17, + + // Some POSIX errors + E2BIG = POSIX_ERROR_BASE + 1, + EFBIG = POSIX_ERROR_BASE + 4, + ENODEV = POSIX_ERROR_BASE + 7, + ERANGE = POSIX_ERROR_BASE + 17, + EOVERFLOW = POSIX_ERROR_BASE + 41, + EOPNOTSUPP = POSIX_ERROR_BASE + 43, + + ENOSYS = POSIX_ERROR_BASE + 9, + EAGAIN = WOULD_BLOCK, + + // New error codes that can be mapped to POSIX errors + TOO_MANY_ARGS_NEG = E2BIG, + FILE_TOO_LARGE_NEG = EFBIG, + DEVICE_NOT_FOUND_NEG = ENODEV, + RESULT_NOT_REPRESENTABLE_NEG = ERANGE, + BUFFER_OVERFLOW_NEG = EOVERFLOW, + NOT_SUPPORTED_NEG = EOPNOTSUPP, + + TOO_MANY_ARGS_POS = -E2BIG, + FILE_TOO_LARGE_POS = -EFBIG, + DEVICE_NOT_FOUND_POS = -ENODEV, + RESULT_NOT_REPRESENTABLE_POS = -ERANGE, + BUFFER_OVERFLOW_POS = -EOVERFLOW, + NOT_SUPPORTED_POS = -EOPNOTSUPP, + + // Media Kit Errors + STREAM_NOT_FOUND = MEDIA_ERROR_BASE + 0, + SERVER_NOT_FOUND = MEDIA_ERROR_BASE + 1, + RESOURCE_NOT_FOUND = MEDIA_ERROR_BASE + 2, + RESOURCE_UNAVAILABLE = MEDIA_ERROR_BASE + 3, + BAD_SUBSCRIBER = MEDIA_ERROR_BASE + 4, + SUBSCRIBER_NOT_ENTERED = MEDIA_ERROR_BASE + 5, + BUFFER_NOT_AVAILABLE = MEDIA_ERROR_BASE + 6, + LAST_BUFFER_ERROR = MEDIA_ERROR_BASE + 7, + MEDIA_SYSTEM_FAILURE = MEDIA_ERROR_BASE + 100, + MEDIA_BAD_NODE = MEDIA_ERROR_BASE + 101, + MEDIA_NODE_BUSY = MEDIA_ERROR_BASE + 102, + MEDIA_BAD_FORMAT = MEDIA_ERROR_BASE + 103, + MEDIA_BAD_BUFFER = MEDIA_ERROR_BASE + 104, + MEDIA_TOO_MANY_NODES = MEDIA_ERROR_BASE + 105, + MEDIA_TOO_MANY_BUFFERS = MEDIA_ERROR_BASE + 106, + MEDIA_NODE_ALREADY_EXISTS = MEDIA_ERROR_BASE + 107, + MEDIA_BUFFER_ALREADY_EXISTS = MEDIA_ERROR_BASE + 108, + MEDIA_CANNOT_SEEK = MEDIA_ERROR_BASE + 109, + MEDIA_CANNOT_CHANGE_RUN_MODE = MEDIA_ERROR_BASE + 110, + MEDIA_APP_ALREADY_REGISTERED = MEDIA_ERROR_BASE + 111, + MEDIA_APP_NOT_REGISTERED = MEDIA_ERROR_BASE + 112, + MEDIA_CANNOT_RECLAIM_BUFFERS = MEDIA_ERROR_BASE + 113, + MEDIA_BUFFERS_NOT_RECLAIMED = MEDIA_ERROR_BASE + 114, + MEDIA_TIME_SOURCE_STOPPED = MEDIA_ERROR_BASE + 115, + MEDIA_TIME_SOURCE_BUSY = MEDIA_ERROR_BASE + 116, + MEDIA_BAD_SOURCE = MEDIA_ERROR_BASE + 117, + MEDIA_BAD_DESTINATION = MEDIA_ERROR_BASE + 118, + MEDIA_ALREADY_CONNECTED = MEDIA_ERROR_BASE + 119, + MEDIA_NOT_CONNECTED = MEDIA_ERROR_BASE + 120, + MEDIA_BAD_CLIP_FORMAT = MEDIA_ERROR_BASE + 121, + MEDIA_ADDON_FAILED = MEDIA_ERROR_BASE + 122, + MEDIA_ADDON_DISABLED = MEDIA_ERROR_BASE + 123, + MEDIA_CHANGE_IN_PROGRESS = MEDIA_ERROR_BASE + 124, + MEDIA_STALE_CHANGE_COUNT = MEDIA_ERROR_BASE + 125, + MEDIA_ADDON_RESTRICTED = MEDIA_ERROR_BASE + 126, + MEDIA_NO_HANDLER = MEDIA_ERROR_BASE + 127, + MEDIA_DUPLICATE_FORMAT = MEDIA_ERROR_BASE + 128, + MEDIA_REALTIME_DISABLED = MEDIA_ERROR_BASE + 129, + MEDIA_REALTIME_UNAVAILABLE = MEDIA_ERROR_BASE + 130, + + // Mail Kit Errors + MAIL_NO_DAEMON = MAIL_ERROR_BASE + 0, + MAIL_UNKNOWN_USER = MAIL_ERROR_BASE + 1, + MAIL_WRONG_PASSWORD = MAIL_ERROR_BASE + 2, + MAIL_UNKNOWN_HOST = MAIL_ERROR_BASE + 3, + MAIL_ACCESS_ERROR = MAIL_ERROR_BASE + 4, + MAIL_UNKNOWN_FIELD = MAIL_ERROR_BASE + 5, + MAIL_NO_RECIPIENT = MAIL_ERROR_BASE + 6, + MAIL_INVALID_MAIL = MAIL_ERROR_BASE + 7, + + // Printing Errors + NO_PRINT_SERVER = PRINT_ERROR_BASE + 0, + + // Device Kit Errors + DEV_INVALID_IOCTL = DEVICE_ERROR_BASE + 0, + DEV_NO_MEMORY = DEVICE_ERROR_BASE + 1, + DEV_BAD_DRIVE_NUM = DEVICE_ERROR_BASE + 2, + DEV_NO_MEDIA = DEVICE_ERROR_BASE + 3, + DEV_UNREADABLE = DEVICE_ERROR_BASE + 4, + DEV_FORMAT_ERROR = DEVICE_ERROR_BASE + 5, + DEV_TIMEOUT = DEVICE_ERROR_BASE + 6, + DEV_RECALIBRATE_ERROR = DEVICE_ERROR_BASE + 7, + DEV_SEEK_ERROR = DEVICE_ERROR_BASE + 8, + DEV_ID_ERROR = DEVICE_ERROR_BASE + 9, + DEV_READ_ERROR = DEVICE_ERROR_BASE + 10, + DEV_WRITE_ERROR = DEVICE_ERROR_BASE + 11, + DEV_NOT_READY = DEVICE_ERROR_BASE + 12, + DEV_MEDIA_CHANGED = DEVICE_ERROR_BASE + 13, + DEV_MEDIA_CHANGE_REQUESTED = DEVICE_ERROR_BASE + 14, + DEV_RESOURCE_CONFLICT = DEVICE_ERROR_BASE + 15, + DEV_CONFIGURATION_ERROR = DEVICE_ERROR_BASE + 16, + DEV_DISABLED_BY_USER = DEVICE_ERROR_BASE + 17, + DEV_DOOR_OPEN = DEVICE_ERROR_BASE + 18, + DEV_INVALID_PIPE = DEVICE_ERROR_BASE + 19, + DEV_CRC_ERROR = DEVICE_ERROR_BASE + 20, + DEV_STALLED = DEVICE_ERROR_BASE + 21, + DEV_BAD_PID = DEVICE_ERROR_BASE + 22, + DEV_UNEXPECTED_PID = DEVICE_ERROR_BASE + 23, + DEV_DATA_OVERRUN = DEVICE_ERROR_BASE + 24, + DEV_DATA_UNDERRUN = DEVICE_ERROR_BASE + 25, + DEV_FIFO_OVERRUN = DEVICE_ERROR_BASE + 26, + DEV_FIFO_UNDERRUN = DEVICE_ERROR_BASE + 27, + DEV_PENDING = DEVICE_ERROR_BASE + 28, + DEV_MULTIPLE_ERRORS = DEVICE_ERROR_BASE + 29, + DEV_TOO_LATE = DEVICE_ERROR_BASE + 30, + + // Translation Kit Errors + TRANSLATION_BASE_ERROR = TRANSLATION_ERROR_BASE + 0, + NO_TRANSLATOR = TRANSLATION_ERROR_BASE + 1, + ILLEGAL_DATA = TRANSLATION_ERROR_BASE + 2, +} + +errno :: #force_inline proc "contextless" () -> Errno { + return Errno(_errnop()^) +} + +foreign import libroot "system:c" +foreign libroot { + _to_positive_error :: proc(error: c.int) -> c.int --- + _to_negative_error :: proc(error: c.int) -> c.int --- + + _errnop :: proc() -> ^c.int --- +} diff --git a/core/sys/haiku/find_directory.odin b/core/sys/haiku/find_directory.odin new file mode 100644 index 000000000..758c4dff4 --- /dev/null +++ b/core/sys/haiku/find_directory.odin @@ -0,0 +1,168 @@ +#+build haiku +package sys_haiku + +import "core:c" + +directory_which :: enum c.int { + // Per volume directories + DESKTOP_DIRECTORY = 0, + TRASH_DIRECTORY, + + // System directories + SYSTEM_DIRECTORY = 1000, + SYSTEM_ADDONS_DIRECTORY = 1002, + SYSTEM_BOOT_DIRECTORY, + SYSTEM_FONTS_DIRECTORY, + SYSTEM_LIB_DIRECTORY, + SYSTEM_SERVERS_DIRECTORY, + SYSTEM_APPS_DIRECTORY, + SYSTEM_BIN_DIRECTORY, + SYSTEM_DOCUMENTATION_DIRECTORY = 1010, + SYSTEM_PREFERENCES_DIRECTORY, + SYSTEM_TRANSLATORS_DIRECTORY, + SYSTEM_MEDIA_NODES_DIRECTORY, + SYSTEM_SOUNDS_DIRECTORY, + SYSTEM_DATA_DIRECTORY, + SYSTEM_DEVELOP_DIRECTORY, + SYSTEM_PACKAGES_DIRECTORY, + SYSTEM_HEADERS_DIRECTORY, + SYSTEM_ETC_DIRECTORY = 2008, + SYSTEM_SETTINGS_DIRECTORY = 2010, + SYSTEM_LOG_DIRECTORY = 2012, + SYSTEM_SPOOL_DIRECTORY, + SYSTEM_TEMP_DIRECTORY, + SYSTEM_VAR_DIRECTORY, + SYSTEM_CACHE_DIRECTORY = 2020, + SYSTEM_NONPACKAGED_DIRECTORY = 2023, + SYSTEM_NONPACKAGED_ADDONS_DIRECTORY, + SYSTEM_NONPACKAGED_TRANSLATORS_DIRECTORY, + SYSTEM_NONPACKAGED_MEDIA_NODES_DIRECTORY, + SYSTEM_NONPACKAGED_BIN_DIRECTORY, + SYSTEM_NONPACKAGED_DATA_DIRECTORY, + SYSTEM_NONPACKAGED_FONTS_DIRECTORY, + SYSTEM_NONPACKAGED_SOUNDS_DIRECTORY, + SYSTEM_NONPACKAGED_DOCUMENTATION_DIRECTORY, + SYSTEM_NONPACKAGED_LIB_DIRECTORY, + SYSTEM_NONPACKAGED_HEADERS_DIRECTORY, + SYSTEM_NONPACKAGED_DEVELOP_DIRECTORY, + + // User directories. These are interpreted in the context of the user making the find_directory call. + USER_DIRECTORY = 3000, + USER_CONFIG_DIRECTORY, + USER_ADDONS_DIRECTORY, + USER_BOOT_DIRECTORY, + USER_FONTS_DIRECTORY, + USER_LIB_DIRECTORY, + USER_SETTINGS_DIRECTORY, + USER_DESKBAR_DIRECTORY, + USER_PRINTERS_DIRECTORY, + USER_TRANSLATORS_DIRECTORY, + USER_MEDIA_NODES_DIRECTORY, + USER_SOUNDS_DIRECTORY, + USER_DATA_DIRECTORY, + USER_CACHE_DIRECTORY, + USER_PACKAGES_DIRECTORY, + USER_HEADERS_DIRECTORY, + USER_NONPACKAGED_DIRECTORY, + USER_NONPACKAGED_ADDONS_DIRECTORY, + USER_NONPACKAGED_TRANSLATORS_DIRECTORY, + USER_NONPACKAGED_MEDIA_NODES_DIRECTORY, + USER_NONPACKAGED_BIN_DIRECTORY, + USER_NONPACKAGED_DATA_DIRECTORY, + USER_NONPACKAGED_FONTS_DIRECTORY, + USER_NONPACKAGED_SOUNDS_DIRECTORY, + USER_NONPACKAGED_DOCUMENTATION_DIRECTORY, + USER_NONPACKAGED_LIB_DIRECTORY, + USER_NONPACKAGED_HEADERS_DIRECTORY, + USER_NONPACKAGED_DEVELOP_DIRECTORY, + USER_DEVELOP_DIRECTORY, + USER_DOCUMENTATION_DIRECTORY, + USER_SERVERS_DIRECTORY, + USER_APPS_DIRECTORY, + USER_BIN_DIRECTORY, + USER_PREFERENCES_DIRECTORY, + USER_ETC_DIRECTORY, + USER_LOG_DIRECTORY, + USER_SPOOL_DIRECTORY, + USER_VAR_DIRECTORY, + + // Global directories + APPS_DIRECTORY = 4000, + PREFERENCES_DIRECTORY, + UTILITIES_DIRECTORY, + PACKAGE_LINKS_DIRECTORY, + + // Obsolete: Legacy BeOS definition to be phased out + BEOS_DIRECTORY = 1000, + BEOS_SYSTEM_DIRECTORY, + BEOS_ADDONS_DIRECTORY, + BEOS_BOOT_DIRECTORY, + BEOS_FONTS_DIRECTORY, + BEOS_LIB_DIRECTORY, + BEOS_SERVERS_DIRECTORY, + BEOS_APPS_DIRECTORY, + BEOS_BIN_DIRECTORY, + BEOS_ETC_DIRECTORY, + BEOS_DOCUMENTATION_DIRECTORY, + BEOS_PREFERENCES_DIRECTORY, + BEOS_TRANSLATORS_DIRECTORY, + BEOS_MEDIA_NODES_DIRECTORY, + BEOS_SOUNDS_DIRECTORY, +} + +find_path_flags :: enum c.int { + CREATE_DIRECTORY = 0x0001, + CREATE_PARENT_DIRECTORY = 0x0002, + EXISTING_ONLY = 0x0004, + + // find_paths() only! + SYSTEM_ONLY = 0x0010, + USER_ONLY = 0x0020, +} + +path_base_directory :: enum c.int { + INSTALLATION_LOCATION_DIRECTORY, + ADD_ONS_DIRECTORY, + APPS_DIRECTORY, + BIN_DIRECTORY, + BOOT_DIRECTORY, + CACHE_DIRECTORY, + DATA_DIRECTORY, + DEVELOP_DIRECTORY, + DEVELOP_LIB_DIRECTORY, + DOCUMENTATION_DIRECTORY, + ETC_DIRECTORY, + FONTS_DIRECTORY, + HEADERS_DIRECTORY, + LIB_DIRECTORY, + LOG_DIRECTORY, + MEDIA_NODES_DIRECTORY, + PACKAGES_DIRECTORY, + PREFERENCES_DIRECTORY, + SERVERS_DIRECTORY, + SETTINGS_DIRECTORY, + SOUNDS_DIRECTORY, + SPOOL_DIRECTORY, + TRANSLATORS_DIRECTORY, + VAR_DIRECTORY, + + // find_path() only! + IMAGE_PATH = 1000, + PACKAGE_PATH, +} + +// value that can be used instead of a pointer to a symbol in the program image +APP_IMAGE_SYMBOL :: rawptr(addr_t(0)) +// pointer to a symbol in the callers image (same as B_CURRENT_IMAGE_SYMBOL) +current_image_symbol :: proc() -> rawptr { return rawptr(current_image_symbol) } + +foreign import libroot "system:c" +foreign libroot { + find_directory :: proc(which: directory_which, volume: dev_t, createIt: bool, pathString: [^]c.char, length: i32) -> status_t --- + find_path :: proc(codePointer: rawptr, baseDirectory: path_base_directory, subPath: cstring, pathBuffer: [^]c.char, bufferSize: c.size_t) -> status_t --- + find_path_etc :: proc(codePointer: rawptr, dependency: cstring, architecture: cstring, baseDirectory: path_base_directory, subPath: cstring, flags: find_path_flags, pathBuffer: [^]c.char, bufferSize: c.size_t) -> status_t --- + find_path_for_path :: proc(path: cstring, baseDirectory: path_base_directory, subPath: cstring, pathBuffer: [^]c.char, bufferSize: c.size_t) -> status_t --- + find_path_for_path_etc :: proc(path: cstring, dependency: cstring, architecture: cstring, baseDirectory: path_base_directory, subPath: cstring, flags: find_path_flags, pathBuffer: [^]c.char, bufferSize: c.size_t) -> status_t --- + find_paths :: proc(baseDirectory: path_base_directory, subPath: cstring, _paths: ^[^][^]c.char, _pathCount: ^c.size_t) -> status_t --- + find_paths_etc :: proc(architecture: cstring, baseDirectory: path_base_directory, subPath: cstring, flags: find_path_flags, _paths: ^[^][^]c.char, _pathCount: ^c.size_t) -> status_t --- +} diff --git a/core/sys/haiku/os.odin b/core/sys/haiku/os.odin new file mode 100644 index 000000000..6ab3ef573 --- /dev/null +++ b/core/sys/haiku/os.odin @@ -0,0 +1,502 @@ +#+build haiku +package sys_haiku + +import "core:c" +import "core:sys/unix" + +foreign import libroot "system:c" + +PATH_MAX :: 1024 +NAME_MAX :: 256 +MAXPATHLEN :: PATH_MAX + +FILE_NAME_LENGTH :: NAME_MAX +PATH_NAME_LENGTH :: MAXPATHLEN +OS_NAME_LENGTH :: 32 + +// Areas + +area_info :: struct { + area: area_id, + name: [OS_NAME_LENGTH]c.char, + size: c.size_t, + lock: u32, + protection: u32, + team: team_id, + ram_size: u32, + copy_count: u32, + in_count: u32, + out_count: u32, + address: rawptr, +} + +area_locking :: enum u32 { + NO_LOCK = 0, + LAZY_LOCK = 1, + FULL_LOCK = 2, + CONTIGUOUS = 3, + LOMEM = 4, // CONTIGUOUS, < 16 MB physical address + _32_BIT_FULL_LOCK = 5, // FULL_LOCK, < 4 GB physical addresses + _32_BIT_CONTIGUOUS = 6, // CONTIGUOUS, < 4 GB physical address +} + +// for create_area() and clone_area() +address_spec :: enum u32 { + ANY_ADDRESS = 0, + EXACT_ADDRESS = 1, + BASE_ADDRESS = 2, + CLONE_ADDRESS = 3, + ANY_KERNEL_ADDRESS = 4, + // ANY_KERNEL_BLOCK_ADDRESS = 5, + RANDOMIZED_ANY_ADDRESS = 6, + RANDOMIZED_BASE_ADDRESS = 7, +} + +area_protection_flags :: enum u32 { + READ_AREA = 1 << 0, + WRITE_AREA = 1 << 1, + EXECUTE_AREA = 1 << 2, + // "stack" protection is not available on most platforms - it's used + // to only commit memory as needed, and have guard pages at the + // bottom of the stack. + STACK_AREA = 1 << 3, + CLONEABLE_AREA = 1 << 8, +} + +foreign libroot { + create_area :: proc(name: cstring, startAddress: ^rawptr, addressSpec: address_spec, size: c.size_t, lock: area_locking, protection: area_protection_flags) -> area_id --- + clone_area :: proc(name: cstring, destAddress: ^rawptr, addressSpec: address_spec, protection: area_protection_flags, source: area_id) -> area_id --- + find_area :: proc(name: cstring) -> area_id --- + area_for :: proc(address: rawptr) -> area_id --- + delete_area :: proc(id: area_id) -> status_t --- + resize_area :: proc(id: area_id, newSize: c.size_t) -> status_t --- + set_area_protection :: proc(id: area_id, newProtection: area_protection_flags) -> status_t --- + _get_area_info :: proc(id: area_id, areaInfo: ^area_info, size: c.size_t) -> status_t --- + _get_next_area_info :: proc(team: team_id, cookie: ^c.ssize_t, areaInfo: ^area_info, size: c.size_t) -> status_t --- +} + +// Ports + +port_info :: struct { + port: port_id, + team: team_id, + name: [OS_NAME_LENGTH]c.char, + capacity: i32, // queue depth + queue_count: i32, // # msgs waiting to be read + total_count: i32, // total # msgs read so far +} + +port_flags :: enum u32 { + USE_USER_MEMCPY = 0x80000000, + // read the message, but don't remove it; kernel-only; memory must be locked + PEEK_PORT_MESSAGE = 0x100, +} + +foreign libroot { + create_port :: proc(capacity: i32, name: cstring) -> port_id --- + find_port :: proc(name: cstring) -> port_id --- + read_port :: proc(port: port_id, code: ^i32, buffer: rawptr, bufferSize: c.size_t) -> c.ssize_t --- + read_port_etc :: proc(port: port_id, code: ^i32, buffer: rawptr, bufferSize: c.size_t, flags: port_flags, timeout: bigtime_t) -> c.ssize_t --- + write_port :: proc(port: port_id, code: i32, buffer: rawptr, bufferSize: c.size_t) -> status_t --- + write_port_etc :: proc(port: port_id, code: i32, buffer: rawptr, bufferSize: c.size_t, flags: port_flags, timeout: bigtime_t) -> status_t --- + close_port :: proc(port: port_id) -> status_t --- + delete_port :: proc(port: port_id) -> status_t --- + port_buffer_size :: proc(port: port_id) -> c.ssize_t --- + port_buffer_size_etc :: proc(port: port_id, flags: port_flags, timeout: bigtime_t) -> c.ssize_t --- + port_count :: proc(port: port_id) -> c.ssize_t --- + set_port_owner :: proc(port: port_id, team: team_id) -> status_t --- + _get_port_info :: proc(port: port_id, portInfo: ^port_info, portInfoSize: c.size_t) -> status_t --- + _get_next_port_info :: proc(team: team_id, cookie: ^i32, portInfo: ^port_info, portInfoSize: c.size_t) -> status_t --- +} + +// Semaphores + +sem_info :: struct { + sem: sem_id, + team: team_id, + name: [OS_NAME_LENGTH]c.char, + count: i32, + latest_holder: thread_id, +} + +semaphore_flags :: enum u32 { + CAN_INTERRUPT = 0x01, // acquisition of the semaphore can be interrupted (system use only) + CHECK_PERMISSION = 0x04, // ownership will be checked (system use only) + KILL_CAN_INTERRUPT = 0x20, // acquisition of the semaphore can be interrupted by SIGKILL[THR], even if not CAN_INTERRUPT (system use only) + + // release_sem_etc() only flags + DO_NOT_RESCHEDULE = 0x02, // thread is not rescheduled + RELEASE_ALL = 0x08, // all waiting threads will be woken up, count will be zeroed + RELEASE_IF_WAITING_ONLY = 0x10, // release count only if there are any threads waiting +} + +foreign libroot { + create_sem :: proc(count: i32, name: cstring) -> sem_id --- + delete_sem :: proc(id: sem_id) -> status_t --- + acquire_sem :: proc(id: sem_id) -> status_t --- + acquire_sem_etc :: proc(id: sem_id, count: i32, flags: semaphore_flags, timeout: bigtime_t) -> status_t --- + release_sem :: proc(id: sem_id) -> status_t --- + release_sem_etc :: proc(id: sem_id, count: i32, flags: semaphore_flags) -> status_t --- + switch_sem :: proc(semToBeReleased: sem_id) -> status_t --- + switch_sem_etc :: proc(semToBeReleased: sem_id, id: sem_id, count: i32, flags: semaphore_flags, timeout: bigtime_t) -> status_t --- + get_sem_count :: proc(id: sem_id, threadCount: ^i32) -> status_t --- + set_sem_owner :: proc(id: sem_id, team: team_id) -> status_t --- + _get_sem_info :: proc(id: sem_id, info: ^sem_info, infoSize: c.size_t) -> status_t --- + _get_next_sem_info :: proc(team: team_id, cookie: ^i32, info: ^sem_info, infoSize: c.size_t) -> status_t --- +} + +// Teams + +team_info :: struct { + team: team_id, + thread_count: i32, + image_count: i32, + area_count: i32, + debugger_nub_thread: thread_id, + debugger_nub_port: port_id, + argc: i32, + args: [64]c.char, + uid: uid_t, + gid: gid_t, + + // Haiku R1 extensions + real_uid: uid_t, + real_gid: gid_t, + group_id: pid_t, + session_id: pid_t, + parent: team_id, + name: [OS_NAME_LENGTH]c.char, + start_time: bigtime_t, +} + +CURRENT_TEAM :: 0 +SYSTEM_TEAM :: 1 + +team_usage_info :: struct { + user_time: bigtime_t, + kernel_time: bigtime_t, +} + +team_usage_who :: enum i32 { + // compatible to sys/resource.h RUSAGE_SELF and RUSAGE_CHILDREN + SELF = 0, + CHILDREN = -1, +} + +foreign libroot { + // see also: send_signal() + kill_team :: proc(team: team_id) -> status_t --- + _get_team_info :: proc(id: team_id, info: ^team_info, size: c.size_t) -> status_t --- + _get_next_team_info :: proc(cookie: ^i32, info: ^team_info, size: c.size_t) -> status_t --- + _get_team_usage_info :: proc(id: team_id, who: team_usage_who, info: ^team_usage_info, size: c.size_t) -> status_t --- +} + +// Threads + +thread_state :: enum c.int { + RUNNING = 1, + READY, + RECEIVING, + ASLEEP, + SUSPENDED, + WAITING, +} + +thread_info :: struct { + thread: thread_id, + team: team_id, + name: [OS_NAME_LENGTH]c.char, + state: thread_state, + priority: thread_priority, + sem: sem_id, + user_time: bigtime_t, + kernel_time: bigtime_t, + stack_base: rawptr, + stack_end: rawptr, +} + +thread_priority :: enum i32 { + IDLE_PRIORITY = 0, + LOWEST_ACTIVE_PRIORITY = 1, + LOW_PRIORITY = 5, + NORMAL_PRIORITY = 10, + DISPLAY_PRIORITY = 15, + URGENT_DISPLAY_PRIORITY = 20, + REAL_TIME_DISPLAY_PRIORITY = 100, + URGENT_PRIORITY = 110, + REAL_TIME_PRIORITY = 120, +} + +FIRST_REAL_TIME_PRIORITY :: thread_priority.REAL_TIME_PRIORITY + +// time base for snooze_*(), compatible with the clockid_t constants defined in +SYSTEM_TIMEBASE :: 0 + +thread_func :: #type proc "c" (rawptr) -> status_t + +foreign libroot { + spawn_thread :: proc(thread_func, name: cstring, priority: thread_priority, data: rawptr) -> thread_id --- + kill_thread :: proc(thread: thread_id) -> status_t --- + resume_thread :: proc(thread: thread_id) -> status_t --- + suspend_thread :: proc(thread: thread_id) -> status_t --- + rename_thread :: proc(thread: thread_id, newName: cstring) -> status_t --- + set_thread_priority :: proc(thread: thread_id, newPriority: thread_priority) -> status_t --- + exit_thread :: proc(status: status_t) --- + wait_for_thread :: proc(thread: thread_id, returnValue: ^status_t) -> status_t --- + // FIXME: Find and define those flags. + wait_for_thread_etc :: proc(id: thread_id, flags: u32, timeout: bigtime_t, _returnCode: ^status_t) -> status_t --- + on_exit_thread :: proc(callback: proc "c" (rawptr), data: rawptr) -> status_t --- + find_thread :: proc(name: cstring) -> thread_id --- + send_data :: proc(thread: thread_id, code: i32, buffer: rawptr, bufferSize: c.size_t) -> status_t --- + receive_data :: proc(sender: ^thread_id, buffer: rawptr, bufferSize: c.size_t) -> i32 --- + has_data :: proc(thread: thread_id) -> bool --- + snooze :: proc(amount: bigtime_t) -> status_t --- + // FIXME: Find and define those flags. + snooze_etc :: proc(amount: bigtime_t, timeBase: c.int, flags: u32) -> status_t --- + snooze_until :: proc(time: bigtime_t, timeBase: c.int) -> status_t --- + _get_thread_info :: proc(id: thread_id, info: ^thread_info, size: c.size_t) -> status_t --- + _get_next_thread_info :: proc(team: team_id, cookie: ^i32, info: ^thread_info, size: c.size_t) -> status_t --- + // bridge to the pthread API + get_pthread_thread_id :: proc(thread: pthread_t) -> thread_id --- +} + +// Time + +foreign libroot { + real_time_clock :: proc() -> c.ulong --- + set_real_time_clock :: proc(secsSinceJan1st1970: c.ulong) --- + real_time_clock_usecs :: proc() -> bigtime_t --- + // time since booting in microseconds + system_time :: proc() -> bigtime_t --- + // time since booting in nanoseconds + system_time_nsecs :: proc() -> nanotime_t --- +} + +// Alarm + +alarm_mode :: enum u32 { + ONE_SHOT_ABSOLUTE_ALARM = 1, + ONE_SHOT_RELATIVE_ALARM, + PERIODIC_ALARM, // "when" specifies the period +} + +foreign libroot { + set_alarm :: proc(_when: bigtime_t, mode: alarm_mode) -> bigtime_t --- +} + +// Debugger + +foreign libroot { + debugger :: proc(message: cstring) --- + /* + calling this function with a non-zero value will cause your thread + to receive signals for any exceptional conditions that occur (i.e. + you'll get SIGSEGV for data access exceptions, SIGFPE for floating + point errors, SIGILL for illegal instructions, etc). + + to re-enable the default debugger pass a zero. + */ + disable_debugger :: proc(state: c.int) -> c.int --- +} + +// System information + +cpu_info :: struct { + active_time: bigtime_t, + enabled: bool, + current_frequency: u64, +} + +system_info :: struct { + boot_time: bigtime_t, // time of boot (usecs since 1/1/1970) + + cpu_count: u32, // number of cpus + + max_pages: u64, // total # of accessible pages + used_pages: u64, // # of accessible pages in use + cached_pages: u64, + block_cache_pages: u64, + ignored_pages: u64, // # of ignored/inaccessible pages + + needed_memory: u64, + free_memory: u64, + + max_swap_pages: u64, + free_swap_pages: u64, + + page_faults: u32, // # of page faults + + max_sems: u32, + used_sems: u32, + + max_ports: u32, + used_ports: u32, + + max_threads: u32, + used_threads: u32, + + max_teams: u32, + used_teams: u32, + + kernel_name: [FILE_NAME_LENGTH]c.char, + kernel_build_date: [OS_NAME_LENGTH]c.char, + kernel_build_time: [OS_NAME_LENGTH]c.char, + + kernel_version: i64, + abi: u32, // the system API +} + +topology_level_type :: enum c.int { + UNKNOWN, + ROOT, + SMT, + CORE, + PACKAGE, +} + +cpu_platform :: enum c.int { + UNKNOWN, + x86, + x86_64, + PPC, + PPC_64, + M68K, + ARM, + ARM_64, + ALPHA, + MIPS, + SH, + SPARC, + RISC_V, +} + +cpu_vendor :: enum c.int { + UNKNOWN, + AMD, + CYRIX, + IDT, + INTEL, + NATIONAL_SEMICONDUCTOR, + RISE, + TRANSMETA, + VIA, + IBM, + MOTOROLA, + NEC, + HYGON, + SUN, + FUJITSU, +} + +cpu_topology_node_info :: struct { + id: u32, + type: topology_level_type, + level: u32, + + data: struct #raw_union { + _root: struct { + platform: cpu_platform, + }, + _package: struct { + vendor: cpu_vendor, + cache_line_size: u32, + }, + _core: struct { + model: u32, + default_frequency: u64, + }, + }, +} + +// FIXME: Add cpuid_info when bit fields are ready. + +foreign libroot { + get_system_info :: proc(info: ^system_info) -> status_t --- + _get_cpu_info_etc :: proc(firstCPU: u32, cpuCount: u32, info: ^cpu_info, size: c.size_t) -> status_t --- + get_cpu_topology_info :: proc(topologyInfos: [^]cpu_topology_node_info, topologyInfoCount: ^u32) -> status_t --- + + is_computer_on :: proc() -> i32 --- + is_computer_on_fire :: proc() -> f64 --- +} + +// Signal.h + +SIG_BLOCK :: 1 +SIG_UNBLOCK :: 2 +SIG_SETMASK :: 3 + +/* + * The list of all defined signals: + * + * The numbering of signals for Haiku attempts to maintain + * some consistency with UN*X conventions so that things + * like "kill -9" do what you expect. + */ + +SIGHUP :: 1 // hangup -- tty is gone! +SIGINT :: 2 // interrupt +SIGQUIT :: 3 // `quit' special character typed in tty +SIGILL :: 4 // illegal instruction +SIGCHLD :: 5 // child process exited +SIGABRT :: 6 // abort() called, dont' catch +SIGPIPE :: 7 // write to a pipe w/no readers +SIGFPE :: 8 // floating point exception +SIGKILL :: 9 // kill a team (not catchable) +SIGSTOP :: 10 // suspend a thread (not catchable) +SIGSEGV :: 11 // segmentation violation (read: invalid pointer) +SIGCONT :: 12 // continue execution if suspended +SIGTSTP :: 13 // `stop' special character typed in tty +SIGALRM :: 14 // an alarm has gone off (see alarm()) +SIGTERM :: 15 // termination requested +SIGTTIN :: 16 // read of tty from bg process +SIGTTOU :: 17 // write to tty from bg process +SIGUSR1 :: 18 // app defined signal 1 +SIGUSR2 :: 19 // app defined signal 2 +SIGWINCH :: 20 // tty window size changed +SIGKILLTHR :: 21 // be specific: kill just the thread, not team +SIGTRAP :: 22 // Trace/breakpoint trap +SIGPOLL :: 23 // Pollable event +SIGPROF :: 24 // Profiling timer expired +SIGSYS :: 25 // Bad system call +SIGURG :: 26 // High bandwidth data is available at socket +SIGVTALRM :: 27 // Virtual timer expired +SIGXCPU :: 28 // CPU time limit exceeded +SIGXFSZ :: 29 // File size limit exceeded +SIGBUS :: 30 // access to undefined portion of a memory object + +sigval :: struct #raw_union { + sival_int: c.int, + sival_ptr: rawptr, +} + +siginfo_t :: struct { + si_signo: c.int, // signal number + si_code: c.int, // signal code + si_errno: c.int, // if non zero, an error number associated with this signal + + si_pid: pid_t, // sending process ID + si_uid: uid_t, // real user ID of sending process + si_addr: rawptr, // address of faulting instruction + si_status: c.int, // exit value or signal + si_band: c.long, // band event for SIGPOLL + si_value: sigval, // signal value +} + +foreign libroot { + // signal set (sigset_t) manipulation + sigemptyset :: proc(set: ^sigset_t) -> c.int --- + sigfillset :: proc(set: ^sigset_t) -> c.int --- + sigaddset :: proc(set: ^sigset_t, _signal: c.int) -> c.int --- + sigdelset :: proc(set: ^sigset_t, _signal: c.int) -> c.int --- + sigismember :: proc(set: ^sigset_t, _signal: c.int) -> c.int --- + // querying and waiting for signals + sigpending :: proc(set: ^sigset_t) -> c.int --- + sigsuspend :: proc(mask: ^sigset_t) -> c.int --- + sigpause :: proc(_signal: c.int) -> c.int --- + sigwait :: proc(set: ^sigset_t, _signal: ^c.int) -> c.int --- + sigwaitinfo :: proc(set: ^sigset_t, info: ^siginfo_t) -> c.int --- + sigtimedwait :: proc(set: ^sigset_t, info: ^siginfo_t, timeout: ^unix.timespec) -> c.int --- + + send_signal :: proc(threadID: thread_id, signal: c.uint) -> c.int --- + set_signal_stack :: proc(base: rawptr, size: c.size_t) --- +} diff --git a/core/sys/haiku/types.odin b/core/sys/haiku/types.odin new file mode 100644 index 000000000..47755b0b7 --- /dev/null +++ b/core/sys/haiku/types.odin @@ -0,0 +1,54 @@ +#+build haiku +package sys_haiku + +import "core:c" + +status_t :: i32 +bigtime_t :: i64 +nanotime_t :: i64 +type_code :: u32 +perform_code :: u32 + +phys_addr_t :: uintptr +phys_size_t :: phys_addr_t +generic_addr_t :: uintptr +generic_size_t :: generic_addr_t + +area_id :: i32 +port_id :: i32 +sem_id :: i32 +team_id :: i32 +thread_id :: i32 + +blkcnt_t :: i64 +blksize_t :: i32 +fsblkcnt_t :: i64 +fsfilcnt_t :: i64 +off_t :: i64 +ino_t :: i64 +cnt_t :: i32 +dev_t :: i32 +pid_t :: i32 +id_t :: i32 + +uid_t :: u32 +gid_t :: u32 +mode_t :: u32 +umode_t :: u32 +nlink_t :: i32 + +caddr_t :: ^c.char + +addr_t :: phys_addr_t +key_t :: i32 + +clockid_t :: i32 + +time_t :: i64 when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 else i32 + +sig_atomic_t :: c.int +sigset_t :: u64 + +image_id :: i32 + +pthread_t :: rawptr diff --git a/core/sys/info/cpu_arm.odin b/core/sys/info/cpu_arm.odin index f66f0e780..960e55a56 100644 --- a/core/sys/info/cpu_arm.odin +++ b/core/sys/info/cpu_arm.odin @@ -1,26 +1,70 @@ -//+build arm32, arm64 +#+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 5a11863d4..95b53dda0 100644 --- a/core/sys/info/cpu_intel.odin +++ b/core/sys/info/cpu_intel.odin @@ -1,7 +1,7 @@ -//+build i386, amd64 +#+build i386, amd64 package sysinfo -import "core:intrinsics" +import "base:intrinsics" // cpuid :: proc(ax, cx: u32) -> (eax, ebc, ecx, edx: u32) --- cpuid :: intrinsics.x86_cpuid @@ -28,6 +28,23 @@ CPU_Feature :: enum u64 { ssse3, // Supplemental streaming SIMD extension 3 sse41, // Streaming SIMD extension 4 and 4.1 sse42, // Streaming SIMD extension 4 and 4.2 + + avx512bf16, // Vector Neural Network Instructions supporting bfloat16 + avx512bitalg, // Bit Algorithms + avx512bw, // Byte and Word instructions + avx512cd, // Conflict Detection instructions + avx512dq, // Doubleword and Quadword instructions + avx512er, // Exponential and Reciprocal instructions + avx512f, // Foundation + avx512fp16, // Vector 16-bit float instructions + avx512ifma, // Integer Fused Multiply Add + avx512pf, // Prefetch instructions + avx512vbmi, // Vector Byte Manipulation Instructions + avx512vbmi2, // Vector Byte Manipulation Instructions 2 + avx512vl, // Vector Length extensions + avx512vnni, // Vector Neural Network Instructions + avx512vp2intersect, // Vector Pair Intersection to a Pair of Mask Registers + avx512vpopcntdq, // Vector Population Count for Doubleword and Quadword } CPU_Features :: distinct bit_set[CPU_Feature; u64] @@ -37,11 +54,11 @@ cpu_name: Maybe(string) @(init, private) init_cpu_features :: proc "c" () { - is_set :: #force_inline proc "c" (hwc: u32, value: u32) -> bool { - return hwc&(1 << value) != 0 + is_set :: #force_inline proc "c" (bit: u32, value: u32) -> bool { + return (value>>bit) & 0x1 != 0 } - try_set :: #force_inline proc "c" (set: ^CPU_Features, feature: CPU_Feature, hwc: u32, value: u32) { - if is_set(hwc, value) { + try_set :: #force_inline proc "c" (set: ^CPU_Features, feature: CPU_Feature, bit: u32, value: u32) { + if is_set(bit, value) { set^ += {feature} } } @@ -67,8 +84,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 @@ -82,9 +99,11 @@ init_cpu_features :: proc "c" () { // // See: crbug.com/375968 os_supports_avx := false + os_supports_avx512 := false if .os_xsave in set && is_set(26, ecx1) { eax, _ := xgetbv(0) os_supports_avx = is_set(1, eax) && is_set(2, eax) + os_supports_avx512 = is_set(5, eax) && is_set(6, eax) && is_set(7, eax) } if os_supports_avx { try_set(&set, .avx, 28, ecx1) @@ -94,11 +113,37 @@ init_cpu_features :: proc "c" () { return } - _, ebx7, _, _ := cpuid(7, 0) + _, ebx7, ecx7, edx7 := cpuid(7, 0) try_set(&set, .bmi1, 3, ebx7) if os_supports_avx { try_set(&set, .avx2, 5, ebx7) } + if os_supports_avx512 { + try_set(&set, .avx512f, 16, ebx7) + try_set(&set, .avx512dq, 17, ebx7) + try_set(&set, .avx512ifma, 21, ebx7) + try_set(&set, .avx512pf, 26, ebx7) + try_set(&set, .avx512er, 27, ebx7) + try_set(&set, .avx512cd, 28, ebx7) + try_set(&set, .avx512bw, 30, ebx7) + + // XMM/YMM are also required for 128/256-bit instructions + if os_supports_avx { + try_set(&set, .avx512vl, 31, ebx7) + } + + try_set(&set, .avx512vbmi, 1, ecx7) + try_set(&set, .avx512vbmi2, 6, ecx7) + try_set(&set, .avx512vnni, 11, ecx7) + try_set(&set, .avx512bitalg, 12, ecx7) + try_set(&set, .avx512vpopcntdq, 14, ecx7) + + try_set(&set, .avx512vp2intersect, 8, edx7) + try_set(&set, .avx512fp16, 23, edx7) + + eax7_1, _, _, _ := cpuid(7, 1) + try_set(&set, .avx512bf16, 5, eax7_1) + } try_set(&set, .bmi2, 8, ebx7) try_set(&set, .erms, 9, ebx7) try_set(&set, .rdseed, 18, ebx7) @@ -117,7 +162,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 +178,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..6408decb7 --- /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/cpu_linux_riscv64.odin b/core/sys/info/cpu_linux_riscv64.odin new file mode 100644 index 000000000..84f6134d4 --- /dev/null +++ b/core/sys/info/cpu_linux_riscv64.odin @@ -0,0 +1,113 @@ +#+build riscv64 +#+build linux +package sysinfo + +import "base:intrinsics" + +import "core:sys/linux" + +@(init, private) +init_cpu_features :: proc() { + _features: CPU_Features + defer cpu_features = _features + + HWCAP_Bits :: enum u64 { + I = 'I' - 'A', + M = 'M' - 'A', + A = 'A' - 'A', + F = 'F' - 'A', + D = 'D' - 'A', + C = 'C' - 'A', + V = 'V' - 'A', + } + HWCAP :: bit_set[HWCAP_Bits; u64] + + // Read HWCAP for base extensions, we can get this info through hwprobe too but that is Linux 6.4+ only. + { + fd, err := linux.open("/proc/self/auxv", {}) + if err != .NONE { return } + defer linux.close(fd) + + // This is probably enough right? + buf: [4096]byte + n, rerr := linux.read(fd, buf[:]) + if rerr != .NONE || n == 0 { return } + + ulong :: u64 + AT_HWCAP :: 16 + + auxv := buf[:n] + for len(auxv) >= size_of(ulong)*2 { + key := intrinsics.unaligned_load((^ulong)(&auxv[0])) + val := intrinsics.unaligned_load((^ulong)(&auxv[size_of(ulong)])) + auxv = auxv[2*size_of(ulong):] + + if key != AT_HWCAP { + continue + } + + cap := transmute(HWCAP)(val) + if .I in cap { + _features += { .I } + } + if .M in cap { + _features += { .M } + } + if .A in cap { + _features += { .A } + } + if .F in cap { + _features += { .F } + } + if .D in cap { + _features += { .D } + } + if .C in cap { + _features += { .C } + } + if .V in cap { + _features += { .V } + } + break + } + } + + // hwprobe for other features. + { + pairs := []linux.RISCV_HWProbe{ + { key = .IMA_EXT_0 }, + { key = .CPUPERF_0 }, + { key = .MISALIGNED_SCALAR_PERF }, + } + err := linux.riscv_hwprobe(raw_data(pairs), len(pairs), 0, nil, {}) + if err != nil { + assert(err == .ENOSYS, "unexpected error from riscv_hwprobe()") + return + } + + assert(pairs[0].key == .IMA_EXT_0) + exts := pairs[0].value.ima_ext_0 + exts -= { .FD, .C, .V } + _features += transmute(CPU_Features)exts + + if pairs[2].key == .MISALIGNED_SCALAR_PERF { + if pairs[2].value.misaligned_scalar_perf == .FAST { + _features += { .Misaligned_Supported, .Misaligned_Fast } + } else if pairs[2].value.misaligned_scalar_perf != .UNSUPPORTED { + _features += { .Misaligned_Supported } + } + } else { + assert(pairs[1].key == .CPUPERF_0) + if .FAST in pairs[1].value.cpu_perf_0 { + _features += { .Misaligned_Supported, .Misaligned_Fast } + } else if .UNSUPPORTED not_in pairs[1].value.cpu_perf_0 { + _features += { .Misaligned_Supported } + } + } + } +} + +@(init, private) +init_cpu_name :: proc() { + cpu_name = "RISCV64" +} diff --git a/core/sys/info/cpu_riscv64.odin b/core/sys/info/cpu_riscv64.odin new file mode 100644 index 000000000..c3319c48c --- /dev/null +++ b/core/sys/info/cpu_riscv64.odin @@ -0,0 +1,100 @@ +package sysinfo + +CPU_Feature :: enum u64 { + // Bit-Manipulation ISA Extensions v1. + Zba = 3, + Zbb, + Zbs, + + // CMOs (ratified). + Zicboz, + + // Bit-Manipulation ISA Extensions v1. + Zbc, + + // Scalar Crypto ISA extensions v1. + Zbkb, + Zbkc, + Zbkx, + Zknd, + Zkne, + Zknh, + Zksed, + Zksh, + Zkt, + + // Cryptography Extensions Volume II v1. + Zvbb, + Zvbc, + Zvkb, + Zvkg, + Zvkned, + Zvknha, + Zvknhb, + Zvksed, + Zvksh, + Zvkt, + + // ISA Manual v1. + Zfh, + Zfhmin, + Zihintntl, + + // ISA manual (ratified). + Zvfh, + Zvfhmin, + Zfa, + Ztso, + + // Atomic Compare-and-Swap Instructions Manual (ratified). + Zacas, + Zicond, + + // ISA manual (ratified). + Zihintpause, + + // Vector Extensions Manual v1. + Zve32x, + Zve32f, + Zve64x, + Zve64f, + Zve64d, + + // ISA manual (ratified). + Zimop, + + // Code Size Reduction (ratified). + Zca, + Zcb, + Zcd, + Zcf, + + // ISA manual (ratified). + Zcmop, + Zawrs, + + // Base features, don't think this is ever not here. + I, + // Integer multiplication and division, currently required by Odin. + M, + // Atomics. + A, + // Single precision floating point, currently required by Odin. + F, + // Double precision floating point, currently required by Odin. + D, + // Compressed instructions. + C, + // Vector operations. + V, + + // Indicates Misaligned Scalar Loads will not trap the program. + Misaligned_Supported, + // Indicates Hardware Support for Misaligned Scalar Loads. + Misaligned_Fast, +} + +CPU_Features :: distinct bit_set[CPU_Feature; u64] + +cpu_features: Maybe(CPU_Features) +cpu_name: Maybe(string) diff --git a/core/sys/info/doc.odin b/core/sys/info/doc.odin index 81c3fb342..2fd34b864 100644 --- a/core/sys/info/doc.odin +++ b/core/sys/info/doc.odin @@ -1,78 +1,88 @@ /* - Copyright 2022 Jeroen van Rijn . - Made available under Odin's BSD-3 license. +Copyright 2022 Jeroen van Rijn . +Made available under Odin's BSD-3 license. - Package `core:sys/info` gathers system information on: - Windows, Linux, macOS, FreeBSD & OpenBSD. - - Simply import the package and you'll have access to the OS version, RAM amount - and CPU information. - - On Windows, GPUs will also be enumerated using the registry. - - CPU feature flags can be tested against `cpu_features`, where applicable, e.g. - `if .aes in si.aes { ... }` +List of contributors: + Jeroen van Rijn: Initial implementation. + Laytan: ARM and RISC-V CPU feature detection, iOS/macOS platform overhaul. */ -//+build ignore -package sysinfo - -import "core:fmt" -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.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) - } -} /* - Example Windows output: - Odin: dev-2022-09 - OS: Windows 10 Professional (version: 20H2), build: 19042.1466 - OS: OS_Version{ - platform = "Windows", - major = 10, - minor = 0, +Package `core:sys/info` gathers system information on: +Windows, Linux, macOS, FreeBSD & OpenBSD. + +Simply import the package and you'll have access to the OS version, RAM amount +and CPU information. + +On Windows, GPUs will also be enumerated using the registry. + +CPU feature flags can be tested against `cpu_features`, where applicable, e.g. +`if .aes in info.cpu_features.? { ... }` + +Example: + package main + + import "core:fmt" + import si "core:sys/info" + + main :: proc() { + 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.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) + } + } + +- Example Windows output: + + Odin: dev-2022-09 + OS: Windows 10 Professional (version: 20H2), build: 19042.1466 + OS: OS_Version{ + platform = "Windows", + major = 10, + minor = 0, + patch = 0, + build = [ + 19042, + 1466, + ], + version = "20H2", + as_string = "Windows 10 Professional (version: 20H2), build: 19042.1466", + } + CPU: AMD Ryzen 7 1800X Eight-Core Processor + RAM: 64.0 GiB + GPU #0: + Vendor: Advanced Micro Devices, Inc. + Model: Radeon RX Vega + VRAM: 8.0 GiB + +- Example macOS output: + + ODIN: dev-2022-09 + OS: OS_Version{ + platform = "MacOS", + major = 21, + minor = 5, patch = 0, build = [ - 19042, - 1466, + 0, + 0, ], - version = "20H2", - as_string = "Windows 10 Professional (version: 20H2), build: 19042.1466", - } - CPU: AMD Ryzen 7 1800X Eight-Core Processor - RAM: 65469 MiB - - GPU #0: - Vendor: Advanced Micro Devices, Inc. - Model: Radeon RX Vega - VRAM: 8176 MiB - - Example macOS output: - ODIN: dev-2022-09 - OS: OS_Version{ - platform = "MacOS", - major = 21, - minor = 5, - patch = 0, - build = [ - 0, - 0, - ], - version = "21F79", - 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 + version = "21F79", + as_string = "macOS Monterey 12.4 (build 21F79, kernel 21.5.0)", + } + CPU: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz + RAM: 8.0 GiB */ +package sysinfo diff --git a/core/sys/info/platform_openbsd.odin b/core/sys/info/platform_bsd.odin similarity index 86% rename from core/sys/info/platform_openbsd.odin rename to core/sys/info/platform_bsd.odin index dbca6eaf3..6bb32cd3d 100644 --- a/core/sys/info/platform_openbsd.odin +++ b/core/sys/info/platform_bsd.odin @@ -1,22 +1,26 @@ -// +build openbsd +#+build openbsd, netbsd package sysinfo import sys "core:sys/unix" import "core:strings" import "core:strconv" -import "core:runtime" +import "base:runtime" @(private) 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 7a56f1e23..7dc49bcd1 100644 --- a/core/sys/info/platform_darwin.odin +++ b/core/sys/info/platform_darwin.odin @@ -1,516 +1,101 @@ -// +build darwin package sysinfo -import sys "core:sys/unix" -import "core:strconv" -import "core:strings" -import "core:runtime" +import "core:strconv" +import "core:strings" +import "core:sys/unix" +import NS "core:sys/darwin/Foundation" @(private) version_string_buf: [1024]u8 @(init, private) -init_os_version :: proc () { - os_version.platform = .MacOS +init_platform :: proc() { + ws :: strings.write_string + wi :: strings.write_int - // Start building display version b := strings.builder_from_bytes(version_string_buf[:]) - mib := []i32{sys.CTL_KERN, sys.KERN_OSVERSION} - build_buf: [12]u8 + version: NS.OperatingSystemVersion + { + NS.scoped_autoreleasepool() - ok := sys.sysctl(mib, &build_buf) - if !ok { - strings.write_string(&b, "macOS Unknown") - os_version.as_string = strings.to_string(b) - return + info := NS.ProcessInfo.processInfo() + version = info->operatingSystemVersion() + mem := info->physicalMemory() + + ram.total_ram = int(mem) } - build := string(cstring(&build_buf[0])) + macos_version = {int(version.majorVersion), int(version.minorVersion), int(version.patchVersion)} - // Do we have an exact match? - match: Darwin_Match - rel, exact := macos_release_map[build] - - if exact { - match = .Exact + when ODIN_PLATFORM_SUBTARGET == .iOS { + os_version.platform = .iOS + ws(&b, "iOS") } else { + os_version.platform = .MacOS + switch version.majorVersion { + case 15: ws(&b, "macOS Sequoia") + case 14: ws(&b, "macOS Sonoma") + case 13: ws(&b, "macOS Ventura") + case 12: ws(&b, "macOS Monterey") + case 11: ws(&b, "macOS Big Sur") + case 10: + switch version.minorVersion { + case 15: ws(&b, "macOS Catalina") + case 14: ws(&b, "macOS Mojave") + case 13: ws(&b, "macOS High Sierra") + case 12: ws(&b, "macOS Sierra") + case 11: ws(&b, "OS X El Capitan") + case 10: ws(&b, "OS X Yosemite") + case: + // `ProcessInfo.operatingSystemVersion` is 10.10 and up. + unreachable() + } + case: + // New version not yet added here. + assert(version.majorVersion > 15) + ws(&b, "macOS Unknown") + } + } + + ws(&b, " ") + wi(&b, int(version.majorVersion)) + ws(&b, ".") + wi(&b, int(version.minorVersion)) + ws(&b, ".") + wi(&b, int(version.patchVersion)) + + { + build_buf: [12]u8 + mib := []i32{unix.CTL_KERN, unix.KERN_OSVERSION} + ok := unix.sysctl(mib, &build_buf) + build := string(cstring(raw_data(build_buf[:]))) if ok else "Unknown" + + ws(&b, " (build ") + + build_start := len(b.buf) + ws(&b, build) + os_version.version = string(b.buf[build_start:][:len(build)]) + } + + { // Match on XNU kernel version - mib = []i32{sys.CTL_KERN, sys.KERN_OSRELEASE} version_bits: [12]u8 // enough for 999.999.999\x00 - have_kernel_version := sys.sysctl(mib, &version_bits) + mib := []i32{unix.CTL_KERN, unix.KERN_OSRELEASE} + ok := unix.sysctl(mib, &version_bits) + kernel := string(cstring(raw_data(version_bits[:]))) if ok else "Unknown" - major_ok, minor_ok, patch_ok: bool + major, _, tail := strings.partition(kernel, ".") + minor, _, patch := strings.partition(tail, ".") - tmp := runtime.default_temp_allocator_temp_begin() + os_version.major, _ = strconv.parse_int(major, 10) + os_version.minor, _ = strconv.parse_int(minor, 10) + os_version.patch, _ = strconv.parse_int(patch, 10) - triplet := strings.split(string(cstring(&version_bits[0])), ".", context.temp_allocator) - if len(triplet) != 3 { - have_kernel_version = false - } else { - rel.darwin.x, major_ok = strconv.parse_int(triplet[0]) - rel.darwin.y, minor_ok = strconv.parse_int(triplet[1]) - rel.darwin.z, patch_ok = strconv.parse_int(triplet[2]) - - if !(major_ok && minor_ok && patch_ok) { - have_kernel_version = false - } - } - - runtime.default_temp_allocator_temp_end(tmp) - - if !have_kernel_version { - // We don't know the kernel version, but we do know the build - strings.write_string(&b, "macOS Unknown (build ") - l := strings.builder_len(b) - strings.write_string(&b, build) - os_version.version = strings.to_string(b)[l:] - strings.write_rune(&b, ')') - os_version.as_string = strings.to_string(b) - return - } - rel, match = map_darwin_kernel_version_to_macos_release(build, rel.darwin) + ws(&b, ", kernel ") + ws(&b, kernel) + ws(&b, ")") } - os_version.major = rel.darwin.x - os_version.minor = rel.darwin.y - os_version.patch = rel.darwin.z - - strings.write_string(&b, rel.os_name) - if match == .Exact || match == .Nearest { - strings.write_rune(&b, ' ') - strings.write_string(&b, rel.release.name) - strings.write_rune(&b, ' ') - strings.write_int(&b, rel.release.version.x) - if rel.release.version.y > 0 || rel.release.version.z > 0 { - strings.write_rune(&b, '.') - strings.write_int(&b, rel.release.version.y) - } - if rel.release.version.z > 0 { - strings.write_rune(&b, '.') - strings.write_int(&b, rel.release.version.z) - } - if match == .Nearest { - strings.write_rune(&b, '?') - } - } else { - strings.write_string(&b, " Unknown") - } - - strings.write_string(&b, " (build ") - l := strings.builder_len(b) - strings.write_string(&b, build) - os_version.version = strings.to_string(b)[l:] - - strings.write_string(&b, ", kernel ") - strings.write_int(&b, rel.darwin.x) - strings.write_rune(&b, '.') - strings.write_int(&b, rel.darwin.y) - strings.write_rune(&b, '.') - strings.write_int(&b, rel.darwin.z) - strings.write_rune(&b, ')') - - os_version.as_string = strings.to_string(b) + os_version.as_string = string(b.buf[:]) } - -@(init) -init_ram :: proc() { - // Retrieve RAM info using `sysctl` - - mib := []i32{sys.CTL_HW, sys.HW_MEMSIZE} - mem_size: u64 - if sys.sysctl(mib, &mem_size) { - ram.total_ram = int(mem_size) - } -} - -@(private) -Darwin_To_Release :: struct { - darwin: [3]int, // Darwin kernel triplet - os_name: string, // OS X, MacOS - release: struct { - name: string, // Monterey, Mojave, etc. - version: [3]int, // 12.4, etc. - }, -} - -// Important: Order from lowest to highest kernel version -@(private) -macos_release_map: map[string]Darwin_To_Release = { - // MacOS Tiger - "8A428" = {{8, 0, 0}, "macOS", {"Tiger", {10, 4, 0}}}, - "8A432" = {{8, 0, 0}, "macOS", {"Tiger", {10, 4, 0}}}, - "8B15" = {{8, 1, 0}, "macOS", {"Tiger", {10, 4, 1}}}, - "8B17" = {{8, 1, 0}, "macOS", {"Tiger", {10, 4, 1}}}, - "8C46" = {{8, 2, 0}, "macOS", {"Tiger", {10, 4, 2}}}, - "8C47" = {{8, 2, 0}, "macOS", {"Tiger", {10, 4, 2}}}, - "8E102" = {{8, 2, 0}, "macOS", {"Tiger", {10, 4, 2}}}, - "8E45" = {{8, 2, 0}, "macOS", {"Tiger", {10, 4, 2}}}, - "8E90" = {{8, 2, 0}, "macOS", {"Tiger", {10, 4, 2}}}, - "8F46" = {{8, 3, 0}, "macOS", {"Tiger", {10, 4, 3}}}, - "8G32" = {{8, 4, 0}, "macOS", {"Tiger", {10, 4, 4}}}, - "8G1165" = {{8, 4, 0}, "macOS", {"Tiger", {10, 4, 4}}}, - "8H14" = {{8, 5, 0}, "macOS", {"Tiger", {10, 4, 5}}}, - "8G1454" = {{8, 5, 0}, "macOS", {"Tiger", {10, 4, 5}}}, - "8I127" = {{8, 6, 0}, "macOS", {"Tiger", {10, 4, 6}}}, - "8I1119" = {{8, 6, 0}, "macOS", {"Tiger", {10, 4, 6}}}, - "8J135" = {{8, 7, 0}, "macOS", {"Tiger", {10, 4, 7}}}, - "8J2135a" = {{8, 7, 0}, "macOS", {"Tiger", {10, 4, 7}}}, - "8K1079" = {{8, 7, 0}, "macOS", {"Tiger", {10, 4, 7}}}, - "8N5107" = {{8, 7, 0}, "macOS", {"Tiger", {10, 4, 7}}}, - "8L127" = {{8, 8, 0}, "macOS", {"Tiger", {10, 4, 8}}}, - "8L2127" = {{8, 8, 0}, "macOS", {"Tiger", {10, 4, 8}}}, - "8P135" = {{8, 9, 0}, "macOS", {"Tiger", {10, 4, 9}}}, - "8P2137" = {{8, 9, 0}, "macOS", {"Tiger", {10, 4, 9}}}, - "8R218" = {{8, 10, 0}, "macOS", {"Tiger", {10, 4, 10}}}, - "8R2218" = {{8, 10, 0}, "macOS", {"Tiger", {10, 4, 10}}}, - "8R2232" = {{8, 10, 0}, "macOS", {"Tiger", {10, 4, 10}}}, - "8S165" = {{8, 11, 0}, "macOS", {"Tiger", {10, 4, 11}}}, - "8S2167" = {{8, 11, 0}, "macOS", {"Tiger", {10, 4, 11}}}, - - // MacOS Leopard - "9A581" = {{9, 0, 0}, "macOS", {"Leopard", {10, 5, 0}}}, - "9B18" = {{9, 1, 0}, "macOS", {"Leopard", {10, 5, 1}}}, - "9B2117" = {{9, 1, 1}, "macOS", {"Leopard", {10, 5, 1}}}, - "9C31" = {{9, 2, 0}, "macOS", {"Leopard", {10, 5, 2}}}, - "9C7010" = {{9, 2, 0}, "macOS", {"Leopard", {10, 5, 2}}}, - "9D34" = {{9, 3, 0}, "macOS", {"Leopard", {10, 5, 3}}}, - "9E17" = {{9, 4, 0}, "macOS", {"Leopard", {10, 5, 4}}}, - "9F33" = {{9, 5, 0}, "macOS", {"Leopard", {10, 5, 5}}}, - "9G55" = {{9, 6, 0}, "macOS", {"Leopard", {10, 5, 6}}}, - "9G66" = {{9, 6, 0}, "macOS", {"Leopard", {10, 5, 6}}}, - "9G71" = {{9, 6, 0}, "macOS", {"Leopard", {10, 5, 6}}}, - "9J61" = {{9, 7, 0}, "macOS", {"Leopard", {10, 5, 7}}}, - "9L30" = {{9, 8, 0}, "macOS", {"Leopard", {10, 5, 8}}}, - "9L34" = {{9, 8, 0}, "macOS", {"Leopard", {10, 5, 8}}}, - - // MacOS Snow Leopard - "10A432" = {{10, 0, 0}, "macOS", {"Snow Leopard", {10, 6, 0}}}, - "10A433" = {{10, 0, 0}, "macOS", {"Snow Leopard", {10, 6, 0}}}, - "10B504" = {{10, 1, 0}, "macOS", {"Snow Leopard", {10, 6, 1}}}, - "10C540" = {{10, 2, 0}, "macOS", {"Snow Leopard", {10, 6, 2}}}, - "10D573" = {{10, 3, 0}, "macOS", {"Snow Leopard", {10, 6, 3}}}, - "10D575" = {{10, 3, 0}, "macOS", {"Snow Leopard", {10, 6, 3}}}, - "10D578" = {{10, 3, 0}, "macOS", {"Snow Leopard", {10, 6, 3}}}, - "10F569" = {{10, 4, 0}, "macOS", {"Snow Leopard", {10, 6, 4}}}, - "10H574" = {{10, 5, 0}, "macOS", {"Snow Leopard", {10, 6, 5}}}, - "10J567" = {{10, 6, 0}, "macOS", {"Snow Leopard", {10, 6, 6}}}, - "10J869" = {{10, 7, 0}, "macOS", {"Snow Leopard", {10, 6, 7}}}, - "10J3250" = {{10, 7, 0}, "macOS", {"Snow Leopard", {10, 6, 7}}}, - "10J4138" = {{10, 7, 0}, "macOS", {"Snow Leopard", {10, 6, 7}}}, - "10K540" = {{10, 8, 0}, "macOS", {"Snow Leopard", {10, 6, 8}}}, - "10K549" = {{10, 8, 0}, "macOS", {"Snow Leopard", {10, 6, 8}}}, - - // MacOS Lion - "11A511" = {{11, 0, 0}, "macOS", {"Lion", {10, 7, 0}}}, - "11A511s" = {{11, 0, 0}, "macOS", {"Lion", {10, 7, 0}}}, - "11A2061" = {{11, 0, 2}, "macOS", {"Lion", {10, 7, 0}}}, - "11A2063" = {{11, 0, 2}, "macOS", {"Lion", {10, 7, 0}}}, - "11B26" = {{11, 1, 0}, "macOS", {"Lion", {10, 7, 1}}}, - "11B2118" = {{11, 1, 0}, "macOS", {"Lion", {10, 7, 1}}}, - "11C74" = {{11, 2, 0}, "macOS", {"Lion", {10, 7, 2}}}, - "11D50" = {{11, 3, 0}, "macOS", {"Lion", {10, 7, 3}}}, - "11E53" = {{11, 4, 0}, "macOS", {"Lion", {10, 7, 4}}}, - "11G56" = {{11, 4, 2}, "macOS", {"Lion", {10, 7, 5}}}, - "11G63" = {{11, 4, 2}, "macOS", {"Lion", {10, 7, 5}}}, - - // MacOS Mountain Lion - "12A269" = {{12, 0, 0}, "macOS", {"Mountain Lion", {10, 8, 0}}}, - "12B19" = {{12, 1, 0}, "macOS", {"Mountain Lion", {10, 8, 1}}}, - "12C54" = {{12, 2, 0}, "macOS", {"Mountain Lion", {10, 8, 2}}}, - "12C60" = {{12, 2, 0}, "macOS", {"Mountain Lion", {10, 8, 2}}}, - "12C2034" = {{12, 2, 0}, "macOS", {"Mountain Lion", {10, 8, 2}}}, - "12C3104" = {{12, 2, 0}, "macOS", {"Mountain Lion", {10, 8, 2}}}, - "12D78" = {{12, 3, 0}, "macOS", {"Mountain Lion", {10, 8, 3}}}, - "12E55" = {{12, 4, 0}, "macOS", {"Mountain Lion", {10, 8, 4}}}, - "12E3067" = {{12, 4, 0}, "macOS", {"Mountain Lion", {10, 8, 4}}}, - "12E4022" = {{12, 4, 0}, "macOS", {"Mountain Lion", {10, 8, 4}}}, - "12F37" = {{12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - "12F45" = {{12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - "12F2501" = {{12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - "12F2518" = {{12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - "12F2542" = {{12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - "12F2560" = {{12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - - // MacOS Mavericks - "13A603" = {{13, 0, 0}, "macOS", {"Mavericks", {10, 9, 0}}}, - "13B42" = {{13, 0, 0}, "macOS", {"Mavericks", {10, 9, 1}}}, - "13C64" = {{13, 1, 0}, "macOS", {"Mavericks", {10, 9, 2}}}, - "13C1021" = {{13, 1, 0}, "macOS", {"Mavericks", {10, 9, 2}}}, - "13D65" = {{13, 2, 0}, "macOS", {"Mavericks", {10, 9, 3}}}, - "13E28" = {{13, 3, 0}, "macOS", {"Mavericks", {10, 9, 4}}}, - "13F34" = {{13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - "13F1066" = {{13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - "13F1077" = {{13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - "13F1096" = {{13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - "13F1112" = {{13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - "13F1134" = {{13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - "13F1507" = {{13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - "13F1603" = {{13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - "13F1712" = {{13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - "13F1808" = {{13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - "13F1911" = {{13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - - // MacOS Yosemite - "14A389" = {{14, 0, 0}, "macOS", {"Yosemite", {10, 10, 0}}}, - "14B25" = {{14, 0, 0}, "macOS", {"Yosemite", {10, 10, 1}}}, - "14C109" = {{14, 1, 0}, "macOS", {"Yosemite", {10, 10, 2}}}, - "14C1510" = {{14, 1, 0}, "macOS", {"Yosemite", {10, 10, 2}}}, - "14C2043" = {{14, 1, 0}, "macOS", {"Yosemite", {10, 10, 2}}}, - "14C1514" = {{14, 1, 0}, "macOS", {"Yosemite", {10, 10, 2}}}, - "14C2513" = {{14, 1, 0}, "macOS", {"Yosemite", {10, 10, 2}}}, - "14D131" = {{14, 3, 0}, "macOS", {"Yosemite", {10, 10, 3}}}, - "14D136" = {{14, 3, 0}, "macOS", {"Yosemite", {10, 10, 3}}}, - "14E46" = {{14, 4, 0}, "macOS", {"Yosemite", {10, 10, 4}}}, - "14F27" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F1021" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F1505" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F1509" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F1605" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F1713" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F1808" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F1909" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F1912" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F2009" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F2109" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F2315" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F2411" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - "14F2511" = {{14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - - // MacOS El Capitan - "15A284" = {{15, 0, 0}, "macOS", {"El Capitan", {10, 11, 0}}}, - "15B42" = {{15, 0, 0}, "macOS", {"El Capitan", {10, 11, 1}}}, - "15C50" = {{15, 2, 0}, "macOS", {"El Capitan", {10, 11, 2}}}, - "15D21" = {{15, 3, 0}, "macOS", {"El Capitan", {10, 11, 3}}}, - "15E65" = {{15, 4, 0}, "macOS", {"El Capitan", {10, 11, 4}}}, - "15F34" = {{15, 5, 0}, "macOS", {"El Capitan", {10, 11, 5}}}, - "15G31" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G1004" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G1011" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G1108" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G1212" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G1217" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G1421" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G1510" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G1611" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G17023" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G18013" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G19009" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G20015" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G21013" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - "15G22010" = {{15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - - // MacOS Sierra - "16A323" = {{16, 0, 0}, "macOS", {"Sierra", {10, 12, 0}}}, - "16B2555" = {{16, 1, 0}, "macOS", {"Sierra", {10, 12, 1}}}, - "16B2657" = {{16, 1, 0}, "macOS", {"Sierra", {10, 12, 1}}}, - "16C67" = {{16, 3, 0}, "macOS", {"Sierra", {10, 12, 2}}}, - "16C68" = {{16, 3, 0}, "macOS", {"Sierra", {10, 12, 2}}}, - "16D32" = {{16, 4, 0}, "macOS", {"Sierra", {10, 12, 3}}}, - "16E195" = {{16, 5, 0}, "macOS", {"Sierra", {10, 12, 4}}}, - "16F73" = {{16, 6, 0}, "macOS", {"Sierra", {10, 12, 5}}}, - "16F2073" = {{16, 6, 0}, "macOS", {"Sierra", {10, 12, 5}}}, - "16G29" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G1036" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G1114" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G1212" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G1314" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G1408" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G1510" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G1618" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G1710" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G1815" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G1917" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G1918" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G2016" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G2127" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G2128" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - "16G2136" = {{16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - - // MacOS High Sierra - "17A365" = {{17, 0, 0}, "macOS", {"High Sierra", {10, 13, 0}}}, - "17A405" = {{17, 0, 0}, "macOS", {"High Sierra", {10, 13, 0}}}, - "17B48" = {{17, 2, 0}, "macOS", {"High Sierra", {10, 13, 1}}}, - "17B1002" = {{17, 2, 0}, "macOS", {"High Sierra", {10, 13, 1}}}, - "17B1003" = {{17, 2, 0}, "macOS", {"High Sierra", {10, 13, 1}}}, - "17C88" = {{17, 3, 0}, "macOS", {"High Sierra", {10, 13, 2}}}, - "17C89" = {{17, 3, 0}, "macOS", {"High Sierra", {10, 13, 2}}}, - "17C205" = {{17, 3, 0}, "macOS", {"High Sierra", {10, 13, 2}}}, - "17C2205" = {{17, 3, 0}, "macOS", {"High Sierra", {10, 13, 2}}}, - "17D47" = {{17, 4, 0}, "macOS", {"High Sierra", {10, 13, 3}}}, - "17D2047" = {{17, 4, 0}, "macOS", {"High Sierra", {10, 13, 3}}}, - "17D102" = {{17, 4, 0}, "macOS", {"High Sierra", {10, 13, 3}}}, - "17D2102" = {{17, 4, 0}, "macOS", {"High Sierra", {10, 13, 3}}}, - "17E199" = {{17, 5, 0}, "macOS", {"High Sierra", {10, 13, 4}}}, - "17E202" = {{17, 5, 0}, "macOS", {"High Sierra", {10, 13, 4}}}, - "17F77" = {{17, 6, 0}, "macOS", {"High Sierra", {10, 13, 5}}}, - "17G65" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G2208" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G2307" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G3025" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G4015" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G5019" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G6029" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G6030" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G7024" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G8029" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G8030" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G8037" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G9016" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G10021" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G11023" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G12034" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G13033" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G13035" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G14019" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G14033" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - "17G14042" = {{17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - - // MacOS Mojave - "18A391" = {{18, 0, 0}, "macOS", {"Mojave", {10, 14, 0}}}, - "18B75" = {{18, 2, 0}, "macOS", {"Mojave", {10, 14, 1}}}, - "18B2107" = {{18, 2, 0}, "macOS", {"Mojave", {10, 14, 1}}}, - "18B3094" = {{18, 2, 0}, "macOS", {"Mojave", {10, 14, 1}}}, - "18C54" = {{18, 2, 0}, "macOS", {"Mojave", {10, 14, 2}}}, - "18D42" = {{18, 2, 0}, "macOS", {"Mojave", {10, 14, 3}}}, - "18D43" = {{18, 2, 0}, "macOS", {"Mojave", {10, 14, 3}}}, - "18D109" = {{18, 2, 0}, "macOS", {"Mojave", {10, 14, 3}}}, - "18E226" = {{18, 5, 0}, "macOS", {"Mojave", {10, 14, 4}}}, - "18E227" = {{18, 5, 0}, "macOS", {"Mojave", {10, 14, 4}}}, - "18F132" = {{18, 6, 0}, "macOS", {"Mojave", {10, 14, 5}}}, - "18G84" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G87" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G95" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G103" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G1012" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G2022" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G3020" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G4032" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G5033" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G6020" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G6032" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G6042" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G7016" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G8012" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G8022" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G9028" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G9216" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - "18G9323" = {{18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - - // MacOS Catalina - "19A583" = {{19, 0, 0}, "macOS", {"Catalina", {10, 15, 0}}}, - "19A602" = {{19, 0, 0}, "macOS", {"Catalina", {10, 15, 0}}}, - "19A603" = {{19, 0, 0}, "macOS", {"Catalina", {10, 15, 0}}}, - "19B88" = {{19, 0, 0}, "macOS", {"Catalina", {10, 15, 1}}}, - "19C57" = {{19, 2, 0}, "macOS", {"Catalina", {10, 15, 2}}}, - "19C58" = {{19, 2, 0}, "macOS", {"Catalina", {10, 15, 2}}}, - "19D76" = {{19, 3, 0}, "macOS", {"Catalina", {10, 15, 3}}}, - "19E266" = {{19, 4, 0}, "macOS", {"Catalina", {10, 15, 4}}}, - "19E287" = {{19, 4, 0}, "macOS", {"Catalina", {10, 15, 4}}}, - "19F96" = {{19, 5, 0}, "macOS", {"Catalina", {10, 15, 5}}}, - "19F101" = {{19, 5, 0}, "macOS", {"Catalina", {10, 15, 5}}}, - "19G73" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 6}}}, - "19G2021" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 6}}}, - "19H2" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H4" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H15" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H114" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H512" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H524" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H1030" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H1217" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H1323" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H1417" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H1419" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H1519" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H1615" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H1713" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H1715" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H1824" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H1922" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - "19H2026" = {{19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - - // MacOS Big Sur - "20A2411" = {{20, 1, 0}, "macOS", {"Big Sur", {11, 0, 0}}}, - "20B29" = {{20, 1, 0}, "macOS", {"Big Sur", {11, 0, 1}}}, - "20B50" = {{20, 1, 0}, "macOS", {"Big Sur", {11, 0, 1}}}, - "20C69" = {{20, 2, 0}, "macOS", {"Big Sur", {11, 1, 0}}}, - "20D64" = {{20, 3, 0}, "macOS", {"Big Sur", {11, 2, 0}}}, - "20D74" = {{20, 3, 0}, "macOS", {"Big Sur", {11, 2, 1}}}, - "20D75" = {{20, 3, 0}, "macOS", {"Big Sur", {11, 2, 1}}}, - "20D80" = {{20, 3, 0}, "macOS", {"Big Sur", {11, 2, 2}}}, - "20D91" = {{20, 3, 0}, "macOS", {"Big Sur", {11, 2, 3}}}, - "20E232" = {{20, 4, 0}, "macOS", {"Big Sur", {11, 3, 0}}}, - "20E241" = {{20, 4, 0}, "macOS", {"Big Sur", {11, 3, 1}}}, - "20F71" = {{20, 5, 0}, "macOS", {"Big Sur", {11, 4, 0}}}, - "20G71" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 5, 0}}}, - "20G80" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 5, 1}}}, - "20G95" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 5, 2}}}, - "20G165" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 6, 0}}}, - "20G224" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 6, 1}}}, - "20G314" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 6, 2}}}, - "20G415" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 6, 3}}}, - "20G417" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 6, 4}}}, - "20G527" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 6, 5}}}, - "20G624" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 6, 6}}}, - "20G630" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 6, 7}}}, - "20G730" = {{20, 6, 0}, "macOS", {"Big Sur", {11, 6, 8}}}, - - // MacOS Monterey - "21A344" = {{21, 0, 1}, "macOS", {"Monterey", {12, 0, 0}}}, - "21A559" = {{21, 1, 0}, "macOS", {"Monterey", {12, 0, 1}}}, - "21C52" = {{21, 2, 0}, "macOS", {"Monterey", {12, 1, 0}}}, - "21D49" = {{21, 3, 0}, "macOS", {"Monterey", {12, 2, 0}}}, - "21D62" = {{21, 3, 0}, "macOS", {"Monterey", {12, 2, 1}}}, - "21E230" = {{21, 4, 0}, "macOS", {"Monterey", {12, 3, 0}}}, - "21E258" = {{21, 4, 0}, "macOS", {"Monterey", {12, 3, 1}}}, - "21F79" = {{21, 5, 0}, "macOS", {"Monterey", {12, 4, 0}}}, - "21F2081" = {{21, 5, 0}, "macOS", {"Monterey", {12, 4, 0}}}, - "21F2092" = {{21, 5, 0}, "macOS", {"Monterey", {12, 4, 0}}}, - "21G72" = {{21, 6, 0}, "macOS", {"Monterey", {12, 5, 0}}}, - "21G83" = {{21, 6, 0}, "macOS", {"Monterey", {12, 5, 1}}}, - "21G115" = {{21, 6, 0}, "macOS", {"Monterey", {12, 6, 0}}}, -} - -@(private) -Darwin_Match :: enum { - Unknown, - Exact, - Nearest, -} - -@(private) -map_darwin_kernel_version_to_macos_release :: proc(build: string, darwin: [3]int) -> (res: Darwin_To_Release, match: Darwin_Match) { - // Find exact release match if possible. - if v, v_ok := macos_release_map[build]; v_ok { - return v, .Exact - } - - nearest: Darwin_To_Release - for _, v in macos_release_map { - // Try an exact match on XNU version first. - if darwin == v.darwin { - return v, .Exact - } - - // Major kernel version needs to match exactly, - // otherwise the release is considered .Unknown - if darwin.x == v.darwin.x { - if nearest == {} { - nearest = v - } - if darwin.y >= v.darwin.y && v.darwin != nearest.darwin { - nearest = v - if darwin.z >= v.darwin.z && v.darwin != nearest.darwin { - nearest = v - } - } - } - } - - if nearest == {} { - return {darwin, "macOS", {"Unknown", {}}}, .Unknown - } else { - return nearest, .Nearest - } -} \ No newline at end of file diff --git a/core/sys/info/platform_freebsd.odin b/core/sys/info/platform_freebsd.odin index 1d53da998..b26fb7875 100644 --- a/core/sys/info/platform_freebsd.odin +++ b/core/sys/info/platform_freebsd.odin @@ -1,10 +1,9 @@ -// +build freebsd package sysinfo import sys "core:sys/unix" import "core:strings" import "core:strconv" -import "core:runtime" +import "base:runtime" @(private) version_string_buf: [1024]u8 @@ -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 14961c2a8..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 "core:intrinsics" -import "core:runtime" -import "core:strings" -import "core:strconv" +import "base:intrinsics" +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 { - errno := linux.close(fd) - assert(errno == .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 9c1c7b04c..4c00ddadf 100644 --- a/core/sys/info/platform_windows.odin +++ b/core/sys/info/platform_windows.odin @@ -1,13 +1,12 @@ -// +build windows package sysinfo import sys "core:sys/windows" -import "core:intrinsics" +import "base:intrinsics" import "core:strings" import "core:unicode/utf16" import "core:fmt" -import "core:runtime" +import "base:runtime" @(private) version_string_buf: [1024]u8 @@ -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..f624a1718 100644 --- a/core/sys/info/sysinfo.odin +++ b/core/sys/info/sysinfo.odin @@ -1,6 +1,6 @@ package sysinfo -when !(ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 || ODIN_ARCH == .arm32 || ODIN_ARCH == .arm64) { +when !(ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 || ODIN_ARCH == .arm32 || ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64) { #assert(false, "This package is unsupported on this architecture.") } @@ -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/kqueue/kqueue.odin b/core/sys/kqueue/kqueue.odin new file mode 100644 index 000000000..56be1cf7a --- /dev/null +++ b/core/sys/kqueue/kqueue.odin @@ -0,0 +1,256 @@ +#+build darwin, netbsd, openbsd, freebsd +package kqueue + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +import "base:intrinsics" + +import "core:c" +import "core:sys/posix" + +KQ :: posix.FD + +kqueue :: proc() -> (kq: KQ, err: posix.Errno) { + kq = _kqueue() + if kq == -1 { + err = posix.errno() + } + return +} + +kevent :: proc(kq: KQ, change_list: []KEvent, event_list: []KEvent, timeout: ^posix.timespec) -> (n_events: c.int, err: posix.Errno) { + n_events = _kevent( + kq, + raw_data(change_list), + c.int(len(change_list)), + raw_data(event_list), + c.int(len(event_list)), + timeout, + ) + if n_events == -1 { + err = posix.errno() + } + return +} + +Flag :: enum _Flags_Backing { + Add = log2(0x0001), // Add event to kq (implies .Enable). + Delete = log2(0x0002), // Delete event from kq. + Enable = log2(0x0004), // Enable event. + Disable = log2(0x0008), // Disable event (not reported). + One_Shot = log2(0x0010), // Only report one occurrence. + Clear = log2(0x0020), // Clear event state after reporting. + Receipt = log2(0x0040), // Force immediate event output. + Dispatch = log2(0x0080), // Disable event after reporting. + + Error = log2(0x4000), // Error, data contains errno. + EOF = log2(0x8000), // EOF detected. +} +Flags :: bit_set[Flag; _Flags_Backing] + +Filter :: enum _Filter_Backing { + Read = _FILTER_READ, // Check for read availability on the file descriptor. + Write = _FILTER_WRITE, // Check for write availability on the file descriptor. + AIO = _FILTER_AIO, // Attached to AIO requests. + VNode = _FILTER_VNODE, // Check for changes to the subject file. + Proc = _FILTER_PROC, // Check for changes to the subject process. + Signal = _FILTER_SIGNAL, // Check for signals delivered to the process. + Timer = _FILTER_TIMER, // Timers. +} + +RW_Flag :: enum u32 { + Low_Water_Mark = log2(0x00000001), +} +RW_Flags :: bit_set[RW_Flag; u32] + +VNode_Flag :: enum u32 { + Delete = log2(0x00000001), // Deleted. + Write = log2(0x00000002), // Contents changed. + Extend = log2(0x00000004), // Size increased. + Attrib = log2(0x00000008), // Attributes changed. + Link = log2(0x00000010), // Link count changed. + Rename = log2(0x00000020), // Renamed. + Revoke = log2(0x00000040), // Access was revoked. +} +VNode_Flags :: bit_set[VNode_Flag; u32] + +Proc_Flag :: enum u32 { + Exit = log2(0x80000000), // Process exited. + Fork = log2(0x40000000), // Process forked. + Exec = log2(0x20000000), // Process exec'd. + Signal = log2(0x08000000), // Shared with `Filter.Signal`. +} +Proc_Flags :: bit_set[Proc_Flag; u32] + +Timer_Flag :: enum u32 { + Seconds = log2(0x00000001), // Data is seconds. + USeconds = log2(0x00000002), // Data is microseconds. + NSeconds = log2(_NOTE_NSECONDS), // Data is nanoseconds. + Absolute = log2(_NOTE_ABSOLUTE), // Absolute timeout. +} +Timer_Flags :: bit_set[Timer_Flag; u32] + +when ODIN_OS == .Darwin { + + _Filter_Backing :: distinct i16 + _Flags_Backing :: distinct u16 + + _FILTER_READ :: -1 + _FILTER_WRITE :: -2 + _FILTER_AIO :: -3 + _FILTER_VNODE :: -4 + _FILTER_PROC :: -5 + _FILTER_SIGNAL :: -6 + _FILTER_TIMER :: -7 + + _NOTE_NSECONDS :: 0x00000004 + _NOTE_ABSOLUTE :: 0x00000008 + + KEvent :: struct #align(4) { + // Value used to identify this event. The exact interpretation is determined by the attached filter. + ident: uintptr, + // Filter for event. + filter: Filter, + // General flags. + flags: Flags, + // Filter specific flags. + fflags: struct #raw_union { + rw: RW_Flags, + vnode: VNode_Flags, + fproc: Proc_Flags, + // vm: VM_Flags, + timer: Timer_Flags, + }, + // Filter specific data. + data: c.long /* intptr_t */, + // Opaque user data passed through the kernel unchanged. + udata: rawptr, + } + +} else when ODIN_OS == .FreeBSD { + + _Filter_Backing :: distinct i16 + _Flags_Backing :: distinct u16 + + _FILTER_READ :: -1 + _FILTER_WRITE :: -2 + _FILTER_AIO :: -3 + _FILTER_VNODE :: -4 + _FILTER_PROC :: -5 + _FILTER_SIGNAL :: -6 + _FILTER_TIMER :: -7 + + _NOTE_NSECONDS :: 0x00000004 + _NOTE_ABSOLUTE :: 0x00000008 + + KEvent :: struct { + // Value used to identify this event. The exact interpretation is determined by the attached filter. + ident: uintptr, + // Filter for event. + filter: Filter, + // General flags. + flags: Flags, + // Filter specific flags. + fflags: struct #raw_union { + rw: RW_Flags, + vnode: VNode_Flags, + fproc: Proc_Flags, + // vm: VM_Flags, + timer: Timer_Flags, + }, + // Filter specific data. + data: i64, + // Opaque user data passed through the kernel unchanged. + udata: rawptr, + // Extensions. + ext: [4]u64, + } +} else when ODIN_OS == .NetBSD { + + _Filter_Backing :: distinct u32 + _Flags_Backing :: distinct u32 + + _FILTER_READ :: 0 + _FILTER_WRITE :: 1 + _FILTER_AIO :: 2 + _FILTER_VNODE :: 3 + _FILTER_PROC :: 4 + _FILTER_SIGNAL :: 5 + _FILTER_TIMER :: 6 + + _NOTE_NSECONDS :: 0x00000003 + _NOTE_ABSOLUTE :: 0x00000010 + + KEvent :: struct #align(4) { + // Value used to identify this event. The exact interpretation is determined by the attached filter. + ident: uintptr, + // Filter for event. + filter: Filter, + // General flags. + flags: Flags, + // Filter specific flags. + fflags: struct #raw_union { + rw: RW_Flags, + vnode: VNode_Flags, + fproc: Proc_Flags, + // vm: VM_Flags, + timer: Timer_Flags, + }, + // Filter specific data. + data: i64, + // Opaque user data passed through the kernel unchanged. + udata: rawptr, + // Extensions. + ext: [4]u64, + } +} else when ODIN_OS == .OpenBSD { + + _Filter_Backing :: distinct i16 + _Flags_Backing :: distinct u16 + + _FILTER_READ :: -1 + _FILTER_WRITE :: -2 + _FILTER_AIO :: -3 + _FILTER_VNODE :: -4 + _FILTER_PROC :: -5 + _FILTER_SIGNAL :: -6 + _FILTER_TIMER :: -7 + + _NOTE_NSECONDS :: 0x00000003 + _NOTE_ABSOLUTE :: 0x00000010 + + KEvent :: struct #align(4) { + // Value used to identify this event. The exact interpretation is determined by the attached filter. + ident: uintptr, + // Filter for event. + filter: Filter, + // General flags. + flags: Flags, + // Filter specific flags. + fflags: struct #raw_union { + rw: RW_Flags, + vnode: VNode_Flags, + fproc: Proc_Flags, + // vm: VM_Flags, + timer: Timer_Flags, + }, + // Filter specific data. + data: i64, + // Opaque user data passed through the kernel unchanged. + udata: rawptr, + } +} + +@(private) +log2 :: intrinsics.constant_log2 + +foreign lib { + @(link_name="kqueue") + _kqueue :: proc() -> KQ --- + @(link_name="kevent") + _kevent :: proc(kq: KQ, change_list: [^]KEvent, n_changes: c.int, event_list: [^]KEvent, n_events: c.int, timeout: ^posix.timespec) -> c.int --- +} diff --git a/core/sys/linux/bits.odin b/core/sys/linux/bits.odin index 0cf90ed3b..c304397de 100644 --- a/core/sys/linux/bits.odin +++ b/core/sys/linux/bits.odin @@ -1,7 +1,9 @@ package linux -/// Represents an error returned by most of syscalls +/* + Represents an error returned by most of syscalls +*/ Errno :: enum i32 { NONE = 0, // Errno-base @@ -46,6 +48,7 @@ Errno :: enum i32 { ENOSYS = 38, ENOTEMPTY = 39, ELOOP = 40, + EUNKNOWN_41 = 41, ENOMSG = 42, EIDRM = 43, ECHRNG = 44, @@ -62,6 +65,7 @@ Errno :: enum i32 { ENOANO = 55, EBADRQC = 56, EBADSLT = 57, + EUNKNOWN_58 = 58, EBFONT = 59, ENOSTR = 60, ENODATA = 61, @@ -142,29 +146,75 @@ Errno :: enum i32 { EDEADLOCK = EDEADLK, } +/* + Bits for Open_Flags. -/// Bits for Open_Flags -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, + RDONLY flag is not present, because it has the value of 0, i.e. it is the + default, unless WRONLY or RDWR is specified. +*/ +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, + } } -/// Bits for FD_Flags bitset +/* + Bits for FD_Flags bitset +*/ FD_Flags_Bits :: enum { SYMLINK_NOFOLLOW = 8, REMOVEDIR = 9, @@ -177,7 +227,9 @@ FD_Flags_Bits :: enum { RECURSIVE = 15, } -/// The bits for the Mode bitset. +/* + The bits for the Mode bitset. +*/ Mode_Bits :: enum { IXOTH = 0, // 0o0000001 IWOTH = 1, // 0o0000002 @@ -191,13 +243,15 @@ 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 } -/// The bits used by the Statx_Mask bitset +/* + The bits used by the Statx_Mask bitset +*/ Statx_Mask_Bits :: enum { TYPE = 0, MODE = 1, @@ -215,8 +269,10 @@ Statx_Mask_Bits :: enum { DIOALIGN = 13, } -/// Bits found in Statx_Attr bitset -/// You should not use these directly +/* + Bits found in Statx_Attr bitset + You should not use these directly +*/ Statx_Attr_Bits :: enum { COMPRESSED = 2, // 0x00000004 IMMUTABLE = 4, // 0x00000010 @@ -229,7 +285,9 @@ Statx_Attr_Bits :: enum { DAX = 21, // 0x00200000 } -/// Magic bits for filesystems returned by Stat_FS +/* + Magic bits for filesystems returned by Stat_FS +*/ FS_Magic :: enum u32 { ADFS_SUPER_MAGIC = 0xadf5, AFFS_SUPER_MAGIC = 0xadff, @@ -317,7 +375,9 @@ FS_Magic :: enum u32 { _XIAFS_SUPER_MAGIC = 0x012fd16d, } -/// Bits for FS_Flags bitset +/* + Bits for FS_Flags bitset +*/ FS_Flags_Bits :: enum { RDONLY = 0, NOSUID = 1, @@ -340,20 +400,26 @@ Seek_Whence :: enum i16 { HOLE = 4, } -/// Bits for Close_Range_Flags +/* + Bits for Close_Range_Flags +*/ Close_Range_Flags_Bits :: enum { CLOEXEC = 2, UNSHARE = 1, } -/// Bits for Rename_Flags +/* + Bits for Rename_Flags +*/ Rename_Flags_Bits :: enum { EXCHANGE = 1, NOREPLACE = 0, WHITEOUT = 2, } -/// Type of the file in a directory entry +/* + Type of the file in a directory entry +*/ Dirent_Type :: enum u8 { UNKNOWN = 0, FIFO = 1, @@ -366,14 +432,18 @@ Dirent_Type :: enum u8 { WHT = 14, } -/// Type of a lock for fcntl.2 +/* + Type of a lock for fcntl(2) +*/ FLock_Type :: enum i16 { RDLCK = 0, WRLCK = 1, UNLCK = 2, } -/// Bits for FD_Notifications +/* + Bits for FD_Notifications +*/ FD_Notifications_Bits :: enum { ACCESS = 0, MODIFY = 1, @@ -384,7 +454,9 @@ FD_Notifications_Bits :: enum { MULTISHOT = 31, } -/// Bits for seal +/* + Bits for seal +*/ Seal_Bits :: enum { SEAL = 0, SHRINK = 1, @@ -408,14 +480,18 @@ FD_Lease :: enum { UNLCK = 2, } -/// Kind of owner for FD_Owner +/* + Kind of owner for FD_Owner +*/ F_Owner_Type :: enum i32 { OWNER_TID = 0, OWNER_PID = 1, OWNER_PGRP = 2, } -/// Command for fcntl.2 +/* + Command for fcntl(2) +*/ FCntl_Command :: enum { DUPFD = 0, GETFD = 1, @@ -465,7 +541,39 @@ Fd_Poll_Events_Bits :: enum { RDHUP = 13, } -/// Bits for Mem_Protection bitfield +Inotify_Init_Bits :: enum { + NONBLOCK = 11, + CLOEXEC = 19, +} + +Inotify_Event_Bits :: enum u32 { + ACCESS = 0, + MODIFY = 1, + ATTRIB = 2, + CLOSE_WRITE = 3, + CLOSE_NOWRITE = 4, + OPEN = 5, + MOVED_FROM = 6, + MOVED_TO = 7, + CREATE = 8, + DELETE = 9, + DELETE_SELF = 10, + MOVE_SELF = 11, + UNMOUNT = 13, + Q_OVERFLOW = 14, + IGNORED = 15, + ONLYDIR = 24, + DONT_FOLLOW = 25, + EXCL_UNLINK = 26, + MASK_CREATE = 28, + MASK_ADD = 29, + ISDIR = 30, + ONESHOT = 31, +} + +/* + Bits for Mem_Protection bitfield +*/ Mem_Protection_Bits :: enum{ READ = 0, WRITE = 1, @@ -479,7 +587,9 @@ Mem_Protection_Bits :: enum{ GROWSUP = 25, } -/// Bits for Map_Flags +/* + Bits for Map_Flags +*/ Map_Flags_Bits :: enum { SHARED = 0, PRIVATE = 1, @@ -504,19 +614,25 @@ Map_Flags_Bits :: enum { UNINITIALIZED = 26, } -/// Bits for MLock_Flags +/* + Bits for MLock_Flags +*/ MLock_Flags_Bits :: enum { ONFAULT = 0, } -/// Bits for MSync_Flags +/* + Bits for MSync_Flags +*/ MSync_Flags_Bits :: enum { ASYNC = 0, INVALIDATE = 1, SYNC = 2, } -/// Argument for madvice.2 +/* + Argument for madvice(2) +*/ MAdvice :: enum { NORMAL = 0, RANDOM = 1, @@ -545,27 +661,35 @@ MAdvice :: enum { SOFT_OFFLINE = 101, } -/// Bits for PKey_Access_Rights +/* + Bits for PKey_Access_Rights +*/ PKey_Access_Bits :: enum { DISABLE_ACCESS = 0, DISABLE_WRITE = 2, } -/// Bits for MRemap_Flags +/* + Bits for MRemap_Flags +*/ MRemap_Flags_Bits :: enum { MAYMOVE = 0, FIXED = 1, DONTUNMAP = 2, } -/// Bits for Get_Random_Flags +/* + Bits for Get_Random_Flags +*/ Get_Random_Flags_Bits :: enum { RANDOM = 0, NONBLOCK = 1, INSECURE = 2, } -/// Bits for Perf_Flags +/* + Bits for Perf_Flags +*/ Perf_Flags_Bits :: enum { FD_NO_GROUP = 0, FD_OUTPUT = 1, @@ -573,7 +697,9 @@ Perf_Flags_Bits :: enum { FD_CLOEXEC = 3, } -/// Union tag for Perf_Event_Attr struct +/* + Union tag for Perf_Event_Attr struct +*/ Perf_Event_Type :: enum u32 { HARDWARE = 0, SOFTWARE = 1, @@ -633,7 +759,9 @@ Perf_Cap_Flags_Bits :: enum u64 { User_Time_Short = 5, } -/// Specifies the type of the hardware event that you want to get info about +/* + Specifies the type of the hardware event that you want to get info about +*/ Perf_Hardware_Id :: enum u64 { CPU_CYCLES = 0, INSTRUCTIONS = 1, @@ -647,7 +775,9 @@ Perf_Hardware_Id :: enum u64 { REF_CPU_CYCLES = 9, } -/// Specifies the cache for the particular cache event that you want to get info about +/* + Specifies the cache for the particular cache event that you want to get info about +*/ Perf_Hardware_Cache_Id :: enum u64 { L1D = 0, L1I = 1, @@ -658,20 +788,26 @@ Perf_Hardware_Cache_Id :: enum u64 { NODE = 6, } -/// Specifies the cache op that you want to get info about +/* + Specifies the cache op that you want to get info about +*/ Perf_Hardware_Cache_Op_Id :: enum u64 { READ = 0, WRITE = 1, PREFETCH = 2, } -/// Specifies the cache operation result that you want to get info about +/* + Specifies the cache operation result that you want to get info about +*/ Perf_Hardware_Cache_Result_Id :: enum u64 { ACCESS = 0, MISS = 1, } -/// Specifies the particular software event that you want to get info about +/* + Specifies the particular software event that you want to get info about +*/ Perf_Software_Id :: enum u64 { CPU_CLOCK = 0, TASK_CLOCK = 1, @@ -688,7 +824,9 @@ Perf_Software_Id :: enum u64 { } -/// Specifies which values to include in the sample +/* + Specifies which values to include in the sample +*/ Perf_Event_Sample_Type_Bits :: enum { IP = 0, TID = 1, @@ -718,7 +856,7 @@ Perf_Event_Sample_Type_Bits :: enum { } /// Describes field sets to include in mmaped page -Perf_Read_Format :: enum { +Perf_Read_Format_Bits :: enum { TOTAL_TIME_ENABLED = 0, TOTAL_TIME_RUNNING = 1, ID = 2, @@ -726,7 +864,9 @@ Perf_Read_Format :: enum { LOST = 4, } -/// Chooses the breakpoint type +/* + Chooses the breakpoint type +*/ Hardware_Breakpoint_Type :: enum u32 { EMPTY = 0, R = 1, @@ -736,7 +876,9 @@ Hardware_Breakpoint_Type :: enum u32 { INVALID = RW | X, } -/// Bits for Branch_Sample_Type +/* + Bits for Branch_Sample_Type +*/ Branch_Sample_Type_Bits :: enum { USER = 0, KERNEL = 1, @@ -759,7 +901,9 @@ Branch_Sample_Type_Bits :: enum { PRIV_SAVE = 18, } -/// Represent the type of Id +/* + Represent the type of Id +*/ Id_Type :: enum uint { ALL = 0, PID = 1, @@ -767,26 +911,32 @@ Id_Type :: enum uint { PIDFD = 3, } -/// Options for wait syscalls +/* + Options for wait syscalls +*/ Wait_Option :: enum { WNOHANG = 0, WUNTRACED = 1, WSTOPPED = 1, WEXITED = 2, WCONTINUED = 3, - WNOWAIT = 24, + WNOWAIT = 24, // // For processes created using clone __WNOTHREAD = 29, __WALL = 30, __WCLONE = 31, } -/// Bits for flags for pidfd +/* + Bits for flags for pidfd +*/ Pid_FD_Flags_Bits :: enum { NONBLOCK = 11, } -/// Priority for process, process group, user +/* + Priority for process, process group, user +*/ Priority_Which :: enum i32 { PROCESS = 0, PGRP = 1, @@ -849,10 +999,39 @@ Sig_Stack_Flag :: enum i32 { AUTODISARM = 31, } -/// Type of socket to create -/// For TCP you want to use SOCK_STREAM -/// For UDP you want to use SOCK_DGRAM -/// Also see Protocol +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, +} + +/* + Translation of code in Sig_Info for when signo is SIGCHLD +*/ +Sig_Child_Code :: enum { + NONE, + EXITED, + KILLED, + DUMPED, + TRAPPED, + STOPPED, + CONTINUED, +} + + +/* + Type of socket to create + - For TCP you want to use SOCK_STREAM + - For UDP you want to use SOCK_DGRAM + Also see `Protocol` +*/ Socket_Type :: enum { STREAM = 1, DGRAM = 2, @@ -863,13 +1042,17 @@ Socket_Type :: enum { PACKET = 10, } -/// Bits for Socket_FD_Flags +/* + Bits for Socket_FD_Flags +*/ Socket_FD_Flags_Bits :: enum { - NONBLOCK = 14, - CLOEXEC = 25, + NONBLOCK = 11, + CLOEXEC = 19, } -/// Protocol family +/* + Protocol family +*/ Protocol_Family :: enum u16 { UNSPEC = 0, LOCAL = 1, @@ -922,11 +1105,13 @@ Protocol_Family :: enum u16 { MCTP = 45, } -/// The protocol number according to IANA protocol number list -/// Full list of protocol numbers: -/// https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml -/// Supported by the OS protocols can be queried by reading: -/// /etc/protocols +/* + The protocol number according to IANA protocol number list + Full list of protocol numbers: + https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml + Supported by the OS protocols can be queried by reading: + /etc/protocols +*/ Protocol :: enum { HOPOPT = 0, ICMP = 1, @@ -1066,7 +1251,9 @@ Protocol :: enum { Reserved = 255, } -/// API Level for get/setsockopt.2 +/* + API Level for getsockopt(2)/setsockopt(2) +*/ Socket_API_Level :: enum { // Comes from SOCKET = 1, @@ -1103,8 +1290,10 @@ Socket_API_Level :: enum { SMC = 286, } -/// If Socket_API_Level == .SOCKET, these are the options -/// you can specify in get/setsockopt.2 +/* + If Socket_API_Level == .SOCKET, these are the options + you can specify in getsockopt(2)/setsockopt(2) +*/ Socket_Option :: enum { DEBUG = 1, REUSEADDR = 2, @@ -1183,14 +1372,16 @@ Socket_Option :: enum { RESERVE_MEM = 73, TXREHASH = 74, RCVMARK = 75, - // Hardcoded 64-bit Time. It's time to move on. - TIMESTAMP = TIMESTAMP_NEW, - TIMESTAMPNS = TIMESTAMPNS_NEW, - TIMESTAMPING = TIMESTAMPING_NEW, - RCVTIMEO = RCVTIMEO_NEW, - SNDTIMEO = SNDTIMEO_NEW, + TIMESTAMP = TIMESTAMP_OLD when _SOCKET_OPTION_OLD else TIMESTAMP_NEW, + TIMESTAMPNS = TIMESTAMPNS_OLD when _SOCKET_OPTION_OLD else TIMESTAMPNS_NEW, + TIMESTAMPING = TIMESTAMPING_OLD when _SOCKET_OPTION_OLD else TIMESTAMPING_NEW, + RCVTIMEO = RCVTIMEO_OLD when _SOCKET_OPTION_OLD else RCVTIMEO_NEW, + SNDTIMEO = SNDTIMEO_OLD when _SOCKET_OPTION_OLD else SNDTIMEO_NEW, } +@(private) +_SOCKET_OPTION_OLD :: size_of(rawptr) == 8 /* || size_of(time_t) == size_of(__kernel_long_t) */ + Socket_UDP_Option :: enum { CORK = 1, ENCAP = 100, @@ -1249,7 +1440,9 @@ Socket_TCP_Option :: enum { TX_DELAY = 37, } -/// Bits for Socket_Msg +/* + Bits for Socket_Msg +*/ Socket_Msg_Bits :: enum { OOB = 0, PEEK = 1, @@ -1275,14 +1468,18 @@ Socket_Msg_Bits :: enum { CMSG_CLOEXEC = 30, } -/// Argument to shutdown.2 +/* + Argument to shutdown(2) +*/ Shutdown_How :: enum i32 { RD = 0, WR = 1, RDWR = 2, } -/// Second argument to futex.2 syscall +/* + Second argument to futex(2) syscall +*/ Futex_Op :: enum u32 { WAIT = 0, WAKE = 1, @@ -1300,27 +1497,33 @@ Futex_Op :: enum u32 { LOCK_PI2 = 13, } -/// Bits for Futex_Flags +/* + Bits for Futex_Flags +*/ Futex_Flags_Bits :: enum { PRIVATE = 7, REALTIME = 8, } -/// Kind of operation on futex, see FUTEX_WAKE_OP +/* + 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<= cmparg) wake */ } -/// The kind of resource limits +/* + The kind of resource limits +*/ RLimit_Kind :: enum i32 { CPU = 0, FSIZE = 1, @@ -1351,7 +1556,9 @@ RLimit_Kind :: enum i32 { NLIMITS = 16, } -/// Represents the user of resources +/* + Represents the user of resources +*/ RUsage_Who :: enum i32 { CHILDREN = -1, SELF = 0, @@ -1359,7 +1566,9 @@ RUsage_Who :: enum i32 { LWP = THREAD, } -/// Bits for Personality_Flags +/* + Bits for Personality_Flags +*/ UNAME26 :: 17 ADDR_NO_RANDOMIZE :: 18 FDPIC_FUNCPTRS :: 19 @@ -1372,8 +1581,10 @@ WHOLE_SECONDS :: 25 STICKY_TIMEOUTS :: 26 ADDR_LIMIT_3GB :: 27 -/// Personality type -/// These go into the bottom 8 bits of the personality value +/* + Personality type + These go into the bottom 8 bits of the personality value +*/ PER_LINUX :: 0x0000 PER_LINUX_32BIT :: 0x0000 | ADDR_LIMIT_32BIT PER_LINUX_FDPIC :: 0x0000 | FDPIC_FUNCPTRS @@ -1398,3 +1609,346 @@ PER_OSF4 :: 0x000f PER_HPUX :: 0x0010 PER_MASK :: 0x00ff +/* + Bits for access modes for shared memory +*/ +IPC_Mode_Bits :: enum { + WROTH = 1, + RDOTH = 2, + WRGRP = 4, + RDGRP = 5, + WRUSR = 7, + RDUSR = 8, + DEST = 9, + LOCKED = 10, +} + +/* + Shared memory flags bits +*/ +IPC_Flags_Bits :: enum { + IPC_CREAT = 9, + IPC_EXCL = 10, + IPC_NOWAIT = 11, + // Semaphore + SEM_UNDO = 9, + // Shared memory + SHM_HUGETLB = 11, + SHM_NORESERVE = 12, + SHM_RDONLY = 12, + SHM_RND = 13, + SHM_REMAP = 14, + SHM_EXEC = 15, + // Message queue + MSG_NOERROR = 12, + MSG_EXCEPT = 13, + MSG_COPY = 14, +} + +/* + IPC memory commands +*/ +IPC_Cmd :: enum i16 { + // IPC common + IPC_RMID = 0, + IPC_SET = 1, + IPC_STAT = 2, + // Shared memory + SHM_LOCK = 11, + SHM_UNLOCK = 12, + SHM_STAT = 13, + SHM_INFO = 14, + SHM_STAT_ANY = 15, + // Semaphore + GETPID = 11, + GETVAL = 12, + GETALL = 13, + GETNCNT = 14, + GETZCNT = 15, + SETVAL = 16, + SETALL = 17, + SEM_STAT = 18, + SEM_INFO = 19, + SEM_STAT_ANY = 20, + // Message queue + MSG_STAT = 11, + MSG_INFO = 12, + MSG_STAT_ANY = 13, +} + +/* + File locking operation bits +*/ +FLock_Op_Bits :: enum { + SH = 1, + EX = 2, + NB = 4, + UN = 8, +} + +/* + ptrace requests +*/ +PTrace_Request :: enum { + TRACEME = 0, + PEEKTEXT = 1, + PEEKDATA = 2, + PEEKUSER = 3, + POKETEXT = 4, + POKEDATA = 5, + POKEUSER = 6, + CONT = 7, + KILL = 8, + SINGLESTEP = 9, + GETREGS = 12, + SETREGS = 13, + GETFPREGS = 14, + SETFPREGS = 15, + ATTACH = 16, + DETACH = 17, + GETFPXREGS = 18, + SETFPXREGS = 19, + SYSCALL = 24, + GET_THREAD_AREA = 25, + SET_THREAD_AREA = 26, + ARCH_PRCTL = 30, + SYSEMU = 31, + SYSEMU_SINGLESTEP = 32, + SINGLEBLOCK = 33, + SETOPTIONS = 0x4200, + GETEVENTMSG = 0x4201, + GETSIGINFO = 0x4202, + SETSIGINFO = 0x4203, + GETREGSET = 0x4204, + SETREGSET = 0x4205, + SEIZE = 0x4206, + INTERRUPT = 0x4207, + LISTEN = 0x4208, + PEEKSIGINFO = 0x4209, + GETSIGMASK = 0x420a, + SETSIGMASK = 0x420b, + SECCOMP_GET_FILTER = 0x420c, + SECCOMP_GET_METADATA = 0x420d, + GET_SYSCALL_INFO = 0x420e, + GET_RSEQ_CONFIGURATION = 0x420f, +} + +/* + ptrace options +*/ +PTrace_Options_Bits :: enum { + TRACESYSGOOD = 0, + TRACEFORK = 1, + TRACEVFORK = 2, + TRACECLONE = 3, + TRACEEXEC = 4, + TRACEVFORKDONE = 5, + TRACEEXIT = 6, + TRACESECCOMP = 7, + EXITKILL = 20, + SUSPEND_SECCOMP = 21, +} + +/* + ptrace event codes. +*/ +PTrace_Event_Code :: enum { + EVENT_FORK = 1, + EVENT_VFORK = 2, + EVENT_CLONE = 3, + EVENT_EXEC = 4, + EVENT_VFORK_DONE = 5, + EVENT_EXIT = 6, + EVENT_SECCOMP = 7, + EVENT_STOP = 128, +} + +/* + ptrace's get syscall info operation. +*/ +PTrace_Get_Syscall_Info_Op :: enum u8 { + NONE = 0, + ENTRY = 1, + EXIT = 2, + SECCOMP = 3, +} + +/* + ptrace's PEEKSIGINFO flags bits +*/ +PTrace_Peek_Sig_Info_Flags_Bits :: enum { + SHARED = 0, +} + +/* + Syslog actions. +*/ +Syslog_Action :: enum i32 { + CLOSE = 0, + OPEN = 1, + READ = 2, + READ_ALL = 3, + READ_CLEAR = 4, + CLEAR = 5, + CONSOLE_OFF = 6, + CONSOLE_ON = 7, + CONSOLE_LEVEL = 8, + SIZE_UNREAD = 9, + SIZE_BUFFER = 10, +} + +/* + Bits for splice flags. +*/ +Splice_Flags_Bits :: enum { + MOVE = 0x01, + NONBLOCK = 0x02, + MORE = 0x04, + GIFT = 0x08, +} + +/* + Clock IDs for various system clocks. +*/ +Clock_Id :: enum { + REALTIME = 0, + MONOTONIC = 1, + PROCESS_CPUTIME_ID = 2, + THREAD_CPUTIME_ID = 3, + MONOTONIC_RAW = 4, + REALTIME_COARSE = 5, + MONOTONIC_COARSE = 6, + BOOTTIME = 7, + REALTIME_ALARM = 8, + BOOTTIME_ALARM = 9, +} + +/* + Bits for POSIX interval timer flags. +*/ +ITimer_Flags_Bits :: enum { + ABSTIME = 1, +} + +/* + Bits for epoll_create(2) flags. +*/ +EPoll_Flags_Bits :: enum { + FDCLOEXEC = 19, +} + +EPoll_Event_Kind :: enum u32 { + IN = 0x001, + PRI = 0x002, + OUT = 0x004, + RDNORM = 0x040, + RDBAND = 0x080, + WRNORM = 0x100, + WRBAND = 0x200, + MSG = 0x400, + ERR = 0x008, + HUP = 0x010, + RDHUP = 0x2000, + EXCLUSIVE = 1<<28, + WAKEUP = 1<<29, + ONESHOT = 1<<30, + ET = 1<<31, +} + +EPoll_Ctl_Opcode :: enum i32 { + ADD = 1, + DEL = 2, + MOD = 3, +} + +/* + Bits for execveat(2) flags. +*/ +Execveat_Flags_Bits :: enum { + AT_SYMLINK_NOFOLLOW = 8, + AT_EMPTY_PATH = 12, +} + +RISCV_HWProbe_Key :: enum i64 { + UNSUPPORTED = -1, + MVENDORID = 0, + MARCHID = 1, + MIMPID = 2, + BASE_BEHAVIOR = 3, + IMA_EXT_0 = 4, + // Deprecated, try `.MISALIGNED_SCALAR_PERF` first, if that is `.UNSUPPORTED`, use this. + CPUPERF_0 = 5, + ZICBOZ_BLOCK_SIZE = 6, + HIGHEST_VIRT_ADDRESS = 7, + TIME_CSR_FREQ = 8, + MISALIGNED_SCALAR_PERF = 9, +} + +RISCV_HWProbe_Flags_Bits :: enum { + WHICH_CPUS, +} + +RISCV_HWProbe_Base_Behavior_Bits :: enum { + IMA, +} + +RISCV_HWProbe_IMA_Ext_0_Bits :: enum { + FD, + C, + V, + EXT_ZBA, + EXT_ZBB, + EXT_ZBS, + EXT_ZICBOZ, + EXT_ZBC, + EXT_ZBKB, + EXT_ZBKC, + EXT_ZBKX, + EXT_ZKND, + EXT_ZKNE, + EXT_ZKNH, + EXT_ZKSED, + EXT_ZKSH, + EXT_ZKT, + EXT_ZVBB, + EXT_ZVBC, + EXT_ZVKB, + EXT_ZVKG, + EXT_ZVKNED, + EXT_ZVKNHA, + EXT_ZVKNHB, + EXT_ZVKSED, + EXT_ZVKSH, + EXT_ZVKT, + EXT_ZFH, + EXT_ZFHMIN, + EXT_ZIHINTNTL, + EXT_ZVFH, + EXT_ZVFHMIN, + EXT_ZFA, + EXT_ZTSO, + EXT_ZACAS, + EXT_ZICOND, + EXT_ZIHINTPAUSE, + EXT_ZVE32X, + EXT_ZVE32F, + EXT_ZVE64X, + EXT_ZVE64F, + EXT_ZVE64D, + EXT_ZIMOP, + EXT_ZCA, + EXT_ZCB, + EXT_ZCD, + EXT_ZCF, + EXT_ZCMOP, + EXT_ZAWRS, +} + +RISCV_HWProbe_Misaligned_Scalar_Perf :: enum { + UNKNOWN, + EMULATED, + SLOW, + FAST, + UNSUPPORTED, +} + diff --git a/core/sys/linux/constants.odin b/core/sys/linux/constants.odin index e725ed2fd..b3bbcafb3 100644 --- a/core/sys/linux/constants.odin +++ b/core/sys/linux/constants.odin @@ -1,74 +1,126 @@ - package linux -/// Special file descriptor to pass to `*at` functions to specify -/// that relative paths are relative to current directory +/* + Standard input file descriptor +*/ +STDIN_FILENO :: Fd(0) + +/* + Standard output file descriptor +*/ +STDOUT_FILENO :: Fd(1) + +/* + Standard error file descriptor +*/ +STDERR_FILENO :: Fd(2) + +/* + Special file descriptor to pass to `*at` functions to specify + that relative paths are relative to current directory. +*/ AT_FDCWD :: Fd(-100) -/// Special value to put into timespec for utimensat() to set timestamp to the current time +/* + Special value to put into timespec for utimensat() to set timestamp to the current time. +*/ UTIME_NOW :: uint((1 << 30) - 1) -/// Special value to put into the timespec for utimensat() to leave the corresponding field of the timestamp unchanged +/* + Special value to put into the timespec for utimensat() to leave the corresponding field of the timestamp unchanged. +*/ UTIME_OMIT :: uint((1 << 30) - 2) -/// For wait4: Pass this pid to wait for any process +/* + For wait4: Pass this pid to wait for any process. +*/ WAIT_ANY :: Pid(-1) -/// For wait4: Pass this pid to wait for any process in current process group +/* + For wait4: Pass this pid to wait for any process in current process group. +*/ WAIT_MYPGRP :: Pid(0) -/// Maximum priority (aka nice value) for the process +/* + Maximum priority (aka nice value) for the process. +*/ PRIO_MAX :: 20 -/// Minimum priority (aka nice value) for the process +/* + Minimum priority (aka nice value) for the process. +*/ PRIO_MIN :: -20 SIGRTMIN :: Signal(32) SIGRTMAX :: Signal(64) -S_IFMT :: Mode{.IFREG, .IFDIR, .IFCHR, .IFFIFO} +S_IFMT :: Mode{.IFREG, .IFDIR, .IFCHR, .IFIFO} S_IFSOCK :: Mode{.IFREG, .IFDIR} S_IFLNK :: Mode{.IFREG, .IFCHR} S_IFBLK :: Mode{.IFDIR, .IFCHR} -S_IFFIFO :: Mode{.IFFIFO} +S_IFIFO :: Mode{.IFIFO} S_IFCHR :: Mode{.IFCHR} S_IFDIR :: Mode{.IFDIR} S_IFREG :: Mode{.IFREG} -/// Checks the Mode bits to see if the file is a named pipe (FIFO) -S_ISFIFO :: #force_inline proc "contextless" (m: Mode) -> bool {return (S_IFFIFO == (m & S_IFMT))} +/* + Checks the Mode bits to see if the file is a named pipe (FIFO). +*/ +S_ISFIFO :: #force_inline proc "contextless" (m: Mode) -> bool {return (S_IFIFO == (m & S_IFMT))} -/// Check the Mode bits to see if the file is a character device +/* + Check the Mode bits to see if the file is a character device. +*/ S_ISCHR :: #force_inline proc "contextless" (m: Mode) -> bool {return (S_IFCHR == (m & S_IFMT))} - -/// Check the Mode bits to see if the file is a directory + +/* + Check the Mode bits to see if the file is a directory. +*/ S_ISDIR :: #force_inline proc "contextless" (m: Mode) -> bool {return (S_IFDIR == (m & S_IFMT))} -/// Check the Mode bits to see if the file is a register +/* + Check the Mode bits to see if the file is a register. +*/ S_ISREG :: #force_inline proc "contextless" (m: Mode) -> bool {return (S_IFREG == (m & S_IFMT))} -/// Check the Mode bits to see if the file is a socket +/* + Check the Mode bits to see if the file is a socket. +*/ S_ISSOCK :: #force_inline proc "contextless" (m: Mode) -> bool {return (S_IFSOCK == (m & S_IFMT))} -/// Check the Mode bits to see if the file is a symlink +/* + Check the Mode bits to see if the file is a symlink. +*/ S_ISLNK :: #force_inline proc "contextless" (m: Mode) -> bool {return (S_IFLNK == (m & S_IFMT))} -/// Check the Mode bits to see if the file is a block device +/* + Check the Mode bits to see if the file is a block device. +*/ S_ISBLK :: #force_inline proc "contextless" (m: Mode) -> bool {return (S_IFBLK == (m & S_IFMT))} -/// For access.2 syscall family: instruct to check if the file exists +/* + For access.2 syscall family: instruct to check if the file exists. +*/ F_OK :: Mode{} -/// For access.2 syscall family: instruct to check if the file is executable +/* + For access.2 syscall family: instruct to check if the file is executable. +*/ X_OK :: Mode{.IXOTH} -/// For access.2 syscall family: instruct to check if the file is writeable +/* + For access.2 syscall family: instruct to check if the file is writeable. +*/ W_OK :: Mode{.IWOTH} -/// For access.2 syscall family: instruct to check if the file is readable +/* + For access.2 syscall family: instruct to check if the file is readable. +*/ R_OK :: Mode{.IROTH} -/// The stats you get by calling `stat` +/* + The stats you get by calling `stat`. +*/ STATX_BASIC_STATS :: Statx_Mask { .TYPE, .MODE, @@ -83,6 +135,35 @@ STATX_BASIC_STATS :: Statx_Mask { .BLOCKS, } +IN_ALL_EVENTS :: Inotify_Event_Mask { + .ACCESS, + .MODIFY, + .ATTRIB, + .CLOSE_WRITE, + .CLOSE_NOWRITE, + .OPEN, + .MOVED_FROM, + .MOVED_TO, + .CREATE, + .DELETE, + .DELETE_SELF, + .MOVE_SELF, +} + +IN_CLOSE :: Inotify_Event_Mask { + .CLOSE_WRITE, + .CLOSE_NOWRITE, +} + +IN_MOVE :: Inotify_Event_Mask { + .MOVED_FROM, + .MOVED_TO, +} + +/* + Tell `shmget` to create a new key +*/ +IPC_PRIVATE :: Key(0) FCntl_Command_DUPFD :: distinct FCntl_Command FCntl_Command_GETFD :: distinct FCntl_Command @@ -165,28 +246,44 @@ Futex_Wait_requeue_Pi_Type :: distinct Futex_Op Futex_Cmp_requeue_Pi_Type :: distinct Futex_Op Futex_Lock_Pi2_Type :: distinct Futex_Op -/// Wait on futex wakeup signal +/* + Wait on futex wakeup signal. +*/ FUTEX_WAIT :: Futex_Wait_Type(.WAIT) -/// Wake up other processes waiting on the futex +/* + Wake up other processes waiting on the futex. +*/ FUTEX_WAKE :: Futex_Wake_Type(.WAKE) -/// Not implemented. Basically, since +/* + Not implemented. Basically, since. +*/ FUTEX_FD :: Futex_Fd_Type(.FD) -/// Requeue waiters from one futex to another +/* + Requeue waiters from one futex to another. +*/ FUTEX_REQUEUE :: Futex_Requeue_Type(.REQUEUE) -/// Requeue waiters from one futex to another if the value at mutex matches +/* + Requeue waiters from one futex to another if the value at mutex matches. +*/ FUTEX_CMP_REQUEUE :: Futex_Cmp_Requeue_Type(.CMP_REQUEUE) -/// See man pages, I'm not describing it here +/* + See man pages, I'm not describing it here. +*/ FUTEX_WAKE_OP :: Futex_Wake_Op_Type(.WAKE_OP) -/// Wait on a futex, but the value is a bitset +/* + Wait on a futex, but the value is a bitset. +*/ FUTEX_WAIT_BITSET :: Futex_Wait_Bitset_Type(.WAIT_BITSET) -/// Wait on a futex, but the value is a bitset +/* + Wait on a futex, but the value is a bitset. +*/ FUTEX_WAKE_BITSET :: Futex_Wake_Bitset_Type(.WAKE_BITSET) // TODO(flysand): Priority inversion futexes @@ -197,3 +294,82 @@ FUTEX_WAIT_REQUEUE_PI :: Futex_Wait_requeue_Pi_Type(.WAIT_REQUEUE_PI) FUTEX_CMP_REQUEUE_PI :: Futex_Cmp_requeue_Pi_Type(.CMP_REQUEUE_PI) FUTEX_LOCK_PI2 :: Futex_Lock_Pi2_Type(.LOCK_PI2) +PTrace_Traceme_Type :: distinct PTrace_Request +PTrace_Peek_Type :: distinct PTrace_Request +PTrace_Poke_Type :: distinct PTrace_Request +PTrace_Cont_Type :: distinct PTrace_Request +PTrace_Kill_Type :: distinct PTrace_Request +PTrace_Singlestep_Type :: distinct PTrace_Request +PTrace_Getregs_Type :: distinct PTrace_Request +PTrace_Setregs_Type :: distinct PTrace_Request +PTrace_Getfpregs_Type :: distinct PTrace_Request +PTrace_Setfpregs_Type :: distinct PTrace_Request +PTrace_Attach_Type :: distinct PTrace_Request +PTrace_Detach_Type :: distinct PTrace_Request +PTrace_Getfpxregs_Type :: distinct PTrace_Request +PTrace_Setfpxregs_Type :: distinct PTrace_Request +PTrace_Syscall_Type :: distinct PTrace_Request +PTrace_Get_Thread_Area_Type :: distinct PTrace_Request +PTrace_Set_Thread_Area_Type :: distinct PTrace_Request +PTrace_Arch_Prctl_Type :: distinct PTrace_Request +PTrace_Sysemu_Type :: distinct PTrace_Request +PTrace_Sysemu_Singlestep_Type :: distinct PTrace_Request +PTrace_Singleblock_Type :: distinct PTrace_Request +PTrace_Setoptions_Type :: distinct PTrace_Request +PTrace_Geteventmsg_Type :: distinct PTrace_Request +PTrace_Getsiginfo_Type :: distinct PTrace_Request +PTrace_Setsiginfo_Type :: distinct PTrace_Request +PTrace_Getregset_Type :: distinct PTrace_Request +PTrace_Setregset_Type :: distinct PTrace_Request +PTrace_Seize_Type :: distinct PTrace_Request +PTrace_Interrupt_Type :: distinct PTrace_Request +PTrace_Listen_Type :: distinct PTrace_Request +PTrace_Peeksiginfo_Type :: distinct PTrace_Request +PTrace_Getsigmask_Type :: distinct PTrace_Request +PTrace_Setsigmask_Type :: distinct PTrace_Request +PTrace_Seccomp_Get_Filter_Type :: distinct PTrace_Request +PTrace_Seccomp_Get_Metadata_Type :: distinct PTrace_Request +PTrace_Get_Syscall_Info_Type :: distinct PTrace_Request +PTrace_Get_RSeq_Configuration_Type :: distinct PTrace_Request + +PTRACE_TRACEME :: PTrace_Traceme_Type(.TRACEME) +PTRACE_PEEKTEXT :: PTrace_Peek_Type(.PEEKTEXT) +PTRACE_PEEKDATA :: PTrace_Peek_Type(.PEEKDATA) +PTRACE_PEEKUSER :: PTrace_Peek_Type(.PEEKUSER) +PTRACE_POKETEXT :: PTrace_Poke_Type(.POKETEXT) +PTRACE_POKEDATA :: PTrace_Poke_Type(.POKEDATA) +PTRACE_POKEUSER :: PTrace_Poke_Type(.POKEUSER) +PTRACE_CONT :: PTrace_Cont_Type(.CONT) +PTRACE_KILL :: PTrace_Kill_Type(.KILL) +PTRACE_SINGLESTEP :: PTrace_Singlestep_Type(.SINGLESTEP) +PTRACE_GETREGS :: PTrace_Getregs_Type(.GETREGS) +PTRACE_SETREGS :: PTrace_Setregs_Type(.SETREGS) +PTRACE_GETFPREGS :: PTrace_Getfpregs_Type(.GETFPREGS) +PTRACE_SETFPREGS :: PTrace_Setfpregs_Type(.SETFPREGS) +PTRACE_ATTACH :: PTrace_Attach_Type(.ATTACH) +PTRACE_DETACH :: PTrace_Detach_Type(.DETACH) +PTRACE_GETFPXREGS :: PTrace_Getfpxregs_Type(.GETFPXREGS) +PTRACE_SETFPXREGS :: PTrace_Setfpxregs_Type(.SETFPXREGS) +PTRACE_SYSCALL :: PTrace_Syscall_Type(.SYSCALL) +PTRACE_GET_THREAD_AREA :: PTrace_Get_Thread_Area_Type(.GET_THREAD_AREA) +PTRACE_SET_THREAD_AREA :: PTrace_Set_Thread_Area_Type(.SET_THREAD_AREA) +PTRACE_ARCH_PRCTL :: PTrace_Arch_Prctl_Type(.ARCH_PRCTL) +PTRACE_SYSEMU :: PTrace_Sysemu_Type(.SYSEMU) +PTRACE_SYSEMU_SINGLESTEP :: PTrace_Sysemu_Singlestep_Type(.SYSEMU_SINGLESTEP) +PTRACE_SINGLEBLOCK :: PTrace_Singleblock_Type(.SINGLEBLOCK) +PTRACE_SETOPTIONS :: PTrace_Setoptions_Type(.SETOPTIONS) +PTRACE_GETEVENTMSG :: PTrace_Geteventmsg_Type(.GETEVENTMSG) +PTRACE_GETSIGINFO :: PTrace_Getsiginfo_Type(.GETSIGINFO) +PTRACE_SETSIGINFO :: PTrace_Setsiginfo_Type(.SETSIGINFO) +PTRACE_GETREGSET :: PTrace_Getregset_Type(.GETREGSET) +PTRACE_SETREGSET :: PTrace_Setregset_Type(.SETREGSET) +PTRACE_SEIZE :: PTrace_Seize_Type(.SEIZE) +PTRACE_INTERRUPT :: PTrace_Interrupt_Type(.INTERRUPT) +PTRACE_LISTEN :: PTrace_Listen_Type(.LISTEN) +PTRACE_PEEKSIGINFO :: PTrace_Peeksiginfo_Type(.PEEKSIGINFO) +PTRACE_GETSIGMASK :: PTrace_Getsigmask_Type(.GETSIGMASK) +PTRACE_SETSIGMASK :: PTrace_Setsigmask_Type(.SETSIGMASK) +PTRACE_SECCOMP_GET_FILTER :: PTrace_Seccomp_Get_Filter_Type(.SECCOMP_GET_FILTER) +PTRACE_SECCOMP_GET_METADATA :: PTrace_Seccomp_Get_Metadata_Type(.SECCOMP_GET_METADATA) +PTRACE_GET_SYSCALL_INFO :: PTrace_Get_Syscall_Info_Type(.GET_SYSCALL_INFO) +PTRACE_GET_RSEQ_CONFIGURATION :: PTrace_Get_RSeq_Configuration_Type(.GET_RSEQ_CONFIGURATION) diff --git a/core/sys/linux/helpers.odin b/core/sys/linux/helpers.odin index cf4143924..9a7550d57 100644 --- a/core/sys/linux/helpers.odin +++ b/core/sys/linux/helpers.odin @@ -1,7 +1,8 @@ -//+build linux +#+build linux +#+no-instrumentation package linux -import "core:intrinsics" +import "base:intrinsics" // Note(flysand): In the case of syscall let's get rid of extra // casting. First of all, let these syscalls return int, because @@ -11,7 +12,7 @@ import "core:intrinsics" @(private) syscall0 :: #force_inline proc "contextless" (nr: uintptr) -> int { - return cast(int) intrinsics.syscall(nr) + return int(intrinsics.syscall(nr)) } @(private) @@ -19,81 +20,84 @@ syscall1 :: #force_inline proc "contextless" (nr: uintptr, p1: $T) -> int where size_of(p1) <= size_of(uintptr) { - return cast(int) intrinsics.syscall(nr, cast(uintptr) p1) + return int(intrinsics.syscall(nr, uintptr(p1))) } @(private) 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, - cast(uintptr) p1, cast(uintptr) p2) + return int(intrinsics.syscall(nr, uintptr(p1), uintptr(p2))) } @(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, - cast(uintptr) p1, - cast(uintptr) p2, - cast(uintptr) p3) + return int(intrinsics.syscall(nr, + uintptr(p1), + uintptr(p2), + uintptr(p3), + )) } @(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, - cast(uintptr) p1, - cast(uintptr) p2, - cast(uintptr) p3, - cast(uintptr) p4) + return int(intrinsics.syscall(nr, + uintptr(p1), + uintptr(p2), + uintptr(p3), + uintptr(p4), + )) } @(private) 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, - cast(uintptr) p1, - cast(uintptr) p2, - cast(uintptr) p3, - cast(uintptr) p4, - cast(uintptr) p5) + return int(intrinsics.syscall(nr, + uintptr(p1), + uintptr(p2), + uintptr(p3), + uintptr(p4), + uintptr(p5), + )) } @(private) 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, - cast(uintptr) p1, - cast(uintptr) p2, - cast(uintptr) p3, - cast(uintptr) p4, - cast(uintptr) p5, - cast(uintptr) p6) + return int(intrinsics.syscall(nr, + uintptr(p1), + uintptr(p2), + uintptr(p3), + uintptr(p4), + uintptr(p5), + uintptr(p6), + )) } syscall :: proc {syscall0, syscall1, syscall2, syscall3, syscall4, syscall5, syscall6} @@ -112,7 +116,7 @@ where default_value: T return default_value, Errno(-ret) } else { - return cast(T) transmute(U) ret, Errno(.NONE) + return T(transmute(U)ret), Errno(.NONE) } } @@ -122,7 +126,7 @@ errno_unwrap2 :: #force_inline proc "contextless" (ret: $P, $T: typeid) -> (T, E default_value: T return default_value, Errno(-ret) } else { - return cast(T) ret, Errno(.NONE) + return T(ret), Errno(.NONE) } } @@ -134,8 +138,8 @@ errno_unwrap :: proc {errno_unwrap2, errno_unwrap3} when size_of(int) == 4 { // xxx64 system calls take some parameters as pairs of ulongs rather than a single pointer @(private) - compat64_arg_pair :: #force_inline proc "contextless" (a: i64) -> (hi: uint, lo: uint) { - no_sign := uint(a) + compat64_arg_pair :: #force_inline proc "contextless" (a: i64) -> (lo: uint, hi: uint) { + no_sign := u64(a) hi = uint(no_sign >> 32) lo = uint(no_sign & 0xffff_ffff) return diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index e0e19056a..fee385fe8 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -1,66 +1,75 @@ -//+build linux +#+no-instrumentation package linux -import "core:intrinsics" +import "base:intrinsics" -/// Read data from file into the buffer -/// Returns the number of bytes successfully read, which may be less than the size -/// of the buffer even if the termination is successfull -/// -/// Available since Linux 1.0 -/// Before Linux 3.14, this operation is not atomic (i.e. not thread safe). -read :: proc "contextless" (fd: Fd, buf: []$T) -> (int, Errno) { - ret := syscall(SYS_read, fd, raw_data(buf), len(buf) * size_of(T)) +/* + Read data from file into the buffer + Returns the number of bytes successfully read, which may be less than the size + of the buffer even if the termination is successfull. + Available since Linux 1.0. + Before Linux 3.14, this operation is not atomic (i.e. not thread safe). +*/ +read :: proc "contextless" (fd: Fd, buf: []u8) -> (int, Errno) { + ret := syscall(SYS_read, fd, raw_data(buf), len(buf)) return errno_unwrap(ret, int) } -/// Write the data from a buffer into the file -/// Returns the number of bytes successfully written, which may be less than the size -/// of the buffer, even if the termination is successfull -/// When using direct I/O, error doesn't mean the write has failed. Partial data may -/// have been written. -/// If .Eintr is returned, the write operation has failed due to interrupt. You'll probably -/// need to restart this syscall -/// -/// Available since Linux 1.0 -/// Before Linux 3.14 this operation is not atomic (i.e. not thread safe) -write :: proc "contextless" (fd: Fd, buf: []$T) -> (int, Errno) { - ret := syscall(SYS_write, fd, raw_data(buf), len(buf)*size_of(T)) +/* + Write the data from a buffer into the file + Returns the number of bytes successfully written, which may be less than the size + of the buffer, even if the termination is successfull + When using direct I/O, error doesn't mean the write has failed. Partial data may + have been written. + If .Eintr is returned, the write operation has failed due to interrupt. You'll probably + need to restart this syscall + + Available since Linux 1.0 + Before Linux 3.14 this operation is not atomic (i.e. not thread safe) +*/ +write :: proc "contextless" (fd: Fd, buf: []u8) -> (int, Errno) { + ret := syscall(SYS_write, fd, raw_data(buf), len(buf)) return errno_unwrap(ret, int) } -/// Open file, get the file descriptor -/// Available since Linux 1.0 -/// On ARM64 available since Linux 2.6.16 +/* + Open file, get the file descriptor + Available since Linux 1.0. + On ARM64 available since Linux 2.6.16. +*/ open :: proc "contextless" (name: cstring, flags: Open_Flags, mode: Mode = {}) -> (Fd, Errno) { - when ODIN_ARCH == .arm64 { - ret := syscall(SYS_openat, AT_FDCWD, transmute(uintptr) name, transmute(u32) mode) + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { + ret := syscall(SYS_openat, AT_FDCWD, transmute(uintptr) name, transmute(u32) flags, transmute(u32) mode) return errno_unwrap(ret, Fd) } else { - ret := syscall(SYS_open, transmute(uintptr) name, transmute(u32) mode) + ret := syscall(SYS_open, transmute(uintptr) name, transmute(u32) flags, transmute(u32) mode) return errno_unwrap(ret, Fd) } } -/// Close the file -/// Available since Linux 1.0 +/* + Close the file. + Available since Linux 1.0. +*/ close :: proc "contextless" (fd: Fd) -> (Errno) { ret := syscall(SYS_close, fd) return Errno(-ret) } -/// Get file status -/// -/// Returns information about the file in struct pointed to by `stat` parameter. -/// -/// Available since Linux 1.0 -/// For 32-bit systems a different syscall is used that became available since 2.4 -/// Not available on arm64 +/* + Get file status. + + Returns information about the file in struct pointed to by `stat` parameter. + + Available since Linux 1.0 + For 32-bit systems a different syscall is used that became available since 2.4. + Not available on ARM64. +*/ 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) + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { + ret := syscall(SYS_fstatat, AT_FDCWD, cast(rawptr) filename, stat, 0) return Errno(-ret) } else { ret := syscall(SYS_stat, cast(rawptr) filename, stat) @@ -72,33 +81,37 @@ stat :: proc "contextless" (filename: cstring, stat: ^Stat) -> (Errno) { } } -/// Get file status from file descriptor -/// -/// Returns information about the file in struct pointed to by `stat` parameter. -/// -/// Available since Linux 1.0 -/// For 32-bit systems a different syscall is used that became available since 2.4 +/* + Get file status from file descriptor. + + Returns information about the file in struct pointed to by `stat` parameter. + + Available since Linux 1.0. + For 32-bit systems a different syscall is used that became available since 2.4. +*/ fstat :: proc "contextless" (fd: Fd, stat: ^Stat) -> (Errno) { when size_of(int) == 8 { - ret := syscall(SYS_fstat, stat) + ret := syscall(SYS_fstat, cast(i32) fd, stat) return Errno(-ret) } else { - ret := syscall(SYS_fstat64, stat) + ret := syscall(SYS_fstat64, cast(i32) fd, stat) return Errno(-ret) } } -/// Get information about the file that's potentially a symbolic link -/// The information is returned in a struct pointed to by `stat` parameter. -/// The difference with stat, fstat is that if the file is a symbolic link, -/// stat and fstat will dereference the link. lstat doesn't dereference symlinks -/// -/// Available since Linux 1.0 -/// For 32-bit systems a different syscall is used that became available since 2.4 -/// Not available on arm64 +/* + Get information about the file that's potentially a symbolic link + The information is returned in a struct pointed to by `stat` parameter. + The difference with stat, fstat is that if the file is a symbolic link, + stat and fstat will dereference the link. lstat doesn't dereference symlinks. + + Available since Linux 1.0. + For 32-bit systems a different syscall is used that became available since 2.4. + Not available on arm64. +*/ lstat :: proc "contextless" (filename: cstring, stat: ^Stat) -> (Errno) { when size_of(int) == 8 { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { return fstatat(AT_FDCWD, filename, stat, {.SYMLINK_NOFOLLOW}) } else { ret := syscall(SYS_lstat, cast(rawptr) filename, stat) @@ -110,10 +123,12 @@ lstat :: proc "contextless" (filename: cstring, stat: ^Stat) -> (Errno) { } } -/// Wait on event on a file descriptor -/// Available since Linux 2.2 +/* + Wait on event on a file descriptor. + Available since Linux 2.2. +*/ poll :: proc "contextless" (fds: []Poll_Fd, timeout: i32) -> (i32, Errno) { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { seconds := cast(uint) timeout / 1000 nanoseconds := cast(uint) (timeout % 1000) * 1_000_000 timeout_spec := Time_Spec{seconds, nanoseconds} @@ -125,23 +140,28 @@ poll :: proc "contextless" (fds: []Poll_Fd, timeout: i32) -> (i32, Errno) { } } - -/// Seek the file stream to specified offset -/// Available since Linux 1.0 -/// On 32-bit platforms available since Linux 1.2 -lseek :: proc "contextless" (fd: Fd, off: i64, whence: Seek_Whence) -> (Errno) { +/* + Seek the file stream to specified offset. + Available since Linux 1.0. + On 32-bit platforms available since Linux 1.2. +*/ +lseek :: proc "contextless" (fd: Fd, off: i64, whence: Seek_Whence) -> (i64, Errno) { when size_of(int) == 8 { ret := syscall(SYS_lseek, fd, off, whence) - return Errno(-ret) + return errno_unwrap(ret, i64) } else { - ret := syscall(SYS__llseek, fd, compat64_arg_pair(off), whence) - return Errno(-ret) + result: i64 = --- + lo, hi := compat64_arg_pair(off) + ret := syscall(SYS__llseek, fd, hi, lo, &result, whence) + return result, Errno(-ret) } } -/// Map files into memory -/// Available since Linux 1.0 -/// On 32-bit platforms since Linux 1.0 +/* + Map files into memory. + Available since Linux 1.0. + On 32-bit platforms since Linux 1.0. +*/ mmap :: proc "contextless" (addr: uintptr, size: uint, prot: Mem_Protection, flags: Map_Flags, fd: Fd = Fd(-1), offset: i64 = 0) -> (rawptr, Errno) { when size_of(int) == 8 { ret := syscall(SYS_mmap, addr, size, transmute(i32) prot, transmute(i32) flags, fd, offset) @@ -152,85 +172,164 @@ mmap :: proc "contextless" (addr: uintptr, size: uint, prot: Mem_Protection, fla } } -/// Protect memory region +/* + Protect memory region. + Available since Linux 1.0. +*/ mprotect :: proc "contextless" (addr: rawptr, size: uint, prot: Mem_Protection) -> (Errno) { ret := syscall(SYS_mprotect, addr, size, transmute(i32) prot) return Errno(-ret) } -/// Unmap memory -/// Available since Linux 1.0 +/* + Unmap memory. + Available since Linux 1.0. +*/ munmap :: proc "contextless" (addr: rawptr, size: uint) -> (Errno) { ret := syscall(SYS_munmap, addr, size) return Errno(-ret) } -// TODO(flysand): brk +/* + Extend the data segment size until the address `addr`. Note: you may be + familiar with sbrk(), which is not actually a syscall, so is not + implemented here. + Available since Linux 1.0. +*/ +brk :: proc "contextless" (addr: uintptr) -> (Errno) { + ret := syscall(SYS_brk, addr) + return Errno(-ret) +} -/// Alter an action taken by a process -rt_sigaction :: proc "contextless" (sig: Signal, sigaction: ^Sig_Action, old_sigaction: ^Sig_Action) -> Errno { +/* + 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($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) } -/// Examime and alter blocked signals -/// Available since Linux 2.2 +/* + Examime and alter blocked signals. + Available since Linux 2.2. +*/ rt_sigprocmask :: proc "contextless" (mask_kind: Sig_Mask_Kind, new_set: ^Sig_Set, old_set: ^Sig_Set) -> Errno { ret := syscall(SYS_rt_sigprocmask, mask_kind, new_set, old_set, size_of(Sig_Set)) return Errno(-ret) } -// TODO(flysand): rt_sigreturn +/* + Control devices. The ioctl syscall is a bit special because + its argument is usually a pointer to some driver-specific structure. + The request value is device-specific. Consult your LibC implementation's + ioctls.h file to learn more. The returned value of ioctl *may* be an error + code value instead of a memory address depending on the request type. + Available since Linux 1.0. +*/ +ioctl :: proc "contextless" (fd: Fd, request: u32, arg: uintptr) -> (uintptr) { + ret := syscall(SYS_ioctl, fd, request, arg) + return uintptr(ret) +} -// TODO(flysand): ioctl - -/// Read the file at a specified offset -/// Note, it is not an error to return less bytes than requested -/// Available since Linux 2.2 -pread :: proc "contextless" (fd: Fd, buf: []$T, offset: i64) -> (int, Errno) { - ret := syscall(SYS_pread64, fd, raw_data(buf), compat64_arg_pair(len(buf)*size_of(T))) +/* + Read the file at a specified offset. + Note, it is not an error to return less bytes than requested. + Available since Linux 2.2. +*/ +pread :: proc "contextless" (fd: Fd, buf: []u8, offset: i64) -> (int, Errno) { + when ODIN_ARCH == .arm32 { + ret := syscall(SYS_pread64, fd, raw_data(buf), len(buf), 0, compat64_arg_pair(offset)) + } else { + ret := syscall(SYS_pread64, fd, raw_data(buf), len(buf), compat64_arg_pair(offset)) + } return errno_unwrap(ret, int) } -/// Read the file at a specified offset -/// Note, it is not an error to return less bytes than requested -/// Available since Linux 2.2 -pwrite :: proc "contextless" (fd: Fd, buf: []$T, offset: i64) -> (int, Errno) { - ret := syscall(SYS_pwrite64, fd, raw_data(buf), compat64_arg_pair(len(buf)*size_of(T))) +/* + Read the file at a specified offset. + Note, it is not an error to return less bytes than requested. + Available since Linux 2.2. +*/ +pwrite :: proc "contextless" (fd: Fd, buf: []u8, offset: i64) -> (int, Errno) { + when ODIN_ARCH == .arm32 { + ret := syscall(SYS_pwrite64, fd, raw_data(buf), len(buf), 0, compat64_arg_pair(offset)) + } else { + ret := syscall(SYS_pwrite64, fd, raw_data(buf), len(buf), compat64_arg_pair(offset)) + } return errno_unwrap(ret, int) } -// TODO(flysand): readv +/* + Read the data from file into multiple buffers. + Available since Linux 2.0. +*/ +readv :: proc "contextless" (fd: Fd, iov: []IO_Vec) -> (int, Errno) { + ret := syscall(SYS_readv, fd, raw_data(iov), len(iov)) + return errno_unwrap(ret, int) +} -// TODO(flysand): writev +/* + Write the data from multiple buffers into a file. + Available since Linux 2.0. +*/ +writev :: proc "contextless" (fd: Fd, iov: []IO_Vec) -> (int, Errno) { + ret := syscall(SYS_writev, fd, raw_data(iov), len(iov)) + return errno_unwrap(ret, int) +} -/// Check user permissions for a file -/// If Mode is F_OK, checks whether the file exists -/// Similarly, X_OK, W_OK, R_OK check if the file is executable, writeable, readable respectively -/// Available since Linux 1.0 -/// For ARM64 available since Linux 2.6.16 -access :: proc "contextless" (name: cstring, mode: Mode = F_OK) -> (bool, Errno) { - when ODIN_ARCH == .arm64 { +/* + Check user permissions for a file. + If Mode is F_OK, checks whether the file exists. + Similarly, X_OK, W_OK, R_OK check if the file is executable, writeable, readable respectively. + Available since Linux 1.0. + For ARM64 available since Linux 2.6.16. +*/ +access :: proc "contextless" (name: cstring, mode: Mode = F_OK) -> (Errno) { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_faccessat, AT_FDCWD, cast(rawptr) name, transmute(u32) mode) - return errno_unwrap(ret, bool) + return Errno(-ret) } else { ret := syscall(SYS_access, cast(rawptr) name, transmute(u32) mode) - return errno_unwrap(ret, bool) + return Errno(-ret) } } -/// Create a pipe -/// Available since Linux 2.6.27 +/* + Create a pipe + Available since Linux 2.6.27 +*/ pipe2 :: proc "contextless" (pipes: ^[2]Fd, flags: Open_Flags) -> (Errno) { ret := syscall(SYS_pipe2, pipes, transmute(u32) flags) return Errno(-ret) } -// TODO(flysand): select +/* + Yield the processor. + Available since Linux 2.0. +*/ +sched_yield :: proc "contextless" () { + syscall(SYS_sched_yield) +} -// TODO(flysand): sched_yield - -// TODO(flysand): add docs here +/* + Remap a virtual memory address. + Available since Linux 2.0. +*/ mremap :: proc "contextless" (old_addr: rawptr, old_size: uint, new_size: uint, flags: MRemap_Flags, new_addr: uintptr = 0) -> (rawptr, Errno) { if .FIXED in flags { ret := syscall(SYS_mremap, old_addr, old_size, new_size, transmute(i32) flags, new_addr) @@ -241,40 +340,83 @@ mremap :: proc "contextless" (old_addr: rawptr, old_size: uint, new_size: uint, } } -/// Sync file with memory map -/// Available since Linux 2.0 +/* + Sync file with memory map. + Available since Linux 2.0. +*/ msync :: proc "contextless" (addr: rawptr, size: uint, flags: MSync_Flags) -> (Errno) { ret := syscall(SYS_msync, addr, size, transmute(i32) flags) return Errno(-ret) } -// TODO(flysand): mincore +/* + Determine if pages are resident in memory. + Available since Linux 2.4. +*/ +mincore :: proc "contextless" (addr: rawptr, size: uint, vec: []b8) -> (Errno) { + ret := syscall(SYS_mincore, addr, size, raw_data(vec)) + return Errno(-ret) +} -/// Give advice about use of memory -/// Available since Linux 2.4 +/* + Give advice about use of memory. + Available since Linux 2.4. +*/ madvise :: proc "contextless" (addr: rawptr, size: uint, advice: MAdvice) -> (Errno) { ret := syscall(SYS_madvise, addr, size, advice) return Errno(-ret) } -// TODO(flysand): shmget +/* + Allocate a SystemV shared memory segment. + Available since Linux 2.0. +*/ +shmget :: proc "contextless" (key: Key, size: uint, flags: IPC_Flags) -> (Key, Errno) { + ret := syscall(SYS_shmget, key, size, transmute(i16) flags) + return errno_unwrap(ret, Key) +} -// TODO(flysand): shmat +/* + SystemV shared memory segment operations. + Available since Linux 2.0. +*/ +shmat :: proc "contextless" (key: Key, addr: rawptr, flags: IPC_Flags) -> (rawptr, Errno) { + ret := syscall(SYS_shmat, key, addr, transmute(i16) flags) + return errno_unwrap(ret, rawptr, uintptr) +} -// TODO(flysand): shmctl +shmctl_ds :: proc "contextless" (key: Key, cmd: IPC_Cmd, buf: ^Shmid_DS) -> (Errno) { + ret := syscall(SYS_shmctl, key, cmd, buf) + return Errno(-ret) +} -/// Allocate a new file descriptor that refers to the same file as the one provided -/// Available since Linux 1.0 +shmctl_info :: proc "contextless" (key: Key, cmd: IPC_Cmd, buf: ^Shm_Info) -> (int, Errno) { + ret := syscall(SYS_shmctl, key, cmd, buf) + return errno_unwrap(ret, int) +} + +/* + SystemV shared memory control. + Available since Linux 2.0. +*/ +shmctl :: proc {shmctl_ds, shmctl_info} + +/* + Allocate a new file descriptor that refers to the same file as the one provided. + Available since Linux 1.0. +*/ dup :: proc "contextless" (fd: Fd) -> (Fd, Errno) { ret := syscall(SYS_dup, fd) return errno_unwrap(ret, Fd) } -/// Adjust an existing file descriptor to point to the same file as `old` -/// Available since Linux 1.0 -/// On ARM64 available since Linux 2.6.27 +/* + Adjust an existing file descriptor to point to the same file as `old`. + Available since Linux 1.0. + On ARM64 available since Linux 2.6.27. +*/ dup2 :: proc "contextless" (old: Fd, new: Fd) -> (Fd, Errno) { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_dup3, old, new, 0) return errno_unwrap(ret, Fd) } else { @@ -283,52 +425,120 @@ dup2 :: proc "contextless" (old: Fd, new: Fd) -> (Fd, Errno) { } } -// TODO(flysand): pause +/* + Wait until the next signal is delivered. + Available since Linux 1.0. + On ARM64 available since Linux 2.6.16. +*/ +pause :: proc "contextless" () { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { + syscall(SYS_ppoll, 0, 0, 0, 0) + } else { + syscall(SYS_pause) + } +} -// TODO(flysand): nanosleep +/* + High-resolution sleep. + Available since Linux 2.0. +*/ +nanosleep :: proc "contextless" (req: ^Time_Spec, rem: ^Time_Spec) -> (Errno) { + ret := syscall(SYS_nanosleep, req, rem) + return Errno(-ret) +} -// TODO(flysand): getitimer +/* + Get the value of an internal timer. + Available since Linux 1.0. +*/ +getitimer :: proc "contextless" (which: ITimer_Which, cur: ^ITimer_Val) -> (Errno) { + ret := syscall(SYS_getitimer, cur) + return Errno(-ret) +} -// TODO(flysand): alarm +/* + Set an alarm clock for delivery of a signal. + Available since Linux 1.0. +*/ +alarm :: proc "contextless" (seconds: u32) -> u32 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { + new := ITimer_Val { value = { seconds = cast(int) seconds } } + old := ITimer_Val {} + syscall(SYS_setitimer, ITimer_Which.REAL, &new, &old) + return u32(old.value.seconds) + u32(new.value.microseconds) + } else { + return cast(u32) syscall(SYS_alarm, seconds) + } +} -// TODO(flysand): setitimer +/* + Set the value of an internal timer. + Available since Linux 1.0. +*/ +setitimer :: proc "contextless" (which: ITimer_Which, new: ^ITimer_Val, old: ^ITimer_Val) -> (Errno) { + ret := syscall(SYS_getitimer, new, old) + return Errno(-ret) +} -/// Returns the thread group ID of the current process -/// Note that it doesn't return the pid, despite it's name. -/// Available since Linux 1.0 +/* + Returns the thread group ID of the current process. + Note that it doesn't return the pid, despite it's name. + Available since Linux 1.0. +*/ getpid :: proc "contextless" () -> Pid { return cast(Pid) syscall(SYS_getpid) } -// TODO(flysand): sendfile +/* + Transfer the data between file descriptors. + Available since Linux 2.2. + On 32-bit platforms available since Linux 2.6. +*/ +sendfile :: proc "contextless" (out_fd: Fd, in_fd: Fd, offset: ^i64, count: uint) -> (int, Errno) { + when size_of(int) == 8 { + ret := syscall(SYS_sendfile, out_fd, in_fd, offset, count) + return errno_unwrap(ret, int) + } else { + ret := syscall(SYS_sendfile64, out_fd, in_fd, offset, count) + return errno_unwrap(ret, int) + } +} -/// Create a socket file descriptor -/// Available since Linux 2.0 +/* + Create a socket file descriptor. + Available since Linux 2.0. +*/ socket :: proc "contextless" (domain: Address_Family, socktype: Socket_Type, sockflags: Socket_FD_Flags, protocol: Protocol) -> (Fd, Errno) { sock_type_flags: int = cast(int) socktype | transmute(int) sockflags ret := syscall(SYS_socket, domain, sock_type_flags, protocol) return errno_unwrap(ret, Fd) } -/// Connect the socket to the address -/// Available since Linux 2.0 +/* + Connect the socket to the address. + Available since Linux 2.0. +*/ 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)) return Errno(-ret) } -/// Accept a pending connection or block until new connection appears -/// Depends on Sock_FD_Flags of the `sock` parameter. -/// Available since Linux 2.0 +/* + Accept a pending connection or block until new connection appears. + Depends on Sock_FD_Flags of the `sock` parameter. + Available since Linux 2.0. +*/ accept :: proc "contextless" (sock: Fd, addr: ^$T, sockflags: Socket_FD_Flags = {}) -> (Fd, Errno) where T == Sock_Addr_In || T == Sock_Addr_In6 || + T == Sock_Addr_Un || T == Sock_Addr_Any { addr_len: i32 = size_of(T) @@ -341,6 +551,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) @@ -358,6 +569,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)) @@ -370,47 +582,97 @@ send_noaddr :: proc "contextless" (sock: Fd, buf: []u8, flags: Socket_Msg) -> (i return errno_unwrap(ret, int) } -/// Receive a message from a socket -/// Available since Linux 2.0 +/* + Receive a message from a socket. + Available since Linux 2.0. +*/ recv :: proc {recvfrom, recv_noaddr} -/// Send a message through a socket -/// Available since Linux 2.0 +/* + Send a message through a socket. + Available since Linux 2.0. +*/ send :: proc {sendto, send_noaddr} -// TODO(flysand): sendmsg +/* + Send a message on a socket. + Available since Linux 2.0. +*/ +sendmsg :: proc "contextless" (sock: Fd, msghdr: ^Msg_Hdr, flags: Socket_Msg) -> (int, Errno) { + ret := syscall(SYS_sendmsg, sock, msghdr, transmute(i32) flags) + return errno_unwrap(ret, int) +} -// TODO(flysand): recvmsg +/* + Receive a message on a socket. + Available since Linux 2.0. +*/ +recvmsg :: proc "contextless" (sock: Fd, msghdr: ^Msg_Hdr, flags: Socket_Msg) -> (int, Errno) { + ret := syscall(SYS_recvmsg, sock, msghdr, transmute(i32) flags) + return errno_unwrap(ret, int) +} +/* + Shutdown a socket. + Available since Linux 2.0. +*/ shutdown :: proc "contextless" (sock: Fd, how: Shutdown_How) -> (Errno) { ret := syscall(SYS_shutdown, sock, how) return Errno(-ret) } -/// Bind a socket to the given local address -/// Available since Linux 2.0 +/* + Bind a socket to the given local address. + Available since Linux 2.0. +*/ 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)) return Errno(-ret) } -/// Marks the socket as a socket that listen to connections using accept.2 -/// Available since Linux 2.0 +/* + Marks the socket as a socket that listen to connections using accept(2). + Available since Linux 2.0. +*/ listen :: proc "contextless" (sock: Fd, queue_len: i32) -> (Errno) { ret := syscall(SYS_listen, sock, queue_len) return Errno(-ret) } -// TODO(flysand): getsockname +/* + Get socket name (aka it's bound address). + Available since Linux 2.0. +*/ +getsockname :: proc "contextless" (sock: Fd, addr: ^Sock_Addr_Any) -> (Errno) { + addr_len := size_of(Sock_Addr_Any) + ret := syscall(SYS_getsockname, sock, addr, &addr_len) + return Errno(-ret) +} -// TODO(flysand): getpeername +/* + Get the name of the connected peer socket. + Available since Linux 2.0. +*/ +getpeername :: proc "contextless" (sock: Fd, addr: ^Sock_Addr_Any) -> (Errno) { + addr_len := size_of(Sock_Addr_Any) + ret := syscall(SYS_getpeername, sock, addr, &addr_len) + return Errno(-ret) +} -// TODO(flysand): socketpair +/* + Create a pair of connected sockets. + Available since Linux 2.0. +*/ +socketpair :: proc "contextless" (domain: Protocol_Family, type: Socket_Type, proto: Protocol, pair: ^[2]Fd) -> (Errno) { + ret := syscall(SYS_socketpair, domain, type, proto, pair) + return Errno(-ret) +} // TODO(flysand): the parameters are the same, maybe there's a way to make it into a single proc, sacrificing type // safety slightly @@ -450,8 +712,10 @@ where return setsockopt_base(sock, cast(int) level, cast(int) opt, val) } -/// Set socket option for a given socket API level -/// Available since Linux 2.0 +/* + Set socket option for a given socket API level. + Available since Linux 2.0. +*/ setsockopt :: proc { setsockopt_sock, setsockopt_tcp, @@ -494,8 +758,10 @@ where return getsockopt_base(sock, cast(int) level, cast(int) opt, val) } -/// Get socket option for a given socket API level -/// Available since Linux 2.0 +/* + Get socket option for a given socket API level. + Available since Linux 2.0. +*/ getsockopt :: proc { getsockopt_sock, getsockopt_tcp, @@ -503,15 +769,13 @@ getsockopt :: proc { getsockopt_base, } -// TODO(flysand): clone (probably not in this PR, maybe not ever) - -/// Creates a copy of the running process -/// Available since Linux 1.0 +/* + Creates a copy of the running process. + Available since Linux 1.0. +*/ fork :: proc "contextless" () -> (Pid, Errno) { - when ODIN_ARCH == .arm64 { - // Note(flysand): this syscall is not documented, but the bottom 8 bits of flags - // are for exit signal - ret := syscall(SYS_clone, Signal.SIGCHLD) + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { + ret := syscall(SYS_clone, u64(Signal.SIGCHLD), cast(rawptr) nil, cast(rawptr) nil, cast(rawptr) nil, u64(0)) return errno_unwrap(ret, Pid) } else { ret := syscall(SYS_fork) @@ -519,51 +783,162 @@ fork :: proc "contextless" () -> (Pid, Errno) { } } -// TODO(flysand): vfork +/* + Create a child process and block parent. + Available since Linux 2.2. +*/ +vfork :: proc "contextless" () -> Pid { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { + return Pid(syscall(SYS_vfork)) + } else { + return Pid(syscall(SYS_clone, Signal.SIGCHLD)) + } +} -// TODO(flysand): execve +/* + Replace the current process with another program. + Available since Linux 1.0. + On ARM64 available since Linux 3.19. +*/ +execve :: proc "contextless" (name: cstring, argv: [^]cstring, envp: [^]cstring) -> (Errno) { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { + ret := syscall(SYS_execve, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp) + return Errno(-ret) + } else { + ret := syscall(SYS_execveat, AT_FDCWD, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, i32(0)) + return Errno(-ret) + } +} -/// Exit the thread with a given exit code -/// Available since Linux 1.0 +/* + Exit the thread with a given exit code. + Available since Linux 1.0. +*/ exit :: proc "contextless" (code: i32) -> ! { syscall(SYS_exit, code) unreachable() } -/// Wait for the process to change state -/// Available since Linux 1.0 -wait4 :: proc "contextless" (pid: Pid, status: ^u32, options: Wait_Options) -> (Pid, Errno) { - ret := syscall(SYS_wait4, pid, status, transmute(u32) options) +/* + Wait for the process to change state. + Available since Linux 1.0. +*/ +wait4 :: proc "contextless" (pid: Pid, status: ^u32, options: Wait_Options, rusage: ^RUsage) -> (Pid, Errno) { + ret := syscall(SYS_wait4, pid, status, transmute(u32) options, rusage) return errno_unwrap(ret, Pid) } -/// See wait4 +/* + See `wait4` for documentation. +*/ waitpid :: wait4 -// TODO(flysand): kill +/* + Send signal to a process. + Available since Linux 1.0. +*/ +kill :: proc "contextless" (pid: Pid, signal: Signal) -> (Errno) { + ret := syscall(SYS_kill, pid, signal) + return Errno(-ret) +} -/// Get system information -/// Available since Linux 1.0 +/* + Get system information. + Available since Linux 1.0. +*/ uname :: proc "contextless" (uts_name: ^UTS_Name) -> (Errno) { ret := syscall(SYS_uname, uts_name) return Errno(-ret) } -// TODO(flysand): semget +/* + Get a SystemV semaphore set identifier. + Available since Linux 2.0. +*/ +semget :: proc "contextless" (key: Key, n: i32, flags: IPC_Flags) -> (Key, Errno) { + ret := syscall(SYS_semget, key, n, transmute(i16) flags) + return errno_unwrap(ret, Key) +} -// TODO(flysand): semop +/* + SystemV semaphore operations. + Available since Linux 2.0. +*/ +semop :: proc "contextless" (key: Key, ops: []Sem_Buf) -> (Errno) { + when ODIN_ARCH != .i386 { + ret := syscall(SYS_semop, key, raw_data(ops), len(ops)) + return Errno(-ret) + } else { + // Note(flysand): Max time limit, let's not worry about nanoseconds... + max_timespec := Time_Spec { + time_sec = max(uint), + time_nsec = 0, + } + ret := syscall(SYS_semtimedop_time64, key, raw_data(ops), len(ops), &max_timespec) + return Errno(-ret) + } +} -// TODO(flysand): semctl +semctl3 :: proc "contextless" (key: Key, semnum: i32, cmd: IPC_Cmd) -> (int, Errno) { + ret := syscall(SYS_semctl, key, semnum, cmd) + return errno_unwrap(ret, int) +} -// TODO(flysand): shmdt +semctl4 :: proc "contextless" (key: Key, semnum: i32, cmd: IPC_Cmd, semun: ^Sem_Un) -> (int, Errno) { + ret := syscall(SYS_semctl, key, semnum, cmd, semun) + return errno_unwrap(ret, int) +} -// TODO(flysand): msgget +/* + SystemV semaphore control operations. + Available since Linux 2.0. +*/ +semctl :: proc {semctl3, semctl4} -// TODO(flysand): msgsnd +/* + SystemV shared memory operations. + Available since Linux 2.0. +*/ +shmdt :: proc "contextless" (shmaddr: rawptr) -> (Errno) { + ret := syscall(SYS_shmdt, shmaddr) + return Errno(-ret) +} -// TODO(flysand): msgrcv +/* + Get SystemV message queue identifier. + Available since Linux 2.0. +*/ +msgget :: proc "contextless" (key: Key, flags: IPC_Flags) -> (Key, Errno) { + ret := syscall(SYS_msgget, key, transmute(i16) flags) + return errno_unwrap(ret, Key) +} -// TODO(flysand): msgctl +/* + Send message to a SystemV message queue. + Available since Linux 2.0. +*/ +msgsnd :: proc "contextless" (key: Key, buf: rawptr, size: int, flags: IPC_Flags) -> (Errno) { + ret := syscall(SYS_msgsnd, key, buf, size, transmute(i16) flags) + return Errno(-ret) +} + +/* + Receive a message from a SystemV message queue. + Available since Linux 2.0. +*/ +msgrcv :: proc "contextless" (key: Key, buf: rawptr, size: int, type: uint, flags: IPC_Flags) -> (int, Errno) { + ret := syscall(SYS_msgrcv, key, buf, size, type, transmute(i16) flags) + return errno_unwrap(ret, int) +} + +/* + SystemV message control operations. + Available since Linux 2.0. +*/ +msgctl :: proc "contextless" (key: Key, cmd: IPC_Cmd, buf: ^Msqid_DS) -> (int, Errno) { + ret := syscall(SYS_msgctl, key, cmd, buf) + return errno_unwrap(ret, int) +} fcntl_dupfd :: proc "contextless" (fd: Fd, cmd: FCntl_Command_DUPFD, newfd: Fd) -> (Fd, Errno) { ret := syscall(SYS_fcntl, fd, cmd, newfd) @@ -727,20 +1102,44 @@ fcntl :: proc { fcntl_set_file_rw_hint, } -// TODO(flysand): flock +/* + Apply or remove advisory lock on an open file. + Available since Linux 2.0. +*/ +flock :: proc "contextless" (fd: Fd, operation: FLock_Op) -> (Errno) { + ret := syscall(SYS_flock, fd, transmute(i32) operation) + return Errno(-ret) +} -/// Sync state of the file with the storage device +/* + Sync state of the file with the storage device. + Available since Linux 1.0. +*/ fsync :: proc "contextless" (fd: Fd) -> (Errno) { ret := syscall(SYS_fsync, fd) return Errno(-ret) } -// TODO(flysand): fdatasync +/* + Synchronize the state of the file with the storage device. Similar to `fsync`, + except does not flush the metadata. + Available since Linux 2.0. +*/ +fdatasync :: proc "contextless" (fd: Fd) -> (Errno) { + ret := syscall(SYS_fdatasync, fd) + return Errno(-ret) +} -/// Truncate a file to specified length -/// On 32-bit architectures available since Linux 2.4 +/* + Truncate a file to specified length. + Available since Linux 1.0. + On 32-bit architectures available since Linux 2.4. +*/ truncate :: proc "contextless" (name: cstring, length: i64) -> (Errno) { - when size_of(int) == 4 { + when ODIN_ARCH == .arm32 { + ret := syscall(SYS_truncate64, cast(rawptr) name, 0, compat64_arg_pair(length)) + return Errno(-ret) + } else when size_of(int) == 4 { ret := syscall(SYS_truncate64, cast(rawptr) name, compat64_arg_pair(length)) return Errno(-ret) } else { @@ -749,52 +1148,67 @@ truncate :: proc "contextless" (name: cstring, length: i64) -> (Errno) { } } -/// Truncate a file specified by file descriptor to specified length -/// On 32-bit architectures available since 2.4 +/* + Truncate a file specified by file descriptor to specified length. + On 32-bit architectures available since 2.4. +*/ ftruncate :: proc "contextless" (fd: Fd, length: i64) -> (Errno) { - when size_of(int) == 4 { + when ODIN_ARCH == .arm32 { + ret := syscall(SYS_ftruncate64, fd, 0, compat64_arg_pair(length)) + return Errno(-ret) + } else when size_of(int) == 4 { 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) } } -/// Retrieve the contents of the directory specified by dirfd -/// Returns the number of bytes written -/// Available since Linux 2.4 +/* + Retrieve the contents of the directory specified by dirfd. + Returns the number of bytes written. + Available since Linux 2.4. +*/ getdents :: proc "contextless" (dirfd: Fd, buf: []u8) -> (int, Errno) { ret := syscall(SYS_getdents64, dirfd, raw_data(buf), len(buf)) return errno_unwrap(ret, int) } -/// Get current working directory -/// Available since Linux 1.0 +/* + Get current working directory. + Available since Linux 1.0. +*/ getcwd :: proc "contextless" (buf: []u8) -> (int, Errno) { ret := syscall(SYS_getcwd, raw_data(buf), len(buf)) return errno_unwrap(ret, int) } -/// Change working directory to the directory specified by path -/// Available since Linux 1.0 +/* + Change working directory to the directory specified by path. + Available since Linux 1.0. +*/ chdir :: proc "contextless" (path: cstring) -> (Errno) { ret := syscall(SYS_chdir, cast(rawptr) path) return Errno(-ret) } -/// Change working directory to the directory specified by dirfd -/// Available since Linux 1.0 +/* + Change working directory to the directory specified by dirfd. + Available since Linux 1.0. +*/ fchdir :: proc "contextless" (fd: Fd) -> (Errno) { ret := syscall(SYS_fchdir, fd) return Errno(-ret) } -/// Rename (move) the file -/// Available since Linux 1.0 -/// On ARM64 available since Linux 2.6.16 +/* + Rename (move) the file. + Available since Linux 1.0. + On ARM64 available since Linux 2.6.16. +*/ rename :: proc "contextless" (old: cstring, new: cstring) -> (Errno) { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_renameat, AT_FDCWD, cast(rawptr) old, AT_FDCWD, cast(rawptr) new) return Errno(-ret) } else { @@ -803,11 +1217,13 @@ rename :: proc "contextless" (old: cstring, new: cstring) -> (Errno) { } } -/// Creates a directory -/// Available since Linux 1.0 -/// On ARM64 available since Linux 2.6.16 +/* + Creates a directory. + Available since Linux 1.0. + On ARM64 available since Linux 2.6.16. +*/ mkdir :: proc "contextless" (name: cstring, mode: Mode) -> (Errno) { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_mkdirat, AT_FDCWD, cast(rawptr) name, transmute(u32) mode) return Errno(-ret) } else { @@ -816,11 +1232,13 @@ mkdir :: proc "contextless" (name: cstring, mode: Mode) -> (Errno) { } } -/// Remove a directory specified by name -/// Available since Linux 1.0 -/// On ARM64 available since Linux 2.6.16 +/* + Remove a directory specified by name. + Available since Linux 1.0. + On ARM64 available since Linux 2.6.16. +*/ rmdir :: proc "contextless" (name: cstring) -> (Errno) { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_unlinkat, AT_FDCWD, cast(rawptr) name, transmute(i32) FD_Flags{.REMOVEDIR}) return Errno(-ret) } else { @@ -829,14 +1247,28 @@ rmdir :: proc "contextless" (name: cstring) -> (Errno) { } } -// TODO(flysand): creat +/* + Create a file. + Available since Linux 1.0. + On ARM64 available since Linux 2.6.16. +*/ +creat :: proc "contextless" (name: cstring, mode: Mode) -> (Fd, Errno) { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { + return openat(AT_FDCWD, name, {.CREAT, .WRONLY,.TRUNC}, mode) + } else { + ret := syscall(SYS_creat, cast(rawptr) name, transmute(u32) mode) + return errno_unwrap(ret, Fd) + } +} -/// Create a hard link on a file -/// Available since Linux 1.0 -/// On ARM64 available since Linux 2.6.16 +/* + Create a hard link on a file. + Available since Linux 1.0. + On ARM64 available since Linux 2.6.16. +*/ 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) + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { + ret := syscall(SYS_linkat, AT_FDCWD, cast(rawptr) target, AT_FDCWD, cast(rawptr) linkpath, 0) return Errno(-ret) } else { ret := syscall(SYS_link, cast(rawptr) target, cast(rawptr) linkpath) @@ -844,11 +1276,13 @@ link :: proc "contextless" (target: cstring, linkpath: cstring) -> (Errno) { } } -/// Delete a name, and possible a file it refers to -/// Available since Linux 1.0 -/// On ARM64 available since Linux 2.6.16 +/* + Delete a name, and possible a file it refers to. + Available since Linux 1.0. + On ARM64 available since Linux 2.6.16. +*/ unlink :: proc "contextless" (name: cstring) -> (Errno) { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_unlinkat, AT_FDCWD, cast(rawptr) name, 0) return Errno(-ret) } else { @@ -857,12 +1291,14 @@ unlink :: proc "contextless" (name: cstring) -> (Errno) { } } -/// Create a symbolic link -/// Available since Linux 1.0 -/// On arm64 available since Linux 2.6.16 +/* + Create a symbolic link. + Available since Linux 1.0. + On arm64 available since Linux 2.6.16. +*/ symlink :: proc "contextless" (target: cstring, linkpath: cstring) -> (Errno) { - when ODIN_ARCH == .arm64 { - ret := syscall(SYS_symlinkat, AT_FDCWD, cast(rawptr) target, cast(rawptr) linkpath) + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { + 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) @@ -870,11 +1306,13 @@ symlink :: proc "contextless" (target: cstring, linkpath: cstring) -> (Errno) { } } -/// Read the value of a symbolic link -/// Available since Linux 1.0 -/// On arm64 available since Linux 2.6.16 +/* + Read the value of a symbolic link. + Available since Linux 1.0. + On arm64 available since Linux 2.6.16. +*/ readlink :: proc "contextless" (name: cstring, buf: []u8) -> (int, Errno) { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_readlinkat, AT_FDCWD, cast(rawptr) name, raw_data(buf), len(buf)) return errno_unwrap(ret, int) } else { @@ -883,12 +1321,14 @@ readlink :: proc "contextless" (name: cstring, buf: []u8) -> (int, Errno) { } } -/// Change file permissions -/// Available since Linux 1.0 -/// On ARM64 available since Linux 2.6.16 +/* + Change file permissions. + Available since Linux 1.0. + On ARM64 available since Linux 2.6.16. +*/ chmod :: proc "contextless" (name: cstring, mode: Mode) -> (Errno) { - when ODIN_ARCH == .arm64 { - ret := syscall(SYS_fchmodat, cast(rawptr) name, transmute(u32) mode, 0) + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { + 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) @@ -896,22 +1336,26 @@ chmod :: proc "contextless" (name: cstring, mode: Mode) -> (Errno) { } } -/// Change file permissions through a file descriptor -/// Available since Linux 1.0 +/* + Change file permissions through a file descriptor. + Available since Linux 1.0. +*/ fchmod :: proc "contextless" (fd: Fd, mode: Mode) -> (Errno) { ret := syscall(SYS_fchmod, fd, transmute(u32) mode) return Errno(-ret) } -/// Change ownership of a file -/// Available since Linux 2.2 -/// On 32-bit architectures available since Linux 2.4 -/// On ARM64 available since Linux 2.6.16 +/* + Change ownership of a file. + Available since Linux 2.2. + On 32-bit architectures available since Linux 2.4. + On ARM64 available since Linux 2.6.16. +*/ chown :: proc "contextless" (name: cstring, uid: Uid, gid: Gid) -> (Errno) { when size_of(int) == 4 { ret := syscall(SYS_chown32, cast(rawptr) name, uid, gid) return Errno(-ret) - } else when ODIN_ARCH == .arm64 { + } else when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_fchownat, AT_FDCWD, cast(rawptr) name, uid, gid, 0) return Errno(-ret) } else { @@ -920,9 +1364,11 @@ chown :: proc "contextless" (name: cstring, uid: Uid, gid: Gid) -> (Errno) { } } -/// Change ownership of a file by file descriptor -/// Available since Linux 1.0 -/// On 32-bit architecvtures available since Linux 2.4 +/* + Change ownership of a file by file descriptor. + Available since Linux 1.0. + On 32-bit architecvtures available since Linux 2.4. +*/ fchown :: proc "contextless" (fd: Fd, uid: Uid, gid: Gid) -> (Errno) { when size_of(int) == 4 { ret := syscall(SYS_fchown32, fd, uid, gid) @@ -933,15 +1379,17 @@ fchown :: proc "contextless" (fd: Fd, uid: Uid, gid: Gid) -> (Errno) { } } -/// Change ownership of a file. Unlike chown, if a file is a symlink dooesn't dereference it -/// Available since Linux 1.0 -/// On 32-bit architectures available since Linux 2.4 -/// On ARM64 available since Linux 2.6.16 +/* + Change ownership of a file. Unlike chown, if a file is a symlink dooesn't dereference it. + Available since Linux 1.0. + On 32-bit architectures available since Linux 2.4. + On ARM64 available since Linux 2.6.16. +*/ lchown :: proc "contextless" (name: cstring, uid: Uid, gid: Gid) -> (Errno) { when size_of(int) == 4 { ret := syscall(SYS_lchown32, cast(rawptr) name, uid, gid) return Errno(-ret) - } else when ODIN_ARCH == .arm64 { + } else when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_fchownat, AT_FDCWD, cast(rawptr) name, uid, gid, transmute(i32) FD_Flags{.SYMLINK_NOFOLLOW}) return Errno(-ret) } else { @@ -950,42 +1398,240 @@ lchown :: proc "contextless" (name: cstring, uid: Uid, gid: Gid) -> (Errno) { } } -// TODO(flysand): umask +/* + Set file mode creation mask. + Available since Linux 1.0. +*/ +umask :: proc "contextless" (mask: Mode) -> Mode { + ret := syscall(SYS_umask, transmute(u32) mask) + return transmute(Mode) cast(u32) ret +} -// TODO(flysand): gettimeofday +/* + Get current time. + Available since Linux 1.0. +*/ +gettimeofday :: proc "contextless" (tv: ^Time_Val) -> (Errno) { + ret := syscall(SYS_gettimeofday, tv) + return Errno(-ret) +} -/// Get limits on resources -/// Available since Linux 1.0 +/* + Get limits on resources. + Available since Linux 1.0. +*/ getrlimit :: proc "contextless" (kind: RLimit_Kind, resource: ^RLimit) -> (Errno) { ret := syscall(SYS_getrlimit, kind, resource) return Errno(-ret) } -/// Get resource usage -/// Available since Linux 1.0 +/* + Get resource usage. + Available since Linux 1.0. +*/ getrusage :: proc "contextless" (who: RUsage_Who, rusage: ^RUsage) -> (Errno) { ret := syscall(SYS_getrusage, who, rusage) return Errno(-ret) } -/// Get information about the system +/* + Get information about the system. + Available since Linux 1.0. +*/ sysinfo :: proc "contextless" (sysinfo: ^Sys_Info) -> (Errno) { ret := syscall(SYS_sysinfo, sysinfo) return Errno(-ret) } -/// Get current process times -/// Available since Linux 1.0 +/* + Get current process times. + Available since Linux 1.0. +*/ times :: proc "contextless" (tms: ^Tms) -> (Errno) { ret := syscall(SYS_times, cast(rawptr) tms) return Errno(-ret) } -// TODO(flysand): ptrace +ptrace_traceme :: proc "contextless" (rq: PTrace_Traceme_Type) -> (Errno) { + ret := syscall(SYS_ptrace, rq) + return Errno(-ret) +} -/// Get real user ID -/// Available since Linux 1.0 -/// On 32-bit platforms available since Linux 2.4 +ptrace_peek :: proc "contextless" (rq: PTrace_Peek_Type, pid: Pid, addr: uintptr) -> (uint, Errno) { + res: uint = --- + ret := syscall(SYS_ptrace, rq, pid, addr, &res) + return res, Errno(-ret) +} + +ptrace_poke :: proc "contextless" (rq: PTrace_Poke_Type, pid: Pid, addr: uintptr, data: uint) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, addr, data) + return Errno(-ret) +} + +ptrace_getregs :: proc "contextless" (rq: PTrace_Getregs_Type, pid: Pid, buf: ^User_Regs) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, 0, buf) + return Errno(-ret) +} + +ptrace_getfpregs :: proc "contextless" (rq: PTrace_Getfpregs_Type, pid: Pid, buf: ^User_FP_Regs) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, 0, buf) + return Errno(-ret) +} + +ptrace_getfpxregs :: proc "contextless" (rq: PTrace_Getfpxregs_Type, pid: Pid, buf: ^User_FPX_Regs) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, 0, buf) + return Errno(-ret) +} + +ptrace_setregs :: proc "contextless" (rq: PTrace_Setregs_Type, pid: Pid, buf: ^User_Regs) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, 0, buf) + return Errno(-ret) +} + +ptrace_setfpregs :: proc "contextless" (rq: PTrace_Setfpregs_Type, pid: Pid, buf: ^User_FP_Regs) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, 0, buf) + return Errno(-ret) +} + +ptrace_setfpxregs :: proc "contextless" (rq: PTrace_Setfpxregs_Type, pid: Pid, buf: ^User_FPX_Regs) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, 0, buf) + return Errno(-ret) +} + +ptrace_getregset :: proc "contextless" (rq: PTrace_Getregset_Type, pid: Pid, note: PTrace_Note_Type, buf: ^IO_Vec) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, note, buf) + return Errno(-ret) +} + +ptrace_setregset :: proc "contextless" (rq: PTrace_Setregset_Type, pid: Pid, note: PTrace_Note_Type, buf: ^IO_Vec) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, note, buf) + return Errno(-ret) +} + +ptrace_getsiginfo :: proc "contextless" (rq: PTrace_Getsiginfo_Type, pid: Pid, si: ^Sig_Info) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, si, rawptr(nil)) + return Errno(-ret) +} + +ptrace_peeksiginfo :: proc "contextless" (rq: PTrace_Peeksiginfo_Type, pid: Pid, si: ^Sig_Info) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, si, rawptr(nil)) + return Errno(-ret) +} + +ptrace_getsigmask :: proc "contextless" (rq: PTrace_Getsigmask_Type, pid: Pid, sigmask: ^Sig_Set) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, size_of(Sig_Set), sigmask) + return Errno(-ret) +} + +ptrace_setsigmask :: proc "contextless" (rq: PTrace_Setsigmask_Type, pid: Pid, sigmask: ^Sig_Set) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, size_of(Sig_Set), sigmask) + return Errno(-ret) +} + +ptrace_setoptions :: proc "contextless" (rq: PTrace_Setoptions_Type, pid: Pid, options: PTrace_Options) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, 0, transmute(u32) options) + return Errno(-ret) +} + +ptrace_geteventmsg :: proc "contextless" (rq: PTrace_Geteventmsg_Type, pid: Pid, msg: ^uint) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, msg, rawptr(nil)) + return Errno(-ret) +} + +ptrace_cont :: proc "contextless" (rq: PTrace_Cont_Type, pid: Pid, sig: Signal) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, rawptr(nil), sig) + return Errno(-ret) +} + +ptrace_singlestep :: proc "contextless" (rq: PTrace_Singlestep_Type, pid: Pid, sig: Signal) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, rawptr(nil), sig) + return Errno(-ret) +} + +ptrace_syscall :: proc "contextless" (rq: PTrace_Syscall_Type, pid: Pid, sig: Signal) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, rawptr(nil), sig) + return Errno(-ret) +} + +ptrace_sysemu :: proc "contextless" (rq: PTrace_Sysemu_Type, pid: Pid, sig: Signal) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, rawptr(nil), sig) + return Errno(-ret) +} + +ptrace_sysemu_singlestep :: proc "contextless" (rq: PTrace_Sysemu_Singlestep_Type, pid: Pid, sig: Signal) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, rawptr(nil), sig) + return Errno(-ret) +} + +ptrace_listen :: proc "contextless" (rq: PTrace_Listen_Type, pid: Pid) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, 0, rawptr(nil)) + return Errno(-ret) +} + +ptrace_interrupt :: proc "contextless" (rq: PTrace_Interrupt_Type, pid: Pid) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, 0, rawptr(nil)) + return Errno(-ret) +} + +ptrace_attach :: proc "contextless" (rq: PTrace_Attach_Type, pid: Pid) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, 0, rawptr(nil)) + return Errno(-ret) +} + +ptrace_seize :: proc "contextless" (rq: PTrace_Seize_Type, pid: Pid, opt: PTrace_Options) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, 0, transmute(u32) opt, rawptr(nil)) + return Errno(-ret) +} + +// TODO(flysand): ptrace_seccomp_get_filter + +ptrace_detach :: proc "contextless" (rq: PTrace_Detach_Type, pid: Pid, sig: Signal) -> (Errno) { + ret := syscall(SYS_ptrace, rq, pid, 0, sig) + return Errno(-ret) +} + +// TODO(flysand): ptrace_get_thread_area +// TODO(flysand): ptrace_set_thread_area +// TODO(flysand): ptrace_get_syscall_info + +/* + Trace process. +*/ +ptrace :: proc { + ptrace_traceme, + ptrace_peek, + ptrace_poke, + ptrace_getregs, + ptrace_getfpregs, + ptrace_getfpxregs, + ptrace_setregs, + ptrace_setfpregs, + ptrace_setfpxregs, + ptrace_getregset, + ptrace_setregset, + ptrace_getsiginfo, + ptrace_peeksiginfo, + ptrace_getsigmask, + ptrace_setsigmask, + ptrace_setoptions, + ptrace_geteventmsg, + ptrace_cont, + ptrace_singlestep, + ptrace_syscall, + ptrace_sysemu, + ptrace_sysemu_singlestep, + ptrace_listen, + ptrace_interrupt, + ptrace_attach, + ptrace_seize, + ptrace_detach, +} + +/* + Get real user ID. + Available since Linux 1.0. + On 32-bit platforms available since Linux 2.4. +*/ getuid :: proc "contextless" () -> Uid { when size_of(int) == 8 { return cast(Uid) syscall(SYS_getuid) @@ -994,11 +1640,20 @@ getuid :: proc "contextless" () -> Uid { } } -// TODO(flysand): syslog +/* + Read or clear kernel message ring buffer. + Available since Linux 1.0. +*/ +syslog :: proc "contextless" (act: Syslog_Action, buf: []u8) -> (int, Errno) { + ret := syscall(SYS_syslog, act, raw_data(buf), len(buf)) + return errno_unwrap(ret, int) +} -/// Get real group ID -/// Available since Linux 1.0 -/// On 32-bit platforms available since Linux 2.4 +/* + Get real group ID. + Available since Linux 1.0. + On 32-bit platforms available since Linux 2.4. +*/ getgid :: proc "contextless" () -> Gid { when size_of(int) == 8 { return cast(Gid) syscall(SYS_getgid) @@ -1007,9 +1662,11 @@ getgid :: proc "contextless" () -> Gid { } } -/// Set effective user id -/// Available since Linux 1.0 -/// On 32-bit platforms available since Linux 2.4 +/* + Set effective User ID. + Available since Linux 1.0. + On 32-bit platforms available since Linux 2.4. +*/ @(require_results) setuid :: proc "contextless" (uid: Uid) -> (Errno) { when size_of(int) == 8 { @@ -1021,10 +1678,12 @@ setuid :: proc "contextless" (uid: Uid) -> (Errno) { } } -/// Set effective group id -/// If the process is privileged also sets real group id -/// Available since Linux 1.0 -/// On 32-bit platforms available since Linux 2.4 +/* + Set effective group id. + If the process is privileged also sets real group id. + Available since Linux 1.0. + On 32-bit platforms available since Linux 2.4. +*/ @(require_results) setgid :: proc "contextless" (gid: Gid) -> (Errno) { when size_of(int) == 8 { @@ -1036,9 +1695,11 @@ setgid :: proc "contextless" (gid: Gid) -> (Errno) { } } -/// Get effective user ID -/// Available since Linux 1.0 -/// On 32-bit platforms available since Linux 2.4 +/* + Get effective user ID. + Available since Linux 1.0. + On 32-bit platforms available since Linux 2.4. +*/ geteuid :: proc "contextless" () -> Uid { when size_of(int) == 8 { return cast(Uid) syscall(SYS_geteuid) @@ -1047,9 +1708,11 @@ geteuid :: proc "contextless" () -> Uid { } } -/// Get effective group ID -/// Available since Linux 1.0 -/// On 32-bit platforms available since Linux 2.4 +/* + Get effective group ID. + Available since Linux 1.0. + On 32-bit platforms available since Linux 2.4. +*/ getegid :: proc "contextless" () -> Gid { when size_of(int) == 8 { return cast(Gid) syscall(SYS_getegid) @@ -1058,23 +1721,29 @@ getegid :: proc "contextless" () -> Gid { } } -/// Set process group -/// Available since Linux 1.0 +/* + Set process group. + Available since Linux 1.0. +*/ setpgid :: proc "contextless" (pid: Pid, pgid: Pid) -> (Errno) { ret := syscall(SYS_setpgid, pid, pgid) return Errno(-ret) } -/// Get the parent process ID -/// Available since Linux 1.0 +/* + Get the parent process ID. + Available since Linux 1.0. +*/ getppid :: proc "contextless" () -> Pid { return cast(Pid) syscall(SYS_getppid) } -/// Get process group -/// Available since Linux 1.0 +/* + Get process group. + Available since Linux 1.0. +*/ getpgrp :: proc "contextless" () -> (Pid, Errno) { - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { ret := syscall(SYS_getpgid, 0) return errno_unwrap(ret, Pid) } else { @@ -1083,17 +1752,21 @@ getpgrp :: proc "contextless" () -> (Pid, Errno) { } } -/// Create a session and set the process group ID -/// Available since Linux 2.0 -setsid :: proc "contextless" () -> (Errno) { +/* + Create a session and set the process group ID. + Available since Linux 2.0. +*/ +setsid :: proc "contextless" () -> (Pid, Errno) { ret := syscall(SYS_setsid) - return Errno(-ret) + return errno_unwrap(ret, Pid) } -/// Set real and/or effective user id -/// If any of the arguments is -1, the corresponding id is not changed -/// Available since Linux 1.0 -/// On 32-bit platforms available since Linux 2.4 +/* + Set real and/or effective user id. + If any of the arguments is -1, the corresponding id is not changed. + Available since Linux 1.0. + On 32-bit platforms available since Linux 2.4. +*/ @(require_results) setreuid :: proc "contextless" (real: Uid, effective: Uid) -> (Errno) { when size_of(int) == 8 { @@ -1105,10 +1778,12 @@ setreuid :: proc "contextless" (real: Uid, effective: Uid) -> (Errno) { } } -/// Set real and/or effective group id -/// If any of the arguments is -1, the corresponding id is not changed -/// Available since Linux 1.0 -/// On 32-bit platforms available since Linux 2.4 +/* + Set real and/or effective group id. + If any of the arguments is -1, the corresponding id is not changed. + Available since Linux 1.0. + On 32-bit platforms available since Linux 2.4. +*/ @(require_results) setregid :: proc "contextless" (real: Gid, effective: Gid) -> (Errno) { when size_of(int) == 8 { @@ -1120,14 +1795,42 @@ setregid :: proc "contextless" (real: Gid, effective: Gid) -> (Errno) { } } -// TODO(flysand): getgroups +/* + Get supplementary group IDs. + Available since Linux 1.0. + On 32-bit platforms available since Linux 2.4. +*/ +getgroups :: proc "contextless" (gids: []Gid) -> (int, Errno) { + when size_of(int) == 8 { + ret := syscall(SYS_getgroups, len(gids), raw_data(gids)) + return errno_unwrap(ret, int) + } else { + ret := syscall(SYS_getgroups32, len(gids), raw_data(gids)) + return errno_unwrap(ret, int) + } +} -// TODO(flysand): setgroups +/* + Set supplementary group IDs. + Available since Linux 1.0. + On 32-bit platforms available since Linux 2.4. +*/ +setgroups :: proc "contextless" (gids: []Gid) -> (Errno) { + when size_of(int) == 8 { + ret := syscall(SYS_setgroups, len(gids), raw_data(gids)) + return Errno(-ret) + } else { + ret := syscall(SYS_setgroups32, len(gids), raw_data(gids)) + return Errno(-ret) + } +} -/// Set real, effective and/or saved user id -/// If any of the arguments is -1, the corresponding id is not changed -/// Available since Linux 2.2 -/// On 32-bit platforms available since Linux 2.4 +/* + Set real, effective and/or saved user id. + If any of the arguments is -1, the corresponding id is not changed. + Available since Linux 2.2. + On 32-bit platforms available since Linux 2.4. +*/ @(require_results) setresuid :: proc "contextless" (real: Uid, effective: Uid, saved: Uid) -> (Errno) { when size_of(int) == 8 { @@ -1139,9 +1842,11 @@ setresuid :: proc "contextless" (real: Uid, effective: Uid, saved: Uid) -> (Errn } } -/// Get real, effective and saved user id -/// Available since Linux 2.2 -/// On 32-bit platforms available since Linux 2.4 +/* + Get real, effective and saved user id. + Available since Linux 2.2. + On 32-bit platforms available since Linux 2.4. +*/ getresuid :: proc "contextless" (real: ^Uid, effective: ^Uid, saved: ^Uid) -> (Errno) { when size_of(int) == 8 { ret := syscall(SYS_getresuid, cast(rawptr) real, cast(rawptr) effective, cast(rawptr) saved) @@ -1152,10 +1857,12 @@ getresuid :: proc "contextless" (real: ^Uid, effective: ^Uid, saved: ^Uid) -> (E } } -/// Set real, effective and/or saved group id -/// If any of the arguments is -1, the corresponding id is not changed -/// Available since Linux 2.2 -/// On 32-bit platforms available since Linux 2.4 +/* + Set real, effective and/or saved group id. + If any of the arguments is -1, the corresponding id is not changed. + Available since Linux 2.2. + On 32-bit platforms available since Linux 2.4. +*/ @(require_results) setresgid :: proc "contextless" (real: Gid, effective: Gid, saved: Uid) -> (Errno) { when size_of(int) == 8 { @@ -1167,9 +1874,11 @@ setresgid :: proc "contextless" (real: Gid, effective: Gid, saved: Uid) -> (Errn } } -/// Get real, effective and saved group id -/// Available since Linux 2.2 -/// On 32-bit platforms available since Linux 2.4 +/* + Get real, effective and saved group id. + Available since Linux 2.2. + On 32-bit platforms available since Linux 2.4. +*/ getresgid :: proc "contextless" (real: ^Gid, effective: ^Gid, saved: ^Gid) -> (Errno) { when size_of(int) == 8 { ret := syscall(SYS_getresgid, cast(rawptr) real, cast(rawptr) effective, cast(rawptr) saved) @@ -1180,17 +1889,19 @@ getresgid :: proc "contextless" (real: ^Gid, effective: ^Gid, saved: ^Gid) -> (E } } -/// Get process group -/// Available since Linux 1.0 +/* + Get process group. + Available since Linux 1.0. +*/ getpgid :: proc "contextless" (pid: Pid) -> (Pid, Errno) { ret := syscall(SYS_getpgid, pid) return errno_unwrap(ret, Pid) } -// NOTE(flysand): setfsuid and setfsgid are deprecated - -/// Get session ID of the calling process -/// Available since Linux 2.0 +/* + Get session ID of the calling process. + Available since Linux 2.0. +*/ getsid :: proc "contextless" (pid: Pid) -> (Pid, Errno) { ret := syscall(SYS_getsid, pid) return errno_unwrap(ret, Pid) @@ -1200,62 +1911,74 @@ getsid :: proc "contextless" (pid: Pid) -> (Pid, Errno) { // TODO(flysand): capset -/// Examine pending signals -/// Available since Linux 2.2 +/* + Examine pending signals. + Available since Linux 2.2. +*/ rt_sigpending :: proc "contextless" (sigs: ^Sig_Set) -> Errno { ret := syscall(SYS_rt_sigpending, sigs, size_of(Sig_Set)) return Errno(-ret) } -/// Synchronously wait for queued signals -/// Available since Linux 2.2 +/* + Synchronously wait for queued signals. + Available since Linux 2.2. +*/ rt_sigtimedwait :: proc "contextless" (sigs: ^Sig_Set, info: ^Sig_Info, time_sus: ^Time_Spec) -> (Signal, Errno) { ret := syscall(SYS_rt_sigtimedwait, sigs, info, time_sus, size_of(Sig_Set)) return errno_unwrap(ret, Signal) } -/// Send signal information to a process -/// Available since Linux 2.2 +/* + Send signal information to a process. + Available since Linux 2.2. +*/ rt_sigqueueinfo :: proc "contextless" (pid: Pid, sig: Signal, si: ^Sig_Info) -> (Errno) { ret := syscall(SYS_rt_sigqueueinfo, pid, sig, si) return Errno(-ret) } -/// Replace the signal mask for a value with the new mask until a signal is received -/// Available since Linux 2.2 +/* + Replace the signal mask for a value with the new mask until a signal is received. + Available since Linux 2.2. +*/ rt_sigsuspend :: proc "contextless" (sigset: ^Sig_Set) -> Errno { ret := syscall(SYS_rt_sigsuspend, sigset, size_of(Sig_Set)) return Errno(-ret) } -/// Set or get signal stack context -/// Available since Linux 2.2 +/* + Set or get signal stack context. + Available since Linux 2.2. +*/ sigaltstack :: proc "contextless" (stack: ^Sig_Stack, old_stack: ^Sig_Stack) -> (Errno) { ret := syscall(SYS_sigaltstack, stack, old_stack) return Errno(-ret) } -// TODO(flysand): utime - -/// Create a special or ordinary file -/// `mode` parameter contains both the the file mode and the type of the node to create -/// -> Add one of S_IFSOCK, S_IFBLK, S_IFFIFO, S_IFCHR to mode -/// Available since Linux 1.0 -/// On ARM64 available since Linux 2.6.16 +/* + Create a special or ordinary file. + + `mode` parameter contains both the the file mode and the type of the node to create. + -> Add one of S_IFSOCK, S_IFBLK, S_IFFIFO, S_IFCHR to mode. + + Available since Linux 1.0. + On ARM64 available since Linux 2.6.16. +*/ mknod :: proc "contextless" (name: cstring, mode: Mode, dev: Dev) -> (Errno) { - when ODIN_ARCH == .arm64 { - ret := syscall(SYS_mknodat, AT_FDCWD, cast(rawptr) name, transmute(u32) mode, dev) + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { + ret := syscall(SYS_mknodat, AT_FDCWD, cast(rawptr) name, transmute(u32) mode, cast(uint) dev) return Errno(-ret) } else { - ret := syscall(SYS_mknod, cast(rawptr) name, transmute(u32) mode, dev) + ret := syscall(SYS_mknod, cast(rawptr) name, transmute(u32) mode, cast(uint) dev) return Errno(-ret) } } -// TODO(flysand): uselib - -/// Set the process execution domain -/// Available since Linux 1.2 +/* + Set the process execution domain. + Available since Linux 1.2. +*/ personality :: proc "contextless" (personality: uint) -> (uint, Errno) { ret := syscall(SYS_personality, personality) return errno_unwrap(ret, uint) @@ -1263,10 +1986,12 @@ personality :: proc "contextless" (personality: uint) -> (uint, Errno) { // TODO(flysand): ustat -/// Query information about filesystem -/// -/// Available since Linux 1.0 -/// For 32-bit systems a different syscall is used that became available since 2.6 +/* + Query information about filesystem. + + Available since Linux 1.0. + For 32-bit systems a different syscall is used that became available since 2.6. +*/ statfs :: proc "contextless" (path: cstring, statfs: ^Stat_FS) -> (Errno) { when size_of(int) == 8 { ret := syscall(SYS_statfs, transmute(uintptr) path, statfs) @@ -1277,10 +2002,12 @@ statfs :: proc "contextless" (path: cstring, statfs: ^Stat_FS) -> (Errno) { } } -/// Query information about filesystem by file descriptor -/// -/// Available since Linux 1.0 -/// For 32-bit systems a different syscall is used that became available since 2.6 +/* + Query information about filesystem by file descriptor. + + Available since Linux 1.0. + For 32-bit systems a different syscall is used that became available since 2.6. +*/ fstatfs :: proc "contextless" (fd: Fd, statfs: ^Stat_FS) -> (Errno) { when size_of(int) == 8 { ret := syscall(SYS_statfs, fd, statfs) @@ -1293,8 +2020,10 @@ fstatfs :: proc "contextless" (fd: Fd, statfs: ^Stat_FS) -> (Errno) { // TODO(flysand): sysfs -/// Get priority on user, process group or process -/// Available since Linux 1.0 +/* + Get priority on user, process group or process. + Available since Linux 1.0. +*/ getpriority :: proc "contextless" (which: Priority_Which, who: i32) -> (i32, Errno) { ret := syscall(SYS_getpriority, which, who) prio, err := errno_unwrap(ret, i32) @@ -1304,8 +2033,10 @@ getpriority :: proc "contextless" (which: Priority_Which, who: i32) -> (i32, Err return prio, err } -/// Set priority on user, process group or process -/// Available since Linux 1.0 +/* + Set priority on user, process group or process. + Available since Linux 1.0. +*/ setpriority :: proc "contextless" (which: Priority_Which, who: i32, prio: i32) -> (Errno) { ret := syscall(SYS_setpriority, which, who, prio) return Errno(-ret) @@ -1325,9 +2056,11 @@ setpriority :: proc "contextless" (which: Priority_Which, who: i32, prio: i32) - // TODO(flysand): sched_rr_get_interval -/// Lock and memory -/// Available since Linux 2.0 -/// If flags specified, available since Linux 4.4 +/* + Lock and memory. + Available since Linux 2.0. + If flags specified, available since Linux 4.4. +*/ mlock :: proc "contextless" (addr: rawptr, size: uint, flags: MLock_Flags = {}) -> (Errno) { // Pretty darn recent syscall, better call simpler version if we can if flags > {} { @@ -1339,20 +2072,28 @@ mlock :: proc "contextless" (addr: rawptr, size: uint, flags: MLock_Flags = {}) } } -/// Unlock memory -/// Available since Linux 2.0 +/* + Unlock memory. + Available since Linux 2.0. +*/ munlock :: proc "contextless" (addr: rawptr, size: uint) -> (Errno) { ret := syscall(SYS_munlock, addr, size) return Errno(-ret) } -/// Lock all memory +/* + Lock all memory. + Available since Linux 2.0. +*/ mlockall :: proc "contextless" (flags: MLock_Flags = {}) -> (Errno) { ret := syscall(SYS_mlockall, transmute(i32) flags) return Errno(-ret) } -/// Unlock all memory +/* + Unlock all memory. + Available since Linux 2.0. +*/ munlockall :: proc "contextless" () -> (Errno) { ret := syscall(SYS_munlockall) return Errno(-ret) @@ -1372,8 +2113,10 @@ munlockall :: proc "contextless" () -> (Errno) { // TODO(flysand): adj_timex -/// Set limits on resources -/// Available since Linux 1.0 +/* + Set limits on resources. + Available since Linux 1.0. +*/ setrlimit :: proc "contextless" (kind: RLimit_Kind, resource: ^RLimit) -> (Errno) { ret := syscall(SYS_setrlimit, kind, resource) return Errno(-ret) @@ -1395,17 +2138,21 @@ setrlimit :: proc "contextless" (kind: RLimit_Kind, resource: ^RLimit) -> (Errno // TODO(flysand): reboot -/// Set hostname -/// Note: to get the host name, use `uname` syscall -/// Available since Linux 1.0 +/* + Set hostname. + Note: to get the host name, use `uname` syscall. + Available since Linux 1.0. +*/ sethostname :: proc "contextless" (hostname: string) -> (Errno) { ret := syscall(SYS_sethostname, raw_data(hostname), len(hostname)) return Errno(-ret) } -/// Set domain name -/// Note: to get the domain name, use `uname` syscall -/// Available since Linux 2.2 +/* + Set domain name. + Note: to get the domain name, use `uname` syscall. + Available since Linux 2.2. +*/ setdomainname :: proc "contextless" (name: string) -> (Errno) { ret := syscall(SYS_setdomainname, raw_data(name), len(name)) return Errno(-ret) @@ -1415,16 +2162,10 @@ setdomainname :: proc "contextless" (name: string) -> (Errno) { // TODO(flysand): ioperm -// TODO(flysand): create_module - // TODO(flysand): init_module // TODO(flysand): delete_module -// TODO(flysand): get_kernel_syms - -// TODO(flysand): query_module - // TODO(flysand): quotactl // TODO(flysand): nfsservctl @@ -1439,12 +2180,14 @@ setdomainname :: proc "contextless" (name: string) -> (Errno) { // TODO(flysand): security -/// Returns the thread ID of the current process -/// This is what the kernel calls "pid" -/// Let me insert a tiny rant here, this terminology is confusing: -/// sometimes pid refers to a thread, and other times it refers -/// to a thread group (process group?) -/// Anyway, this syscall is available since Linux 1.0 +/* + Returns the thread ID of the current process + This is what the kernel calls "pid". + Let me insert a tiny rant here, this terminology is confusing: + sometimes pid refers to a thread, and other times it refers + to a thread group (process group?) + Anyway, this syscall is available since Linux 1.0 +*/ gettid :: proc "contextless" () -> Pid { return cast(Pid) syscall(SYS_gettid) } @@ -1475,19 +2218,35 @@ gettid :: proc "contextless" () -> Pid { // TODO(flysand): fremovexattr -// TODO(flysand): tkill +/* + Get current time in seconds. + Available since Linux 1.0. +*/ +time :: proc "contextless" (tloc: ^uint) -> (Errno) { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { + ret := syscall(SYS_time, tloc) + return Errno(-ret) + } else { + ts: Time_Spec + ret := syscall(SYS_clock_gettime, Clock_Id.REALTIME, &ts) + tloc^ = ts.time_sec + return Errno(-ret) + } +} -// TODO(flysand): time - -/// Wait on a futex until it's signaled +/* + Wait on a futex until it's signaled. +*/ futex_wait :: proc "contextless" (futex: ^Futex, op: Futex_Wait_Type, flags: Futex_Flags, val: u32, timeout: ^Time_Spec = nil) -> (Errno) { futex_flags := cast(u32) op + transmute(u32) flags ret := syscall(SYS_futex, futex, futex_flags, val, timeout) return Errno(-ret) } -/// Wake up other threads on a futex -/// n_wakeup specifies the number of processes to wakeup. Specify max(i32) to wake up all processes waiting +/* + Wake up other threads on a futex + n_wakeup specifies the number of processes to wakeup. Specify max(i32) to wake up all processes waiting +*/ futex_wake :: proc "contextless" (futex: ^Futex, op: Futex_Wake_Type, flags: Futex_Flags, n_wakeup: i32) -> (int, Errno) { futex_flags := cast(u32) op + transmute(u32) flags ret := syscall(SYS_futex, futex, futex_flags, n_wakeup) @@ -1496,52 +2255,57 @@ futex_wake :: proc "contextless" (futex: ^Futex, op: Futex_Wake_Type, flags: Fut // NOTE(flysand): futex_fd is racy, so not implemented -/// Requeues processes waiting on futex `futex` to wait on futex `requeue_futex` -/// `requeue_threshold` specifies the maximum amount of waiters to wake up, the rest of the waiters will be requeued -/// `requeue_max` specifies the maximum amount of waiters that are required at `requeue_futex` -/// The operation blocks until the `requeue_max` requirement is satisfied -/// If the value of the mutex is not equal to `val`, fails with EAGAIN before any further checks -/// Returns the total number of waiters that have been woken up plus the number of waiters requeued +/* + Requeues processes waiting on futex `futex` to wait on futex `requeue_futex` + `requeue_threshold` specifies the maximum amount of waiters to wake up, the rest of the waiters will be requeued + `requeue_max` specifies the maximum amount of waiters that are required at `requeue_futex` + The operation blocks until the `requeue_max` requirement is satisfied + If the value of the mutex is not equal to `val`, fails with EAGAIN before any further checks + 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) } -/// See `futex_cmp_requeue`, this function does the same thing but doesn't check the value of the futex -/// Returns the total number of waiters that have been woken up +/* + See `futex_cmp_requeue`, this function does the same thing but doesn't check the value of the futex. + 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) } -/// Okay, for this one, see the man pages, the description for it is pretty long and very specific. It's sole -/// purpose is to allow implementing conditional values sync primitive, it seems like +/* + Okay, for this one, see the man pages, the description for it is pretty long and very specific. It's sole. + 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) } -/// Same as wait, but mask specifies bits that must be equal for the mutex to wake up +/* + 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) } -/// 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) -{ +/* + 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_flags := cast(u32) op + transmute(u32) flags ret := syscall(SYS_futex, futex, futex_flags, n_wakeup, 0, 0, mask) return errno_unwrap(ret, int) @@ -1549,7 +2313,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, @@ -1579,57 +2343,170 @@ futex :: proc { // TODO(flysand): lookup_dcookie -// TODO(flysand): epoll_create - -// TODO(flysand): epoll_ctl_old - -// TODO(flysand): epoll_wait_old +/* + Open an epoll file descriptor. + + The `size` argument is ignored but must be greater than zero. + + Available since Linux 2.6. +*/ +epoll_create :: proc(size: i32 = 1) -> (Fd, Errno) { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { + ret := syscall(SYS_epoll_create, i32(1)) + return errno_unwrap(ret, Fd) + } else { + ret := syscall(SYS_epoll_create1, i32(0)) + return errno_unwrap(ret, Fd) + } +} // TODO(flysand): remap_file_pages -/// Set the address of the futex that's gonna be waken when -/// current thread terminates -/// Available since Linux 2.6 +/* + Set the address of the futex that's gonna be waken when. + current thread terminates. + Available since Linux 2.6. +*/ set_tid_address :: proc "contextless" (tidptr: ^u32) { syscall(SYS_set_tid_address, tidptr) } -// TODO(flysand): restart_syscall - // TODO(flysand): semtimedop // TODO(flysand): fadvise64 -// TODO(flysand): timer_create +/* + Create POSIX per-process timer. + Available since Linux 2.6. +*/ +timer_create :: proc "contextless" (clock_id: Clock_Id, sigevent: ^Sig_Event, timer: ^Timer) -> (Errno) { + ret := syscall(SYS_timer_create, clock_id, sigevent, timer) + return Errno(-ret) +} -// TODO(flysand): timer_settime +/* + Get the state of the POSIX per-process timer. + Available since Linux 2.6. +*/ +timer_gettime :: proc "contextless" (timer: Timer, curr_value: ^ITimer_Spec) -> (Errno) { + ret := syscall(SYS_timer_gettime, timer, curr_value) + return Errno(-ret) +} -// TODO(flysand): timer_gettime +/* + Arm/disarm the state of the POSIX per-process timer. + Available since Linux 2.6. +*/ +timer_settime :: proc "contextless" (timer: Timer, flags: ITimer_Flags, #no_alias new_value, old_value: ^ITimer_Spec) -> (Errno) { + ret := syscall(SYS_timer_settime, timer, transmute(u32) flags, new_value, old_value) + return Errno(-ret) +} -// TODO(flysand): timer_getoverrun +/* + Get overrun count of the POSIX per-process timer. + Available since Linux 2.6. +*/ +timer_getoverrun :: proc "contextless" (timer: Timer) -> (int, Errno) { + ret := syscall(SYS_timer_getoverrun, timer) + return errno_unwrap(ret, int) +} -// TODO(flysand): timer_delete +/* + Delete a POSIX per-process timer. + Available since Linux 2.6. +*/ +timer_delete :: proc "contextless" (timer: Timer) -> (Errno) { + ret := syscall(SYS_timer_delete, timer) + return Errno(-ret) +} -// TODO(flysand): clock_settime +/* + Set the time of the specified clock. + Available since Linux 2.6. +*/ +clock_settime :: proc "contextless" (clock: Clock_Id, ts: ^Time_Spec) -> (Errno) { + ret := syscall(SYS_clock_settime, clock, ts) + return Errno(-ret) +} -// TODO(flysand): clock_gettime +/* + Retrieve the time of the specified clock. + Available since Linux 2.6. +*/ +clock_gettime :: proc "contextless" (clock: Clock_Id) -> (ts: Time_Spec, err: Errno) { + ret := syscall(SYS_clock_gettime, clock, &ts) + err = Errno(-ret) + return +} -// TODO(flysand): clock_getres -// TODO(flysand): clock_nanosleep +/* + Finds the resolution of the specified clock. + Available since Linux 2.6. +*/ +clock_getres :: proc "contextless" (clock: Clock_Id) -> (res: Time_Spec, err: Errno) { + ret := syscall(SYS_clock_getres, clock, &res) + err = Errno(-ret) + return +} -/// Exit the thread group -/// Available since Linux 2.6 +/* + Sleep for an interval specified with nanosecond precision. + Available since Linux 2.6. +*/ +clock_nanosleep :: proc "contextless" (clock: Clock_Id, flags: ITimer_Flags, request: ^Time_Spec, remain: ^Time_Spec) -> (Errno) { + ret := syscall(SYS_clock_nanosleep, clock, transmute(u32) flags, request, remain) + return Errno(-ret) +} + +/* + Exit the thread group. + Available since Linux 2.6. +*/ exit_group :: proc "contextless" (code: i32) -> ! { syscall(SYS_exit_group, code) unreachable() } -// TODO(flysand): epoll_wait +/* + Wait for an I/O event on an epoll file descriptor. + + `timeout` is specified in milliseconds. + + Available since Linux 2.6. +*/ +epoll_wait :: proc(epfd: Fd, events: [^]EPoll_Event, count: i32, timeout: i32) -> (i32, Errno) { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { + ret := syscall(SYS_epoll_wait, epfd, events, count, timeout) + return errno_unwrap(ret, i32) + } else { + // Convert milliseconds to nanosecond timespec + timeout_ns := Time_Spec { + time_sec = uint(timeout * 1000), + time_nsec = 0, + } + ret := syscall(SYS_epoll_pwait, epfd, events, count, &timeout_ns, rawptr(nil)) + return errno_unwrap(ret, i32) + } +} -// TODO(flysand): epoll_ctl +/* + Control interface for an epoll file descriptor. + Available since Linux 2.6. +*/ +epoll_ctl :: proc(epfd: Fd, op: EPoll_Ctl_Opcode, fd: Fd, event: ^EPoll_Event) -> (Errno) { + ret := syscall(SYS_epoll_ctl, epfd, op, fd, event) + return Errno(-ret) +} -// TODO(flysand): tgkill +/* + Send a signal to a specific thread in a thread group. + Available since Linux 2.6. +*/ +tgkill :: proc "contextless" (tgid, tid: Pid, sig: Signal) -> (Errno) { + ret := syscall(SYS_tgkill, tgid, tid, sig) + return Errno(-ret) +} // TODO(flysand): utimes @@ -1655,11 +2532,12 @@ exit_group :: proc "contextless" (code: i32) -> ! { // TODO(flysand): kexec_load - -/// 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) +/* + 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, rusage: ^RUsage) -> (Errno) { + ret := syscall(SYS_waitid, id_type, id, sig_info, transmute(i32) options, rusage) return Errno(-ret) } @@ -1673,46 +2551,73 @@ waitid :: proc "contextless" (id_type: Id_Type, id: Id, sig_info: ^Sig_Info, opt // TODO(flysand): ioprio_get -// TODO(flysand): inotify_init +inotify_init :: proc "contextless" () -> (Fd, Errno) { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { + ret := syscall(SYS_inotify_init1, 0) + return errno_unwrap(ret, Fd) + } else { + ret := syscall(SYS_inotify_init) + return errno_unwrap(ret, Fd) + } +} -// TODO(flysand): inotify_add_watch - -// TODO(flysand): inotify_rm_watch - -// TODO(flysand): migrate_pages - -/// Open file at the specified file descriptor -/// 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) +inotify_init1 :: proc "contextless" (flags: Inotify_Init_Flags) -> (Fd, Errno) { + ret := syscall(SYS_inotify_init1, transmute(i32)flags) return errno_unwrap(ret, Fd) } -/// Create a directory relative to specified dirfd -/// Available since Linux 2.6.16 +inotify_add_watch :: proc "contextless" (fd: Fd, pathname: cstring, mask: Inotify_Event_Mask) -> (Wd, Errno) { + ret := syscall(SYS_inotify_add_watch, fd, transmute(uintptr) pathname, transmute(u32) mask) + return errno_unwrap(ret, Wd) +} + +inotify_rm_watch :: proc "contextless" (fd: Fd, wd: Wd) -> (Errno) { + ret := syscall(SYS_inotify_rm_watch, fd, wd) + return Errno(-ret) +} + +// TODO(flysand): migrate_pages + +/* + Open file at the specified file descriptor. + 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, transmute(uintptr) name, transmute(u32) flags, transmute(u32) mode) + return errno_unwrap(ret, Fd) +} + +/* + Create a directory relative to specified dirfd. + Available since Linux 2.6.16. +*/ mkdirat :: proc "contextless" (dirfd: Fd, name: cstring, mode: Mode) -> (Errno) { ret := syscall(SYS_mkdirat, dirfd, cast(rawptr) name, transmute(u32) mode) return Errno(-ret) } -/// Create a special or ordinary file wrt given directory specified by dirfd -/// Available since Linux 2.6.16 +/* + Create a special or ordinary file wrt given directory specified by dirfd. + Available since Linux 2.6.16. +*/ mknodat :: proc "contextless" (dirfd: Fd, name: cstring, mode: Mode, dev: Dev) -> (Errno) { - ret := syscall(SYS_mknodat, dirfd, cast(rawptr) name, transmute(u32) mode, dev) + ret := syscall(SYS_mknodat, dirfd, cast(rawptr) name, transmute(u32) mode, cast(uint) dev) return Errno(-ret) } -/// Change the ownership of the file specified relative to directory -/// Available since Linux 2.6.16 +/* + Change the ownership of the file specified relative to directory. + Available since Linux 2.6.16. +*/ fchownat :: proc "contextless" (dirfd: Fd, name: cstring, uid: Uid, gid: Gid) -> (Errno) { ret := syscall(SYS_fchownat, dirfd, cast(rawptr) name, uid, gid) return Errno(-ret) } -// TODO(flysand): futimesat - -/// Get information about a file at a specific directory -/// Available since Linux 2.6.16 +/* + Get information about a file at a specific directory. + Available since Linux 2.6.16. +*/ fstatat :: proc "contextless" (dirfd: Fd, name: cstring, stat: ^Stat, flags: FD_Flags) -> (Errno) { when size_of(int) == 4 { ret := syscall(SYS_fstatat64, dirfd, cast(rawptr) name, stat, transmute(i32) flags) @@ -1726,67 +2631,76 @@ fstatat :: proc "contextless" (dirfd: Fd, name: cstring, stat: ^Stat, flags: FD_ } } -/// Remove a directory entry relative to a directory file descriptor -/// Available since Linux 2.6.16 +/* + Remove a directory entry relative to a directory file descriptor. + Available since Linux 2.6.16. +*/ unlinkat :: proc "contextless" (dirfd: Fd, name: cstring, flags: FD_Flags) -> (Errno) { ret := syscall(SYS_unlinkat, dirfd, cast(rawptr) name, transmute(i32) flags) return Errno(-ret) } -/// Rename the file with names relative to the specified dirfd's -/// Available since Linux 2.6.16 +/* + Rename the file with names relative to the specified dirfd's. + Available since Linux 2.6.16. +*/ renameat :: proc "contextless" (oldfd: Fd, old: cstring, newfd: Fd, new: cstring) -> (Errno) { ret := syscall(SYS_renameat, oldfd, cast(rawptr) old, newfd, cast(rawptr) new) return Errno(-ret) } -/// Creates a hard link on a file relative to specified dirfd -/// Available since Linux 2.6.16 +/* + Creates a hard link on a file relative to specified dirfd. + Available since Linux 2.6.16. +*/ linkat :: proc "contextless" (target_dirfd: Fd, oldpath: cstring, link_dirfd: Fd, link: cstring, flags: FD_Flags) -> (Errno) { ret := syscall(SYS_linkat, target_dirfd, cast(rawptr) oldpath, link_dirfd, cast(rawptr) link, transmute(i32) flags) return Errno(-ret) } -/// 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) +/* + Create a symbolic link at specified dirfd. + Available since Linux 2.6.16. +*/ +symlinkat :: proc "contextless" (target: cstring, dirfd: Fd, linkpath: cstring) -> (Errno) { + ret := syscall(SYS_symlinkat, cast(rawptr) target, dirfd, cast(rawptr) linkpath) return Errno(-ret) } -/// Read the value of a symbolic link at given dirfd -/// Available since Linux 2.6.16 +/* + Read the value of a symbolic link at given dirfd. + Available since Linux 2.6.16. +*/ readlinkat :: proc "contextless" (dirfd: Fd, name: cstring, buf: []u8) -> (int, Errno) { ret := syscall(SYS_readlinkat, dirfd, cast(rawptr) name, raw_data(buf), len(buf)) return errno_unwrap(ret, int) } -/// Change the file mode at a specified file descriptor -/// Available since Linux 2.6.16 +/* + Change the file mode at a specified file descriptor. + Available since Linux 2.6.16. +*/ fchmodat :: proc "contextless" (dirfd: Fd, name: cstring, mode: Mode, flags: FD_Flags) -> (Errno) { ret := syscall(SYS_fchmodat, cast(rawptr) name, transmute(u32) mode, transmute(i32) flags) return Errno(-ret) } -/// Checks the user permissions for a file at specified dirfd -/// Available since Linux 2.6.16 -faccessat :: proc "contextless" (dirfd: Fd, name: cstring, mode: Mode = F_OK) -> (bool, Errno) { +/* + Checks the user permissions for a file at specified dirfd. + Available since Linux 2.6.16. +*/ +faccessat :: proc "contextless" (dirfd: Fd, name: cstring, mode: Mode = F_OK) -> (Errno) { ret := syscall(SYS_faccessat, dirfd, cast(rawptr) name, transmute(u32) mode) - return errno_unwrap(ret, bool) + return Errno(-ret) } -// TODO(flysand): pselect6 - -/// 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) { - when size_of(int) == 8 { - ret := syscall(SYS_ppoll, raw_data(fds), len(fds), timeout, sigmask, size_of(Sig_Set)) - return Errno(-ret) - } else { - ret := syscall(SYS_ppoll_time64, raw_data(fds), len(fds), timeout, sigmask, size_of(Sig_Set)) - return Errno(-ret) - } +/* + 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) -> (i32, Errno) { + ret := syscall(SYS_ppoll, raw_data(fds), len(fds), timeout, sigmask, size_of(Sig_Set)) + return errno_unwrap(ret, i32) } // TODO(flysand): unshare @@ -1795,9 +2709,23 @@ ppoll :: proc "contextless" (fds: []Poll_Fd, timeout: ^Time_Spec, sigmask: ^Sig_ // TODO(flysand): get_robust_list -// TODO(flysand): splice +/* + Transfer the data between file descriptors. + Available since Linux 2.6.17. +*/ +splice :: proc "contextless" (fd_in: Fd, off_in: ^i64, fd_out: Fd, off_out: ^i64, len: uint, flags: Splice_Flags) -> (int, Errno) { + ret := syscall(SYS_splice, fd_in, off_in, fd_out, off_out, len, transmute(u32) flags) + return errno_unwrap(ret, int) +} -// TODO(flysand): tee +/* + Transfer the data between file descriptors. + Available since Linux 2.6.16. +*/ +tee :: proc "contextless" (fd_in: Fd, fd_out: Fd, len: uint, flags: Splice_Flags) -> (int, Errno) { + ret := syscall(SYS_tee, fd_in, fd_out, len, transmute(u32) flags) + return errno_unwrap(ret, int) +} // TODO(flysand): sync_file_range @@ -1805,14 +2733,25 @@ ppoll :: proc "contextless" (fds: []Poll_Fd, timeout: ^Time_Spec, sigmask: ^Sig_ // TODO(flysand): move_pages -/// Change file timestamps with nanosecond precision -/// Available since Linux 2.6.22 -utimensat :: proc "contextless" (dirfd: Fd, name: cstring, timespec: ^Time_Spec, flags: FD_Flags) -> (Errno) { - ret := syscall(SYS_utimensat, dirfd, cast(rawptr) name, timespec, transmute(i32) flags) +/* + Change file timestamps with nanosecond precision. + **utimes** must point to an array of two `Time_Spec`'s. The "utime" is the + last access time, the second is last modification time. + Available since Linux 2.6.22. +*/ +utimensat :: proc "contextless" (dirfd: Fd, name: cstring, utimes: [^]Time_Spec, flags: FD_Flags) -> (Errno) { + ret := syscall(SYS_utimensat, dirfd, cast(rawptr) name, utimes, transmute(i32) flags) return Errno(-ret) } -// TODO(flysand): epoll_pwait +/* + Wait for an I/O event on an epoll file descriptor. + Available since Linux 2.6. +*/ +epoll_pwait :: proc(epfd: Fd, events: [^]EPoll_Event, count: i32, timeout: i32, sigmask: ^Sig_Set) -> (i32, Errno) { + ret := syscall(SYS_epoll_pwait, epfd, events, count, timeout, sigmask) + return errno_unwrap(ret, i32) +} // TODO(flysand): signalfd @@ -1832,11 +2771,16 @@ utimensat :: proc "contextless" (dirfd: Fd, name: cstring, timespec: ^Time_Spec, // TODO(flysand): eventfd2 -// TODO(flysand): epoll_create1 +epoll_create1 :: proc(flags: EPoll_Flags) -> (Fd, Errno) { + ret := syscall(SYS_epoll_create1, transmute(i32) flags) + return errno_unwrap(ret, Fd) +} -/// Adjust an existing file descriptor to point to the same file as `old` -/// In addition to dup2 allows to pass O_CLOEXEC flag -/// Available since Linux 2.6.27 +/* + Adjust an existing file descriptor to point to the same file as `old`. + In addition to dup2 allows to pass O_CLOEXEC flag. + Available since Linux 2.6.27. +*/ dup3 :: proc "contextless" (old: Fd, new: Fd, flags: Open_Flags) -> (Fd, Errno) { ret := syscall(SYS_dup3, old, new, transmute(i32) flags) return errno_unwrap(ret, Fd) @@ -1848,7 +2792,6 @@ dup3 :: proc "contextless" (old: Fd, new: Fd, flags: Open_Flags) -> (Fd, Errno) // TODO(flysand): pwritev - /// Send signal information to a thread /// Available since Linux 2.2 rt_tgsigqueueinfo :: proc "contextless" (tgid: Pid, pid: Pid, sig: Signal, si: ^Sig_Info) -> (Errno) { @@ -1856,14 +2799,23 @@ rt_tgsigqueueinfo :: proc "contextless" (tgid: Pid, pid: Pid, sig: Signal, si: ^ return Errno(-ret) } -/// Set up performance monitoring -/// Available since Linux 2.6.31 +/* + Set up performance monitoring. + Available since Linux 2.6.31. +*/ perf_event_open :: proc "contextless" (attr: ^Perf_Event_Attr, pid: Pid, cpu: int, group_fd: Fd, flags: Perf_Flags = {}) -> (Fd, Errno) { ret := syscall(SYS_perf_event_open, attr, pid, cpu, group_fd, transmute(uint) flags) return errno_unwrap(ret, Fd) } -// TODO(flysand): recvmmsg +/* + Receive multiple messages from a socket. + Available since Linux 2.6.33. +*/ +recvmmsg :: proc "contextless" (sock: Fd, msg_vec: []MMsg_Hdr, flags: Socket_Msg) -> (int, Errno) { + ret := syscall(SYS_recvmmsg, sock, raw_data(msg_vec), len(msg_vec), transmute(i32) flags) + return errno_unwrap(ret, int) +} // TODO(flysand): fanotify_init @@ -1879,7 +2831,14 @@ perf_event_open :: proc "contextless" (attr: ^Perf_Event_Attr, pid: Pid, cpu: in // TODO(flysand): syncfs -// TODO(flysand): sendmmsg +/* + Send multiple messages on a socket. + Available since Linux 3.0. +*/ +sendmmsg :: proc "contextless" (sock: Fd, msg_vec: []MMsg_Hdr, flags: Socket_Msg) -> (int, Errno) { + ret := syscall(SYS_sendmmsg, sock, raw_data(msg_vec), len(msg_vec), transmute(i32) flags) + return errno_unwrap(ret, int) +} // TODO(flysand): setns @@ -1897,8 +2856,10 @@ perf_event_open :: proc "contextless" (attr: ^Perf_Event_Attr, pid: Pid, cpu: in // TODO(flysand): sched_getattr -/// Rename the file with names relative to the specified dirfd's with other options -/// Available since Linux 3.15 +/* + Rename the file with names relative to the specified dirfd's with other options. + Available since Linux 3.15. +*/ renameat2 :: proc "contextless" (oldfd: Fd, old: cstring, newfd: Fd, new: cstring, flags: Rename_Flags) -> (Errno) { ret := syscall(SYS_renameat2, oldfd, cast(rawptr) old, newfd, cast(rawptr) new, transmute(u32) flags) return Errno(-ret) @@ -1917,7 +2878,14 @@ getrandom :: proc "contextless" (buf: []u8, flags: Get_Random_Flags) -> (int, Er // TODO(flysand): bpf -// TODO(flysand): execveat +/* + 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) { + ret := syscall(SYS_execveat, dirfd, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, transmute(i32) flags) + return Errno(-ret) +} // TODO(flysand): userfaultfd @@ -1937,14 +2905,16 @@ getrandom :: proc "contextless" (buf: []u8, flags: Get_Random_Flags) -> (int, Er // TODO(flysand): pkey_free -/// Query extended information about the file -/// -/// The file can be specified as: -/// absolute pathname: `dir` parameter is ignored -/// relatvie pathname: `dir` parameter specifies the base directory's fd -/// file descriptor: `AT_EMPTY_PATH` is passed in flags, pathname is empty, `dir` specifies the file descriptor -/// -/// Available since Linux 4.11 +/* + Query extended information about the file + + The file can be specified as: + - absolute pathname: `dir` parameter is ignored + - relatvie pathname: `dir` parameter specifies the base directory's fd + - file descriptor: `AT_EMPTY_PATH` is passed in flags, pathname is empty, `dir` specifies the file descriptor + + Available since Linux 4.11 +*/ statx :: proc "contextless" (dir: Fd, pathname: cstring, flags: FD_Flags, mask: Statx_Mask, statx: ^Statx) -> (Errno) { ret := syscall(SYS_statx, dir, transmute(uintptr) pathname, transmute(i32) flags, transmute(u32) mask, statx) return Errno(-ret) @@ -1974,10 +2944,12 @@ statx :: proc "contextless" (dir: Fd, pathname: cstring, flags: FD_Flags, mask: // TODO(flysand): fspick -/// Creates a new PID file descriptor -/// The process identified by `pid` must be a pid group leader -/// The returned `pidfd` has `CLOEXEC` semantics -/// Available since Linux 5.3 +/* + Creates a new PID file descriptor. + The process identified by `pid` must be a pid group leader. + The returned `pidfd` has `CLOEXEC` semantics. + Available since Linux 5.3. +*/ pidfd_open :: proc "contextless" (pid: Pid, flags: Pid_FD_Flags) -> (Pid_FD, Errno) { ret := syscall(SYS_pidfd_open, pid, transmute(i32) flags) return errno_unwrap(ret, Pid_FD) @@ -1985,9 +2957,11 @@ pidfd_open :: proc "contextless" (pid: Pid, flags: Pid_FD_Flags) -> (Pid_FD, Err // TODO(flysand): clone3 (probably not this PR) -/// Close the range of files as an atomic operation -/// The range of file descriptors is inclusive, and may contain invalid file descriptors -/// Available since Linux 5.9 +/* + Close the range of files as an atomic operation. + The range of file descriptors is inclusive, and may contain invalid file descriptors. + Available since Linux 5.9. +*/ close_range :: proc "contextless" (lo: Fd, hi: Fd, flags: Close_Range_Flags) -> (Errno) { ret := syscall(SYS_close_range, lo, hi, transmute(u32) flags) return Errno(-ret) @@ -1995,25 +2969,36 @@ close_range :: proc "contextless" (lo: Fd, hi: Fd, flags: Close_Range_Flags) -> // TODO(flysand): openat2 -/// Get a file descriptor from another process -/// `fd` refers to a file descriptor number to get -/// `flags` must be zero -/// Available since Linux 5.3 +/* + Get a file descriptor from another process. + - `fd` refers to a file descriptor number to get. + - `flags` must be zero. + Available since Linux 5.3. +*/ pidfd_getfd :: proc "contextless" (pidfd: Pid_FD, fd: Fd, flags: i32 = 0) -> (Fd, Errno) { ret := syscall(SYS_pidfd_getfd, pidfd, fd, flags) return errno_unwrap(ret, Fd) } -/// Checks the user permissions for a file at specified dirfd (with flags) -/// Available since Linux 5.8 -faccessat2 :: proc "contextless" (dirfd: Fd, name: cstring, mode: Mode = F_OK, flags: FD_Flags = FD_Flags{}) -> (bool, Errno) { +/* + Checks the user permissions for a file at specified dirfd (with flags). + Available since Linux 5.8. +*/ +faccessat2 :: proc "contextless" (dirfd: Fd, name: cstring, mode: Mode = F_OK, flags: FD_Flags = FD_Flags{}) -> (Errno) { ret := syscall(SYS_faccessat2, dirfd, cast(rawptr) name, transmute(u32) mode, transmute(i32) flags) - return errno_unwrap(ret, bool) + return Errno(-ret) } // TODO(flysand): process_madvise -// TODO(flysand): epoll_pwait2 +/* + Wait for an I/O event on an epoll file descriptor. + Available since Linux 2.6. +*/ +epoll_pwait2 :: proc(epfd: Fd, events: [^]EPoll_Event, count: i32, timeout: ^Time_Spec, sigmask: ^Sig_Set) -> (i32, Errno) { + ret := syscall(SYS_epoll_pwait2, epfd, events, count, timeout, sigmask) + return errno_unwrap(ret, i32) +} // TODO(flysand): mount_setattr @@ -2038,3 +3023,18 @@ faccessat2 :: proc "contextless" (dirfd: Fd, name: cstring, mode: Mode = F_OK, f // TODO(flysand): fchmodat2 // TODO(flysand): map_shadow_stack + +when ODIN_ARCH == .riscv64 { + /* + Probe for RISC-V Hardware Support. + Available since Linux 6.4. + + TODO: cpu_set_t + + See: https://docs.kernel.org/arch/riscv/hwprobe.html + */ + riscv_hwprobe :: proc "contextless" (pairs: [^]RISCV_HWProbe, pair_count: uint, cpu_count: uint, cpus: rawptr /* cpu_set_t */, flags: RISCV_HWProbe_Flags) -> Errno { + ret := syscall(SYS_riscv_hwprobe, pairs, pair_count, cpu_count, cpus, transmute(u32)flags) + return Errno(-ret) + } +} diff --git a/core/sys/linux/syscall_amd64.odin b/core/sys/linux/syscall_amd64.odin index ee4e16280..31c8ed61c 100644 --- a/core/sys/linux/syscall_amd64.odin +++ b/core/sys/linux/syscall_amd64.odin @@ -1,4 +1,4 @@ -//+build amd64 +#+build amd64 package linux // AMD64 uses the new way to define syscalls, i.e. one that diff --git a/core/sys/linux/syscall_arm32.odin b/core/sys/linux/syscall_arm32.odin index 74640a1a3..731ce36a5 100644 --- a/core/sys/linux/syscall_arm32.odin +++ b/core/sys/linux/syscall_arm32.odin @@ -1,4 +1,4 @@ -//+build arm32 +#+build arm32 package linux // This file was taken and transformed from diff --git a/core/sys/linux/syscall_arm64.odin b/core/sys/linux/syscall_arm64.odin index 61b5a31b7..da8eb45da 100644 --- a/core/sys/linux/syscall_arm64.odin +++ b/core/sys/linux/syscall_arm64.odin @@ -1,4 +1,4 @@ -//+build arm64 +#+build arm64 package linux // Syscalls for arm64 are defined using the new way, i.e. differently from diff --git a/core/sys/linux/syscall_i386.odin b/core/sys/linux/syscall_i386.odin index 4609fc99c..affdff02c 100644 --- a/core/sys/linux/syscall_i386.odin +++ b/core/sys/linux/syscall_i386.odin @@ -1,4 +1,4 @@ -//+build i386 +#+build i386 package linux // The numbers are taken from diff --git a/core/sys/linux/syscall_riscv64.odin b/core/sys/linux/syscall_riscv64.odin new file mode 100644 index 000000000..17845c5ed --- /dev/null +++ b/core/sys/linux/syscall_riscv64.odin @@ -0,0 +1,335 @@ +#+build riscv64 +package linux + +// https://github.com/riscv-collab/riscv-gnu-toolchain/blob/master/linux-headers/include/asm-generic/unistd.h + +SYS_io_setup :: uintptr(0) +SYS_io_destroy :: uintptr(1) +SYS_io_submit :: uintptr(2) +SYS_io_cancel :: uintptr(3) +SYS_io_getevents :: uintptr(4) +SYS_setxattr :: uintptr(5) +SYS_lsetxattr :: uintptr(6) +SYS_fsetxattr :: uintptr(7) +SYS_getxattr :: uintptr(8) +SYS_lgetxattr :: uintptr(9) +SYS_fgetxattr :: uintptr(10) +SYS_listxattr :: uintptr(11) +SYS_llistxattr :: uintptr(12) +SYS_flistxattr :: uintptr(13) +SYS_removexattr :: uintptr(14) +SYS_lremovexattr :: uintptr(15) +SYS_fremovexattr :: uintptr(16) +SYS_getcwd :: uintptr(17) +SYS_lookup_dcookie :: uintptr(18) +SYS_eventfd2 :: uintptr(19) +SYS_epoll_create1 :: uintptr(20) +SYS_epoll_ctl :: uintptr(21) +SYS_epoll_pwait :: uintptr(22) +SYS_dup :: uintptr(23) +SYS_dup3 :: uintptr(24) +SYS_fcntl :: uintptr(25) +SYS_inotify_init1 :: uintptr(26) +SYS_inotify_add_watch :: uintptr(27) +SYS_inotify_rm_watch :: uintptr(28) +SYS_ioctl :: uintptr(29) +SYS_ioprio_set :: uintptr(30) +SYS_ioprio_get :: uintptr(31) +SYS_flock :: uintptr(32) +SYS_mknodat :: uintptr(33) +SYS_mkdirat :: uintptr(34) +SYS_unlinkat :: uintptr(35) +SYS_symlinkat :: uintptr(36) +SYS_linkat :: uintptr(37) +SYS_renameat :: uintptr(38) +SYS_umount2 :: uintptr(39) +SYS_mount :: uintptr(40) +SYS_pivot_root :: uintptr(41) +SYS_nfsservctl :: uintptr(42) +SYS_statfs :: uintptr(43) +SYS_fstatfs :: uintptr(44) +SYS_truncate :: uintptr(45) +SYS_ftruncate :: uintptr(46) +SYS_fallocate :: uintptr(47) +SYS_faccessat :: uintptr(48) +SYS_chdir :: uintptr(49) +SYS_fchdir :: uintptr(50) +SYS_chroot :: uintptr(51) +SYS_fchmod :: uintptr(52) +SYS_fchmodat :: uintptr(53) +SYS_fchownat :: uintptr(54) +SYS_fchown :: uintptr(55) +SYS_openat :: uintptr(56) +SYS_close :: uintptr(57) +SYS_vhangup :: uintptr(58) +SYS_pipe2 :: uintptr(59) +SYS_quotactl :: uintptr(60) +SYS_getdents64 :: uintptr(61) +SYS_lseek :: uintptr(62) +SYS_read :: uintptr(63) +SYS_write :: uintptr(64) +SYS_readv :: uintptr(65) +SYS_writev :: uintptr(66) +SYS_pread64 :: uintptr(67) +SYS_pwrite64 :: uintptr(68) +SYS_preadv :: uintptr(69) +SYS_pwritev :: uintptr(70) +SYS_sendfile :: uintptr(71) +SYS_pselect6 :: uintptr(72) +SYS_ppoll :: uintptr(73) +SYS_signalfd4 :: uintptr(74) +SYS_vmsplice :: uintptr(75) +SYS_splice :: uintptr(76) +SYS_tee :: uintptr(77) +SYS_readlinkat :: uintptr(78) +SYS_fstatat :: uintptr(79) +SYS_fstat :: uintptr(80) +SYS_sync :: uintptr(81) +SYS_fsync :: uintptr(82) +SYS_fdatasync :: uintptr(83) +SYS_sync_file_range2 :: uintptr(84) +SYS_sync_file_range :: uintptr(84) +SYS_timerfd_create :: uintptr(85) +SYS_timerfd_settime :: uintptr(86) +SYS_timerfd_gettime :: uintptr(87) +SYS_utimensat :: uintptr(88) +SYS_acct :: uintptr(89) +SYS_capget :: uintptr(90) +SYS_capset :: uintptr(91) +SYS_personality :: uintptr(92) +SYS_exit :: uintptr(93) +SYS_exit_group :: uintptr(94) +SYS_waitid :: uintptr(95) +SYS_set_tid_address :: uintptr(96) +SYS_unshare :: uintptr(97) +SYS_futex :: uintptr(98) +SYS_set_robust_list :: uintptr(99) +SYS_get_robust_list :: uintptr(100) +SYS_nanosleep :: uintptr(101) +SYS_getitimer :: uintptr(102) +SYS_setitimer :: uintptr(103) +SYS_kexec_load :: uintptr(104) +SYS_init_module :: uintptr(105) +SYS_delete_module :: uintptr(106) +SYS_timer_create :: uintptr(107) +SYS_timer_gettime :: uintptr(108) +SYS_timer_getoverrun :: uintptr(109) +SYS_timer_settime :: uintptr(110) +SYS_timer_delete :: uintptr(111) +SYS_clock_settime :: uintptr(112) +SYS_clock_gettime :: uintptr(113) +SYS_clock_getres :: uintptr(114) +SYS_clock_nanosleep :: uintptr(115) +SYS_syslog :: uintptr(116) +SYS_ptrace :: uintptr(117) +SYS_sched_setparam :: uintptr(118) +SYS_sched_setscheduler :: uintptr(119) +SYS_sched_getscheduler :: uintptr(120) +SYS_sched_getparam :: uintptr(121) +SYS_sched_setaffinity :: uintptr(122) +SYS_sched_getaffinity :: uintptr(123) +SYS_sched_yield :: uintptr(124) +SYS_sched_get_priority_max :: uintptr(125) +SYS_sched_get_priority_min :: uintptr(126) +SYS_sched_rr_get_interval :: uintptr(127) +SYS_restart_syscall :: uintptr(128) +SYS_kill :: uintptr(129) +SYS_tkill :: uintptr(130) +SYS_tgkill :: uintptr(131) +SYS_sigaltstack :: uintptr(132) +SYS_rt_sigsuspend :: uintptr(133) +SYS_rt_sigaction :: uintptr(134) +SYS_rt_sigprocmask :: uintptr(135) +SYS_rt_sigpending :: uintptr(136) +SYS_rt_sigtimedwait :: uintptr(137) +SYS_rt_sigqueueinfo :: uintptr(138) +SYS_rt_sigreturn :: uintptr(139) +SYS_setpriority :: uintptr(140) +SYS_getpriority :: uintptr(141) +SYS_reboot :: uintptr(142) +SYS_setregid :: uintptr(143) +SYS_setgid :: uintptr(144) +SYS_setreuid :: uintptr(145) +SYS_setuid :: uintptr(146) +SYS_setresuid :: uintptr(147) +SYS_getresuid :: uintptr(148) +SYS_setresgid :: uintptr(149) +SYS_getresgid :: uintptr(150) +SYS_setfsuid :: uintptr(151) +SYS_setfsgid :: uintptr(152) +SYS_times :: uintptr(153) +SYS_setpgid :: uintptr(154) +SYS_getpgid :: uintptr(155) +SYS_getsid :: uintptr(156) +SYS_setsid :: uintptr(157) +SYS_getgroups :: uintptr(158) +SYS_setgroups :: uintptr(159) +SYS_uname :: uintptr(160) +SYS_sethostname :: uintptr(161) +SYS_setdomainname :: uintptr(162) +SYS_getrlimit :: uintptr(163) +SYS_setrlimit :: uintptr(164) +SYS_getrusage :: uintptr(165) +SYS_umask :: uintptr(166) +SYS_prctl :: uintptr(167) +SYS_getcpu :: uintptr(168) +SYS_gettimeofday :: uintptr(169) +SYS_settimeofday :: uintptr(170) +SYS_adjtimex :: uintptr(171) +SYS_getpid :: uintptr(172) +SYS_getppid :: uintptr(173) +SYS_getuid :: uintptr(174) +SYS_geteuid :: uintptr(175) +SYS_getgid :: uintptr(176) +SYS_getegid :: uintptr(177) +SYS_gettid :: uintptr(178) +SYS_sysinfo :: uintptr(179) +SYS_mq_open :: uintptr(180) +SYS_mq_unlink :: uintptr(181) +SYS_mq_timedsend :: uintptr(182) +SYS_mq_timedreceive :: uintptr(183) +SYS_mq_notify :: uintptr(184) +SYS_mq_getsetattr :: uintptr(185) +SYS_msgget :: uintptr(186) +SYS_msgctl :: uintptr(187) +SYS_msgrcv :: uintptr(188) +SYS_msgsnd :: uintptr(189) +SYS_semget :: uintptr(190) +SYS_semctl :: uintptr(191) +SYS_semtimedop :: uintptr(192) +SYS_semop :: uintptr(193) +SYS_shmget :: uintptr(194) +SYS_shmctl :: uintptr(195) +SYS_shmat :: uintptr(196) +SYS_shmdt :: uintptr(197) +SYS_socket :: uintptr(198) +SYS_socketpair :: uintptr(199) +SYS_bind :: uintptr(200) +SYS_listen :: uintptr(201) +SYS_accept :: uintptr(202) +SYS_connect :: uintptr(203) +SYS_getsockname :: uintptr(204) +SYS_getpeername :: uintptr(205) +SYS_sendto :: uintptr(206) +SYS_recvfrom :: uintptr(207) +SYS_setsockopt :: uintptr(208) +SYS_getsockopt :: uintptr(209) +SYS_shutdown :: uintptr(210) +SYS_sendmsg :: uintptr(211) +SYS_recvmsg :: uintptr(212) +SYS_readahead :: uintptr(213) +SYS_brk :: uintptr(214) +SYS_munmap :: uintptr(215) +SYS_mremap :: uintptr(216) +SYS_add_key :: uintptr(217) +SYS_request_key :: uintptr(218) +SYS_keyctl :: uintptr(219) +SYS_clone :: uintptr(220) +SYS_execve :: uintptr(221) +SYS_mmap :: uintptr(222) +SYS_fadvise64 :: uintptr(223) +SYS_swapon :: uintptr(224) +SYS_swapoff :: uintptr(225) +SYS_mprotect :: uintptr(226) +SYS_msync :: uintptr(227) +SYS_mlock :: uintptr(228) +SYS_munlock :: uintptr(229) +SYS_mlockall :: uintptr(230) +SYS_munlockall :: uintptr(231) +SYS_mincore :: uintptr(232) +SYS_madvise :: uintptr(233) +SYS_remap_file_pages :: uintptr(234) +SYS_mbind :: uintptr(235) +SYS_get_mempolicy :: uintptr(236) +SYS_set_mempolicy :: uintptr(237) +SYS_migrate_pages :: uintptr(238) +SYS_move_pages :: uintptr(239) +SYS_rt_tgsigqueueinfo :: uintptr(240) +SYS_perf_event_open :: uintptr(241) +SYS_accept4 :: uintptr(242) +SYS_recvmmsg :: uintptr(243) +SYS_riscv_hwprobe :: uintptr(258) +SYS_wait4 :: uintptr(260) +SYS_prlimit64 :: uintptr(261) +SYS_fanotify_init :: uintptr(262) +SYS_fanotify_mark :: uintptr(263) +SYS_name_to_handle_at :: uintptr(264) +SYS_open_by_handle_at :: uintptr(265) +SYS_clock_adjtime :: uintptr(266) +SYS_syncfs :: uintptr(267) +SYS_setns :: uintptr(268) +SYS_sendmmsg :: uintptr(269) +SYS_process_vm_readv :: uintptr(270) +SYS_process_vm_writev :: uintptr(271) +SYS_kcmp :: uintptr(272) +SYS_finit_module :: uintptr(273) +SYS_sched_setattr :: uintptr(274) +SYS_sched_getattr :: uintptr(275) +SYS_renameat2 :: uintptr(276) +SYS_seccomp :: uintptr(277) +SYS_getrandom :: uintptr(278) +SYS_memfd_create :: uintptr(279) +SYS_bpf :: uintptr(280) +SYS_execveat :: uintptr(281) +SYS_userfaultfd :: uintptr(282) +SYS_membarrier :: uintptr(283) +SYS_mlock2 :: uintptr(284) +SYS_copy_file_range :: uintptr(285) +SYS_preadv2 :: uintptr(286) +SYS_pwritev2 :: uintptr(287) +SYS_pkey_mprotect :: uintptr(288) +SYS_pkey_alloc :: uintptr(289) +SYS_pkey_free :: uintptr(290) +SYS_statx :: uintptr(291) +SYS_io_pgetevents :: uintptr(292) +SYS_rseq :: uintptr(293) +SYS_kexec_file_load :: uintptr(294) +SYS_clock_gettime64 :: uintptr(403) +SYS_clock_settime64 :: uintptr(404) +SYS_clock_adjtime64 :: uintptr(405) +SYS_clock_getres_time64 :: uintptr(406) +SYS_clock_nanosleep_time64 :: uintptr(407) +SYS_timer_gettime64 :: uintptr(408) +SYS_timer_settime64 :: uintptr(409) +SYS_timerfd_gettime64 :: uintptr(410) +SYS_timerfd_settime64 :: uintptr(411) +SYS_utimensat_time64 :: uintptr(412) +SYS_pselect6_time64 :: uintptr(413) +SYS_ppoll_time64 :: uintptr(414) +SYS_io_pgetevents_time64 :: uintptr(416) +SYS_recvmmsg_time64 :: uintptr(417) +SYS_mq_timedsend_time64 :: uintptr(418) +SYS_mq_timedreceive_time64 :: uintptr(419) +SYS_semtimedop_time64 :: uintptr(420) +SYS_rt_sigtimedwait_time64 :: uintptr(421) +SYS_futex_time64 :: uintptr(422) +SYS_sched_rr_get_interval_time64 :: uintptr(423) +SYS_pidfd_send_signal :: uintptr(424) +SYS_io_uring_setup :: uintptr(425) +SYS_io_uring_enter :: uintptr(426) +SYS_io_uring_register :: uintptr(427) +SYS_open_tree :: uintptr(428) +SYS_move_mount :: uintptr(429) +SYS_fsopen :: uintptr(430) +SYS_fsconfig :: uintptr(431) +SYS_fsmount :: uintptr(432) +SYS_fspick :: uintptr(433) +SYS_pidfd_open :: uintptr(434) +SYS_clone3 :: uintptr(435) +SYS_close_range :: uintptr(436) +SYS_openat2 :: uintptr(437) +SYS_pidfd_getfd :: uintptr(438) +SYS_faccessat2 :: uintptr(439) +SYS_process_madvise :: uintptr(440) +SYS_epoll_pwait2 :: uintptr(441) +SYS_mount_setattr :: uintptr(442) +SYS_quotactl_fd :: uintptr(443) +SYS_landlock_create_ruleset :: uintptr(444) +SYS_landlock_add_rule :: uintptr(445) +SYS_landlock_restrict_self :: uintptr(446) +SYS_memfd_secret :: uintptr(447) +SYS_process_mrelease :: uintptr(448) +SYS_futex_waitv :: uintptr(449) +SYS_set_mempolicy_home_node :: uintptr(450) +SYS_cachestat :: uintptr(451) +SYS_fchmodat2 :: uintptr(452) diff --git a/core/sys/linux/types.odin b/core/sys/linux/types.odin index 8789ca2d1..dcc72f72b 100644 --- a/core/sys/linux/types.odin +++ b/core/sys/linux/types.odin @@ -1,56 +1,108 @@ -//+build linux package linux -/// Represents storage device handle -Dev :: distinct int +/* + Type for storage device handle. +*/ +Dev :: distinct u64 -/// Represents 32-bit user id +/* + Type for 32-bit User IDs. +*/ Uid :: distinct u32 -/// Represents 32-bit group id +/* + Type for 32-bit Group IDs. +*/ Gid :: distinct u32 -/// Process id's -Pid :: distinct int +/* + Type for Process IDs, Thread IDs, Thread group ID. +*/ +Pid :: distinct i32 -/// Represents pid, pifd, pgid values in general +/* + Type for any of: pid, pidfd, pgid. +*/ Id :: distinct uint -/// Represents a file descriptor +/* + Represents a file descriptor. +*/ Fd :: distinct i32 -/// Represents a PID file descriptor +/* + Represents a watch descriptor. +*/ +Wd :: distinct i32 + +/* + Type for PID file descriptors. +*/ Pid_FD :: distinct i32 -/// Represents 64-bit inode number for files -/// Used pretty much only in struct Stat64 for 32-bit platforms +/* + Type for 64-bit inode number for files. + Used pretty much only in struct Stat64 for 32-bit platforms. +*/ Inode :: distinct u64 -/// Represents time with nanosecond precision +/* + Shared memory identifiers used by shmget(2) and other calls. +*/ +Key :: distinct i32 + +/* + Represents timer IDs. +*/ +Timer :: distinct i32 + +/* + Represents time with nanosecond precision. +*/ Time_Spec :: struct { time_sec: uint, time_nsec: uint, } -/// Represents time with millisecond precision +/* + Represents time with millisecond precision. +*/ Time_Val :: struct { seconds: int, microseconds: int, } -/// open.2 flags +/* + Access and modification times for files +*/ +UTim_Buf :: struct { + actime: uint, + modtime: uint, +} + +/* + Flags for open(2). +*/ Open_Flags :: bit_set[Open_Flags_Bits; u32] -/// Flags for the file descriptor to be passed in some syscalls +/* + Flags for the file descriptors. +*/ 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 -/// sys.Mode{.S_IXOTH, .S_IROTH} | sys.S_IRWXU | sys.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. +/* + 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. +*/ Mode :: bit_set[Mode_Bits; u32] when ODIN_ARCH == .amd64 { @@ -72,7 +124,7 @@ when ODIN_ARCH == .amd64 { ctime: Time_Spec, _: [3]uint, } -} else when ODIN_ARCH == .arm64 { +} else when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { _Arch_Stat :: struct { dev: Dev, ino: Inode, @@ -89,7 +141,7 @@ when ODIN_ARCH == .amd64 { atime: Time_Spec, mtime: Time_Spec, ctime: Time_Spec, - _: [3]uint, + _: [2]u32, } } else { _Arch_Stat :: struct { @@ -101,39 +153,49 @@ when ODIN_ARCH == .amd64 { uid: Uid, gid: Gid, rdev: Dev, + _: [4]u8, 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, } } -/// Represents the file state. -/// Mirrors struct stat in glibc/linux kernel. -/// If you're on 32-bit platform, consider using Stat64 instead +/* + Represents the file state. + If you're on 32-bit platform, consider using Stat64 instead. +*/ Stat :: struct { using _impl_stat: _Arch_Stat, } -/// Timestamp type used for Statx struct +/* + Timestamp type used for Statx struct +*/ Statx_Timestamp :: struct { sec: i64, nsec: u32, _: i32, } -/// Query params/results for `statx()` +/* + Query params/results for `statx()`. +*/ Statx_Mask :: bit_set[Statx_Mask_Bits; u32] -/// File attributes, returned by statx. This bitset is also -/// used to specify which attributes are present, not just -/// their value. +/* + File attributes, returned by statx. This bitset is also + used to specify which attributes are present, not just + their value. +*/ Statx_Attr :: bit_set[Statx_Attr_Bits; u64] -/// The extended Stat struct +/* + The extended Stat struct, the argument to statx(2) syscall. +*/ Statx :: struct { mask: Statx_Mask, blksize: u32, @@ -165,7 +227,9 @@ Statx :: struct { _: [12]u64, } -/// Mount flags for filesystem +/* + Mount flags for filesystem. +*/ FS_Flags :: bit_set[FS_Flags_Bits; u32] when size_of(int) == 8 { @@ -205,19 +269,28 @@ when size_of(int) == 8 { } } +/* + Struct for statfs(2). +*/ Stat_FS :: struct { using _impl_stat_fs: _Arch_Stat_FS, } -/// Flags for close_range.2 +/* + Flags for close_range(2). +*/ Close_Range_Flags :: bit_set[Close_Range_Flags_Bits; u32] -/// Flags for rename.2 +/* + Flags for rename(2). +*/ Rename_Flags :: bit_set[Rename_Flags_Bits; u32] -/// Directory entry -/// Recommended to use this with dirent_iterator() -/// and dirent_name() +/* + Directory entry record. + Recommended iterate these with `dirent_iterator()`, + and obtain the name via `dirent_name()`. +*/ Dirent :: struct { ino: Inode, off: i64, @@ -226,7 +299,9 @@ Dirent :: struct { name: [0]u8, // See dirent_name } -/// Lock record for fcntl.2 +/* + Lock record for fcntl(2). +*/ FLock :: struct { type: FLock_Type, whence: Seek_Whence, @@ -235,62 +310,113 @@ FLock :: struct { pid: Pid, } -/// Flags for fcntl_notify +/* + File locking operations. + Use one of `EX`, `RW` or `UN` to specify the operation, and add `UN` if + you need a non-blocking operation. +*/ +FLock_Op :: bit_set[FLock_Op_Bits; i32] + +/* + Flags for `fcntl_notify()`. +*/ FD_Notifications :: bit_set[FD_Notifications_Bits; i32] -/// Seals for fcntl_add_seals +/* + Seals for `fcntl_add_seals()`. +*/ Seal :: bit_set[Seal_Bits; i32] -/// Represents owner that receives events on file updates +/* + Represents owner that receives events on file updates. +*/ F_Owner :: struct { type: F_Owner_Type, pid: Pid, } -/// Events for ppoll +/* + Events for ppoll(2). +*/ Fd_Poll_Events :: bit_set[Fd_Poll_Events_Bits; u16] -/// Struct for ppoll +/* + Struct for ppoll(2). +*/ Poll_Fd :: struct { fd: Fd, events: Fd_Poll_Events, revents: Fd_Poll_Events, } -/// Specifies protection for memory pages +Inotify_Init_Flags :: bit_set[Inotify_Init_Bits; i32] + +Inotify_Event :: struct { + wd: Wd, + mask: Inotify_Event_Mask, + cookie: u32, + len: u32, + name: [0]u8, +} + +Inotify_Event_Mask :: bit_set[Inotify_Event_Bits; u32] + +/* + Specifies protection for memory pages. +*/ Mem_Protection :: bit_set[Mem_Protection_Bits; i32] -/// Flags for mmap +/* + Flags for mmap. +*/ Map_Flags :: bit_set[Map_Flags_Bits; i32] -/// Flags for mlock.2 +/* + Flags for mlock(2). +*/ MLock_Flags :: bit_set[MLock_Flags_Bits; u32] -/// Flags for msync.2 +/* + Flags for msync(2). +*/ MSync_Flags :: bit_set[MSync_Flags_Bits; i32] -/// Access rights for pkey_alloc.2 +/* + Access rights for pkey_alloc(2). +*/ PKey_Access_Rights :: bit_set[PKey_Access_Bits; u32] -/// Flags for mremap.2 +/* + Flags for mremap(2). +*/ MRemap_Flags :: bit_set[MRemap_Flags_Bits; i32] -/// Flags for getrandom syscall +/* + Flags for getrandom(2) syscall. +*/ Get_Random_Flags :: bit_set[Get_Random_Flags_Bits; i32] -/// Flags for perf_event_open syscall +/* + Flags for perf_event_open(2) syscall. +*/ Perf_Flags :: bit_set[Perf_Flags_Bits; uint] Perf_Event_Flags :: distinct bit_set[Perf_Event_Flags_Bits; u64] +Perf_Read_Format :: distinct bit_set[Perf_Read_Format_Bits; u64] + Perf_Cap_Flags :: distinct bit_set[Perf_Cap_Flags_Bits; u64] Perf_Event_Sample_Type :: bit_set[Perf_Event_Sample_Type_Bits; u64] -/// Specifies which branches to include in branch record +/* + Specifies which branches to include in branch record. +*/ Branch_Sample_Type :: bit_set[Branch_Sample_Type_Bits; u64] -/// The struct for perf_event_open +/* + The struct for perf_event_open. +*/ Perf_Event_Attr :: struct #packed { type: Perf_Event_Type, size: u32, @@ -334,7 +460,9 @@ Perf_Event_Attr :: struct #packed { _: u16, } -/// The ring buffer structure when mmaping Perf_Event_Attr +/* + The ring buffer structure when mmaping Perf_Event_Attr. +*/ Perf_Event_Mmap_Page :: struct #packed { version: u32, compat_version: u32, @@ -369,10 +497,14 @@ Perf_Event_Mmap_Page :: struct #packed { // TODO(flysand): Its taking too much effort to bind the other data structures related to perf_event_open -/// Options for wait4() and waitpid() +/* + Options for wait4(2) and waitpid(2). +*/ Wait_Options :: bit_set[Wait_Option; i32] -/// Flags for pidfd_open.2 +/* + Flags for pidfd_open(2). +*/ Pid_FD_Flags :: bit_set[Pid_FD_Flags_Bits; i32] // Note(flysand): these could, in principle be implemented with bitfields, @@ -381,60 +513,219 @@ 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) when size_of(rawptr) == 8 else 3 * 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, - errno: Errno, - code: i32, - _pad0: i32, - using _union: struct #raw_union { - _pad1: [SI_PAD_SIZE]u8, - using _kill: struct { - pid: Pid, /* sender's pid */ - uid: Uid, /* sender's uid */ +when size_of(rawptr) == 8 { + Sig_Info :: struct #packed { + signo: Signal, + errno: Errno, + code: i32, + _pad0: i32, + using _union: struct #raw_union { + _pad1: [SI_PAD_SIZE]u8, + using _kill: struct { + pid: Pid, /* sender's pid */ + uid: Uid, /* sender's uid */ + }, + using _timer: struct { + timerid: i32, /* timer id */ + overrun: i32, /* overrun count */ + value: Sig_Val, /* timer value */ + }, + /* POSIX.1b signals */ + using _rt: struct { + _pid0: Pid, /* sender's pid */ + _uid0: Uid, /* sender's uid */ + }, + /* SIGCHLD */ + using _sigchld: struct { + _pid1: Pid, /* which child */ + _uid1: Uid, /* sender's uid */ + status: i32, /* exit code */ + utime: uint, + stime: uint, //clock_t + }, + /* SIGILL, SIGFPE, SIGSEGV, SIGBUS */ + using _sigfault: struct { + addr: rawptr, /* faulting insn/memory ref. */ + 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 { + band: int, /* POLL_IN, POLL_OUT, POLL_MSG */ + fd: Fd, + }, + /* SIGSYS */ + using _sigsys: struct { + call_addr: rawptr, /* calling user insn */ + syscall: i32, /* triggering system call number */ + arch: u32, /* AUDIT_ARCH_* of syscall */ + }, }, - using _timer: struct { - timerid: i32, /* timer id */ - overrun: i32, /* overrun count */ + } + + #assert(size_of(Sig_Info) == 128) + #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 { + Sig_Info :: struct { + signo: Signal, + errno: Errno, + code: i32, + using _union: struct #raw_union { + _pad1: [SI_PAD_SIZE]u8, + using _kill: struct { + pid: Pid, /* sender's pid */ + uid: Uid, /* sender's uid */ + }, + using _timer: struct { + timerid: i32, /* timer id */ + overrun: i32, /* overrun count */ + value: Sig_Val, /* timer value */ + }, + /* POSIX.1b signals */ + using _rt: struct { + _pid0: Pid, /* sender's pid */ + _uid0: Uid, /* sender's uid */ + }, + /* SIGCHLD */ + using _sigchld: struct { + _pid1: Pid, /* which child */ + _uid1: Uid, /* sender's uid */ + status: i32, /* exit code */ + utime: uint, + stime: uint, //clock_t + }, + /* SIGILL, SIGFPE, SIGSEGV, SIGBUS */ + using _sigfault: struct { + addr: rawptr, /* faulting insn/memory ref. */ + using _: struct #raw_union { + trapno: i32, /* Trap number that caused signal */ + addr_lsb: i16, /* LSB of the reported address */ + using _addr_bnd: struct { + _pad2: u32, + lower: rawptr, /* lower bound during fault */ + upper: rawptr, /* upper bound during fault */ + }, + using _addr_pkey: struct { + _pad3: u32, + pkey: u32, /* protection key on PTE that faulted */ + }, + using _perf: struct { + perf_data: u32, + perf_type: u32, + perf_flags: u32, + }, + }, + }, + /* SIGPOLL */ + using _sigpoll: struct { + band: int, /* POLL_IN, POLL_OUT, POLL_MSG */ + fd: Fd, + }, + /* SIGSYS */ + using _sigsys: struct { + call_addr: rawptr, /* calling user insn */ + syscall: i32, /* triggering system call number */ + arch: u32, /* AUDIT_ARCH_* of syscall */ + }, }, - /* POSIX.1b signals */ - using _rt: struct { - _pid0: Pid, /* sender's pid */ - _uid0: Uid, /* sender's uid */ - }, - /* SIGCHLD */ - using _sigchld: struct { - _pid1: Pid, /* which child */ - _uid1: Uid, /* sender's uid */ - status: i32, /* exit code */ - utime: uint, - stime: uint, //clock_t - }, - /* SIGILL, SIGFPE, SIGSEGV, SIGBUS */ - using _sigfault: struct { - addr: rawptr, /* faulting insn/memory ref. */ - addr_lsb: i16, /* LSB of the reported address */ - }, - /* SIGPOLL */ - using _sigpoll: struct { - band: int, /* POLL_IN, POLL_OUT, POLL_MSG */ - fd: Fd, - }, - /* SIGSYS */ - using _sigsys: struct { - call_addr: rawptr, /* calling user insn */ - syscall: i32, /* triggering system call number */ - arch: u32, /* AUDIT_ARCH_* of syscall */ + } + + #assert(size_of(Sig_Info) == 128) + #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) == 0x0c) + #assert(offset_of(Sig_Info, uid) == 0x10) + #assert(offset_of(Sig_Info, timerid) == 0x0c) + #assert(offset_of(Sig_Info, overrun) == 0x10) + #assert(offset_of(Sig_Info, value) == 0x14) + #assert(offset_of(Sig_Info, status) == 0x14) + #assert(offset_of(Sig_Info, utime) == 0x18) + #assert(offset_of(Sig_Info, stime) == 0x1c) + #assert(offset_of(Sig_Info, addr) == 0x0c) + #assert(offset_of(Sig_Info, addr_lsb) == 0x10) + #assert(offset_of(Sig_Info, trapno) == 0x10) + #assert(offset_of(Sig_Info, lower) == 0x14) + #assert(offset_of(Sig_Info, upper) == 0x18) + #assert(offset_of(Sig_Info, pkey) == 0x14) + #assert(offset_of(Sig_Info, perf_data) == 0x10) + #assert(offset_of(Sig_Info, perf_type) == 0x14) + #assert(offset_of(Sig_Info, perf_flags) == 0x18) + #assert(offset_of(Sig_Info, band) == 0x0c) + #assert(offset_of(Sig_Info, fd) == 0x10) + #assert(offset_of(Sig_Info, call_addr) == 0x0c) + #assert(offset_of(Sig_Info, syscall) == 0x10) + #assert(offset_of(Sig_Info, arch) == 0x14) +} + +SIGEV_MAX_SIZE :: 64 +SIGEV_PAD_SIZE :: ((SIGEV_MAX_SIZE-size_of(i32)*2+size_of(Sig_Val))/size_of(i32)) + +Sig_Val :: struct #raw_union { + sival_int: i32, + sival_ptr: rawptr, +} + +Sig_Event :: struct { + value: Sig_Val, + signo: i32, + notify: i32, + using _: struct #raw_union { + _: [SIGEV_PAD_SIZE]u32, + thread_id: Pid, + using _: struct { + notify_function: #type proc "c" (val: Sig_Val), + notify_attribute: rawptr, }, }, } @@ -447,37 +738,63 @@ 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, } - -/// Flags for the socket file descriptor -/// Note, on linux these are technically passed by OR'ing together -/// with Socket_Type, our wrapper does this under the hood. +/* + Flags for the socket file descriptor. + Note, on linux these are technically passed by OR'ing together + with Socket_Type, our wrapper does this under the hood. +*/ Socket_FD_Flags :: bit_set[Socket_FD_Flags_Bits; int] -/// Address family for the socket -/// Typically there's one address family for every protocol family +/* + Address family for the socket. + Typically there's one address family for every protocol family. +*/ Address_Family :: distinct Protocol_Family -/// Flags for the socket for send/recv calls +/* + Flags for the socket for send/recv calls. +*/ Socket_Msg :: bit_set[Socket_Msg_Bits; i32] -/// Struct representing IPv4 socket address +/* + Struct representing a generic socket address. +*/ +Sock_Addr :: struct #packed { + sa_family: Address_Family, + sa_data: [14]u8, +} + +/* + Struct representing IPv4 socket address. +*/ Sock_Addr_In :: struct #packed { sin_family: Address_Family, sin_port: u16be, sin_addr: [4]u8, + sin_zero: [size_of(Sock_Addr) - size_of(Address_Family) - size_of(u16be) - size_of([4]u8)]u8, } -/// Struct representing IPv6 socket address +/* + Struct representing IPv6 socket address. +*/ Sock_Addr_In6 :: struct #packed { sin6_family: Address_Family, sin6_port: u16be, @@ -486,23 +803,60 @@ Sock_Addr_In6 :: struct #packed { sin6_scope_id: u32, } -/// Struct representing an arbitrary socket address +/* + 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. +*/ Sock_Addr_Any :: struct #raw_union { using _: struct { family: Address_Family, port: u16be, }, + using generic: Sock_Addr, using ipv4: Sock_Addr_In, using ipv6: Sock_Addr_In6, + using uds: Sock_Addr_Un, } -/// Just an alias to make futex-values more visible +/* + Message header for sendmsg/recvmsg +*/ +Msg_Hdr :: struct { + name: rawptr, + namelen: i32, + iov: []IO_Vec, // ptr followed by length, abi matches + control: []u8, + flags: Socket_Msg, +} + +/* + Multiple message header for sendmmsg/recvmmsg +*/ +MMsg_Hdr :: struct { + hdr: Msg_Hdr, + len: u32, +} + +/* + Just an alias to make futex-values more visible +*/ Futex :: u32 -/// Flags for the futex (they are kept separately) +/* + Flags for the futex (they are kept separately) +*/ Futex_Flags :: bit_set[Futex_Flags_Bits; u32] -/// Times +/* + Times +*/ Tms :: struct { tms_utime: int, tms_stime: int, @@ -510,8 +864,10 @@ Tms :: struct { tms_cstime: int, } -/// "Unix time-sharing system name", allegedly -/// Basically system info +/* + "Unix time-sharing system name", allegedly. + Basically system info. +*/ UTS_Name :: struct { sysname: [65]u8 `fmt:"s,0"`, nodename: [65]u8 `fmt:"s,0"`, @@ -521,7 +877,9 @@ UTS_Name :: struct { domainname: [65]u8 `fmt:"s,0"`, } -/// Return buffer for the sysinfo syscall +/* + Return buffer for the sysinfo syscall +*/ Sys_Info :: struct { uptime: int, loads: [3]int, @@ -538,14 +896,17 @@ Sys_Info :: struct { _padding: [20 - (2 * size_of(int)) - size_of(i32)]u8, } -/// Resource limit +/* + Resource limit +*/ RLimit :: struct { cur: uint, max: uint, } -/// Structure representing how much of each resource -/// got used. +/* + Structure representing how much of each resource got used. +*/ RUsage :: struct { utime: Time_Val, stime: Time_Val, @@ -564,3 +925,553 @@ RUsage :: struct { nvcsw_word: int, nivcsw_word: int, } + +/* + Struct used for IO operations +*/ +IO_Vec :: struct { + base: rawptr, + len: uint, +} + +/* + Access mode for shared memory +*/ +IPC_Mode :: bit_set[IPC_Mode_Bits; u32] + +/* + Flags used by IPC objects +*/ +IPC_Flags :: bit_set[IPC_Flags_Bits; i16] + +/* + Permissions for IPC objects +*/ +IPC_Perm :: struct { + key: Key, + uid: u32, + gid: u32, + cuid: u32, + cgid: u32, + mode: IPC_Mode, + seq: u16, + _: [2 + 2*size_of(int)]u8, +} + +when size_of(int) == 8 || ODIN_ARCH == .i386 { + // 32-bit and 64-bit x86, 64-bit arm + _Arch_Shmid_DS :: struct { + perm: IPC_Perm, + segsz: uint, + atime: int, + dtime: int, + ctime: int, + cpid: Pid, + lpid: Pid, + nattch: uint, + _: [2]uint, + } +} else { + // Other 32-bit platforms + // NOTE(flysand): I'm not risking assuming it's little endian... + _Arch_Shmid_DS :: struct { + perm: IPC_Perm, + segsz: uint, + atime: uint, + atime_high: uint, + dtime: uint, + dtime_high: uint, + ctime: uint, + ctime_high: uint, + cpid: Pid, + lpid: Pid, + nattach: uint, + _: [2]uint, + } +} + +/* + SystemV shared memory data. +*/ +Shmid_DS :: _Arch_Shmid_DS + +/* + SystemV shared memory info. +*/ +Shm_Info :: struct { + used_ids: i32, + shm_tot: uint, + shm_rss: uint, + shm_swp: uint, + swap_attempts: uint, + swap_successes: uint, +} + +/* + SystemV semaphore operation. +*/ +Sem_Buf :: struct { + num: u16, + op: IPC_Cmd, + flg: IPC_Flags, +} + +when ODIN_ARCH == .i386 { + _Arch_Semid_DS :: struct { + perm: IPC_Perm, + otime: uint, + otime_high: uint, + ctime: uint, + ctime_high: uint, + nsems: uint, + _: [2]uint, + } +} else when ODIN_ARCH == .amd64 { + _Arch_Semid_DS :: struct { + perm: IPC_Perm, + otime: int, + ctime: int, + nsems: uint, + _: [2]uint, + } +} else when ODIN_ARCH == .arm32 { + _Arch_Semid_DS :: struct { + perm: IPC_Perm, + otime: uint, + otime_high: uint, + ctime: uint, + ctime_high: uint, + nsems: uint, + _: [2]uint, + } +} else when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { + _Arch_Semid_DS :: struct { + perm: IPC_Perm, + otime: int, + ctime: int, + sem_nsems: uint, + __unused3: uint, + __unused4: uint, + } +} + +/* + Architecture-specific semaphore data. +*/ +Semid_DS :: _Arch_Semid_DS + +/* + Argument for semctl functions +*/ +Sem_Un :: struct #raw_union { + val: i32, + buf: rawptr, + array: u16, + __buf: Sem_Info, + _: uintptr, +} + +/* + SystenV semaphore info. +*/ +Sem_Info :: struct { + semmap: i32, + mni: i32, + mns: i32, + mnu: i32, + msl: i32, + opm: i32, + ume: i32, + usz: i32, + vmx: i32, + aem: i32, +} + +/* + Template for the struct used for sending and receiving messages +*/ +Msg_Buf :: struct { + type: int, + text: [0]u8, +} + +/* + SystemV message queue data. +*/ +Msqid_DS :: struct { + perm: IPC_Perm, + stime: uint, + rtime: uint, + ctime: uint, + cbytes: uint, + qnum: uint, + qbytes: uint, + lspid: Pid, + lrpid: Pid, + _: [2]uint, +} + +/* + Interval timer types +*/ +ITimer_Which :: enum { + REAL = 0, + VIRTUAL = 1, + PROF = 2, +} + +/* + Interval timer value +*/ +ITimer_Val :: struct { + interval: Time_Val, + value: Time_Val, +} + +ITimer_Spec :: struct { + interval: Time_Spec, + value: Time_Spec, +} + +/* + Flags for POSIX interval timers. +*/ +ITimer_Flags :: bit_set[ITimer_Flags_Bits; u32] + +when ODIN_ARCH == .arm32 { + _Arch_User_Regs :: struct { + cpsr: uint, + pc: uint, + lr: uint, + sp: uint, + ip: uint, + fp: uint, + r10: uint, + r9: uint, + r8: uint, + r7: uint, + r6: uint, + r5: uint, + r4: uint, + r3: uint, + r2: uint, + r1: uint, + r0: uint, + ORIG_r0: uint, + } + // TODO(flysand): Idk what to do about these, couldn't find their + // definitions + _Arch_User_FP_Regs :: struct {} + _Arch_User_FPX_Regs :: struct {} +} else when ODIN_ARCH == .arm64 { + _Arch_User_Regs :: struct { + regs: [31]uint, + sp: uint, + pc: uint, + pstate: uint, + } + _Arch_User_FP_Regs :: struct { + vregs: [32]u128, + fpsr: u32, + fpcr: u32, + _: [2]u32, + } + _Arch_User_FPX_Regs :: struct {} +} else when ODIN_ARCH == .i386 { + _Arch_User_Regs :: struct { + ebx: uint, + ecx: uint, + edx: uint, + esi: uint, + edi: uint, + ebp: uint, + eax: uint, + xds: uint, + xes: uint, + xfs: uint, + xgs: uint, + orig_eax: uint, + eip: uint, + xcs: uint, + eflags: uint, + esp: uint, + xss: uint, + } + // x87 FPU state + _Arch_User_FP_Regs :: struct { + cwd: uint, + swd: uint, + twd: uint, + fip: uint, + fcs: uint, + foo: uint, + fos: uint, + st_space: [20]uint, + } + // FXSR instruction set state + _Arch_User_FPX_Regs :: struct { + cwd: u16, + swd: u16, + twd: u16, + fop: u16, + fip: uint, + fcs: uint, + foo: uint, + fos: uint, + mxcsr: uint, + _: uint, + st_space: [32]uint, + xmm_space: [32]uint, + padding: [56]uint, + } +} else when ODIN_ARCH == .amd64 { + _Arch_User_Regs :: struct { + // Callee-preserved, may not be correct if the syscall doesn't need + // these registers + r15: uint, + r14: uint, + r13: uint, + r12: uint, + rbp: uint, + rbx: uint, + // Always saved + r11: uint, + r10: uint, + r9: uint, + r8: uint, + rax: uint, + rcx: uint, + rdx: uint, + rsi: uint, + rdi: uint, + // On syscall entry this is the syscall number, on CPU exception this + // is the error code, on hardware interrupt this is its IRQ number + orig_rax: uint, + // Return frame for iretq + rip: uint, + cs: uint, + eflags: uint, + rsp: uint, + ss: uint, + fs_base: uint, + gs_base: uint, + ds: uint, + es: uint, + fs: uint, + gs: uint, + } + // All floating point state + _Arch_User_FP_Regs :: struct { + cwd: u16, + swd: u16, + twd: u16, + fop: u16, + rip: uint, + rdp: uint, + mxcsr: u32, + mxcsr_mask: u32, + st_space: [32]u32, + xmm_space: [64]u32, + _: [24]u32, + } + // FXSR instruction set state + _Arch_User_FPX_Regs :: struct { + cwd: u16, + swd: u16, + twd: u16, + fop: u16, + fip: uint, + fcs: uint, + foo: uint, + fos: uint, + mxcsr: uint, + _: uint, + st_space: [32]uint, + xmm_space: [32]uint, + padding: [56]uint, + } +} else when ODIN_ARCH == .riscv64 { + _Arch_User_Regs :: struct { + pc, ra, sp, gp, tp, + t0, t1, t2, + s0, s1, + a0, a1, a2, a3, a4, a5, a6, a7, + s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, + t3, t4, t5, t6: uint, + } + _Arch_User_FP_Regs :: struct #raw_union { + f_ext: struct { + f: [32]u32, + fcsr: u32, + }, + d_ext: struct { + f: [32]u64, + fcsr: u32, + }, + q_ext: struct { + using _: struct #align(16) { + f: [64]u64, + }, + fcsr: u32, + reserved: [3]u32, + }, + } + _Arch_User_FPX_Regs :: struct {} +} + +/* + Architecture-specific registers struct. +*/ +User_Regs :: _Arch_User_Regs + +/* + Architecture-specific floating-point registers +*/ +User_FP_Regs :: _Arch_User_FP_Regs + +/* + Architecture-specific extended floating-point registers. + Currently only used for x86 CPU's. +*/ +User_FPX_Regs :: _Arch_User_FPX_Regs + +/* + ptrace options. +*/ +PTrace_Options :: bit_set[PTrace_Options_Bits; u32] + +/* + ptrace's PEEKSIGINFO argument. +*/ +PTrace_Peek_Sig_Info_Args :: struct { + off: u64, + flags: PTrace_Peek_Sig_Info_Flags, + nr: i32, +} + +/* + ptrace's PEEKSIGINFO flags. +*/ +PTrace_Peek_Sig_Info_Flags :: bit_set[PTrace_Peek_Sig_Info_Flags_Bits; u32] + +/* + ptrace's SECCOMP metadata. +*/ +PTrace_Seccomp_Metadata :: struct { + filter_off: u64, + flags: u64, +} + +/* + ptrace's results of GET_SYSCALL_INFO. +*/ +PTrace_Syscall_Info :: struct { + op: PTrace_Get_Syscall_Info_Op, + arch: u32, // TODO: AUDIT_ARCH* + instruction_pointer: u64, + stack_pointer: u64, + using _: struct #raw_union { + entry: struct { + nr: u64, + args: [6]u64, + }, + exit: struct { + rval: i64, + is_error: b8, + }, + seccomp: struct { + nr: u64, + args: [6]u64, + ret_data: u32, + }, + }, +} + +/* + ptrace's results of GET_RSEQ_CONFIGURATION. +*/ +PTrace_RSeq_Configuration :: struct { + rseq_abi_pointer: u64, + rseq_abi_size: u32, + signature: u32, + flags: u32, + _: u32, +} + +/* + Note types for PTRACE_GETREGSET. Mirrors constants in `elf` definition, + files though this enum only contains the constants defined for architectures + Odin can compile to. +*/ +PTrace_Note_Type :: enum { + NT_PRSTATUS = 1, + NT_PRFPREG = 2, + NT_PRPSINFO = 3, + NT_TASKSTRUCT = 4, + NT_AUXV = 6, + NT_SIGINFO = 0x53494749, + NT_FILE = 0x46494c45, + NT_PRXFPREG = 0x46e62b7f, + NT_386_TLS = 0x200, + NT_386_IOPERM = 0x201, + NT_X86_XSTATE = 0x202, + NT_X86_SHSTK = 0x204, + NT_ARM_VFP = 0x400, + NT_ARM_TLS = 0x401, + NT_ARM_HW_BREAK = 0x402, + NT_ARM_HW_WATCH = 0x403, + NT_ARM_SYSTEM_CALL = 0x404, + NT_ARM_SVE = 0x405, + NT_ARM_PAC_MASK = 0x406, + NT_ARM_PACA_KEYS = 0x407, + NT_ARM_PACG_KEYS = 0x408, + NT_ARM_TAGGED_ADDR_CTRL = 0x409, + NT_ARM_PAC_ENABLED_KEYS = 0x40a, + NT_ARM_SSVE = 0x40b, + NT_ARM_ZA = 0x40c, + NT_ARM_ZT = 0x40d, +} + +/* + Flags for splice(2) and tee(2) syscalls. +*/ +Splice_Flags :: bit_set[Splice_Flags_Bits; u32] + +/* + Flags for epoll_create(2) syscall. +*/ +EPoll_Flags :: bit_set[EPoll_Flags_Bits; i32] + +EPoll_Data :: struct #raw_union { + ptr: rawptr, + fd: Fd, + u32: u32, + u64: u64, +} + +EPoll_Event :: struct #packed { + events: EPoll_Event_Kind, + data: EPoll_Data, +} + +/* + Flags for execveat(2) syscall. +*/ +Execveat_Flags :: bit_set[Execveat_Flags_Bits; i32] + +RISCV_HWProbe_Flags :: bit_set[RISCV_HWProbe_Flags_Bits; u32] +RISCV_HWProbe_CPU_Perf_0 :: bit_set[RISCV_HWProbe_Misaligned_Scalar_Perf; u64] +RISCV_HWProbe_Base_Behavior :: bit_set[RISCV_HWProbe_Base_Behavior_Bits; u64] +RISCV_HWProbe_IMA_Ext_0 :: bit_set[RISCV_HWProbe_IMA_Ext_0_Bits; u64] + +RISCV_HWProbe :: struct { + // set to `.UNSUPPORTED` by the kernel if that is the case. + key: RISCV_HWProbe_Key, + value: struct #raw_union { + base_behavior: RISCV_HWProbe_Base_Behavior, + ima_ext_0: RISCV_HWProbe_IMA_Ext_0, + cpu_perf_0: RISCV_HWProbe_CPU_Perf_0, + misaligned_scalar_perf: RISCV_HWProbe_Misaligned_Scalar_Perf, + raw: u64, + }, +} diff --git a/core/sys/linux/wrappers.odin b/core/sys/linux/wrappers.odin index 13073315d..4f6118c80 100644 --- a/core/sys/linux/wrappers.odin +++ b/core/sys/linux/wrappers.odin @@ -1,4 +1,4 @@ -//+build linux +#+build linux package linux /// Low 8 bits of the exit code @@ -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/llvm/bit_manipulation.odin b/core/sys/llvm/bit_manipulation.odin index 39464773d..2e237dd32 100644 --- a/core/sys/llvm/bit_manipulation.odin +++ b/core/sys/llvm/bit_manipulation.odin @@ -1,4 +1,5 @@ // Bit Manipulation Intrinsics + package sys_llvm /* diff --git a/core/sys/llvm/code_generator.odin b/core/sys/llvm/code_generator.odin index 7d41ed67b..6422976c5 100644 --- a/core/sys/llvm/code_generator.odin +++ b/core/sys/llvm/code_generator.odin @@ -1,4 +1,5 @@ // Code Generator Intrinsics + package sys_llvm @(default_calling_convention="none") diff --git a/core/sys/llvm/standard_c_library.odin b/core/sys/llvm/standard_c_library.odin index 108d1268e..1818e8462 100644 --- a/core/sys/llvm/standard_c_library.odin +++ b/core/sys/llvm/standard_c_library.odin @@ -1,4 +1,5 @@ // Standard C Library Intrinsics + package sys_llvm @(default_calling_convention="none") diff --git a/core/sys/orca/macros.odin b/core/sys/orca/macros.odin new file mode 100644 index 000000000..d6a1a0f82 --- /dev/null +++ b/core/sys/orca/macros.odin @@ -0,0 +1,252 @@ +// File contains implementations of the Orca API that are defined as macros in Orca. + +package orca + +//////////////////////////////////////////////////////////////////////////////// +// Helpers for logging, asserting and aborting. +//////////////////////////////////////////////////////////////////////////////// + +log_error :: proc "contextless" (msg: cstring, loc := #caller_location) { + log_ext( + .ERROR, + cstring(raw_data(loc.procedure)), + cstring(raw_data(loc.file_path)), + loc.line, + msg, + ) +} + +log_warning :: proc "contextless" (msg: cstring, loc := #caller_location) { + log_ext( + .WARNING, + cstring(raw_data(loc.procedure)), + cstring(raw_data(loc.file_path)), + loc.line, + msg, + ) +} + +log_info :: proc "contextless" (msg: cstring, loc := #caller_location) { + log_ext( + .INFO, + cstring(raw_data(loc.procedure)), + cstring(raw_data(loc.file_path)), + loc.line, + msg, + ) +} + +abort :: proc "contextless" (msg: cstring, loc := #caller_location) { + abort_ext( + cstring(raw_data(loc.procedure)), + cstring(raw_data(loc.file_path)), + loc.line, + msg, + ) +} + +//////////////////////////////////////////////////////////////////////////////// +// Types and helpers for doubly-linked lists. +//////////////////////////////////////////////////////////////////////////////// + +// Get the entry for a given list element. +list_entry :: proc "contextless" (elt: ^list_elt, $T: typeid, $member: string) -> ^T { + return container_of(elt, T, member) +} + +// Get the next entry in a list. +list_next_entry :: proc "contextless" (list: ^list, elt: ^list_elt, $T: typeid, $member: string) -> ^T { + if elt.next != list.last { + return list_entry(elt.next, T, member) + } + + return nil +} + +// Get the previous entry in a list. +list_prev_entry :: proc "contextless" (list: ^list, elt: ^list_elt, $T: typeid, $member: string) -> ^T { + if elt.prev != list.last { + return list_entry(elt.prev, T, member) + } + + return nil +} + +// Same as `list_entry` but `elt` might be `nil`. +list_checked_entry :: proc "contextless" (elt: ^list_elt, $T: typeid, $member: string) -> ^T { + if elt != nil { + return list_entry(elt, T, member) + } + + return nil +} + +list_first_entry :: proc "contextless" (list: ^list, $T: typeid, $member: string) -> ^T { + return list_checked_entry(list.first, T, member) +} + +list_last_entry :: proc "contextless" (list: ^list, $T: typeid, $member: string) -> ^T { + return list_checked_entry(list.last, T, member) +} + +// Example: +// +// _elt: ^list_elt +// for elt in oc.list_for(list, &_elt, int, "elt") { +// } +list_for :: proc "contextless" (list: ^list, elt: ^^list_elt, $T: typeid, $member: string) -> (^T, bool) { + if elt == nil { + assert_fail(#file, #procedure, #line, "elt != nil", "misuse of `list_for`, expected `elt` to not be nil") + } + + if elt^ == nil { + elt^ = list.first + entry := list_checked_entry(elt^, T, member) + return entry, entry != nil + } + + elt^ = elt^.next + entry := list_checked_entry(elt^, T, member) + return entry, entry != nil +} + +list_iter :: list_for + +list_for_reverse :: proc "contextless" (list: ^list, elt: ^^list_elt, $T: typeid, $member: string) -> (^T, bool) { + if elt^ == nil { + elt^ = list.last + entry := list_checked_entry(elt^, T, member) + return entry, entry != nil + } + + elt^ = elt^.prev + entry := list_checked_entry(elt^, T, member) + return entry, entry != nil +} + +list_iter_reverse :: list_for_reverse + +list_pop_front_entry :: proc "contextless" (list: ^list, $T: typeid, $member: string) -> ^T { + if list_empty(list^) { + return nil + } + + return list_entry(list_pop_front(list), T, member) +} + +list_pop_back_entry :: proc "contextless" (list: ^list, $T: typeid, $member: string) -> ^T { + if list_empty(list^) { + return nil + } + + return list_entry(list_pop_back(list), T, member) +} + +//////////////////////////////////////////////////////////////////////////////// +// Base allocator and memory arenas. +//////////////////////////////////////////////////////////////////////////////// + +arena_push_type :: proc "contextless" (arena: ^arena, $T: typeid) -> ^T { + return (^T)(arena_push_aligned(arena, size_of(T), align_of(T))) +} + +arena_push_array :: proc "contextless" (arena: ^arena, $T: typeid, count: u64) -> []T { + return ([^]T)(arena_push_aligned(arena, size_of(T) * count, align_of(T)))[:count] +} + +scratch_end :: arena_scope_end + +//////////////////////////////////////////////////////////////////////////////// +// String slices and string lists. +//////////////////////////////////////////////////////////////////////////////// + +str8_list_first :: proc "contextless" (sl: ^str8_list) -> str8 { + if list_empty(sl.list) { + return "" + } + + return list_first_entry(&sl.list, str8_elt, "listElt").string +} + +str8_list_last :: proc "contextless" (sl: ^str8_list) -> str8 { + if list_empty(sl.list) { + return "" + } + + return list_last_entry(&sl.list, str8_elt, "listElt").string +} + +str8_list_for :: proc "contextless" (list: ^str8_list, elt: ^^list_elt) -> (^str8_elt, bool) { + return list_for(&list.list, elt, str8_elt, "listElt") +} + +str8_list_iter :: str8_list_for + +str8_list_empty :: proc "contextless" (list: str8_list) -> bool { + return list_empty(list.list) +} + +str16_list_first :: proc "contextless" (sl: ^str16_list) -> str16 { + if list_empty(sl.list) { + return {} + } + + return list_first_entry(&sl.list, str16_elt, "listElt").string +} + +str16_list_last :: proc "contextless" (sl: ^str16_list) -> str16 { + if list_empty(sl.list) { + return {} + } + + return list_last_entry(&sl.list, str16_elt, "listElt").string +} + +str16_list_for :: proc "contextless" (list: ^str16_list, elt: ^^list_elt) -> (^str16_elt, bool) { + return list_for(&list.list, elt, str16_elt, "listElt") +} + +str32_list_first :: proc "contextless" (sl: ^str32_list) -> str32 { + if list_empty(sl.list) { + return {} + } + + return list_first_entry(&sl.list, str32_elt, "listElt").string +} + +str32_list_last :: proc "contextless" (sl: ^str32_list) -> str32 { + if list_empty(sl.list) { + return {} + } + + return list_last_entry(&sl.list, str32_elt, "listElt").string +} + +str32_list_for :: proc "contextless" (list: ^str32_list, elt: ^^list_elt) -> (^str32_elt, bool) { + return list_for(&list.list, elt, str32_elt, "listElt") +} + +@(deferred_none=ui_box_end) +ui_container :: proc "contextless" (name: string, flags: ui_flags = {}) -> ^ui_box { + return ui_box_begin_str8(name, flags) +} + +@(deferred_none=ui_end_frame) +ui_frame :: proc "contextless" (frame_size: [2]f32, style: ui_style, mask: ui_style_mask) { + ui_begin_frame(frame_size, style, mask) +} + +@(deferred_none=ui_panel_end) +ui_panel :: proc "contextless" (name: cstring, flags: ui_flags) { + ui_panel_begin(name, flags) +} + +@(deferred_none=ui_menu_end) +ui_menu :: proc "contextless" (name: cstring) { + ui_menu_begin(name) +} + +@(deferred_none=ui_menu_bar_end) +ui_menu_bar :: proc "contextless" (name: cstring) { + ui_menu_bar_begin(name) +} diff --git a/core/sys/orca/odin.odin b/core/sys/orca/odin.odin new file mode 100644 index 000000000..5c3e3e4d9 --- /dev/null +++ b/core/sys/orca/odin.odin @@ -0,0 +1,22 @@ +// File contains Odin specific helpers. + +package orca + +import "base:runtime" + +create_odin_logger :: proc(lowest := runtime.Logger_Level.Debug, ident := "") -> runtime.Logger { + return runtime.Logger{odin_logger_proc, nil, lowest, {}} +} + +odin_logger_proc :: proc(logger_data: rawptr, level: runtime.Logger_Level, text: string, options: runtime.Logger_Options, location := #caller_location) { + cbuf := make([]byte, len(text)+1, context.temp_allocator) + copy(cbuf, text) + ctext := cstring(raw_data(cbuf)) + + switch level { + case .Debug, .Info: log_info(ctext, location) + case .Warning: log_warning(ctext, location) + case: fallthrough + case .Error, .Fatal: log_error(ctext, location) + } +} diff --git a/core/sys/orca/orca.odin b/core/sys/orca/orca.odin new file mode 100644 index 000000000..d1e7dbf66 --- /dev/null +++ b/core/sys/orca/orca.odin @@ -0,0 +1,2178 @@ +package orca + +import "core:c" + +char :: c.char + +// currently missing in the api.json +window :: distinct u64 + +// currently missing in the api.json +pool :: struct { + arena: arena, + freeList: list, + blockSize: u64, +} + +@(link_prefix="OC_") +foreign { + UI_DARK_THEME: ui_theme + UI_LIGHT_THEME: ui_theme + + UI_DARK_PALETTE: ui_palette + UI_LIGHT_PALETTE: ui_palette +} + + +SYS_MAX_ERROR :: 1024 + +sys_err_def :: struct { + msg: [SYS_MAX_ERROR]u8 `fmt:"s,0"`, + code: i32, +} + +@(link_prefix="oc_") +foreign { + sys_error: sys_err_def +} +UNICODE_BASIC_LATIN :: unicode_range { 0x0000, 127 } +UNICODE_C1_CONTROLS_AND_LATIN_1_SUPPLEMENT :: unicode_range { 0x0080, 127 } +UNICODE_LATIN_EXTENDED_A :: unicode_range { 0x0100, 127 } +UNICODE_LATIN_EXTENDED_B :: unicode_range { 0x0180, 207 } +UNICODE_IPA_EXTENSIONS :: unicode_range { 0x0250, 95 } +UNICODE_SPACING_MODIFIER_LETTERS :: unicode_range { 0x02b0, 79 } +UNICODE_COMBINING_DIACRITICAL_MARKS :: unicode_range { 0x0300, 111 } +UNICODE_GREEK_COPTIC :: unicode_range { 0x0370, 143 } +UNICODE_CYRILLIC :: unicode_range { 0x0400, 255 } +UNICODE_CYRILLIC_SUPPLEMENT :: unicode_range { 0x0500, 47 } +UNICODE_ARMENIAN :: unicode_range { 0x0530, 95 } +UNICODE_HEBREW :: unicode_range { 0x0590, 111 } +UNICODE_ARABIC :: unicode_range { 0x0600, 255 } +UNICODE_SYRIAC :: unicode_range { 0x0700, 79 } +UNICODE_THAANA :: unicode_range { 0x0780, 63 } +UNICODE_DEVANAGARI :: unicode_range { 0x0900, 127 } +UNICODE_BENGALI_ASSAMESE :: unicode_range { 0x0980, 127 } +UNICODE_GURMUKHI :: unicode_range { 0x0a00, 127 } +UNICODE_GUJARATI :: unicode_range { 0x0a80, 127 } +UNICODE_ORIYA :: unicode_range { 0x0b00, 127 } +UNICODE_TAMIL :: unicode_range { 0x0b80, 127 } +UNICODE_TELUGU :: unicode_range { 0x0c00, 127 } +UNICODE_KANNADA :: unicode_range { 0x0c80, 127 } +UNICODE_MALAYALAM :: unicode_range { 0x0d00, 255 } +UNICODE_SINHALA :: unicode_range { 0x0d80, 127 } +UNICODE_THAI :: unicode_range { 0x0e00, 127 } +UNICODE_LAO :: unicode_range { 0x0e80, 127 } +UNICODE_TIBETAN :: unicode_range { 0x0f00, 255 } +UNICODE_MYANMAR :: unicode_range { 0x1000, 159 } +UNICODE_GEORGIAN :: unicode_range { 0x10a0, 95 } +UNICODE_HANGUL_JAMO :: unicode_range { 0x1100, 255 } +UNICODE_ETHIOPIC :: unicode_range { 0x1200, 383 } +UNICODE_CHEROKEE :: unicode_range { 0x13a0, 95 } +UNICODE_UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS :: unicode_range { 0x1400, 639 } +UNICODE_OGHAM :: unicode_range { 0x1680, 31 } +UNICODE_RUNIC :: unicode_range { 0x16a0, 95 } +UNICODE_TAGALOG :: unicode_range { 0x1700, 31 } +UNICODE_HANUNOO :: unicode_range { 0x1720, 31 } +UNICODE_BUHID :: unicode_range { 0x1740, 31 } +UNICODE_TAGBANWA :: unicode_range { 0x1760, 31 } +UNICODE_KHMER :: unicode_range { 0x1780, 127 } +UNICODE_MONGOLIAN :: unicode_range { 0x1800, 175 } +UNICODE_LIMBU :: unicode_range { 0x1900, 79 } +UNICODE_TAI_LE :: unicode_range { 0x1950, 47 } +UNICODE_KHMER_SYMBOLS :: unicode_range { 0x19e0, 31 } +UNICODE_PHONETIC_EXTENSIONS :: unicode_range { 0x1d00, 127 } +UNICODE_LATIN_EXTENDED_ADDITIONAL :: unicode_range { 0x1e00, 255 } +UNICODE_GREEK_EXTENDED :: unicode_range { 0x1f00, 255 } +UNICODE_GENERAL_PUNCTUATION :: unicode_range { 0x2000, 111 } +UNICODE_SUPERSCRIPTS_AND_SUBSCRIPTS :: unicode_range { 0x2070, 47 } +UNICODE_CURRENCY_SYMBOLS :: unicode_range { 0x20a0, 47 } +UNICODE_COMBINING_DIACRITICAL_MARKS_FOR_SYMBOLS :: unicode_range { 0x20d0, 47 } +UNICODE_LETTERLIKE_SYMBOLS :: unicode_range { 0x2100, 79 } +UNICODE_NUMBER_FORMS :: unicode_range { 0x2150, 63 } +UNICODE_ARROWS :: unicode_range { 0x2190, 111 } +UNICODE_MATHEMATICAL_OPERATORS :: unicode_range { 0x2200, 255 } +UNICODE_MISCELLANEOUS_TECHNICAL :: unicode_range { 0x2300, 255 } +UNICODE_CONTROL_PICTURES :: unicode_range { 0x2400, 63 } +UNICODE_OPTICAL_CHARACTER_RECOGNITION :: unicode_range { 0x2440, 31 } +UNICODE_ENCLOSED_ALPHANUMERICS :: unicode_range { 0x2460, 159 } +UNICODE_BOX_DRAWING :: unicode_range { 0x2500, 127 } +UNICODE_BLOCK_ELEMENTS :: unicode_range { 0x2580, 31 } +UNICODE_GEOMETRIC_SHAPES :: unicode_range { 0x25a0, 95 } +UNICODE_MISCELLANEOUS_SYMBOLS :: unicode_range { 0x2600, 255 } +UNICODE_DINGBATS :: unicode_range { 0x2700, 191 } +UNICODE_MISCELLANEOUS_MATHEMATICAL_SYMBOLS_A :: unicode_range { 0x27c0, 47 } +UNICODE_SUPPLEMENTAL_ARROWS_A :: unicode_range { 0x27f0, 15 } +UNICODE_BRAILLE_PATTERNS :: unicode_range { 0x2800, 255 } +UNICODE_SUPPLEMENTAL_ARROWS_B :: unicode_range { 0x2900, 127 } +UNICODE_MISCELLANEOUS_MATHEMATICAL_SYMBOLS_B :: unicode_range { 0x2980, 127 } +UNICODE_SUPPLEMENTAL_MATHEMATICAL_OPERATORS :: unicode_range { 0x2a00, 255 } +UNICODE_MISCELLANEOUS_SYMBOLS_AND_ARROWS :: unicode_range { 0x2b00, 255 } +UNICODE_CJK_RADICALS_SUPPLEMENT :: unicode_range { 0x2e80, 127 } +UNICODE_KANGXI_RADICALS :: unicode_range { 0x2f00, 223 } +UNICODE_IDEOGRAPHIC_DESCRIPTION_CHARACTERS :: unicode_range { 0x2ff0, 15 } +UNICODE_CJK_SYMBOLS_AND_PUNCTUATION :: unicode_range { 0x3000, 63 } +UNICODE_HIRAGANA :: unicode_range { 0x3040, 95 } +UNICODE_KATAKANA :: unicode_range { 0x30a0, 95 } +UNICODE_BOPOMOFO :: unicode_range { 0x3100, 47 } +UNICODE_HANGUL_COMPATIBILITY_JAMO :: unicode_range { 0x3130, 95 } +UNICODE_KANBUN_KUNTEN :: unicode_range { 0x3190, 15 } +UNICODE_BOPOMOFO_EXTENDED :: unicode_range { 0x31a0, 31 } +UNICODE_KATAKANA_PHONETIC_EXTENSIONS :: unicode_range { 0x31f0, 15 } +UNICODE_ENCLOSED_CJK_LETTERS_AND_MONTHS :: unicode_range { 0x3200, 255 } +UNICODE_CJK_COMPATIBILITY :: unicode_range { 0x3300, 255 } +UNICODE_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A :: unicode_range { 0x3400, 6591 } +UNICODE_YIJING_HEXAGRAM_SYMBOLS :: unicode_range { 0x4dc0, 63 } +UNICODE_CJK_UNIFIED_IDEOGRAPHS :: unicode_range { 0x4e00, 20911 } +UNICODE_YI_SYLLABLES :: unicode_range { 0xa000, 1167 } +UNICODE_YI_RADICALS :: unicode_range { 0xa490, 63 } +UNICODE_HANGUL_SYLLABLES :: unicode_range { 0xac00, 11183 } +UNICODE_HIGH_SURROGATE_AREA :: unicode_range { 0xd800, 1023 } +UNICODE_LOW_SURROGATE_AREA :: unicode_range { 0xdc00, 1023 } +UNICODE_PRIVATE_USE_AREA :: unicode_range { 0xe000, 6399 } +UNICODE_CJK_COMPATIBILITY_IDEOGRAPHS :: unicode_range { 0xf900, 511 } +UNICODE_ALPHABETIC_PRESENTATION_FORMS :: unicode_range { 0xfb00, 79 } +UNICODE_ARABIC_PRESENTATION_FORMS_A :: unicode_range { 0xfb50, 687 } +UNICODE_VARIATION_SELECTORS :: unicode_range { 0xfe00, 15 } +UNICODE_COMBINING_HALF_MARKS :: unicode_range { 0xfe20, 15 } +UNICODE_CJK_COMPATIBILITY_FORMS :: unicode_range { 0xfe30, 31 } +UNICODE_SMALL_FORM_VARIANTS :: unicode_range { 0xfe50, 31 } +UNICODE_ARABIC_PRESENTATION_FORMS_B :: unicode_range { 0xfe70, 143 } +UNICODE_HALFWIDTH_AND_FULLWIDTH_FORMS :: unicode_range { 0xff00, 239 } +UNICODE_SPECIALS :: unicode_range { 0xfff0, 15 } +UNICODE_LINEAR_B_SYLLABARY :: unicode_range { 0x10000, 127 } +UNICODE_LINEAR_B_IDEOGRAMS :: unicode_range { 0x10080, 127 } +UNICODE_AEGEAN_NUMBERS :: unicode_range { 0x10100, 63 } +UNICODE_OLD_ITALIC :: unicode_range { 0x10300, 47 } +UNICODE_GOTHIC :: unicode_range { 0x10330, 31 } +UNICODE_UGARITIC :: unicode_range { 0x10380, 31 } +UNICODE_DESERET :: unicode_range { 0x10400, 79 } +UNICODE_SHAVIAN :: unicode_range { 0x10450, 47 } +UNICODE_OSMANYA :: unicode_range { 0x10480, 47 } +UNICODE_CYPRIOT_SYLLABARY :: unicode_range { 0x10800, 63 } +UNICODE_BYZANTINE_MUSICAL_SYMBOLS :: unicode_range { 0x1d000, 255 } +UNICODE_MUSICAL_SYMBOLS :: unicode_range { 0x1d100, 255 } +UNICODE_TAI_XUAN_JING_SYMBOLS :: unicode_range { 0x1d300, 95 } +UNICODE_MATHEMATICAL_ALPHANUMERIC_SYMBOLS :: unicode_range { 0x1d400, 1023 } +UNICODE_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B :: unicode_range { 0x20000, 42719 } +UNICODE_CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT :: unicode_range { 0x2f800, 543 } +UNICODE_TAGS :: unicode_range { 0xe0000, 127 } +UNICODE_VARIATION_SELECTORS_SUPPLEMENT :: unicode_range { 0xe0100, 239 } +UNICODE_SUPPLEMENTARY_PRIVATE_USE_AREA_A :: unicode_range { 0xf0000, 65533 } +UNICODE_SUPPLEMENTARY_PRIVATE_USE_AREA_B :: unicode_range { 0x100000, 65533 } + +clock_kind :: enum c.int { + MONOTONIC, + UPTIME, + DATE, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + clock_time :: proc(clock: clock_kind) -> f64 --- +} + +file_write_slice :: proc(file: file, slice: []char) -> u64 { + return file_write(file, u64(len(slice)), raw_data(slice)) +} + +file_read_slice :: proc(file: file, slice: []char) -> u64 { + return file_read(file, u64(len(slice)), raw_data(slice)) +} + +style_enum :: enum { + SIZE_WIDTH = 1, + SIZE_HEIGHT, + + LAYOUT_AXIS, + LAYOUT_ALIGN_X, + LAYOUT_ALIGN_Y, + LAYOUT_SPACING, + LAYOUT_MARGIN_X, + LAYOUT_MARGIN_Y, + + FLOAT_X, + FLOAT_Y, + + COLOR, + BG_COLOR, + BORDER_COLOR, + BORDER_SIZE, + ROUNDNESS, + + FONT, + FONT_SIZE, + + ANIMATION_TIME, + ANIMATION_MASK, +} + +ui_style_mask :: bit_set[style_enum; u64] + +// Masks like the C version that can be used as common combinations +SIZE :: ui_style_mask { .SIZE_WIDTH, .SIZE_HEIGHT } +LAYOUT_MARGINS :: ui_style_mask { .LAYOUT_MARGIN_X, .LAYOUT_MARGIN_Y } +LAYOUT :: ui_style_mask { .LAYOUT_AXIS, .LAYOUT_ALIGN_X, .LAYOUT_ALIGN_Y, .LAYOUT_SPACING, .LAYOUT_MARGIN_X, .LAYOUT_MARGIN_Y } +FLOAT :: ui_style_mask { .FLOAT_X, .FLOAT_Y } +MASK_INHERITED :: ui_style_mask { .COLOR, .FONT, .FONT_SIZE, .ANIMATION_TIME, .ANIMATION_MASK } + +//////////////////////////////////////////////////////////////////////////////// +// Utility data structures and helpers used throughout the Orca API. +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Types and helpers for vectors and matrices. +//////////////////////////////////////////////////////////////////////////////// + +// A 2D vector type. +vec2 :: [2]f32 + +// A 3D vector type. +vec3 :: [3]f32 + +// A 2D integer vector type. +vec2i :: [2]i32 + +// A 4D vector type. +vec4 :: [4]f32 + +// A 2-by-3 matrix. +mat2x3 :: [6]f32 + +// An axis-aligned rectangle. +rect :: struct { x, y, w, h: f32 } + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + // Check if two 2D vectors are equal. + vec2_equal :: proc(v0: vec2, v1: vec2) -> bool --- + // Multiply a 2D vector by a scalar. + vec2_mul :: proc(f: f32, v: vec2) -> vec2 --- + // Add two 2D vectors + vec2_add :: proc(v0: vec2, v1: vec2) -> vec2 --- + // Transforms a vector by an affine transformation represented as a 2x3 matrix. + mat2x3_mul :: proc(m: mat2x3, p: vec2) -> vec2 --- + // Multiply two affine transformations represented as 2x3 matrices. Both matrices are treated as 3x3 matrices with an implicit `(0, 0, 1)` bottom row + mat2x3_mul_m :: proc(lhs: mat2x3, rhs: mat2x3) -> mat2x3 --- + // Invert an affine transform represented as a 2x3 matrix. + mat2x3_inv :: proc(x: mat2x3) -> mat2x3 --- + // Return a 2x3 matrix representing a rotation. + mat2x3_rotate :: proc(radians: f32) -> mat2x3 --- + // Return a 2x3 matrix representing a translation. + mat2x3_translate :: proc(x: f32, y: f32) -> mat2x3 --- +} + +//////////////////////////////////////////////////////////////////////////////// +// Helpers for logging, asserting and aborting. +//////////////////////////////////////////////////////////////////////////////// + +// Constants allowing to specify the level of logging verbosity. +log_level :: enum u32 { + // Only errors are logged. + ERROR = 0, + // Only warnings and errors are logged. + WARNING = 1, + // All messages are logged. + INFO = 2, + COUNT = 3, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + /* + Abort the application, showing an error message. + + This function should not be called directly by user code, which should use the `OC_ABORT` macro instead, as the macro takes care of filling in the source location parameters of the function. + */ + abort_ext :: proc(file: cstring, function: cstring, line: i32, fmt: cstring, #c_vararg args: ..any) -> ! --- + /* + Tigger a failed assertion. This aborts the application, showing the failed assertion and an error message. + + This function should not be called directly by user code, which should use the `OC_ASSERT` macro instead. The macro checks the assert condition and calls the function if it is false. It also takes care of filling in the source location parameters of the function. + */ + assert_fail :: proc(file: cstring, function: cstring, line: i32, src: cstring, fmt: cstring, #c_vararg args: ..any) -> ! --- + // Set the logging verbosity. + log_set_level :: proc(level: log_level) --- + /* + Log a message to the console. + + This function should not be called directly by user code, which should use the `oc_log_XXX` family of macros instead. The macros take care of filling in the message level and source location parameters of the function. + */ + log_ext :: proc(level: log_level, function: cstring, file: cstring, line: i32, fmt: cstring, #c_vararg args: ..any) --- +} + +//////////////////////////////////////////////////////////////////////////////// +// Types and helpers for doubly-linked lists. +//////////////////////////////////////////////////////////////////////////////// + +// An element of an intrusive doubly-linked list. +list_elt :: struct { + // Points to the previous element in the list. + prev: ^list_elt, + // Points to the next element in the list. + next: ^list_elt, +} + +// A doubly-linked list. +list :: struct { + // Points to the first element in the list. + first: ^list_elt, + // Points to the last element in the list. + last: ^list_elt, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + // Check if a list is empty. + list_empty :: proc(list: list) -> bool --- + // Zero-initializes a linked list. + list_init :: proc(list: ^list) --- + // Insert an element in a list after a given element. + list_insert :: proc(list: ^list, afterElt: ^list_elt, elt: ^list_elt) --- + // Insert an element in a list before a given element. + list_insert_before :: proc(list: ^list, beforeElt: ^list_elt, elt: ^list_elt) --- + // Remove an element from a list. + list_remove :: proc(list: ^list, elt: ^list_elt) --- + // Add an element at the end of a list. + list_push_back :: proc(list: ^list, elt: ^list_elt) --- + // Remove the last element from a list. + list_pop_back :: proc(list: ^list) -> ^list_elt --- + // Add an element at the beginning of a list. + list_push_front :: proc(list: ^list, elt: ^list_elt) --- + // Remove the first element from a list. + list_pop_front :: proc(list: ^list) -> ^list_elt --- +} + +//////////////////////////////////////////////////////////////////////////////// +// Base allocator and memory arenas. +//////////////////////////////////////////////////////////////////////////////// + +// The prototype of a procedure to reserve memory from the system. +mem_reserve_proc :: proc "c" (_context: ^base_allocator, size: u64) -> rawptr + +// The prototype of a procedure to modify a memory reservation. +mem_modify_proc :: proc "c" (_context: ^base_allocator, ptr: rawptr, size: u64) + +// A structure that defines how to allocate memory from the system. +base_allocator :: struct { + // A procedure to reserve memory from the system. + reserve: mem_reserve_proc, + // A procedure to commit memory from the system. + commit: mem_modify_proc, + // A procedure to decommit memory from the system. + decommit: mem_modify_proc, + // A procedure to release memory previously reserved from the system. + release: mem_modify_proc, +} + +// A contiguous chunk of memory managed by a memory arena. +arena_chunk :: struct { + listElt: list_elt, + ptr: cstring, + offset: u64, + committed: u64, + cap: u64, +} + +// A memory arena, allowing to allocate memory in a linear or stack-like fashion. +arena :: struct { + // An allocator providing memory pages from the system + base: ^base_allocator, + // A list of `oc_arena_chunk` chunks. + chunks: list, + // The chunk new memory allocations are pulled from. + currentChunk: ^arena_chunk, +} + +// This struct provides a way to store the current offset in a given arena, in order to reset the arena to that offset later. This allows using arenas in a stack-like fashion, e.g. to create temporary "scratch" allocations +arena_scope :: struct { + // The arena which offset is stored. + arena: ^arena, + // The arena chunk to which the offset belongs. + chunk: ^arena_chunk, + // The offset to rewind the arena to. + offset: u64, +} + +// Options for arena creation. +arena_options :: struct { + // The base allocator to use with this arena + base: ^base_allocator, + // The amount of memory to reserve up-front when creating the arena. + reserve: u64, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + // Initialize a memory arena. + arena_init :: proc(arena: ^arena) --- + // Initialize a memory arena with additional options. + arena_init_with_options :: proc(arena: ^arena, options: ^arena_options) --- + // Release all resources allocated to a memory arena. + arena_cleanup :: proc(arena: ^arena) --- + // Allocate a block of memory from an arena. + arena_push :: proc(arena: ^arena, size: u64) -> rawptr --- + // Allocate an aligned block of memory from an arena. + arena_push_aligned :: proc(arena: ^arena, size: u64, alignment: u32) -> rawptr --- + // Reset an arena. All memory that was previously allocated from this arena is released to the arena, and can be reallocated by later calls to `oc_arena_push` and similar functions. No memory is actually released _to the system_. + arena_clear :: proc(arena: ^arena) --- + // Begin a memory scope. This creates an `oc_arena_scope` object that stores the current offset of the arena. The arena can later be reset to that offset by calling `oc_arena_scope_end`, releasing all memory that was allocated within the scope to the arena. + arena_scope_begin :: proc(arena: ^arena) -> arena_scope --- + // End a memory scope. This resets an arena to the offset it had when the scope was created. All memory allocated within the scope is released back to the arena. + arena_scope_end :: proc(scope: arena_scope) --- + /* + Begin a scratch scope. This creates a memory scope on a per-thread, global "scratch" arena. This allows easily creating temporary memory for scratch computations or intermediate results, in a stack-like fashion. + + If you must return results in an arena passed by the caller, and you also use a scratch arena to do intermediate computations, beware that the results arena could itself be a scatch arena. In this case, you have to be careful not to intermingle your scratch computations with the final result, or clear your result entirely. You can either: + + - Allocate memory for the result upfront and call `oc_scratch_begin` afterwards, if possible. + - Use `oc_scratch_begin_next()` and pass it the result arena, to get a scratch arena that does not conflict with it. + */ + scratch_begin :: proc() -> arena_scope --- + // Begin a scratch scope that does not conflict with a given arena. See `oc_scratch_begin()` for more details about when to use this function. + scratch_begin_next :: proc(used: ^arena) -> arena_scope --- +} + +//////////////////////////////////////////////////////////////////////////////// +// String slices and string lists. +//////////////////////////////////////////////////////////////////////////////// + +// A type representing a string of bytes. +str8 :: string + +// A type representing an element of a string list. +str8_elt :: struct { + // The string element is linked into its parent string list through this field. + listElt: list_elt, + // The string for this element. + string: str8, +} + +// A type representing a string list. +str8_list :: struct { + // A linked-list of `oc_str8_elt`. + list: list, + // The number of elements in `list`. + eltCount: u64, + // The total length of the string list, which is the sum of the lengths over all elements. + len: u64, +} + +// A type describing a string of 16-bits characters (typically used for UTF-16). +str16 :: distinct []u16 + +// A type representing an element of an `oc_str16` list. +str16_elt :: struct { + // The string element is linked into its parent string list through this field. + listElt: list_elt, + // The string for this element. + string: str16, +} + +str16_list :: struct { + // A linked-list of `oc_str16_elt`. + list: list, + // The number of elements in `list`. + eltCount: u64, + // The total length of the string list, which is the sum of the lengths over all elements. + len: u64, +} + +// A type describing a string of 32-bits characters (typically used for UTF-32 codepoints). +str32 :: distinct []rune + +// A type representing an element of an `oc_str32` list. +str32_elt :: struct { + // The string element is linked into its parent string list through this field. + listElt: list_elt, + // The string for this element. + string: str32, +} + +str32_list :: struct { + // A linked-list of `oc_str32_elt`. + list: list, + // The number of elements in `list`. + eltCount: u64, + // The total length of the string list, which is the sum of the lengths over all elements. + len: u64, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + // Make a string from a bytes buffer and a length. + str8_from_buffer :: proc(len: u64, buffer: [^]char) -> str8 --- + // Make a string from a slice of another string. The resulting string designates some subsequence of the input string. + str8_slice :: proc(s: str8, start: u64, end: u64) -> str8 --- + // Pushes a copy of a buffer to an arena, and makes a string refering to that copy. + str8_push_buffer :: proc(arena: ^arena, len: u64, buffer: [^]char) -> str8 --- + // Pushes a copy of a C null-terminated string to an arena, and makes a string referring to that copy. + str8_push_cstring :: proc(arena: ^arena, str: cstring) -> str8 --- + // Copy the contents of a string on an arena and make a new string referring to the copied bytes. + str8_push_copy :: proc(arena: ^arena, s: str8) -> str8 --- + // Make a copy of a string slice. This function copies a subsequence of the input string onto an arena, and returns a new string referring to the copied content. + str8_push_slice :: proc(arena: ^arena, s: str8, start: u64, end: u64) -> str8 --- + // Lexicographically compare the contents of two strings. + str8_cmp :: proc(s1: str8, s2: str8) -> i32 --- + // Create a null-terminated C-string from an `oc_str8` string. + str8_to_cstring :: proc(arena: ^arena, string: str8) -> cstring --- + // Push a string element to the back of a string list. This creates a `oc_str8_elt` element referring to the contents of the input string, and links that element at the end of the string list. + str8_list_push :: proc(arena: ^arena, list: ^str8_list, str: str8) --- + // Build a string from a null-terminated format string an variadic arguments, and append it to a string list. + str8_list_pushf :: proc(arena: ^arena, list: ^str8_list, format: cstring, #c_vararg args: ..any) --- + // Build a string by combining the elements of a string list with a prefix, a suffix, and separators. + str8_list_collate :: proc(arena: ^arena, list: str8_list, prefix: str8, separator: str8, suffix: str8) -> str8 --- + // Build a string by joining the elements of a string list. + str8_list_join :: proc(arena: ^arena, list: str8_list) -> str8 --- + /* + Split a list into a string list according to separators. + + No string copies are made. The elements of the resulting string list refer to subsequences of the input string. + */ + str8_split :: proc(arena: ^arena, str: str8, separators: str8_list) -> str8_list --- + // Make an `oc_str16` string from a buffer of 16-bit characters. + str16_from_buffer :: proc(len: u64, buffer: [^]u16) -> str16 --- + // Make an `oc_str16` string from a slice of another `oc_str16` string. + str16_slice :: proc(s: str16, start: u64, end: u64) -> str16 --- + // Copy the content of a 16-bit character buffer on an arena and make a new `oc_str16` referencing the copied contents. + str16_push_buffer :: proc(arena: ^arena, len: u64, buffer: [^]u16) -> str16 --- + // Copy the contents of an `oc_str16` string and make a new string referencing the copied contents. + str16_push_copy :: proc(arena: ^arena, s: str16) -> str16 --- + // Copy a slice of an `oc_str16` string an make a new string referencing the copies contents. + str16_push_slice :: proc(arena: ^arena, s: str16, start: u64, end: u64) -> str16 --- + // Push a string element to the back of a string list. This creates a `oc_str16_elt` element referring to the contents of the input string, and links that element at the end of the string list. + str16_list_push :: proc(arena: ^arena, list: ^str16_list, str: str16) --- + // Build a string by joining the elements of a string list. + str16_list_join :: proc(arena: ^arena, list: str16_list) -> str16 --- + /* + Split a list into a string list according to separators. + + No string copies are made. The elements of the resulting string list refer to subsequences of the input string. + */ + str16_split :: proc(arena: ^arena, str: str16, separators: str16_list) -> str16_list --- + // Make an `oc_str32` string from a buffer of 32-bit characters. + str32_from_buffer :: proc(len: u64, buffer: [^]u32) -> str32 --- + // Make an `oc_str32` string from a slice of another `oc_str32` string. + str32_slice :: proc(s: str32, start: u64, end: u64) -> str32 --- + // Copy the content of a 32-bit character buffer on an arena and make a new `oc_str32` referencing the copied contents. + str32_push_buffer :: proc(arena: ^arena, len: u64, buffer: [^]u32) -> str32 --- + // Copy the contents of an `oc_str32` string and make a new string referencing the copied contents. + str32_push_copy :: proc(arena: ^arena, s: str32) -> str32 --- + // Copy a slice of an `oc_str32` string an make a new string referencing the copies contents. + str32_push_slice :: proc(arena: ^arena, s: str32, start: u64, end: u64) -> str32 --- + // Push a string element to the back of a string list. This creates a `oc_str32_elt` element referring to the contents of the input string, and links that element at the end of the string list. + str32_list_push :: proc(arena: ^arena, list: ^str32_list, str: str32) --- + // Build a string by joining the elements of a string list. + str32_list_join :: proc(arena: ^arena, list: str32_list) -> str32 --- + /* + Split a list into a string list according to separators. + + No string copies are made. The elements of the resulting string list refer to subsequences of the input string. + */ + str32_split :: proc(arena: ^arena, str: str32, separators: str32_list) -> str32_list --- +} + +//////////////////////////////////////////////////////////////////////////////// +// UTF8 encoding/decoding. +//////////////////////////////////////////////////////////////////////////////// + +// A unicode codepoint. +utf32 :: rune + +// A type representing the result of decoding of utf8-encoded codepoint. +utf8_dec :: struct { + // The decoded codepoint. + codepoint: utf32, + // The size of the utf8 sequence encoding that codepoint. + size: u32, +} + +// A type representing a contiguous range of unicode codepoints. +unicode_range :: struct { + // The first codepoint of the range. + firstCodePoint: utf32, + // The number of codepoints in the range. + count: u32, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + // Get the size of a utf8-encoded codepoint for the first byte of the encoded sequence. + utf8_size_from_leading_char :: proc(leadingChar: char) -> u32 --- + // Get the size of the utf8 encoding of a codepoint. + utf8_codepoint_size :: proc(codePoint: utf32) -> u32 --- + utf8_codepoint_count_for_string :: proc(string: str8) -> u64 --- + // Get the length of the utf8 encoding of a sequence of unicode codepoints. + utf8_byte_count_for_codepoints :: proc(codePoints: str32) -> u64 --- + // Get the offset of the next codepoint after a given offset, in a utf8 encoded string. + utf8_next_offset :: proc(string: str8, byteOffset: u64) -> u64 --- + // Get the offset of the previous codepoint before a given offset, in a utf8 encoded string. + utf8_prev_offset :: proc(string: str8, byteOffset: u64) -> u64 --- + // Decode a utf8 encoded codepoint. + utf8_decode :: proc(string: str8) -> utf8_dec --- + // Decode a codepoint at a given offset in a utf8 encoded string. + utf8_decode_at :: proc(string: str8, offset: u64) -> utf8_dec --- + // Encode a unicode codepoint into a utf8 sequence. + utf8_encode :: proc(dst: cstring, codePoint: utf32) -> str8 --- + // Decode a utf8 string to a string of unicode codepoints using memory passed by the caller. + utf8_to_codepoints :: proc(maxCount: u64, backing: ^utf32, string: str8) -> str32 --- + // Encode a string of unicode codepoints into a utf8 string using memory passed by the caller. + utf8_from_codepoints :: proc(maxBytes: u64, backing: cstring, codePoints: str32) -> str8 --- + // Decode a utf8 encoded string to a string of unicode codepoints using an arena. + utf8_push_to_codepoints :: proc(arena: ^arena, string: str8) -> str32 --- + // Encode a string of unicode codepoints into a utf8 string using an arena. + utf8_push_from_codepoints :: proc(arena: ^arena, codePoints: str32) -> str8 --- +} + +//////////////////////////////////////////////////////////////////////////////// +// Input, windowing, dialogs. +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Application events. +//////////////////////////////////////////////////////////////////////////////// + +// This enum defines the type events that can be sent to the application by the runtime. This determines which member of the `oc_event` union field is active. +event_type :: enum u32 { + // No event. That could be used simply to wake up the application. + NONE = 0, + // A modifier key event. This event is sent when a key such as Alt, Control, Command or Shift are pressed, released, or repeated. The `key` field contains the event's details. + KEYBOARD_MODS = 1, + // A key event. This event is sent when a normal key is pressed, released, or repeated. The `key` field contains the event's details. + KEYBOARD_KEY = 2, + // A character input event. This event is sent when an input character is produced by the keyboard. The `character` field contains the event's details. + KEYBOARD_CHAR = 3, + // A mouse button event. This is event sent when one of the mouse buttons is pressed, released, or clicked. The `key` field contains the event's details. + MOUSE_BUTTON = 4, + // A mouse move event. This is event sent when the mouse is moved. The `mouse` field contains the event's details. + MOUSE_MOVE = 5, + // A mouse wheel event. This is event sent when the mouse wheel is moved (or when a trackpad is scrolled). The `mouse` field contains the event's details. + MOUSE_WHEEL = 6, + // A mouse enter event. This event is sent when the mouse enters the application's window. The `mouse` field contains the event's details. + MOUSE_ENTER = 7, + // A mouse leave event. This event is sent when the mouse leaves the application's window. + MOUSE_LEAVE = 8, + // A clipboard paste event. This event is sent when the user uses the paste shortcut while the application window has focus. + CLIPBOARD_PASTE = 9, + // A resize event. This event is sent when the application's window is resized. The `move` field contains the event's details. + WINDOW_RESIZE = 10, + // A move event. This event is sent when the window is moved. The `move` field contains the event's details. + WINDOW_MOVE = 11, + // A focus event. This event is sent when the application gains focus. + WINDOW_FOCUS = 12, + // An unfocus event. This event is sent when the application looses focus. + WINDOW_UNFOCUS = 13, + // A hide event. This event is sent when the application's window is hidden or minimized. + WINDOW_HIDE = 14, + // A show event. This event is sent when the application's window is shown or de-minimized. + WINDOW_SHOW = 15, + // A close event. This event is sent when the window is about to be closed. + WINDOW_CLOSE = 16, + // A path drop event. This event is sent when the user drops files onto the application's window. The `paths` field contains the event's details. + PATHDROP = 17, + // A frame event. This event is sent when the application should render a frame. + FRAME = 18, + // A quit event. This event is sent when the application has been requested to quit. + QUIT = 19, +} + +// This enum describes the actions that can happen to a key. +key_action :: enum u32 { + // No action happened on that key. + NO_ACTION = 0, + // The key was pressed. + PRESS = 1, + // The key was released. + RELEASE = 2, + // The key was maintained pressed at least for the system's key repeat period. + REPEAT = 3, +} + +// A code representing a key's physical location. This is independent of the system's keyboard layout. +scan_code :: enum u32 { + UNKNOWN = 0, + SPACE = 32, + APOSTROPHE = 39, + COMMA = 44, + MINUS = 45, + PERIOD = 46, + SLASH = 47, + _0 = 48, + _1 = 49, + _2 = 50, + _3 = 51, + _4 = 52, + _5 = 53, + _6 = 54, + _7 = 55, + _8 = 56, + _9 = 57, + SEMICOLON = 59, + EQUAL = 61, + LEFT_BRACKET = 91, + BACKSLASH = 92, + RIGHT_BRACKET = 93, + GRAVE_ACCENT = 96, + A = 97, + B = 98, + C = 99, + D = 100, + E = 101, + F = 102, + G = 103, + H = 104, + I = 105, + J = 106, + K = 107, + L = 108, + M = 109, + N = 110, + O = 111, + P = 112, + Q = 113, + R = 114, + S = 115, + T = 116, + U = 117, + V = 118, + W = 119, + X = 120, + Y = 121, + Z = 122, + WORLD_1 = 161, + WORLD_2 = 162, + ESCAPE = 256, + ENTER = 257, + TAB = 258, + BACKSPACE = 259, + INSERT = 260, + DELETE = 261, + RIGHT = 262, + LEFT = 263, + DOWN = 264, + UP = 265, + PAGE_UP = 266, + PAGE_DOWN = 267, + HOME = 268, + END = 269, + CAPS_LOCK = 280, + SCROLL_LOCK = 281, + NUM_LOCK = 282, + PRINT_SCREEN = 283, + PAUSE = 284, + F1 = 290, + F2 = 291, + F3 = 292, + F4 = 293, + F5 = 294, + F6 = 295, + F7 = 296, + F8 = 297, + F9 = 298, + F10 = 299, + F11 = 300, + F12 = 301, + F13 = 302, + F14 = 303, + F15 = 304, + F16 = 305, + F17 = 306, + F18 = 307, + F19 = 308, + F20 = 309, + F21 = 310, + F22 = 311, + F23 = 312, + F24 = 313, + F25 = 314, + KP_0 = 320, + KP_1 = 321, + KP_2 = 322, + KP_3 = 323, + KP_4 = 324, + KP_5 = 325, + KP_6 = 326, + KP_7 = 327, + KP_8 = 328, + KP_9 = 329, + KP_DECIMAL = 330, + KP_DIVIDE = 331, + KP_MULTIPLY = 332, + KP_SUBTRACT = 333, + KP_ADD = 334, + KP_ENTER = 335, + KP_EQUAL = 336, + LEFT_SHIFT = 340, + LEFT_CONTROL = 341, + LEFT_ALT = 342, + LEFT_SUPER = 343, + RIGHT_SHIFT = 344, + RIGHT_CONTROL = 345, + RIGHT_ALT = 346, + RIGHT_SUPER = 347, + MENU = 348, + COUNT = 349, +} + +// A code identifying a key. The physical location of the key corresponding to a given key code depends on the system's keyboard layout. +key_code :: enum u32 { + UNKNOWN = 0, + SPACE = 32, + APOSTROPHE = 39, + COMMA = 44, + MINUS = 45, + PERIOD = 46, + SLASH = 47, + _0 = 48, + _1 = 49, + _2 = 50, + _3 = 51, + _4 = 52, + _5 = 53, + _6 = 54, + _7 = 55, + _8 = 56, + _9 = 57, + SEMICOLON = 59, + EQUAL = 61, + LEFT_BRACKET = 91, + BACKSLASH = 92, + RIGHT_BRACKET = 93, + GRAVE_ACCENT = 96, + A = 97, + B = 98, + C = 99, + D = 100, + E = 101, + F = 102, + G = 103, + H = 104, + I = 105, + J = 106, + K = 107, + L = 108, + M = 109, + N = 110, + O = 111, + P = 112, + Q = 113, + R = 114, + S = 115, + T = 116, + U = 117, + V = 118, + W = 119, + X = 120, + Y = 121, + Z = 122, + WORLD_1 = 161, + WORLD_2 = 162, + ESCAPE = 256, + ENTER = 257, + TAB = 258, + BACKSPACE = 259, + INSERT = 260, + DELETE = 261, + RIGHT = 262, + LEFT = 263, + DOWN = 264, + UP = 265, + PAGE_UP = 266, + PAGE_DOWN = 267, + HOME = 268, + END = 269, + CAPS_LOCK = 280, + SCROLL_LOCK = 281, + NUM_LOCK = 282, + PRINT_SCREEN = 283, + PAUSE = 284, + F1 = 290, + F2 = 291, + F3 = 292, + F4 = 293, + F5 = 294, + F6 = 295, + F7 = 296, + F8 = 297, + F9 = 298, + F10 = 299, + F11 = 300, + F12 = 301, + F13 = 302, + F14 = 303, + F15 = 304, + F16 = 305, + F17 = 306, + F18 = 307, + F19 = 308, + F20 = 309, + F21 = 310, + F22 = 311, + F23 = 312, + F24 = 313, + F25 = 314, + KP_0 = 320, + KP_1 = 321, + KP_2 = 322, + KP_3 = 323, + KP_4 = 324, + KP_5 = 325, + KP_6 = 326, + KP_7 = 327, + KP_8 = 328, + KP_9 = 329, + KP_DECIMAL = 330, + KP_DIVIDE = 331, + KP_MULTIPLY = 332, + KP_SUBTRACT = 333, + KP_ADD = 334, + KP_ENTER = 335, + KP_EQUAL = 336, + LEFT_SHIFT = 340, + LEFT_CONTROL = 341, + LEFT_ALT = 342, + LEFT_SUPER = 343, + RIGHT_SHIFT = 344, + RIGHT_CONTROL = 345, + RIGHT_ALT = 346, + RIGHT_SUPER = 347, + MENU = 348, + COUNT = 349, +} + +keymod_flag :: enum u32 { + ALT = 0, + SHIFT, + CTRL, + CMD, + MAIN_MODIFIER, +} +keymod_flags :: bit_set[keymod_flag; u32] + +// A code identifying a mouse button. +mouse_button :: enum u32 { + LEFT = 0, + RIGHT = 1, + MIDDLE = 2, + EXT1 = 3, + EXT2 = 4, + BUTTON_COUNT = 5, +} + +// A structure describing a key event or a mouse button event. +key_event :: struct { + // The action that was done on the key. + action: key_action, + // The scan code of the key. Only valid for key events. + scanCode: scan_code, + // The key code of the key. Only valid for key events. + keyCode: key_code, + // The button of the mouse. Only valid for mouse button events. + button: mouse_button, + // Modifier flags indicating which modifier keys where pressed at the time of the event. + mods: keymod_flags, + // The number of clicks that where detected for the button. Only valid for mouse button events. + clickCount: u8, +} + +// A structure describing a character input event. +char_event :: struct { + // The unicode codepoint of the character. + codepoint: utf32, + // The utf8 sequence of the character. + sequence: [8]char, + // The utf8 sequence length. + seqLen: u8, +} + +// A structure describing a mouse move or a mouse wheel event. Mouse coordinates have their origin at the top-left corner of the window, with the y axis going down. +mouse_event :: struct { + // The x coordinate of the mouse. + x: f32, + // The y coordinate of the mouse. + y: f32, + // The delta from the last x coordinate of the mouse, or the scroll value along the x coordinate. + deltaX: f32, + // The delta from the last y coordinate of the mouse, or the scoll value along the y coordinate. + deltaY: f32, + // Modifier flags indicating which modifier keys where pressed at the time of the event. + mods: keymod_flags, +} + +// A structure describing a window move or resize event. +move_event :: struct { + // The position and dimension of the frame rectangle, i.e. including the window title bar and border. + frame: rect, + // The position and dimension of the content rectangle, relative to the frame rectangle. + content: rect, +} + +// A structure describing an event sent to the application. +event :: struct { + // The window in which this event happened. + window: window, + // The type of the event. This determines which member of the event union is active. + type: event_type, + using _: struct #raw_union { + key: key_event, + character: char_event, + mouse: mouse_event, + move: move_event, + paths: str8_list, + }, +} + +// This enum describes the kinds of possible file dialogs. +file_dialog_kind :: enum u32 { + // The file dialog is a save dialog. + SAVE = 0, + // The file dialog is an open dialog. + OPEN = 1, +} + +// A type for flags describing various file dialog options. +// File dialog flags. +file_dialog_flag :: enum u32 { + // This dialog allows selecting files. + FILES = 1, + // This dialog allows selecting directories. + DIRECTORIES, + // This dialog allows selecting multiple items. + MULTIPLE, + // This dialog allows creating directories. + CREATE_DIRECTORIES, +} +file_dialog_flags :: bit_set[file_dialog_flag; u32] + +// A structure describing a file dialog. +file_dialog_desc :: struct { + // The kind of file dialog, see `oc_file_dialog_kind`. + kind: file_dialog_kind, + // A combination of file dialog flags used to enable file dialog options. + flags: file_dialog_flags, + // The title of the dialog, displayed in the dialog title bar. + title: str8, + // Optional. The label of the OK button, e.g. "Save" or "Open". + okLabel: str8, + // Optional. A file handle to the root directory for the dialog. If set to zero, the root directory is the application's default data directory. + startAt: file, + // Optional. The path of the starting directory of the dialog, relative to its root directory. If set to nil, the dialog starts at its root directory. + startPath: str8, + // A list of file extensions used to restrict which files can be selected in this dialog. An empty list allows all files to be selected. Extensions should be provided without a leading dot. + filters: str8_list, +} + +// An enum identifying the button clicked by the user when a file dialog returns. +file_dialog_button :: enum u32 { + // The user clicked the "Cancel" button, or closed the dialog box. + CANCEL = 0, + // The user clicked the "OK" button. + OK = 1, +} + +// A structure describing the result of a file dialog. +file_dialog_result :: struct { + // The button clicked by the user. + button: file_dialog_button, + // The path that was selected when the user clicked the OK button. If the dialog box had the `OC_FILE_DIALOG_MULTIPLE` flag set, this is the first file of the list of selected paths. + path: str8, + // If the dialog box had the `OC_FILE_DIALOG_MULTIPLE` flag set and the user clicked the OK button, this list contains the selected paths. + selection: str8_list, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + // Set the title of the application's window. + window_set_title :: proc(title: str8) --- + // Set the size of the application's window. + window_set_size :: proc(size: vec2) --- + // Request the system to quit the application. + request_quit :: proc() --- + // Convert a scancode to a keycode, according to current keyboard layout. + scancode_to_keycode :: proc(scanCode: scan_code) -> key_code --- + // Put a string in the clipboard. + clipboard_set_string :: proc(string: str8) --- +} + +//////////////////////////////////////////////////////////////////////////////// +// File input/output. +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// API for opening, reading and writing files. +//////////////////////////////////////////////////////////////////////////////// + +// An opaque handle identifying an opened file. +file :: distinct u64 + +// The type of file open flags describing file open options. +// Flags for the `oc_file_open()` function. +file_open_flag :: enum u16 { + // Open the file in 'append' mode. All writes append data at the end of the file. + APPEND = 1, + // Truncate the file to 0 bytes when opening. + TRUNCATE, + // Create the file if it does not exist. + CREATE, + // If the file is a symlink, open the symlink itself instead of following it. + SYMLINK, + // If the file is a symlink, the call to open will fail. + NO_FOLLOW, + // Reserved. + RESTRICT, +} +file_open_flags :: bit_set[file_open_flag; u16] + +// This enum describes the access permissions of a file handle. +file_access_flag :: enum u16 { + // The file handle can be used for reading from the file. + READ = 1, + // The file handle can be used for writing to the file. + WRITE, +} +file_access :: bit_set[file_access_flag; u16] + +// This enum is used in `oc_file_seek()` to specify the starting point of the seek operation. +file_whence :: enum u32 { + // Set the file position relative to the beginning of the file. + SET = 0, + // Set the file position relative to the end of the file. + END = 1, + // Set the file position relative to the current position. + CURRENT = 2, +} + +// A type used to identify I/O requests. +io_req_id :: u64 + +// A type used to identify I/O operations. +// This enum declares all I/O operations. +io_op :: enum u32 { + // ['Open a file at a path relative to a given root directory.', '', " - `handle` is the handle to the root directory. If it is nil, the application's default directory is used.", ' - `size` is the size of the path, in bytes.', ' - `buffer` points to an array containing the path of the file to open, relative to the directory identified by `handle`.', ' - `open` contains the permissions and flags for the open operation.'] + OPEN_AT = 0, + // ['Close a file handle.', '', ' - `handle` is the handle to close.'] + CLOSE = 1, + // ['Get status information for a file handle.', '', ' - `handle` is the handle to stat.', ' - `size` is the size of the result buffer. It should be at least `sizeof(oc_file_status)`.', ' - `buffer` is the result buffer.'] + FSTAT = 2, + // ['Move the file position in a file.', '', ' - `handle` is the handle of the file.', ' - `offset` specifies the offset of the new position, relative to the base position specified by `whence`.', ' - `whence` determines the base position for the seek operation.'] + SEEK = 3, + // ['Read data from a file.', '', ' - `handle` is the handle of the file.', ' - `size` is the number of bytes to read.', ' - `buffer` is the result buffer. It should be big enough to hold `size` bytes.'] + READ = 4, + // ['Write data to a file.', '', ' - `handle` is the handle of the file.', ' - `size` is the number of bytes to write.', ' - `buffer` contains the data to write to the file.'] + WRITE = 5, + // ['Get the error attached to a file handle.', '', ' - `handle` is the handle of the file.'] + OC_OC_IO_ERROR = 6, +} + +// A structure describing an I/O request. +io_req :: struct { + // An identifier for the request. You can set this to any value you want. It is passed back in the `oc_io_cmp` completion and can be used to match requests and completions. + id: io_req_id, + // The requested operation. + op: io_op, + // A file handle used by some operations. + handle: file, + // An offset used by some operations. + offset: i64, + // A size indicating the capacity of the buffer pointed to by `buffer`, in bytes. + size: u64, + using _: struct #raw_union { + buffer: [^]char, + unused: u64, + }, + using _: struct #raw_union { + open: struct { + // The access permissions requested on the file to open. + rights: file_access, + // The options to use when opening the file. + flags: file_open_flags, + }, + whence: file_whence, + }, +} + +// A type identifying an I/O error. +// This enum declares all I/O error values. +io_error :: enum u32 { + // No error. + OK = 0, + // An unexpected error happened. + UNKNOWN = 1, + // The request had an invalid operation. + OP = 2, + // The request had an invalid handle. + HANDLE = 3, + // The operation was not carried out because the file handle has previous errors. + PREV = 4, + // The request contained wrong arguments. + ARG = 5, + // The operation requires permissions that the file handle doesn't have. + PERM = 6, + // The operation couldn't complete due to a lack of space in the result buffer. + SPACE = 7, + // One of the directory in the path does not exist or couldn't be traversed. + NO_ENTRY = 8, + // The file already exists. + EXISTS = 9, + // The file is not a directory. + NOT_DIR = 10, + // The file is a directory. + DIR = 11, + // There are too many opened files. + MAX_FILES = 12, + // The path contains too many symbolic links (this may be indicative of a symlink loop). + MAX_LINKS = 13, + // The path is too long. + PATH_LENGTH = 14, + // The file is too large. + FILE_SIZE = 15, + // The file is too large to be opened. + OVERFLOW = 16, + // The file is locked or the device on which it is stored is not ready. + NOT_READY = 17, + // The system is out of memory. + MEM = 18, + // The operation was interrupted by a signal. + INTERRUPT = 19, + // A physical error happened. + PHYSICAL = 20, + // The device on which the file is stored was not found. + NO_DEVICE = 21, + // One element along the path is outside the root directory subtree. + WALKOUT = 22, +} + +// A structure describing the completion of an I/O operation. +io_cmp :: struct { + // The request ID as passed in the `oc_io_req` request that generated this completion. + id: io_req_id, + // The error value for the operation. + error: io_error, + using _: struct #raw_union { + result: i64, + size: u64, + offset: i64, + handle: file, + }, +} + +// An enum identifying the type of a file. +file_type :: enum u32 { + // The file is of unknown type. + UNKNOWN = 0, + // The file is a regular file. + REGULAR = 1, + // The file is a directory. + DIRECTORY = 2, + // The file is a symbolic link. + SYMLINK = 3, + // The file is a block device. + BLOCK = 4, + // The file is a character device. + CHARACTER = 5, + // The file is a FIFO pipe. + FIFO = 6, + // The file is a socket. + SOCKET = 7, +} + +// A type describing file permissions. +file_perm_flag :: enum u16 { + OTHER_EXEC = 1, + OTHER_WRITE, + OTHER_READ, + GROUP_EXEC, + GROUP_WRITE, + GROUP_READ, + OWNER_EXEC, + OWNER_WRITE, + OWNER_READ, + STICKY_BIT, + SET_GID, + SET_UID, +} +file_perm :: bit_set[file_perm_flag; u16] + +datestamp :: struct { + seconds: i64, + fraction: u64, +} + +file_status :: struct { + uid: u64, + type: file_type, + perm: file_perm, + size: u64, + creationDate: datestamp, + accessDate: datestamp, + modificationDate: datestamp, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + // Send a single I/O request and wait for its completion. + io_wait_single_req :: proc(req: ^io_req) -> io_cmp --- + // Returns a `nil` file handle + file_nil :: proc() -> file --- + // Test if a file handle is `nil`. + file_is_nil :: proc(handle: file) -> bool --- + // Open a file in the applications' default directory subtree. + file_open :: proc(path: str8, rights: file_access, flags: file_open_flags) -> file --- + // Open a file in a given directory's subtree. + file_open_at :: proc(dir: file, path: str8, rights: file_access, flags: file_open_flags) -> file --- + // Close a file. + file_close :: proc(file: file) --- + // Get the current position in a file. + file_pos :: proc(file: file) -> i64 --- + // Set the current position in a file. + file_seek :: proc(file: file, offset: i64, whence: file_whence) -> i64 --- + // Write data to a file. + file_write :: proc(file: file, size: u64, buffer: [^]char) -> u64 --- + // Read from a file. + file_read :: proc(file: file, size: u64, buffer: [^]char) -> u64 --- + // Get the last error on a file handle. + file_last_error :: proc(handle: file) -> io_error --- + file_get_status :: proc(file: file) -> file_status --- + file_size :: proc(file: file) -> u64 --- + file_open_with_request :: proc(path: str8, rights: file_access, flags: file_open_flags) -> file --- +} + +//////////////////////////////////////////////////////////////////////////////// +// API for obtaining file capabilities through open/save dialogs. +//////////////////////////////////////////////////////////////////////////////// + +// An element of a list of file handles acquired through a file dialog. +file_open_with_dialog_elt :: struct { + listElt: list_elt, + file: file, +} + +// A structure describing the result of a call to `oc_file_open_with_dialog()`. +file_open_with_dialog_result :: struct { + // The button of the file dialog clicked by the user. + button: file_dialog_button, + // The file that was opened through the dialog. If the dialog had the `OC_FILE_DIALOG_MULTIPLE` flag set, this is equal to the first handle in the `selection` list. + file: file, + // If the dialog had the `OC_FILE_DIALOG_MULTIPLE` flag set, this list of `oc_file_open_with_dialog_elt` contains the handles of the opened files. + selection: list, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + // Open files through a file dialog. This allows the user to select files outside the root directories currently accessible to the applications, giving them a way to provide new file capabilities to the application. + file_open_with_dialog :: proc(arena: ^arena, rights: file_access, flags: file_open_flags, desc: ^file_dialog_desc) -> file_open_with_dialog_result --- +} + +//////////////////////////////////////////////////////////////////////////////// +// API for handling filesystem paths. +//////////////////////////////////////////////////////////////////////////////// + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + // Get a string slice of the directory part of a path. + path_slice_directory :: proc(path: str8) -> str8 --- + // Get a string slice of the file name part of a path. + path_slice_filename :: proc(path: str8) -> str8 --- + // Split a path into path elements. + path_split :: proc(arena: ^arena, path: str8) -> str8_list --- + // Join path elements to form a path. + path_join :: proc(arena: ^arena, elements: str8_list) -> str8 --- + // Append a path to another path. + path_append :: proc(arena: ^arena, parent: str8, relPath: str8) -> str8 --- + // Test wether a path is an absolute path. + path_is_absolute :: proc(path: str8) -> bool --- +} + +//////////////////////////////////////////////////////////////////////////////// +// 2D/3D rendering APIs. +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// A 2D Vector Graphics API. +//////////////////////////////////////////////////////////////////////////////// + +surface :: distinct u64 + +canvas_renderer :: distinct u64 + +canvas_context :: distinct u64 + +font :: distinct u64 + +image :: distinct u64 + +gradient_blend_space :: enum u32 { + LINEAR = 0, + SRGB = 1, +} + +color_space :: enum u32 { + RGB = 0, + SRGB = 1, +} + +color :: struct { using c: [4]f32, colorSpace: color_space } + +joint_type :: enum u32 { + MITER = 0, + BEVEL = 1, + NONE = 2, +} + +cap_type :: enum u32 { + NONE = 0, + SQUARE = 1, +} + +font_metrics :: struct { + ascent: f32, + descent: f32, + lineGap: f32, + xHeight: f32, + capHeight: f32, + width: f32, +} + +glyph_metrics :: struct { + ink: rect, + advance: vec2, +} + +text_metrics :: struct { + ink: rect, + logical: rect, + advance: vec2, +} + +rect_atlas :: struct {} + +image_region :: struct { + image: image, + rect: rect, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + surface_nil :: proc() -> surface --- + surface_is_nil :: proc(surface: surface) -> bool --- + surface_destroy :: proc(surface: surface) --- + surface_get_size :: proc(surface: surface) -> vec2 --- + surface_contents_scaling :: proc(surface: surface) -> vec2 --- + surface_bring_to_front :: proc(surface: surface) --- + surface_send_to_back :: proc(surface: surface) --- + surface_get_hidden :: proc(surface: surface) -> bool --- + surface_set_hidden :: proc(surface: surface, hidden: bool) --- + color_rgba :: proc(r: f32, g: f32, b: f32, a: f32) -> color --- + color_srgba :: proc(r: f32, g: f32, b: f32, a: f32) -> color --- + color_convert :: proc(_color: color, colorSpace: color_space) -> color --- + canvas_renderer_nil :: proc() -> canvas_renderer --- + canvas_renderer_is_nil :: proc(renderer: canvas_renderer) -> bool --- + canvas_renderer_create :: proc() -> canvas_renderer --- + canvas_renderer_destroy :: proc(renderer: canvas_renderer) --- + canvas_render :: proc(renderer: canvas_renderer, _context: canvas_context, surface: surface) --- + canvas_present :: proc(renderer: canvas_renderer, surface: surface) --- + canvas_surface_create :: proc(renderer: canvas_renderer) -> surface --- + canvas_surface_swap_interval :: proc(surface: surface, swap: i32) --- + canvas_context_nil :: proc() -> canvas_context --- + canvas_context_is_nil :: proc(_context: canvas_context) -> bool --- + canvas_context_create :: proc() -> canvas_context --- + canvas_context_destroy :: proc(_context: canvas_context) --- + canvas_context_select :: proc(_context: canvas_context) -> canvas_context --- + canvas_context_set_msaa_sample_count :: proc(_context: canvas_context, sampleCount: u32) --- + font_nil :: proc() -> font --- + font_is_nil :: proc(font: font) -> bool --- + font_create_from_memory :: proc(mem: str8, rangeCount: u32, ranges: ^unicode_range) -> font --- + font_create_from_file :: proc(file: file, rangeCount: u32, ranges: ^unicode_range) -> font --- + font_create_from_path :: proc(path: str8, rangeCount: u32, ranges: ^unicode_range) -> font --- + font_destroy :: proc(font: font) --- + font_get_glyph_indices :: proc(font: font, codePoints: str32, backing: str32) -> str32 --- + font_push_glyph_indices :: proc(arena: ^arena, font: font, codePoints: str32) -> str32 --- + font_get_glyph_index :: proc(font: font, codePoint: utf32) -> u32 --- + font_get_metrics :: proc(font: font, emSize: f32) -> font_metrics --- + font_get_metrics_unscaled :: proc(font: font) -> font_metrics --- + font_get_scale_for_em_pixels :: proc(font: font, emSize: f32) -> f32 --- + font_text_metrics_utf32 :: proc(font: font, fontSize: f32, codepoints: str32) -> text_metrics --- + font_text_metrics :: proc(font: font, fontSize: f32, text: str8) -> text_metrics --- + image_nil :: proc() -> image --- + image_is_nil :: proc(a: image) -> bool --- + image_create :: proc(renderer: canvas_renderer, width: u32, height: u32) -> image --- + image_create_from_rgba8 :: proc(renderer: canvas_renderer, width: u32, height: u32, pixels: [^]u8) -> image --- + image_create_from_memory :: proc(renderer: canvas_renderer, mem: str8, flip: bool) -> image --- + image_create_from_file :: proc(renderer: canvas_renderer, file: file, flip: bool) -> image --- + image_create_from_path :: proc(renderer: canvas_renderer, path: str8, flip: bool) -> image --- + image_destroy :: proc(image: image) --- + image_upload_region_rgba8 :: proc(image: image, region: rect, pixels: [^]u8) --- + image_size :: proc(image: image) -> vec2 --- + rect_atlas_create :: proc(arena: ^arena, width: i32, height: i32) -> ^rect_atlas --- + rect_atlas_alloc :: proc(atlas: ^rect_atlas, width: i32, height: i32) -> rect --- + rect_atlas_recycle :: proc(atlas: ^rect_atlas, rect: rect) --- + image_atlas_alloc_from_rgba8 :: proc(atlas: ^rect_atlas, backingImage: image, width: u32, height: u32, pixels: [^]u8) -> image_region --- + image_atlas_alloc_from_memory :: proc(atlas: ^rect_atlas, backingImage: image, mem: str8, flip: bool) -> image_region --- + image_atlas_alloc_from_file :: proc(atlas: ^rect_atlas, backingImage: image, file: file, flip: bool) -> image_region --- + image_atlas_alloc_from_path :: proc(atlas: ^rect_atlas, backingImage: image, path: str8, flip: bool) -> image_region --- + image_atlas_recycle :: proc(atlas: ^rect_atlas, imageRgn: image_region) --- + matrix_push :: proc(_matrix: mat2x3) --- + matrix_multiply_push :: proc(_matrix: mat2x3) --- + matrix_pop :: proc() --- + matrix_top :: proc() -> mat2x3 --- + clip_push :: proc(x: f32, y: f32, w: f32, h: f32) --- + clip_pop :: proc() --- + clip_top :: proc() -> rect --- + set_color :: proc(_color: color) --- + set_color_rgba :: proc(r: f32, g: f32, b: f32, a: f32) --- + set_color_srgba :: proc(r: f32, g: f32, b: f32, a: f32) --- + set_gradient :: proc(blendSpace: gradient_blend_space, bottomLeft: color, bottomRight: color, topRight: color, topLeft: color) --- + set_width :: proc(width: f32) --- + set_tolerance :: proc(tolerance: f32) --- + set_joint :: proc(joint: joint_type) --- + set_max_joint_excursion :: proc(maxJointExcursion: f32) --- + set_cap :: proc(cap: cap_type) --- + set_font :: proc(font: font) --- + set_font_size :: proc(size: f32) --- + set_text_flip :: proc(flip: bool) --- + set_image :: proc(image: image) --- + set_image_source_region :: proc(region: rect) --- + get_color :: proc() -> color --- + get_width :: proc() -> f32 --- + get_tolerance :: proc() -> f32 --- + get_joint :: proc() -> joint_type --- + get_max_joint_excursion :: proc() -> f32 --- + get_cap :: proc() -> cap_type --- + get_font :: proc() -> font --- + get_font_size :: proc() -> f32 --- + get_text_flip :: proc() -> bool --- + get_image :: proc() -> image --- + get_image_source_region :: proc() -> rect --- + get_position :: proc() -> vec2 --- + move_to :: proc(x: f32, y: f32) --- + line_to :: proc(x: f32, y: f32) --- + quadratic_to :: proc(x1: f32, y1: f32, x2: f32, y2: f32) --- + cubic_to :: proc(x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) --- + close_path :: proc() --- + glyph_outlines :: proc(glyphIndices: str32) -> rect --- + codepoints_outlines :: proc(string: str32) --- + text_outlines :: proc(string: str8) --- + clear :: proc() --- + fill :: proc() --- + stroke :: proc() --- + rectangle_fill :: proc(x: f32, y: f32, w: f32, h: f32) --- + rectangle_stroke :: proc(x: f32, y: f32, w: f32, h: f32) --- + rounded_rectangle_fill :: proc(x: f32, y: f32, w: f32, h: f32, r: f32) --- + rounded_rectangle_stroke :: proc(x: f32, y: f32, w: f32, h: f32, r: f32) --- + ellipse_fill :: proc(x: f32, y: f32, rx: f32, ry: f32) --- + ellipse_stroke :: proc(x: f32, y: f32, rx: f32, ry: f32) --- + circle_fill :: proc(x: f32, y: f32, r: f32) --- + circle_stroke :: proc(x: f32, y: f32, r: f32) --- + arc :: proc(x: f32, y: f32, r: f32, arcAngle: f32, startAngle: f32) --- + text_fill :: proc(x: f32, y: f32, text: str8) --- + image_draw :: proc(image: image, rect: rect) --- + image_draw_region :: proc(image: image, srcRegion: rect, dstRegion: rect) --- +} + +//////////////////////////////////////////////////////////////////////////////// +// A surface for rendering using the GLES API. +//////////////////////////////////////////////////////////////////////////////// + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + gles_surface_create :: proc() -> surface --- + gles_surface_make_current :: proc(surface: surface) --- + gles_surface_swap_interval :: proc(surface: surface, interval: i32) --- + gles_surface_swap_buffers :: proc(surface: surface) --- +} + +//////////////////////////////////////////////////////////////////////////////// +// Graphical User Interface API. +//////////////////////////////////////////////////////////////////////////////// + +key_state :: struct { + lastUpdate: u64, + transitionCount: u32, + repeatCount: u32, + down: bool, + sysClicked: bool, + sysDoubleClicked: bool, + sysTripleClicked: bool, +} + +keyboard_state :: struct { + keys: [349]key_state, + mods: keymod_flags, +} + +mouse_state :: struct { + lastUpdate: u64, + posValid: bool, + pos: vec2, + delta: vec2, + wheel: vec2, + using _: struct #raw_union { + buttons: [5]key_state, + using _: struct { + left: key_state, + right: key_state, + middle: key_state, + ext1: key_state, + ext2: key_state, + }, + }, +} + +BACKING_SIZE :: 64 + +text_state :: struct { + lastUpdate: u64, + backing: [64]utf32, + codePoints: str32, +} + +clipboard_state :: struct { + lastUpdate: u64, + pastedText: str8, +} + +input_state :: struct { + frameCounter: u64, + keyboard: keyboard_state, + mouse: mouse_state, + text: text_state, + clipboard: clipboard_state, +} + +ui_key :: struct { + hash: u64, +} + +ui_axis :: enum u32 { + X = 0, + Y = 1, + COUNT = 2, +} + +ui_align :: enum u32 { + START = 0, + END = 1, + CENTER = 2, +} + +ui_layout_align :: [2]ui_align + +ui_layout :: struct { + axis: ui_axis, + spacing: f32, + margin: [2]f32, + align: ui_layout_align, +} + +ui_size_kind :: enum u32 { + TEXT = 0, + PIXELS = 1, + CHILDREN = 2, + PARENT = 3, + PARENT_MINUS_PIXELS = 4, +} + +ui_size :: struct { + kind: ui_size_kind, + value: f32, + relax: f32, + minSize: f32, +} + +ui_box_size :: [2]ui_size + +ui_box_floating :: [2]bool + +ui_style :: struct { + size: ui_box_size, + layout: ui_layout, + floating: ui_box_floating, + floatTarget: vec2, + _color: color, + bgColor: color, + borderColor: color, + font: font, + fontSize: f32, + borderSize: f32, + roundness: f32, + animationTime: f32, + animationMask: ui_style_mask, +} + +ui_palette :: struct { + red0: color, + red1: color, + red2: color, + red3: color, + red4: color, + red5: color, + red6: color, + red7: color, + red8: color, + red9: color, + orange0: color, + orange1: color, + orange2: color, + orange3: color, + orange4: color, + orange5: color, + orange6: color, + orange7: color, + orange8: color, + orange9: color, + amber0: color, + amber1: color, + amber2: color, + amber3: color, + amber4: color, + amber5: color, + amber6: color, + amber7: color, + amber8: color, + amber9: color, + yellow0: color, + yellow1: color, + yellow2: color, + yellow3: color, + yellow4: color, + yellow5: color, + yellow6: color, + yellow7: color, + yellow8: color, + yellow9: color, + lime0: color, + lime1: color, + lime2: color, + lime3: color, + lime4: color, + lime5: color, + lime6: color, + lime7: color, + lime8: color, + lime9: color, + lightGreen0: color, + lightGreen1: color, + lightGreen2: color, + lightGreen3: color, + lightGreen4: color, + lightGreen5: color, + lightGreen6: color, + lightGreen7: color, + lightGreen8: color, + lightGreen9: color, + green0: color, + green1: color, + green2: color, + green3: color, + green4: color, + green5: color, + green6: color, + green7: color, + green8: color, + green9: color, + teal0: color, + teal1: color, + teal2: color, + teal3: color, + teal4: color, + teal5: color, + teal6: color, + teal7: color, + teal8: color, + teal9: color, + cyan0: color, + cyan1: color, + cyan2: color, + cyan3: color, + cyan4: color, + cyan5: color, + cyan6: color, + cyan7: color, + cyan8: color, + cyan9: color, + lightBlue0: color, + lightBlue1: color, + lightBlue2: color, + lightBlue3: color, + lightBlue4: color, + lightBlue5: color, + lightBlue6: color, + lightBlue7: color, + lightBlue8: color, + lightBlue9: color, + blue0: color, + blue1: color, + blue2: color, + blue3: color, + blue4: color, + blue5: color, + blue6: color, + blue7: color, + blue8: color, + blue9: color, + indigo0: color, + indigo1: color, + indigo2: color, + indigo3: color, + indigo4: color, + indigo5: color, + indigo6: color, + indigo7: color, + indigo8: color, + indigo9: color, + violet0: color, + violet1: color, + violet2: color, + violet3: color, + violet4: color, + violet5: color, + violet6: color, + violet7: color, + violet8: color, + violet9: color, + purple0: color, + purple1: color, + purple2: color, + purple3: color, + purple4: color, + purple5: color, + purple6: color, + purple7: color, + purple8: color, + purple9: color, + pink0: color, + pink1: color, + pink2: color, + pink3: color, + pink4: color, + pink5: color, + pink6: color, + pink7: color, + pink8: color, + pink9: color, + grey0: color, + grey1: color, + grey2: color, + grey3: color, + grey4: color, + grey5: color, + grey6: color, + grey7: color, + grey8: color, + grey9: color, + black: color, + white: color, +} + +ui_theme :: struct { + white: color, + primary: color, + primaryHover: color, + primaryActive: color, + border: color, + fill0: color, + fill1: color, + fill2: color, + bg0: color, + bg1: color, + bg2: color, + bg3: color, + bg4: color, + text0: color, + text1: color, + text2: color, + text3: color, + sliderThumbBorder: color, + elevatedBorder: color, + roundnessSmall: f32, + roundnessMedium: f32, + roundnessLarge: f32, + palette: ^ui_palette, +} + +ui_tag :: struct { + hash: u64, +} + +ui_selector_kind :: enum u32 { + ANY = 0, + OWNER = 1, + TEXT = 2, + TAG = 3, + STATUS = 4, + KEY = 5, +} + +ui_status_flag :: enum u8 { + HOVER = 1, + HOT, + ACTIVE, + DRAGGING, +} +ui_status :: bit_set[ui_status_flag; u8] + +ui_selector_op :: enum u32 { + DESCENDANT = 0, + AND = 1, +} + +ui_selector :: struct { + listElt: list_elt, + kind: ui_selector_kind, + op: ui_selector_op, + using _: struct #raw_union { + text: str8, + key: ui_key, + tag: ui_tag, + status: ui_status, + }, +} + +ui_pattern :: struct { + l: list, +} + +ui_box :: struct { + listElt: list_elt, + children: list, + parent: ^ui_box, + overlayElt: list_elt, + bucketElt: list_elt, + key: ui_key, + frameCounter: u64, + flags: ui_flags, + string: str8, + tags: list, + drawProc: ui_box_draw_proc, + drawData: rawptr, + beforeRules: list, + afterRules: list, + targetStyle: ^ui_style, + style: ui_style, + z: u32, + floatPos: vec2, + childrenSum: [2]f32, + spacing: [2]f32, + minSize: [2]f32, + rect: rect, + sig: ^ui_sig, + fresh: bool, + closed: bool, + parentClosed: bool, + dragging: bool, + hot: bool, + active: bool, + scroll: vec2, + pressedMouse: vec2, + hotTransition: f32, + activeTransition: f32, +} + +ui_style_rule :: struct { + boxElt: list_elt, + buildElt: list_elt, + tmpElt: list_elt, + owner: ^ui_box, + pattern: ui_pattern, + mask: ui_style_mask, + style: ^ui_style, +} + +ui_sig :: struct { + box: ^ui_box, + mouse: vec2, + delta: vec2, + wheel: vec2, + pressed: bool, + released: bool, + clicked: bool, + doubleClicked: bool, + tripleClicked: bool, + rightPressed: bool, + dragging: bool, + hovering: bool, + pasted: bool, +} + +ui_box_draw_proc :: proc "c" (arg0: ^ui_box, arg1: rawptr) + +ui_flag :: enum u32 { + CLICKABLE = 0, + SCROLL_WHEEL_X, + SCROLL_WHEEL_Y, + BLOCK_MOUSE, + HOT_ANIMATION, + ACTIVE_ANIMATION, + OVERFLOW_ALLOW_X, + OVERFLOW_ALLOW_Y, + CLIP, + DRAW_BACKGROUND, + DRAW_FOREGROUND, + DRAW_BORDER, + DRAW_TEXT, + DRAW_PROC, + OVERLAY, +} +ui_flags :: bit_set[ui_flag; u32] + +MAX_INPUT_CHAR_PER_FRAME :: 64 + +ui_input_text :: struct { + count: u8 `fmt:"-"`, + codePoints: [64]utf32 `fmt:"s,count"`, +} + +ui_stack_elt :: struct { + parent: ^ui_stack_elt, + using _: struct #raw_union { + box: ^ui_box, + size: ui_size, + clip: rect, + }, +} + +ui_tag_elt :: struct { + listElt: list_elt, + tag: ui_tag, +} + +BOX_MAP_BUCKET_COUNT :: 1024 + +ui_edit_move :: enum u32 { + NONE = 0, + CHAR = 1, + WORD = 2, + LINE = 3, +} + +ui_context :: struct { + init: bool, + input: input_state, + frameCounter: u64, + frameTime: f64, + lastFrameDuration: f64, + frameArena: arena, + boxPool: pool, + boxMap: [1024]list, + root: ^ui_box, + overlay: ^ui_box, + overlayList: list, + boxStack: ^ui_stack_elt, + clipStack: ^ui_stack_elt, + nextBoxBeforeRules: list, + nextBoxAfterRules: list, + nextBoxTags: list, + z: u32, + hovered: ^ui_box, + focus: ^ui_box, + editCursor: i32, + editMark: i32, + editFirstDisplayedChar: i32, + editCursorBlinkStart: f64, + editSelectionMode: ui_edit_move, + editWordSelectionInitialCursor: i32, + editWordSelectionInitialMark: i32, + theme: ^ui_theme, +} + +ui_text_box_result :: struct { + changed: bool, + accepted: bool, + text: str8, +} + +ui_select_popup_info :: struct { + changed: bool, + selectedIndex: i32, + optionCount: i32 `fmt:"-"`, + options: [^]str8 `fmt:"s,optionCount"`, + placeholder: str8, +} + +ui_radio_group_info :: struct { + changed: bool, + selectedIndex: i32, + optionCount: i32 `fmt:"-"`, + options: [^]str8 `fmt:"s,optionCount"`, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + input_process_event :: proc(arena: ^arena, state: ^input_state, event: ^event) --- + input_next_frame :: proc(state: ^input_state) --- + key_down :: proc(state: ^input_state, key: key_code) -> bool --- + key_press_count :: proc(state: ^input_state, key: key_code) -> u8 --- + key_release_count :: proc(state: ^input_state, key: key_code) -> u8 --- + key_repeat_count :: proc(state: ^input_state, key: key_code) -> u8 --- + key_down_scancode :: proc(state: ^input_state, key: scan_code) -> bool --- + key_press_count_scancode :: proc(state: ^input_state, key: scan_code) -> u8 --- + key_release_count_scancode :: proc(state: ^input_state, key: scan_code) -> u8 --- + key_repeat_count_scancode :: proc(state: ^input_state, key: scan_code) -> u8 --- + mouse_down :: proc(state: ^input_state, button: mouse_button) -> bool --- + mouse_pressed :: proc(state: ^input_state, button: mouse_button) -> u8 --- + mouse_released :: proc(state: ^input_state, button: mouse_button) -> u8 --- + mouse_clicked :: proc(state: ^input_state, button: mouse_button) -> bool --- + mouse_double_clicked :: proc(state: ^input_state, button: mouse_button) -> bool --- + mouse_position :: proc(state: ^input_state) -> vec2 --- + mouse_delta :: proc(state: ^input_state) -> vec2 --- + mouse_wheel :: proc(state: ^input_state) -> vec2 --- + input_text_utf32 :: proc(arena: ^arena, state: ^input_state) -> str32 --- + input_text_utf8 :: proc(arena: ^arena, state: ^input_state) -> str8 --- + clipboard_pasted :: proc(state: ^input_state) -> bool --- + clipboard_pasted_text :: proc(state: ^input_state) -> str8 --- + key_mods :: proc(state: ^input_state) -> keymod_flags --- + ui_init :: proc(_context: ^ui_context) --- + ui_get_context :: proc() -> ^ui_context --- + ui_set_context :: proc(_context: ^ui_context) --- + ui_process_event :: proc(event: ^event) --- + ui_begin_frame :: proc(size: vec2, #by_ptr defaultStyle: ui_style, mask: ui_style_mask) --- + ui_end_frame :: proc() --- + ui_draw :: proc() --- + ui_set_theme :: proc(theme: ^ui_theme) --- + ui_key_make_str8 :: proc(string: str8) -> ui_key --- + ui_key_make_path :: proc(path: str8_list) -> ui_key --- + ui_box_make_str8 :: proc(string: str8, flags: ui_flags) -> ^ui_box --- + ui_box_begin_str8 :: proc(string: str8, flags: ui_flags) -> ^ui_box --- + ui_box_end :: proc() -> ^ui_box --- + ui_box_push :: proc(box: ^ui_box) --- + ui_box_pop :: proc() --- + ui_box_top :: proc() -> ^ui_box --- + ui_box_lookup_key :: proc(key: ui_key) -> ^ui_box --- + ui_box_lookup_str8 :: proc(string: str8) -> ^ui_box --- + ui_box_set_draw_proc :: proc(box: ^ui_box, _proc: ui_box_draw_proc, data: rawptr) --- + ui_box_closed :: proc(box: ^ui_box) -> bool --- + ui_box_set_closed :: proc(box: ^ui_box, closed: bool) --- + ui_box_active :: proc(box: ^ui_box) -> bool --- + ui_box_activate :: proc(box: ^ui_box) --- + ui_box_deactivate :: proc(box: ^ui_box) --- + ui_box_hot :: proc(box: ^ui_box) -> bool --- + ui_box_set_hot :: proc(box: ^ui_box, hot: bool) --- + ui_box_sig :: proc(box: ^ui_box) -> ui_sig --- + ui_tag_make_str8 :: proc(string: str8) -> ui_tag --- + ui_tag_box_str8 :: proc(box: ^ui_box, string: str8) --- + ui_tag_next_str8 :: proc(string: str8) --- + ui_apply_style_with_mask :: proc(dst: ^ui_style, src: ^ui_style, mask: ui_style_mask) --- + ui_pattern_push :: proc(arena: ^arena, pattern: ^ui_pattern, selector: ui_selector) --- + ui_pattern_all :: proc() -> ui_pattern --- + ui_pattern_owner :: proc() -> ui_pattern --- + ui_style_next :: proc(#by_ptr style: ui_style, mask: ui_style_mask) --- + ui_style_match_before :: proc(pattern: ui_pattern, #by_ptr style: ui_style, mask: ui_style_mask) --- + ui_style_match_after :: proc(pattern: ui_pattern, #by_ptr style: ui_style, mask: ui_style_mask) --- + ui_label :: proc(label: cstring) -> ui_sig --- + ui_label_str8 :: proc(label: str8) -> ui_sig --- + ui_button :: proc(label: cstring) -> ui_sig --- + ui_checkbox :: proc(name: cstring, checked: ^bool) -> ui_sig --- + ui_slider :: proc(name: cstring, value: ^f32) -> ^ui_box --- + ui_scrollbar :: proc(name: cstring, thumbRatio: f32, scrollValue: ^f32) -> ^ui_box --- + ui_tooltip :: proc(label: cstring) --- + ui_panel_begin :: proc(name: cstring, flags: ui_flags) --- + ui_panel_end :: proc() --- + ui_menu_bar_begin :: proc(name: cstring) --- + ui_menu_bar_end :: proc() --- + ui_menu_begin :: proc(label: cstring) --- + ui_menu_end :: proc() --- + ui_menu_button :: proc(label: cstring) -> ui_sig --- + ui_text_box :: proc(name: cstring, arena: ^arena, text: str8) -> ui_text_box_result --- + ui_select_popup :: proc(name: cstring, info: ^ui_select_popup_info) -> ui_select_popup_info --- + ui_radio_group :: proc(name: cstring, info: ^ui_radio_group_info) -> ui_radio_group_info --- +} + diff --git a/core/sys/posix/arpa_inet.odin b/core/sys/posix/arpa_inet.odin new file mode 100644 index 000000000..d3592dd80 --- /dev/null +++ b/core/sys/posix/arpa_inet.odin @@ -0,0 +1,59 @@ +#+build darwin, linux, freebsd, openbsd, netbsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// arpa/inet.h - definitions for internet operations + +foreign lib { + // Use Odin's native big endian types `u32be` and `u16be` instead. + // htonl :: proc(c.uint32_t) -> c.uint32_t --- + // htons :: proc(c.uint16_t) -> c.uint16_t --- + // ntohl :: proc(c.uint32_t) -> c.uint32_t --- + // ntohs :: proc(c.uint16_t) -> c.uint16_t --- + + // Use of this function is problematic because -1 is a valid address (255.255.255.255). + // Avoid its use in favor of inet_aton(), inet_pton(3), or getaddrinfo(3) which provide a cleaner way to indicate error return. + // inet_addr :: proc(cstring) -> in_addr_t --- + + // Convert the Internet host address specified by in to a string in the Internet standard dot notation. + // + // NOTE: returns a static string overwritten by further calls. + // + // [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/inet_ntoa.html ]] + inet_ntoa :: proc(in_addr) -> cstring --- + + // Convert a numeric address into a text string suitable for presentation. + // + // Returns `nil` and sets `errno` on failure. + // + // [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/inet_ntop.html ]] + inet_ntop :: proc( + af: AF, // INET or INET6 + src: rawptr, // either ^in_addr or ^in_addr6 + dst: [^]byte, // use `INET_ADDRSTRLEN` or `INET6_ADDRSTRLEN` for minimum lengths + size: socklen_t, + ) -> cstring --- + + // Convert an address in its standard text presentation form into its numeric binary form. + // + // [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/inet_ntop.html ]] + inet_pton :: proc( + af: AF, // INET or INET6 + src: cstring, + dst: rawptr, // either ^in_addr or ^in_addr6 + size: socklen_t, // size_of(dst^) + ) -> pton_result --- +} + +pton_result :: enum c.int { + AFNOSUPPORT = -1, + INVALID = 0, + SUCCESS = 1, +} diff --git a/core/sys/posix/dirent.odin b/core/sys/posix/dirent.odin new file mode 100644 index 000000000..bf32be8cf --- /dev/null +++ b/core/sys/posix/dirent.odin @@ -0,0 +1,230 @@ +#+build darwin, linux, freebsd, openbsd, netbsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// dirent.h - format of directory entries + +foreign lib { + /* + can be used as the comparison function for the scandir() function to sort the directory entries, d1 and d2, into alphabetical order. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/scandir.html ]] + */ + @(link_name=LALPHASORT) + alphasort :: proc([^]^dirent, [^]^dirent) -> c.int --- + + /* + Scan the directory dir, calling the function referenced by sel on each directory entry. + + Example: + list: [^]^posix.dirent + ret := posix.scandir(#directory, &list, nil, posix.alphasort) + if ret < 0 { + panic(string(posix.strerror(posix.errno()))) + } + defer posix.free(list) + + entries := list[:ret] + for entry in entries { + log.info(entry) + posix.free(entry) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/scandir.html ]] + */ + @(link_name=LSCANDIR) + scandir :: proc( + dir: cstring, + sel: ^[^]^dirent, + filter: proc "c" (^dirent) -> b32 = nil, + compar: proc "c" ([^]^dirent, [^]^dirent) -> c.int = alphasort, + ) -> c.int --- + + /* + Close the directory stream referred to by the argument dirp. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/closedir.html ]] + */ + closedir :: proc(dirp: DIR) -> result --- + + /* + Equivalent to the opendir() function except that the directory is specified by a file descriptor + rather than by a name. + The file offset associated with the file descriptor at the time of the call determines + which entries are returned. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fdopendir.html ]] + */ + @(link_name="fdopendir" + INODE_SUFFIX) + fdopendir :: proc(dirp: FD) -> DIR --- + + /* + Open a directory stream corresponding to the directory named by the dirname argument. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fdopendir.html ]] + */ + @(link_name=LOPENDIR) + opendir :: proc(path: cstring) -> DIR --- + + /* + Returns a pointer to a structure representing the directory entry at the current position + in the directory stream specified by the argument dirp, and position the directory stream at + the next entry. + + Returns nil when the end is reached or an error occurred (which sets errno). + + Example: + posix.set_errno(.NONE) + entry := posix.readdir(dirp) + if entry == nil { + if errno := posix.errno(); errno != .NONE { + panic(string(posix.strerror(errno))) + } else { + fmt.println("end of directory stream") + } + } else { + fmt.println(entry) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/readdir.html ]] + */ + @(link_name=LREADDIR) + readdir :: proc(dirp: DIR) -> ^dirent --- + + /* + Reset the position of the directory stream to which dirp refers to the beginning of the directory. + It shall also cause the directory stream to refer to the current state of the corresponding directory, + as a call to opendir() would have done. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/rewinddir.html ]] + */ + @(link_name="rewinddir" + INODE_SUFFIX) + rewinddir :: proc(dirp: DIR) --- + + /* + The seekdir() function shall set the position of the next readdir() operation on the directory + stream specified by dirp to the position specified by loc. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/seekdir.html ]] + */ + @(link_name="seekdir" + INODE_SUFFIX) + seekdir :: proc(dirp: DIR, loc: dir_loc) --- + + /* + The telldir() function shall obtain the current location associated with the directory stream + specified by dirp. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/telldir.html ]] + */ + @(link_name="telldir" + INODE_SUFFIX) + telldir :: proc(dirp: DIR) -> dir_loc --- + + // deprecated. + // readdir_r :: proc(DIR, ^dirent, ^^dirent) -> c.int --- +} + +DIR :: distinct rawptr + +dir_loc :: c.long + +// NOTE: `d_type` is not a POSIX standard field, but all targets we support add it. +D_Type :: enum c.uint8_t { + UNKNOWN = 0, + FIFO = 1, + CHR = 2, + DIR = 4, + BLK = 6, + REG = 8, + LNK = 10, + SOCK = 12, + WHT = 14, +} + +when ODIN_OS == .NetBSD { + @(private) LALPHASORT :: "__alphasort30" + @(private) LSCANDIR :: "__scandir30" + @(private) LOPENDIR :: "__opendir30" + @(private) LREADDIR :: "__readdir30" + + /* + Return a file descriptor referring to the same directory as the dirp argument. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dirfd.html ]] + */ + dirfd :: proc "c" (dirp: DIR) -> FD { + _dirdesc :: struct { + dd_fd: FD, + + // more stuff... + } + + return (^_dirdesc)(dirp).dd_fd + } + +} else { + @(private) LALPHASORT :: "alphasort" + INODE_SUFFIX + @(private) LSCANDIR :: "scandir" + INODE_SUFFIX + @(private) LOPENDIR :: "opendir" + INODE_SUFFIX + @(private) LREADDIR :: "readdir" + INODE_SUFFIX + + foreign lib { + /* + Return a file descriptor referring to the same directory as the dirp argument. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dirfd.html ]] + */ + dirfd :: proc(dirp: DIR) -> FD --- + } +} + +when ODIN_OS == .Darwin { + + dirent :: struct { + d_ino: ino_t, /* [PSX] file number of entry */ + d_seekoff: c.uint64_t, /* seek offset */ + d_reclen: c.uint16_t, /* length of this record */ + d_namelen: c.uint16_t, /* length of string in d_name */ + d_type: D_Type, /* file type */ + d_name: [1024]c.char `fmt:"s,0"`, /* [PSX] entry name */ + } + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD { + + dirent :: struct { + d_ino: ino_t, /* [PSX] file number of entry */ + d_off: off_t, /* directory offset of the next entry */ + d_reclen: c.uint16_t, /* length of this record */ + d_type: D_Type, /* file type */ + d_namelen: c.uint8_t, /* length of string in d_name */ + d_pad0: c.uint32_t, + d_name: [256]c.char `fmt:"s,0"`, /* [PSX] entry name */ + } + +} else when ODIN_OS == .NetBSD { + + dirent :: struct { + d_ino: ino_t, /* [PSX] file number of entry */ + d_reclen: c.uint16_t, /* length of this record */ + d_namelen: c.uint16_t, /* length of string in d_name */ + d_type: D_Type, /* file type */ + d_name: [512]c.char `fmt:"s,0"`, /* [PSX] entry name */ + } + +} else when ODIN_OS == .Linux { + + dirent :: struct { + d_ino: u64, /* [PSX] file number of entry */ + d_off: i64, /* directory offset of the next entry */ + d_reclen: u16, /* length of this record */ + d_type: D_Type, /* file type */ + d_name: [256]c.char `fmt:"s,0"`, /* [PSX] entry name */ + } + +} diff --git a/core/sys/posix/dlfcn.odin b/core/sys/posix/dlfcn.odin new file mode 100644 index 000000000..e84b29d79 --- /dev/null +++ b/core/sys/posix/dlfcn.odin @@ -0,0 +1,125 @@ +#+build darwin, linux, freebsd, openbsd, netbsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { + foreign import lib "system:dl" +} else { + foreign import lib "system:c" +} + +// dlfcn.h - dynamic linking + +foreign lib { + /* + inform the system that the object referenced by a handle returned from a previous dlopen() + invocation is no longer needed by the application. + + Returns: 0 on success, non-zero on failure (use dlerror() for more information) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dlclose.html ]] + */ + dlclose :: proc(handle: Symbol_Table) -> c.int --- + + /* + return a null-terminated character string (with no trailing ) that describes + the last error that occurred during dynamic linking processing. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dlerror.html ]] + */ + dlerror :: proc() -> cstring --- + + /* + Make the symbols (function identifiers and data object identifiers) in the executable object + file specified by file available to the calling program. + + Returns: a reference to the symbol table on success, nil on failure (use dlerror() for more information) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dlopen.html ]] + */ + dlopen :: proc(file: cstring, mode: RTLD_Flags) -> Symbol_Table --- + + /* + Obtain the address of a symbol (a function identifier or a data object identifier) + defined in the symbol table identified by the handle argument. + + Returns: the address of the matched symbol on success, nil on failure (use dlerror() for more information) + + Example: + handle := posix.dlopen("/usr/home/me/libfoo.so", posix.RTLD_LOCAL + { .RTLD_LAZY }) + defer posix.dlclose(handle) + + if handle == nil { + panic(string(posix.dlerror())) + } + + foo: proc(a, b: int) -> int + foo = auto_cast posix.dlsym(handle, "foo") + + if foo == nil { + panic(string(posix.dlerror())) + } + + fmt.printfln("foo(%v, %v) == %v", 1, 2, foo(1, 2)) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dlsym.html ]] + */ + dlsym :: proc(handle: Symbol_Table, name: cstring) -> rawptr --- +} + +RTLD_Flag_Bits :: enum c.int { + LAZY = log2(RTLD_LAZY), + NOW = log2(RTLD_NOW), + GLOBAL = log2(RTLD_GLOBAL), + + // NOTE: use with `posix.RTLD_LOCAL + { .OTHER_FLAG, .OTHER_FLAG }`, unfortunately can't be in + // this bit set enum because it is 0 on some platforms and a value on others. + // LOCAL = RTLD_LOCAL + + _MAX = 31, +} +RTLD_Flags :: bit_set[RTLD_Flag_Bits; c.int] + +Symbol_Table :: distinct rawptr + +when ODIN_OS == .Darwin { + + RTLD_LAZY :: 0x1 + RTLD_NOW :: 0x2 + _RTLD_LOCAL :: 0x4 + RTLD_GLOBAL :: 0x8 + + RTLD_LOCAL :: RTLD_Flags{RTLD_Flag_Bits(log2(_RTLD_LOCAL))} + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD { + + RTLD_LAZY :: 1 + RTLD_NOW :: 2 + _RTLD_LOCAL :: 0 + RTLD_GLOBAL :: 0x100 + + RTLD_LOCAL :: RTLD_Flags{} + +} else when ODIN_OS == .NetBSD { + + RTLD_LAZY :: 0x1 + RTLD_NOW :: 0x2 + _RTLD_LOCAL :: 0x200 + RTLD_GLOBAL :: 0x100 + + RTLD_LOCAL :: RTLD_Flags{RTLD_Flag_Bits(log2(_RTLD_LOCAL))} + +} else when ODIN_OS == .Linux { + + RTLD_LAZY :: 0x001 + RTLD_NOW :: 0x002 + RTLD_GLOBAL :: 0x100 + + _RTLD_LOCAL :: 0 + RTLD_LOCAL :: RTLD_Flags{} + +} + diff --git a/core/sys/posix/errno.odin b/core/sys/posix/errno.odin new file mode 100644 index 000000000..9bc77f12e --- /dev/null +++ b/core/sys/posix/errno.odin @@ -0,0 +1,540 @@ +#+build windows, darwin, linux, freebsd, openbsd, netbsd +package posix + +import "core:c" +import "core:c/libc" + +// errno.h - system error numbers + +EDOM :: libc.EDOM +EILSEQ :: libc.EILSEQ +ERANGE :: libc.ERANGE + +@(no_instrumentation) +get_errno :: #force_inline proc "contextless" () -> Errno { + return (^Errno)(libc.errno())^ +} + +set_errno :: #force_inline proc "contextless" (err: Errno) { + libc.errno()^ = i32(err) +} + +errno :: proc { + get_errno, + set_errno, +} + +Errno :: enum c.int { + NONE = 0, + EDOM = EDOM, + EILSEQ = EILSEQ, + ERANGE = ERANGE, + E2BIG = E2BIG, + EACCES = EACCES, + EADDRINUSE = EADDRINUSE, + EADDRNOTAVAIL = EADDRNOTAVAIL, + EAFNOSUPPORT = EAFNOSUPPORT, + EAGAIN = EAGAIN, + EALREADY = EALREADY, + EBADF = EBADF, + EBADMSG = EBADMSG, + EBUSY = EBUSY, + ECANCELED = ECANCELED, + ECHILD = ECHILD, + ECONNABORTED = ECONNABORTED, + ECONNREFUSED = ECONNREFUSED, + ECONNRESET = ECONNRESET, + EDEADLK = EDEADLK, + EDESTADDRREQ = EDESTADDRREQ, + EDQUOT = EDQUOT, + EEXIST = EEXIST, + EFAULT = EFAULT, + EFBIG = EFBIG, + EHOSTUNREACH = EHOSTUNREACH, + EIDRM = EIDRM, + EINPROGRESS = EINPROGRESS, + EINTR = EINTR, + EINVAL = EINVAL, + EIO = EIO, + EISCONN = EISCONN, + EISDIR = EISDIR, + ELOOP = ELOOP, + EMFILE = EMFILE, + EMLINK = EMLINK, + EMSGSIZE = EMSGSIZE, + EMULTIHOP = EMULTIHOP, + ENAMETOOLONG = ENAMETOOLONG, + ENETDOWN = ENETDOWN, + ENETRESET = ENETRESET, + ENETUNREACH = ENETUNREACH, + ENFILE = ENFILE, + ENOBUFS = ENOBUFS, + ENODATA = ENODATA, + ENODEV = ENODEV, + ENOENT = ENOENT, + ENOEXEC = ENOEXEC, + ENOLCK = ENOLCK, + ENOLINK = ENOLINK, + ENOMEM = ENOMEM, + ENOMSG = ENOMSG, + ENOPROTOOPT = ENOPROTOOPT, + ENOSPC = ENOSPC, + ENOSR = ENOSR, + ENOSTR = ENOSTR, + ENOSYS = ENOSYS, + ENOTCONN = ENOTCONN, + ENOTDIR = ENOTDIR, + ENOTEMPTY = ENOTEMPTY, + ENOTRECOVERABLE = ENOTRECOVERABLE, + ENOTSOCK = ENOTSOCK, + ENOTSUP = ENOTSUP, + ENOTTY = ENOTTY, + ENXIO = ENXIO, + EOPNOTSUPP = EOPNOTSUPP, + EOVERFLOW = EOVERFLOW, + EOWNERDEAD = EOWNERDEAD, + EPERM = EPERM, + EPIPE = EPIPE, + EPROTO = EPROTO, + EPROTONOSUPPORT = EPROTONOSUPPORT, + EPROTOTYPE = EPROTOTYPE, + EROFS = EROFS, + ESPIPE = ESPIPE, + ESRCH = ESRCH, + ESTALE = ESTALE, + ETIME = ETIME, + ETIMEDOUT = ETIMEDOUT, + ETXTBSY = ETXTBSY, + EWOULDBLOCK = EWOULDBLOCK, + EXDEV = EXDEV, +} + +when ODIN_OS == .Darwin { + EPERM :: 1 + ENOENT :: 2 + ESRCH :: 3 + EINTR :: 4 + EIO :: 5 + ENXIO :: 6 + E2BIG :: 7 + ENOEXEC :: 8 + EBADF :: 9 + ECHILD :: 10 + EDEADLK :: 11 + ENOMEM :: 12 + EACCES :: 13 + EFAULT :: 14 + EBUSY :: 16 + EEXIST :: 17 + EXDEV :: 18 + ENODEV :: 19 + ENOTDIR :: 20 + EISDIR :: 21 + EINVAL :: 22 + ENFILE :: 23 + EMFILE :: 24 + ENOTTY :: 25 + ETXTBSY :: 26 + EFBIG :: 27 + ENOSPC :: 28 + ESPIPE :: 29 + EROFS :: 30 + EMLINK :: 31 + EPIPE :: 32 + EAGAIN :: 35 + EWOULDBLOCK :: EAGAIN + EINPROGRESS :: 36 + EALREADY :: 37 + ENOTSOCK :: 38 + EDESTADDRREQ :: 39 + EMSGSIZE :: 40 + EPROTOTYPE :: 41 + ENOPROTOOPT :: 42 + EPROTONOSUPPORT :: 43 + ENOTSUP :: 45 + EOPNOTSUPP :: ENOTSUP + EAFNOSUPPORT :: 47 + EADDRINUSE :: 48 + EADDRNOTAVAIL :: 49 + ENETDOWN :: 50 + ENETUNREACH :: 51 + ENETRESET :: 52 + ECONNABORTED :: 53 + ECONNRESET :: 54 + ENOBUFS :: 55 + EISCONN :: 56 + ENOTCONN :: 57 + ETIMEDOUT :: 60 + ECONNREFUSED :: 61 + ELOOP :: 62 + ENAMETOOLONG :: 63 + EHOSTUNREACH :: 65 + ENOTEMPTY :: 66 + EDQUOT :: 69 + ESTALE :: 70 + ENOLCK :: 77 + ENOSYS :: 78 + EOVERFLOW :: 84 + ECANCELED :: 89 + EIDRM :: 90 + ENOMSG :: 91 + EBADMSG :: 94 + EMULTIHOP :: 95 + ENODATA :: 96 + ENOLINK :: 97 + ENOSR :: 98 + ENOSTR :: 99 + EPROTO :: 100 + ETIME :: 101 + ENOTRECOVERABLE :: 104 + EOWNERDEAD :: 105 +} else when ODIN_OS == .FreeBSD { + EPERM :: 1 + ENOENT :: 2 + ESRCH :: 3 + EINTR :: 4 + EIO :: 5 + ENXIO :: 6 + E2BIG :: 7 + ENOEXEC :: 8 + EBADF :: 9 + ECHILD :: 10 + EDEADLK :: 11 + ENOMEM :: 12 + EACCES :: 13 + EFAULT :: 14 + EBUSY :: 16 + EEXIST :: 17 + EXDEV :: 18 + ENODEV :: 19 + ENOTDIR :: 20 + EISDIR :: 21 + EINVAL :: 22 + ENFILE :: 23 + EMFILE :: 24 + ENOTTY :: 25 + ETXTBSY :: 26 + EFBIG :: 27 + ENOSPC :: 28 + ESPIPE :: 29 + EROFS :: 30 + EMLINK :: 31 + EPIPE :: 32 + EAGAIN :: 35 + EWOULDBLOCK :: EAGAIN + EINPROGRESS :: 36 + EALREADY :: 37 + ENOTSOCK :: 38 + EDESTADDRREQ :: 39 + EMSGSIZE :: 40 + EPROTOTYPE :: 41 + ENOPROTOOPT :: 42 + EPROTONOSUPPORT :: 43 + ENOTSUP :: 45 + EOPNOTSUPP :: ENOTSUP + EAFNOSUPPORT :: 47 + EADDRINUSE :: 48 + EADDRNOTAVAIL :: 49 + ENETDOWN :: 50 + ENETUNREACH :: 51 + ENETRESET :: 52 + ECONNABORTED :: 53 + ECONNRESET :: 54 + ENOBUFS :: 55 + EISCONN :: 56 + ENOTCONN :: 57 + ETIMEDOUT :: 60 + ECONNREFUSED :: 61 + ELOOP :: 62 + ENAMETOOLONG :: 63 + EHOSTUNREACH :: 65 + ENOTEMPTY :: 66 + EDQUOT :: 69 + ESTALE :: 70 + ENOLCK :: 77 + ENOSYS :: 78 + EOVERFLOW :: 84 + EIDRM :: 82 + ENOMSG :: 83 + ECANCELED :: 85 + EBADMSG :: 89 + EMULTIHOP :: 90 + ENOLINK :: 91 + EPROTO :: 92 + ENOTRECOVERABLE :: 95 + EOWNERDEAD :: 96 + + // NOTE: not defined for freebsd + ENODATA :: -1 + ENOSR :: -1 + ENOSTR :: -1 + ETIME :: -1 +} else when ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + EPERM :: 1 + ENOENT :: 2 + ESRCH :: 3 + EINTR :: 4 + EIO :: 5 + ENXIO :: 6 + E2BIG :: 7 + ENOEXEC :: 8 + EBADF :: 9 + ECHILD :: 10 + EDEADLK :: 11 + ENOMEM :: 12 + EACCES :: 13 + EFAULT :: 14 + EBUSY :: 16 + EEXIST :: 17 + EXDEV :: 18 + ENODEV :: 19 + ENOTDIR :: 20 + EISDIR :: 21 + EINVAL :: 22 + ENFILE :: 23 + EMFILE :: 24 + ENOTTY :: 25 + ETXTBSY :: 26 + EFBIG :: 27 + ENOSPC :: 28 + ESPIPE :: 29 + EROFS :: 30 + EMLINK :: 31 + EPIPE :: 32 + EAGAIN :: 35 + EWOULDBLOCK :: EAGAIN + EINPROGRESS :: 36 + EALREADY :: 37 + ENOTSOCK :: 38 + EDESTADDRREQ :: 39 + EMSGSIZE :: 40 + EPROTOTYPE :: 41 + ENOPROTOOPT :: 42 + EPROTONOSUPPORT :: 43 + ENOTSUP :: 45 + EOPNOTSUPP :: ENOTSUP + EAFNOSUPPORT :: 47 + EADDRINUSE :: 48 + EADDRNOTAVAIL :: 49 + ENETDOWN :: 50 + ENETUNREACH :: 51 + ENETRESET :: 52 + ECONNABORTED :: 53 + ECONNRESET :: 54 + ENOBUFS :: 55 + EISCONN :: 56 + ENOTCONN :: 57 + ETIMEDOUT :: 60 + ECONNREFUSED :: 61 + ELOOP :: 62 + ENAMETOOLONG :: 63 + EHOSTUNREACH :: 65 + ENOTEMPTY :: 66 + EDQUOT :: 69 + ESTALE :: 70 + ENOLCK :: 77 + ENOSYS :: 78 + + when ODIN_OS == .NetBSD { + EOVERFLOW :: 84 + EIDRM :: 82 + ENOMSG :: 83 + ECANCELED :: 87 + EBADMSG :: 88 + ENODATA :: 89 + EMULTIHOP :: 94 + ENOLINK :: 95 + EPROTO :: 96 + ENOTRECOVERABLE :: 98 + EOWNERDEAD :: 97 + ENOSR :: 90 + ENOSTR :: 91 + ETIME :: 92 + } else { + EOVERFLOW :: 87 + EIDRM :: 89 + ENOMSG :: 90 + ECANCELED :: 88 + EBADMSG :: 92 + EPROTO :: 95 + ENOTRECOVERABLE :: 93 + EOWNERDEAD :: 94 + // NOTE: not defined for openbsd + ENODATA :: -1 + EMULTIHOP :: -1 + ENOLINK :: -1 + ENOSR :: -1 + ENOSTR :: -1 + ETIME :: -1 + } + +} else when ODIN_OS == .Linux { + EPERM :: 1 + ENOENT :: 2 + ESRCH :: 3 + EINTR :: 4 + EIO :: 5 + ENXIO :: 6 + E2BIG :: 7 + ENOEXEC :: 8 + EBADF :: 9 + ECHILD :: 10 + EAGAIN :: 11 + EWOULDBLOCK :: EAGAIN + ENOMEM :: 12 + EACCES :: 13 + EFAULT :: 14 + EBUSY :: 16 + EEXIST :: 17 + EXDEV :: 18 + ENODEV :: 19 + ENOTDIR :: 20 + EISDIR :: 21 + EINVAL :: 22 + ENFILE :: 23 + EMFILE :: 24 + ENOTTY :: 25 + ETXTBSY :: 26 + EFBIG :: 27 + ENOSPC :: 28 + ESPIPE :: 29 + EROFS :: 30 + EMLINK :: 31 + EPIPE :: 32 + + EDEADLK :: 35 + ENAMETOOLONG :: 36 + ENOLCK :: 37 + ENOSYS :: 38 + ENOTEMPTY :: 39 + ELOOP :: 40 + ENOMSG :: 42 + EIDRM :: 43 + + ENOSTR :: 60 + ENODATA :: 61 + ETIME :: 62 + ENOSR :: 63 + + ENOLINK :: 67 + + EPROTO :: 71 + EMULTIHOP :: 72 + EBADMSG :: 74 + EOVERFLOW :: 75 + + ENOTSOCK :: 88 + EDESTADDRREQ :: 89 + EMSGSIZE :: 90 + EPROTOTYPE :: 91 + ENOPROTOOPT :: 92 + EPROTONOSUPPORT :: 93 + + EOPNOTSUPP :: 95 + ENOTSUP :: EOPNOTSUPP + EAFNOSUPPORT :: 97 + EADDRINUSE :: 98 + EADDRNOTAVAIL :: 99 + ENETDOWN :: 100 + ENETUNREACH :: 101 + ENETRESET :: 102 + ECONNABORTED :: 103 + ECONNRESET :: 104 + ENOBUFS :: 105 + EISCONN :: 106 + ENOTCONN :: 107 + + ETIMEDOUT :: 110 + ECONNREFUSED :: 111 + + EHOSTUNREACH :: 113 + EALREADY :: 114 + EINPROGRESS :: 115 + ESTALE :: 116 + + EDQUOT :: 122 + ECANCELED :: 125 + + EOWNERDEAD :: 130 + ENOTRECOVERABLE :: 131 +} else when ODIN_OS == .Windows { + E2BIG :: 7 + EACCES :: 13 + EADDRINUSE :: 100 + EADDRNOTAVAIL :: 101 + EAFNOSUPPORT :: 102 + EAGAIN :: 11 + EALREADY :: 103 + EBADF :: 9 + EBADMSG :: 104 + EBUSY :: 16 + ECANCELED :: 105 + ECHILD :: 10 + ECONNABORTED :: 106 + ECONNREFUSED :: 107 + ECONNRESET :: 108 + EDEADLK :: 36 + EDESTADDRREQ :: 109 + EDQUOT :: -1 // NOTE: not defined + EEXIST :: 17 + EFAULT :: 14 + EFBIG :: 27 + EHOSTUNREACH :: 110 + EIDRM :: 111 + EINPROGRESS :: 112 + EINTR :: 4 + EINVAL :: 22 + EIO :: 5 + EISCONN :: 113 + EISDIR :: 21 + ELOOP :: 114 + EMFILE :: 24 + EMLINK :: 31 + EMSGSIZE :: 115 + EMULTIHOP :: -1 // NOTE: not defined + ENAMETOOLONG :: 38 + ENETDOWN :: 116 + ENETRESET :: 117 + ENETUNREACH :: 118 + ENFILE :: 23 + ENOBUFS :: 119 + ENODATA :: 120 + ENODEV :: 19 + ENOENT :: 2 + ENOEXEC :: 8 + ENOLCK :: 39 + ENOLINK :: 121 + ENOMEM :: 12 + ENOMSG :: 122 + ENOPROTOOPT :: 123 + ENOSPC :: 28 + ENOSR :: 124 + ENOSTR :: 125 + ENOSYS :: 40 + ENOTCONN :: 126 + ENOTDIR :: 20 + ENOTEMPTY :: 41 + ENOTRECOVERABLE :: 127 + ENOTSOCK :: 128 + ENOTSUP :: 129 + ENOTTY :: 25 + ENXIO :: 6 + EOPNOTSUPP :: 130 + EOVERFLOW :: 132 + EOWNERDEAD :: 133 + EPERM :: 1 + EPIPE :: 32 + EPROTO :: 134 + EPROTONOSUPPORT :: 135 + EPROTOTYPE :: 136 + EROFS :: 30 + ESPIPE :: 29 + ESRCH :: 3 + ESTALE :: -1 // NOTE: not defined + ETIME :: 137 + ETIMEDOUT :: 138 + ETXTBSY :: 139 + EWOULDBLOCK :: 140 + EXDEV :: 18 +} + diff --git a/core/sys/posix/fcntl.odin b/core/sys/posix/fcntl.odin new file mode 100644 index 000000000..d948af600 --- /dev/null +++ b/core/sys/posix/fcntl.odin @@ -0,0 +1,477 @@ +#+build linux, darwin, openbsd, freebsd, netbsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// fcntl.h - file control options + +foreign lib { + /* + Implemented as `return open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);` + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/creat.html ]] + */ + creat :: proc(path: cstring, mode: mode_t) -> FD --- + + /* + Perform the operations on open files. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html ]] + */ + fcntl :: proc(fd: FD, cmd: FCNTL_Cmd, #c_vararg args: ..any) -> c.int --- + + /* + Establish the connection between a file and a file descriptor. + It shall create an open file description that refers to a file and a file descriptor that + refers to that open file description. The file descriptor is used by other I/O functions to + refer to that file. + The path argument points to a pathname naming the file + + Returns: -1 on failure (setting errno), a file descriptor on success. + + Example: + // The following example opens the file /tmp/file, either by creating it (if it does not already exist), + // or by truncating its length to 0 (if it does exist). In the former case, if the call creates a new file, + // the access permission bits in the file mode of the file are set to permit reading and writing by the owner, + // and to permit reading only by group members and others. + fd := posix.open("/tmp/file", { .WRONLY, .CREAT, .TRUNC }, { .IRUSR, .IWUSR, .IRGRP, .IROTH }) + + // The following example uses the open() function to try to create the LOCKFILE file and open it for writing. + // Since the open() function specifies the O_EXCL flag, the call fails if the file already exists. + // In that case, the program assumes that someone else is updating the password file and exits. + fd := posix.open("/etc/ptmp", { .WRONLY, .CREAT, .EXCL }, { .IRUSR, .IWUSR, .IRGRP, .IROTH }) + if fd == -1 { + fmt.println("cannot open /etc/ptmp") + } + + // The following example opens a file for writing, creating the file if it does not already exist. + // If the file does exist, the system truncates the file to zero bytes. + fd := posix.open("/etc/ptmp", { .WRONLY, .CREAT, .TRUNC }, { .IRUSR, .IWUSR, .IRGRP, .IROTH }) + if fd == -1 { + fmt.println("cannot open output file") + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html ]] + */ + open :: proc(path: cstring, flags: O_Flags, #c_vararg mode: ..mode_t) -> FD --- + + /* + Equivalent to the open() function except in the case where path specifies a relative path. + In this case the file to be opened is determined relative to the directory associated with the + file descriptor fd instead of the current working directory. + + Returns: -1 on failure (setting errno), a file descriptor on success. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html ]] + */ + openat :: proc(fd: FD, path: cstring, flags: O_Flags, mode: mode_t = {}) -> FD --- +} + +FCNTL_Cmd :: enum c.int { + DUPFD = F_DUPFD, + DUPFD_CLOEXEC = F_DUPFD_CLOEXEC, + GETFD = F_GETFD, + SETFD = F_SETFD, + GETFL = F_GETFL, + SETFL = F_SETFL, + GETLK = F_GETLK, + SETLK = F_SETLK, + SETLKW = F_SETLKW, + GETOWN = F_GETOWN, + SETOWN = F_SETOWN, +} + +Lock_Type :: enum c.short { + RDLCK = F_RDLCK, + UNLCK = F_UNLCK, + WRLCK = F_WRLCK, +} + +O_Flag_Bits :: enum c.int { + // Sets FD_CLOEXEC on the file descriptor. + CLOEXEC = log2(O_CLOEXEC), + // If not exists, combined with DIRECTORY will cause creation of a directory, otherwise a regular file. + CREAT = log2(O_CREAT), + // Fails if the opened descriptor would not be a directory. + DIRECTORY = log2(O_DIRECTORY), + // If combined with CREAT, causes a failure if the file already exists. + EXCL = log2(O_EXCL), + // If terminal device, do not make it the controlling terminal for the process. + NOCTTY = log2(O_NOCTTY), + // Don't follow symbolic links, fail with errno ELOOP. + NOFOLLOW = log2(O_NOFOLLOW), + // If exists and regular, truncate the length to 0. + TRUNC = log2(O_TRUNC), + + // NOTE: use with `posix.O_TTY_INIT + { .OTHER_FLAG, .OTHER_FLAG }`, unfortunately can't be in + // this bit set enum because it is 0 on some platforms and a value on others. + // TTY_INIT = O_TTY_INIT, + + // Set file offset to end of file prior to each write. + APPEND = log2(O_APPEND), + // Write I/O shall complete as defined by synchronized I/O data integrity completion. + DSYNC = log2(O_DSYNC), + // Causes nonblocking behaviour in various situations. + NONBLOCK = log2(O_NONBLOCK), + // Write I/O shall complete as defined by synchronized I/O file integrity completion. + SYNC = log2(O_SYNC), + + // NOTE: use with `posix.O_RSYNC + { .OTHER_FLAG, .OTHER_FLAG }`, unfortunately can't be in + // this bit set enum because it is 0 on some platforms and a value on others. + // RSYNC = O_RSYNC, + + // Execute only. + EXEC = log2(O_EXEC), + // Reading and writing. + RDWR = log2(O_RDWR), + // Writing only. + WRONLY = log2(O_WRONLY), + // Reading only. + // RDONLY = 0, // Default +} + +O_Flags :: bit_set[O_Flag_Bits; c.int] + +O_ACCMODE :: O_Flags{ .EXEC, .RDWR, .WRONLY } + +AT_Flag_Bits :: enum c.int { + EACCESS = log2(AT_EACCESS), + SYMLINK_NOFOLLOW = log2(AT_SYMLINK_NOFOLLOW), + SYMLINK_FOLLOW = log2(AT_SYMLINK_FOLLOW), + REMOVEDIR = log2(AT_REMOVEDIR), +} +AT_Flags :: bit_set[AT_Flag_Bits; c.int] + +when ODIN_OS == .Darwin { + + off_t :: distinct c.int64_t + pid_t :: distinct c.int32_t + + F_DUPFD :: 0 + F_DUPFD_CLOEXEC :: 67 + F_GETFD :: 1 + F_SETFD :: 2 + F_GETFL :: 3 + F_SETFL :: 4 + F_GETLK :: 7 + F_SETLK :: 8 + F_SETLKW :: 9 + F_GETOWN :: 5 + F_SETOWN :: 6 + + FD_CLOEXEC :: 1 + + F_RDLCK :: 1 + F_UNLCK :: 2 + F_WRLCK :: 3 + + O_CLOEXEC :: 0x01000000 + O_CREAT :: 0x00000200 + O_DIRECTORY :: 0x00100000 + O_EXCL :: 0x00000800 + O_NOCTTY :: 0x00020000 + O_NOFOLLOW :: 0x00000100 + O_TRUNC :: 0x00000400 + + _O_TTY_INIT :: 0 + O_TTY_INIT :: O_Flags{} + + O_APPEND :: 0x00000008 + O_DSYNC :: 0x00400000 + O_NONBLOCK :: 0x00000004 + O_SYNC :: 0x0080 + + _O_RSYNC :: 0 + O_RSYNC :: O_Flags{} + + O_EXEC :: 0x40000000 + O_RDONLY :: 0 + O_RDWR :: 0x0002 + O_WRONLY :: 0x0001 + + _O_SEARCH :: O_EXEC | O_DIRECTORY + O_SEARCH :: O_Flags{.EXEC, .DIRECTORY} + + AT_FDCWD: FD: -2 + + AT_EACCESS :: 0x0010 + AT_SYMLINK_NOFOLLOW :: 0x0020 + AT_SYMLINK_FOLLOW :: 0x0040 + AT_REMOVEDIR :: 0x0080 + + flock :: struct { + l_start: off_t, /* [PSX] relative offset in bytes */ + l_len: off_t, /* [PSX] size; if 0 then until EOF */ + l_pid: pid_t, /* [PSX] process ID of the process holding the lock */ + l_type: Lock_Type, /* [PSX] type of lock */ + l_whence: c.short, /* [PSX] flag (Whence) of starting offset */ + } + +} else when ODIN_OS == .FreeBSD { + + off_t :: distinct c.int64_t + pid_t :: distinct c.int32_t + + F_DUPFD :: 0 + F_DUPFD_CLOEXEC :: 17 + F_GETFD :: 1 + F_SETFD :: 2 + F_GETFL :: 3 + F_SETFL :: 4 + F_GETLK :: 7 + F_SETLK :: 8 + F_SETLKW :: 9 + F_GETOWN :: 5 + F_SETOWN :: 6 + + FD_CLOEXEC :: 1 + + F_RDLCK :: 1 + F_UNLCK :: 2 + F_WRLCK :: 3 + + O_CLOEXEC :: 0x00100000 + O_CREAT :: 0x0200 + O_DIRECTORY :: 0x00020000 + O_EXCL :: 0x0800 + O_NOCTTY :: 0x8000 + O_NOFOLLOW :: 0x0100 + O_TRUNC :: 0x0400 + + _O_TTY_INIT :: 0x00080000 + O_TTY_INIT :: O_Flags{O_Flag_Bits(log2(_O_TTY_INIT))} + + O_APPEND :: 0x0008 + O_DSYNC :: 0x01000000 + O_NONBLOCK :: 0x0004 + O_SYNC :: 0x0080 + _O_RSYNC :: 0 + O_RSYNC :: O_Flags{} // NOTE: not defined in headers + + O_EXEC :: 0x00040000 + O_RDONLY :: 0 + O_RDWR :: 0x0002 + O_WRONLY :: 0x0001 + + _O_SEARCH :: O_EXEC + O_SEARCH :: O_Flags{ .EXEC } + + AT_FDCWD: FD: -100 + + AT_EACCESS :: 0x0100 + AT_SYMLINK_NOFOLLOW :: 0x0200 + AT_SYMLINK_FOLLOW :: 0x0400 + AT_REMOVEDIR :: 0x0800 + + flock :: struct { + l_start: off_t, /* [PSX] relative offset in bytes */ + l_len: off_t, /* [PSX] size; if 0 then until EOF */ + l_pid: pid_t, /* [PSX] process ID of the process holding the lock */ + l_type: Lock_Type, /* [PSX] type of lock */ + l_whence: c.short, /* [PSX] flag (Whence) of starting offset */ + l_sysid: c.int, + } + +} else when ODIN_OS == .NetBSD { + + off_t :: distinct c.int64_t + pid_t :: distinct c.int32_t + + F_DUPFD :: 0 + F_DUPFD_CLOEXEC :: 12 + F_GETFD :: 1 + F_SETFD :: 2 + F_GETFL :: 3 + F_SETFL :: 4 + F_GETLK :: 7 + F_SETLK :: 8 + F_SETLKW :: 9 + F_GETOWN :: 5 + F_SETOWN :: 6 + + FD_CLOEXEC :: 1 + + F_RDLCK :: 1 + F_UNLCK :: 2 + F_WRLCK :: 3 + + O_CLOEXEC :: 0x00400000 + O_CREAT :: 0x0200 + O_DIRECTORY :: 0x0020000 + O_EXCL :: 0x0800 + O_NOCTTY :: 0x8000 + O_NOFOLLOW :: 0x0100 + O_TRUNC :: 0x0400 + + _O_TTY_INIT :: 0 + O_TTY_INIT :: O_Flags{} // NOTE: not defined in the headers + + O_APPEND :: 0x0008 + O_DSYNC :: 0x010000 + O_NONBLOCK :: 0x0004 + O_SYNC :: 0x0080 + + _O_RSYNC :: 0x0002 + O_RSYNC :: O_Flags{O_Flag_Bits(log2(_O_RSYNC))} + + + O_EXEC :: 0x04000000 + O_RDONLY :: 0 + O_RDWR :: 0x0002 + O_WRONLY :: 0x0001 + + _O_SEARCH :: 0x00800000 + O_SEARCH :: O_Flags{O_Flag_Bits(log2(_O_SEARCH))} + + AT_FDCWD: FD: -100 + + AT_EACCESS :: 0x100 + AT_SYMLINK_NOFOLLOW :: 0x200 + AT_SYMLINK_FOLLOW :: 0x400 + AT_REMOVEDIR :: 0x800 + + flock :: struct { + l_start: off_t, /* [PSX] relative offset in bytes */ + l_len: off_t, /* [PSX] size; if 0 then until EOF */ + l_pid: pid_t, /* [PSX] process ID of the process holding the lock */ + l_type: Lock_Type, /* [PSX] type of lock */ + l_whence: c.short, /* [PSX] flag (Whence) of starting offset */ + } +} else when ODIN_OS == .OpenBSD { + + off_t :: distinct c.int64_t + pid_t :: distinct c.int32_t + + F_DUPFD :: 0 + F_DUPFD_CLOEXEC :: 10 + F_GETFD :: 1 + F_SETFD :: 2 + F_GETFL :: 3 + F_SETFL :: 4 + F_GETLK :: 7 + F_SETLK :: 8 + F_SETLKW :: 9 + F_GETOWN :: 5 + F_SETOWN :: 6 + + FD_CLOEXEC :: 1 + + F_RDLCK :: 1 + F_UNLCK :: 2 + F_WRLCK :: 3 + + O_CLOEXEC :: 0x10000 + O_CREAT :: 0x0200 + O_DIRECTORY :: 0x20000 + O_EXCL :: 0x0800 + O_NOCTTY :: 0x8000 + O_NOFOLLOW :: 0x0100 + O_TRUNC :: 0x0400 + + _O_TTY_INIT :: 0 + O_TTY_INIT :: O_Flags{} // NOTE: not defined in the headers + + O_APPEND :: 0x0008 + O_DSYNC :: 0x010000 + O_NONBLOCK :: 0x0004 + O_SYNC :: 0x0080 + + _O_RSYNC :: O_SYNC + O_RSYNC :: O_Flags{.SYNC} + + O_EXEC :: 0x04000000 // NOTE: not defined in the headers + O_RDONLY :: 0 + O_RDWR :: 0x0002 + O_WRONLY :: 0x0001 + + _O_SEARCH :: 0 + O_SEARCH :: O_Flags{} // NOTE: not defined in the headers + + AT_FDCWD: FD: -100 + + AT_EACCESS :: 0x01 + AT_SYMLINK_NOFOLLOW :: 0x02 + AT_SYMLINK_FOLLOW :: 0x04 + AT_REMOVEDIR :: 0x08 + + flock :: struct { + l_start: off_t, /* [PSX] relative offset in bytes */ + l_len: off_t, /* [PSX] size; if 0 then until EOF */ + l_pid: pid_t, /* [PSX] process ID of the process holding the lock */ + l_type: Lock_Type, /* [PSX] type of lock */ + l_whence: c.short, /* [PSX] flag (Whence) of starting offset */ + } + +} else when ODIN_OS == .Linux { + + off_t :: distinct c.int64_t + pid_t :: distinct c.int + + F_DUPFD :: 0 + F_GETFD :: 1 + F_SETFD :: 2 + F_GETFL :: 3 + F_SETFL :: 4 + F_GETLK :: 5 + F_SETLK :: 6 + F_SETLKW :: 7 + F_SETOWN :: 8 + F_GETOWN :: 9 + F_RDLCK :: 0 + F_UNLCK :: 2 + F_WRLCK :: 1 + + F_DUPFD_CLOEXEC :: 1030 + + FD_CLOEXEC :: 1 + + O_CREAT :: 0o0_000_100 + O_EXCL :: 0o0_000_200 + O_NOCTTY :: 0o0_000_400 + O_TRUNC :: 0o0_001_000 + O_DIRECTORY :: 0o0_200_000 + O_NOFOLLOW :: 0o0_400_000 + O_CLOEXEC :: 0o2_000_000 + + _O_TTY_INIT :: 0 + O_TTY_INIT :: O_Flags{} + + O_APPEND :: 0o0_002_000 + O_NONBLOCK :: 0o0_004_000 + O_DSYNC :: 0o0_010_000 + O_SYNC :: 0o4_010_000 + + _O_RSYNC :: 0 + O_RSYNC :: O_Flags{} + + O_EXEC :: 0x04000000 // NOTE: not defined in the headers + + O_RDONLY :: 0 + O_WRONLY :: 0o1 + O_RDWR :: 0o2 + + _O_SEARCH :: 0 + O_SEARCH :: O_Flags{} + + AT_FDCWD: FD: -100 + + AT_EACCESS :: 0x200 + AT_SYMLINK_NOFOLLOW :: 0x100 + AT_SYMLINK_FOLLOW :: 0x400 + AT_REMOVEDIR :: 0x200 + + flock :: struct { + l_type: Lock_Type, /* [PSX] type of lock. */ + l_whence: c.short, /* [PSX] flag (Whence) of starting offset. */ + l_start: off_t, /* [PSX] relative offset in bytes. */ + l_len: off_t, /* [PSX] size; if 0 then until EOF. */ + l_pid: pid_t, /* [PSX] process ID of the process holding the lock. */ + } + +} diff --git a/core/sys/posix/fnmatch.odin b/core/sys/posix/fnmatch.odin new file mode 100644 index 000000000..2d582705c --- /dev/null +++ b/core/sys/posix/fnmatch.odin @@ -0,0 +1,65 @@ +#+build darwin, linux, openbsd, freebsd, netbsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// fnmatch.h - filename-matching types + +foreign lib { + /* + Match patterns as described in XCU [[ Patterns Matching a Single Character; https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_01 ]] + // and [[ Patterns Matching Multiple Characters; https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_02 ]]. + It checks the string specified by the string argument to see if it matches the pattern specified by the pattern argument. + + Returns: 0 when matched. if there is no match, fnmatch() shall return FNM_NOMATCH. Non-zero on other errors. + + Example: + assert(posix.fnmatch("*.odin", "foo.odin", {}) == 0) + assert(posix.fnmatch("*.txt", "foo.odin", {}) == posix.FNM_NOMATCH) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fnmatch.html ]] + */ + fnmatch :: proc(pattern: cstring, string: cstring, flags: FNM_Flags) -> c.int --- +} + +FNM_Flag_Bits :: enum c.int { + // A character ( '/' ) in string shall be explicitly matched by a in pattern; + // it shall not be matched by either the or special characters, + // nor by a bracket expression. + PATHNAME = log2(FNM_PATHNAME), + + // A leading ( '.' ) in string shall match a in pattern; + // as described by rule 2 in XCU [[ Patterns Used for Filename Expansion; https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_03 ]] + // where the location of "leading" is indicated by the value of PATHNAME: + // 1. If PATHNAME is set, a is "leading" if it is the first character in string or if it immediately follows a . + // 2. If PATHNAME is not set, a is "leading" only if it is the first character of string. + PERIOD = log2(FNM_PERIOD), + + // A character shall be treated as an ordinary character. + NOESCAPE = log2(FNM_NOESCAPE), +} +FNM_Flags :: bit_set[FNM_Flag_Bits; c.int] + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + FNM_NOMATCH :: 1 + + FNM_PATHNAME :: 0x02 + FNM_PERIOD :: 0x04 + FNM_NOESCAPE :: 0x01 + +} else when ODIN_OS == .Linux { + + FNM_NOMATCH :: 1 + + FNM_PATHNAME :: 0x01 + FNM_NOESCAPE :: 0x02 + FNM_PERIOD :: 0x04 + +} diff --git a/core/sys/posix/glob.odin b/core/sys/posix/glob.odin new file mode 100644 index 000000000..7c8009a59 --- /dev/null +++ b/core/sys/posix/glob.odin @@ -0,0 +1,207 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// glob.h - pathname pattern-matching types + +foreign lib { + /* + The glob() function is a pathname generator that shall implement the rules defined in + [[ XCU Pattern Matching Notation; https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13 ]], + with optional support for rule 3 in XCU [[ Patterns Used for Filename Expansion; https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_03 ]]. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/glob.html ]] + */ + @(link_name=LGLOB) + glob :: proc( + pattern: cstring, + flags: Glob_Flags, + errfunc: proc "c" (epath: cstring, eerrno: Errno) -> b32 = nil, // Return `true` to abort the glob(). + pglob: ^glob_t, + ) -> Glob_Result --- + + /* + Free the glob results. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/glob.html ]] + */ + @(link_name=LGLOBFREE) + globfree :: proc(^glob_t) --- +} + +Glob_Flag_Bits :: enum c.int { + // Append pathnames generated to the ones from a previous call to glob(). + APPEND = log2(GLOB_APPEND), + // Make use of pglob->gl_offs. If this flag is set, pglob->gl_offs is used to specify how many null pointers to add to the beginning of pglob->gl_pathv. + // In other words, pglob->gl_pathv shall point to pglob->gl_offs null pointers, followed by pglob->gl_pathc pathname pointers, followed by a null pointer. + DOOFFS = log2(GLOB_DOOFFS), + // Cause glob() to return when it encounters a directory that it cannot open or read. Ordinarily, + // glob() continues to find matches. + ERR = log2(GLOB_ERR), + // Each pathname that is a directory that matches pattern shall have a appended. + MARK = log2(GLOB_MARK), + // Supports rule 3 in [[ XCU Patterns Used for Filename Expansion; https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_03 ]]. + // If pattern does not match any pathname, then glob() shall return a list consisting of only pattern, + // and the number of matched pathnames is 1. + NOCHECK = log2(GLOB_NOCHECK), + // Disable backslash escaping. + NOESCAPE = log2(GLOB_NOESCAPE), + // Ordinarily, glob() sorts the matching pathnames according to the current setting of the + // LC_COLLATE category; see XBD LC_COLLATE. When this flag is used, + // the order of pathnames returned is unspecified. + NOSORT = log2(GLOB_NOSORT), +} +Glob_Flags :: bit_set[Glob_Flag_Bits; c.int] + +Glob_Result :: enum c.int { + SUCCESS = 0, + ABORTED = GLOB_ABORTED, + NOMATCH = GLOB_NOMATCH, + NOSPACE = GLOB_NOSPACE, +} + +when ODIN_OS == .NetBSD { + @(private) LGLOB :: "__glob30" + @(private) LGLOBFREE :: "__globfree30" +} else { + @(private) LGLOB :: "glob" + INODE_SUFFIX + @(private) LGLOBFREE :: "globfree" +} + +when ODIN_OS == .Darwin { + + glob_t :: struct { + gl_pathc: c.size_t, /* [PSX] count of paths matched by pattern */ + gl_matchc: c.int, /* count of paths matching pattern */ + gl_offs: c.size_t, /* [PSX] slots to reserve at the beginning of gl_pathv */ + gl_flags: Glob_Flags, /* copy of flags parameter to glob */ + gl_pathv: [^]cstring `fmt:"v,gl_pathc"`, /* [PSX] pointer to list of matched pathnames */ + + // Non-standard alternate file system access functions: + + using _: struct #raw_union { + gl_errfunc: proc "c" (cstring, c.int) -> c.int, + gl_errblk: proc "c" (cstring, c.int) -> c.int, + }, + gl_closedir: proc "c" (dirp: DIR), + gl_readdir: proc "c" (dirp: DIR) -> ^dirent, + gl_opendir: proc "c" (path: cstring) -> DIR, + gl_lstat: proc "c" (path: cstring, buf: ^stat_t) -> result, + gl_stat: proc "c" (path: cstring, buf: ^stat_t) -> result, + } + + GLOB_APPEND :: 0x0001 + GLOB_DOOFFS :: 0x0002 + GLOB_ERR :: 0x0004 + GLOB_MARK :: 0x0008 + GLOB_NOCHECK :: 0x0010 + GLOB_NOESCAPE :: 0x2000 + GLOB_NOSORT :: 0x0020 + + GLOB_ABORTED :: -2 + GLOB_NOMATCH :: -3 + GLOB_NOSPACE :: -1 + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { + + glob_t :: struct { + gl_pathc: c.size_t, /* [PSX] count of paths matched by pattern */ + gl_matchc: c.size_t, /* count of paths matching pattern */ + gl_offs: c.size_t, /* [PSX] slots to reserve at the beginning of gl_pathv */ + gl_flags: Glob_Flags, /* copy of flags parameter to glob */ + gl_pathv: [^]cstring `fmt:"v,gl_pathc"`, /* [PSX] pointer to list of matched pathnames */ + + // Non-standard alternate file system access functions: + + gl_errfunc: proc "c" (cstring, c.int) -> c.int, + + gl_closedir: proc "c" (dirp: DIR), + gl_readdir: proc "c" (dirp: DIR) -> ^dirent, + gl_opendir: proc "c" (path: cstring) -> DIR, + gl_lstat: proc "c" (path: cstring, buf: ^stat_t) -> result, + gl_stat: proc "c" (path: cstring, buf: ^stat_t) -> result, + } + + GLOB_APPEND :: 0x0001 + GLOB_DOOFFS :: 0x0002 + GLOB_ERR :: 0x0004 + GLOB_MARK :: 0x0008 + GLOB_NOCHECK :: 0x0010 + GLOB_NOESCAPE :: 0x2000 when ODIN_OS == .FreeBSD else 0x0100 + GLOB_NOSORT :: 0x0020 + + GLOB_ABORTED :: -2 + GLOB_NOMATCH :: -3 + GLOB_NOSPACE :: -1 + +} else when ODIN_OS == .OpenBSD { + + glob_t :: struct { + gl_pathc: c.size_t, /* [PSX] count of paths matched by pattern */ + gl_matchc: c.size_t, /* count of paths matching pattern */ + gl_offs: c.size_t, /* [PSX] slots to reserve at the beginning of gl_pathv */ + gl_flags: Glob_Flags, /* copy of flags parameter to glob */ + gl_pathv: [^]cstring `fmt:"v,gl_pathc"`, /* [PSX] pointer to list of matched pathnames */ + + gl_statv: [^]stat_t, + + // Non-standard alternate file system access functions: + + gl_errfunc: proc "c" (cstring, c.int) -> c.int, + + gl_closedir: proc "c" (dirp: DIR), + gl_readdir: proc "c" (dirp: DIR) -> ^dirent, + gl_opendir: proc "c" (path: cstring) -> DIR, + gl_lstat: proc "c" (path: cstring, buf: ^stat_t) -> result, + gl_stat: proc "c" (path: cstring, buf: ^stat_t) -> result, + } + + GLOB_APPEND :: 0x0001 + GLOB_DOOFFS :: 0x0002 + GLOB_ERR :: 0x0004 + GLOB_MARK :: 0x0008 + GLOB_NOCHECK :: 0x0010 + GLOB_NOESCAPE :: 0x1000 + GLOB_NOSORT :: 0x0020 + + GLOB_ABORTED :: -2 + GLOB_NOMATCH :: -3 + GLOB_NOSPACE :: -1 + +} else when ODIN_OS == .Linux { + + glob_t :: struct { + gl_pathc: c.size_t, /* [PSX] count of paths matched by pattern */ + gl_pathv: [^]cstring `fmt:"v,gl_pathc"`, /* [PSX] pointer to list of matched pathnames */ + gl_offs: c.size_t, /* [PSX] slots to reserve at the beginning of gl_pathv */ + gl_flags: Glob_Flags, /* copy of flags parameter to glob */ + + // Non-standard alternate file system access functions: + + gl_closedir: proc "c" (dirp: DIR), + gl_readdir: proc "c" (dirp: DIR) -> ^dirent, + gl_opendir: proc "c" (path: cstring) -> DIR, + gl_lstat: proc "c" (path: cstring, buf: ^stat_t) -> result, + gl_stat: proc "c" (path: cstring, buf: ^stat_t) -> result, + } + + GLOB_ERR :: 1 << 0 + GLOB_MARK :: 1 << 1 + GLOB_NOSORT :: 1 << 2 + GLOB_DOOFFS :: 1 << 3 + GLOB_NOCHECK :: 1 << 4 + GLOB_APPEND :: 1 << 5 + GLOB_NOESCAPE :: 1 << 6 + + GLOB_NOSPACE :: 1 + GLOB_ABORTED :: 2 + GLOB_NOMATCH :: 3 + +} diff --git a/core/sys/posix/grp.odin b/core/sys/posix/grp.odin new file mode 100644 index 000000000..956ed148b --- /dev/null +++ b/core/sys/posix/grp.odin @@ -0,0 +1,129 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// grp.h - group structure + +foreign lib { + /* + Closes the group database. + + Checking status would be done by setting errno to 0, calling this, and checking errno. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/endgrent.html ]] + */ + endgrent :: proc() --- + + /* + Rewinds the group database so getgrent() returns the first entry again. + + Checking status would be done by setting errno to 0, calling this, and checking errno. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/endgrent.html ]] + */ + setgrent :: proc() --- + + /* + Returns a pointer to an entry of the group database. + + Opens the group database if it isn't. + + Returns: nil on failure (setting errno) or EOF (not setting errno), the entry otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/endgrent.html ]] + */ + getgrent :: proc() -> ^group --- + + /* + Searches for an entry with a matching gid in the group database. + + Returns: nil (setting errno) on failure, a pointer to the entry on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getgrgid.html ]] + */ + getgrgid :: proc(gid: gid_t) -> ^group --- + + /* + Searches for an entry with a matching gid in the group database. + + Updates grp with the matching entry and stores it (or a nil pointer (setting errno)) into result. + + Strings are allocated into the given buffer, you can call `sysconf(._GETGR_R_SIZE_MAX)` for an appropriate size. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getgrgid.html ]] + */ + getgrgid_r :: proc(gid: gid_t, grp: ^group, buffer: [^]byte, bufsize: c.size_t, result: ^^group) -> Errno --- + + /* + Searches for an entry with a matching gid in the group database. + + Returns: nil (setting errno) on failure, a pointer to the entry on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getgrnam.html ]] + */ + getgrnam :: proc(name: cstring) -> ^group --- + + /* + Searches for an entry with a matching gid in the group database. + + Updates grp with the matching entry and stores it (or a nil pointer (setting errno)) into result. + + Strings are allocated into the given buffer, you can call `sysconf(._GETGR_R_SIZE_MAX)` for an appropriate size. + + Example: + length := posix.sysconf(._GETGR_R_SIZE_MAX) + if length == -1 { + length = 1024 + } + + result: posix.group + resultp: ^posix.group + + e: posix.Errno + + buffer: [dynamic]byte + defer delete(buffer) + + for { + mem_err := resize(&buffer, length) + assert(mem_err == nil) + + e = posix.getgrnam_r("nobody", &result, raw_data(buffer), len(buffer), &resultp) + if e != .ERANGE { + break + } + + length *= 2 + assert(length > 0) + } + + if e != .NONE { + panic(string(posix.strerror(e))) + } + + fmt.println(result) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getgrnam.html ]] + */ + getgrnam_r :: proc(name: cstring, grp: ^group, buffer: [^]byte, bufsize: c.size_t, result: ^^group) -> Errno --- +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { + + gid_t :: distinct c.uint32_t + + group :: struct { + gr_name: cstring, /* [PSX] group name */ + gr_passwd: cstring, /* group password */ + gr_gid: gid_t, /* [PSX] group id */ + gr_mem: [^]cstring, /* [PSX] group members */ + } + +} diff --git a/core/sys/posix/iconv.odin b/core/sys/posix/iconv.odin new file mode 100644 index 000000000..f7447be9e --- /dev/null +++ b/core/sys/posix/iconv.odin @@ -0,0 +1,51 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + // NOTE: iconv is in a different library + foreign import lib "system:iconv" +} else { + foreign import lib "system:c" +} + +// iconv.h - codeset conversion facility + +iconv_t :: distinct rawptr + +foreign lib { + /* + Convert the sequence of characters from one codeset, in the array specified by inbuf, + into a sequence of corresponding characters in another codeset, in the array specified by outbuf. + + Returns: -1 (setting errno) on failure, the number of non-identical conversions performed on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/iconv.html ]] + */ + iconv :: proc( + cd: iconv_t, + inbuf: ^[^]byte, + inbytesleft: ^c.size_t, + outbuf: ^[^]byte, + outbyteslen: ^c.size_t, + ) -> c.size_t --- + + /* + Deallocates the conversion descriptor cd and all other associated resources allocated by iconv_open(). + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/iconv_close.html ]] + */ + iconv_close :: proc(cd: iconv_t) -> result --- + + /* + Returns a conversion descriptor that describes a conversion from the codeset specified by the + string pointed to by the fromcode argument to the codeset specified by the string pointed to by + the tocode argument. + + Returns: -1 (setting errno) on failure, a conversion descriptor on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/iconv_open.html ]] + */ + iconv_open :: proc(tocode: cstring, fromcode: cstring) -> iconv_t --- +} diff --git a/core/sys/posix/langinfo.odin b/core/sys/posix/langinfo.odin new file mode 100644 index 000000000..3c001aee0 --- /dev/null +++ b/core/sys/posix/langinfo.odin @@ -0,0 +1,370 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// langinfo.h - language information constants + +foreign lib { + /* + Return a pointer to a string containing information relevant to the particular language or + cultural area defined in the current locale. + + Returns: a string that should not be freed or modified, and that can be invalidated at any time later + + Example: + for item in posix.nl_item { + fmt.printfln("%v: %q", item, posix.nl_langinfo(item)) + } + + Possible Output: + CODESET: "US-ASCII" + D_T_FMT: "%a %b %e %H:%M:%S %Y" + D_FMT: "%m/%d/%y" + T_FMT: "%H:%M:%S" + T_FMT_AMPM: "%I:%M:%S %p" + AM_STR: "AM" + PM_STR: "PM" + DAY_1: "Sunday" + DAY_2: "Monday" + DAY_3: "Tuesday" + DAY_4: "Wednesday" + DAY_5: "Thursday" + DAY_6: "Friday" + DAY_7: "Saturday" + ABDAY_1: "Sun" + ABDAY_2: "Mon" + ABDAY_3: "Tue" + ABDAY_4: "Wed" + ABDAY_5: "Thu" + ABDAY_6: "Fri" + ABDAY_7: "Sat" + MON_1: "January" + MON_2: "February" + MON_3: "March" + MON_4: "April" + MON_5: "May" + MON_6: "June" + MON_7: "July" + MON_8: "August" + MON_9: "September" + MON_10: "October" + MON_11: "November" + MON_12: "December" + ABMON_1: "Jan" + ABMON_2: "Feb" + ABMON_3: "Mar" + ABMON_4: "Apr" + ABMON_5: "May" + ABMON_6: "Jun" + ABMON_7: "Jul" + ABMON_8: "Aug" + ABMON_9: "Sep" + ABMON_10: "Oct" + ABMON_11: "Nov" + ABMON_12: "Dec" + ERA: "" + ERA_D_FMT: "" + ERA_D_T_FMT: "" + ERA_T_FMT: "" + ALT_DIGITS: "" + RADIXCHAR: "." + THOUSEP: "" + YESEXPR: "^[yY]" + NOEXPR: "^[nN]" + CRNCYSTR: "" + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/nl_langinfo.html ]] + */ + nl_langinfo :: proc(nl_item) -> cstring --- +} + +nl_item :: enum nl_item_t { + CODESET = CODESET, + D_T_FMT = D_T_FMT, + D_FMT = D_FMT, + T_FMT = T_FMT, + T_FMT_AMPM = T_FMT_AMPM, + AM_STR = AM_STR, + PM_STR = PM_STR, + DAY_1 = DAY_1, + DAY_2 = DAY_2, + DAY_3 = DAY_3, + DAY_4 = DAY_4, + DAY_5 = DAY_5, + DAY_6 = DAY_6, + DAY_7 = DAY_7, + ABDAY_1 = ABDAY_1, + ABDAY_2 = ABDAY_2, + ABDAY_3 = ABDAY_3, + ABDAY_4 = ABDAY_4, + ABDAY_5 = ABDAY_5, + ABDAY_6 = ABDAY_6, + ABDAY_7 = ABDAY_7, + MON_1 = MON_1, + MON_2 = MON_2, + MON_3 = MON_3, + MON_4 = MON_4, + MON_5 = MON_5, + MON_6 = MON_6, + MON_7 = MON_7, + MON_8 = MON_8, + MON_9 = MON_9, + MON_10 = MON_10, + MON_11 = MON_11, + MON_12 = MON_12, + ABMON_1 = ABMON_1, + ABMON_2 = ABMON_2, + ABMON_3 = ABMON_3, + ABMON_4 = ABMON_4, + ABMON_5 = ABMON_5, + ABMON_6 = ABMON_6, + ABMON_7 = ABMON_7, + ABMON_8 = ABMON_8, + ABMON_9 = ABMON_9, + ABMON_10 = ABMON_10, + ABMON_11 = ABMON_11, + ABMON_12 = ABMON_12, + ERA = ERA, + ERA_D_FMT = ERA_D_FMT, + ERA_D_T_FMT = ERA_D_T_FMT, + ERA_T_FMT = ERA_T_FMT, + ALT_DIGITS = ALT_DIGITS, + RADIXCHAR = RADIXCHAR, + THOUSEP = THOUSEP, + YESEXPR = YESEXPR, + NOEXPR = NOEXPR, + CRNCYSTR = CRNCYSTR, +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD { + + // NOTE: declared with `_t` so we can enumerate the real `nl_info`. + nl_item_t :: distinct c.int + + CODESET :: 0 + D_T_FMT :: 1 + D_FMT :: 2 + T_FMT :: 3 + T_FMT_AMPM :: 4 + AM_STR :: 5 + PM_STR :: 6 + + DAY_1 :: 7 + DAY_2 :: 8 + DAY_3 :: 9 + DAY_4 :: 10 + DAY_5 :: 11 + DAY_6 :: 12 + DAY_7 :: 13 + + ABDAY_1 :: 14 + ABDAY_2 :: 15 + ABDAY_3 :: 16 + ABDAY_4 :: 17 + ABDAY_5 :: 18 + ABDAY_6 :: 19 + ABDAY_7 :: 20 + + MON_1 :: 21 + MON_2 :: 22 + MON_3 :: 23 + MON_4 :: 24 + MON_5 :: 25 + MON_6 :: 26 + MON_7 :: 27 + MON_8 :: 28 + MON_9 :: 29 + MON_10 :: 30 + MON_11 :: 31 + MON_12 :: 32 + + ABMON_1 :: 33 + ABMON_2 :: 34 + ABMON_3 :: 35 + ABMON_4 :: 36 + ABMON_5 :: 37 + ABMON_6 :: 38 + ABMON_7 :: 39 + ABMON_8 :: 40 + ABMON_9 :: 41 + ABMON_10 :: 42 + ABMON_11 :: 43 + ABMON_12 :: 44 + + ERA :: 45 + ERA_D_FMT :: 46 + ERA_D_T_FMT :: 47 + ERA_T_FMT :: 48 + ALT_DIGITS :: 49 + + RADIXCHAR :: 50 + THOUSEP :: 51 + + YESEXPR :: 52 + NOEXPR :: 53 + + CRNCYSTR :: 56 + +} else when ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + // NOTE: declared with `_t` so we can enumerate the real `nl_info`. + nl_item_t :: distinct c.int + + CODESET :: 51 + D_T_FMT :: 0 + D_FMT :: 1 + T_FMT :: 2 + T_FMT_AMPM :: 3 + AM_STR :: 4 + PM_STR :: 5 + + DAY_1 :: 6 + DAY_2 :: 7 + DAY_3 :: 8 + DAY_4 :: 9 + DAY_5 :: 10 + DAY_6 :: 11 + DAY_7 :: 12 + + ABDAY_1 :: 13 + ABDAY_2 :: 14 + ABDAY_3 :: 15 + ABDAY_4 :: 16 + ABDAY_5 :: 17 + ABDAY_6 :: 18 + ABDAY_7 :: 19 + + MON_1 :: 20 + MON_2 :: 21 + MON_3 :: 22 + MON_4 :: 23 + MON_5 :: 24 + MON_6 :: 25 + MON_7 :: 26 + MON_8 :: 27 + MON_9 :: 28 + MON_10 :: 29 + MON_11 :: 30 + MON_12 :: 31 + + ABMON_1 :: 32 + ABMON_2 :: 33 + ABMON_3 :: 34 + ABMON_4 :: 35 + ABMON_5 :: 36 + ABMON_6 :: 37 + ABMON_7 :: 38 + ABMON_8 :: 39 + ABMON_9 :: 40 + ABMON_10 :: 41 + ABMON_11 :: 42 + ABMON_12 :: 43 + + ERA :: 52 + ERA_D_FMT :: 53 + ERA_D_T_FMT :: 54 + ERA_T_FMT :: 55 + ALT_DIGITS :: 56 + + RADIXCHAR :: 44 + THOUSEP :: 45 + + YESEXPR :: 47 + NOEXPR :: 49 + + CRNCYSTR :: 50 + +} else when ODIN_OS == .Linux { + + // NOTE: declared with `_t` so we can enumerate the real `nl_info`. + nl_item_t :: distinct c.int + + // NOTE: All these values are set in an enum on the Linux implementation. + // Some depend on locale.h contants (bits/locale.h to be precise). + + // NOTE: ABDAY_1 is set to LC_TIME << 16 (LC_TIME is 2) on the enum group of + // the Linux implementation. + ABDAY_1 :: 0x20_000 + ABDAY_2 :: 0x20_001 + ABDAY_3 :: 0x20_002 + ABDAY_4 :: 0x20_003 + ABDAY_5 :: 0x20_004 + ABDAY_6 :: 0x20_005 + ABDAY_7 :: 0x20_006 + + DAY_1 :: 0x20_007 + DAY_2 :: 0x20_008 + DAY_3 :: 0x20_009 + DAY_4 :: 0x20_00A + DAY_5 :: 0x20_00B + DAY_6 :: 0x20_00C + DAY_7 :: 0x20_00D + + ABMON_1 :: 0x20_00E + ABMON_2 :: 0x20_010 + ABMON_3 :: 0x20_011 + ABMON_4 :: 0x20_012 + ABMON_5 :: 0x20_013 + ABMON_6 :: 0x20_014 + ABMON_7 :: 0x20_015 + ABMON_8 :: 0x20_016 + ABMON_9 :: 0x20_017 + ABMON_10 :: 0x20_018 + ABMON_11 :: 0x20_019 + ABMON_12 :: 0x20_01A + + MON_1 :: 0x20_01B + MON_2 :: 0x20_01C + MON_3 :: 0x20_01D + MON_4 :: 0x20_01E + MON_5 :: 0x20_020 + MON_6 :: 0x20_021 + MON_7 :: 0x20_022 + MON_8 :: 0x20_023 + MON_9 :: 0x20_024 + MON_10 :: 0x20_025 + MON_11 :: 0x20_026 + MON_12 :: 0x20_027 + + AM_STR :: 0x20_028 + PM_STR :: 0x20_029 + + D_T_FMT :: 0x20_02A + D_FMT :: 0x20_02B + T_FMT :: 0x20_02C + T_FMT_AMPM :: 0x20_02D + + ERA :: 0x20_02E + ERA_D_FMT :: 0x20_030 + ALT_DIGITS :: 0x20_031 + ERA_D_T_FMT :: 0x20_032 + ERA_T_FMT :: 0x20_033 + + // NOTE: CODESET is the 16th member of the enum group starting with value + // LC_CTYPE << 16, LC_CTYPE is 0. + CODESET :: 0x0F + + // NOTE: CRNCYSTR is the 16th member of the enum group starting with value + // LC_MONETARY << 16, LC_MONETARY is 4. + CRNCYSTR :: 0x40_00F + + // NOTE: RADIXCHAR is the 1st member of the enum group starting with value + // LC_NUMERIC << 16, LC_NUMERIC is 1. + RADIXCHAR :: 0x10_000 + THOUSEP :: 0x10_001 + + // NOTE: YESEXPR is the 1st member of the enum group starting with value + // LC_MESSAGES << 16, LC_MESSAGES is 5. + YESEXPR :: 0x50_000 + NOEXPR :: 0x50_001 + +} else { + #panic("posix is unimplemented for the current target") +} diff --git a/core/sys/posix/libgen.odin b/core/sys/posix/libgen.odin new file mode 100644 index 000000000..69176a557 --- /dev/null +++ b/core/sys/posix/libgen.odin @@ -0,0 +1,82 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// libgen.h - definitions for pattern matching functions + +foreign lib { + /* + Takes the pathname pointed to by path and return a pointer to the final component of the + pathname, deleting any trailing '/' characters. + + NOTE: may modify input, so don't give it string literals. + + Returns: a string that might be a modification of the input string or a static string overwritten by subsequent calls + + Example: + tests := []string{ + "usr", "usr/", "", "/", "//", "///", "/usr/", "/usr/lib", + "//usr//lib//", "/home//dwc//test", + } + + tbl: table.Table + table.init(&tbl) + table.header(&tbl, "input", "dirname", "basename") + + for test in tests { + din := strings.clone_to_cstring(test); defer delete(din) + dir := strings.clone_from_cstring(posix.dirname(din)) + + bin := strings.clone_to_cstring(test); defer delete(bin) + base := strings.clone_from_cstring(posix.basename(bin)) + table.row(&tbl, test, dir, base) + } + + table.write_plain_table(os.stream_from_handle(os.stdout), &tbl) + + Output: + +----------------+----------+--------+ + |input |dirname |basename| + +----------------+----------+--------+ + |usr |. |usr | + |usr/ |. |usr | + | |. |. | + |/ |/ |/ | + |// |/ |/ | + |/// |/ |/ | + |/usr/ |/ |usr | + |/usr/lib |/usr |lib | + |//usr//lib// |//usr |lib | + |/home//dwc//test|/home//dwc|test | + +----------------+----------+--------+ + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/basename.html ]] + */ + @(link_name=LBASENAME) + basename :: proc(path: cstring) -> cstring --- + + /* + Takes a string that contains a pathname, and returns a string that is a pathname of the parent + directory of that file. + + NOTE: may modify input, so don't give it string literals. + + Returns: a string that might be a modification of the input string or a static string overwritten by subsequent calls + + See example for basename(). + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dirname.html ]] + */ + dirname :: proc(path: cstring) -> cstring --- +} + +when ODIN_OS == .Linux { + @(private) LBASENAME :: "__xpg_basename" +} else { + @(private) LBASENAME :: "basename" +} diff --git a/core/sys/posix/limits.odin b/core/sys/posix/limits.odin new file mode 100644 index 000000000..6680986ad --- /dev/null +++ b/core/sys/posix/limits.odin @@ -0,0 +1,553 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +// limits.h - implementation-defined constants + +// NOTE: numerical limits are left out because Odin provides `min(T)` and `max(T)`. + +// The header shall define the following symbolic constants with the values shown. +// These are the most restrictive values for certain features on an implementation. +// A conforming implementation shall provide values no larger than these values. +// A conforming application must not require a smaller value for correct operation. + +_POSIX_CLOCKRES_MIN :: 20000000 + +// The header shall define the following symbolic constants with the values shown. +// These are the most restrictive values for certain features on an implementation conforming to +// this volume of POSIX.1-2017. +// Related symbolic constants are defined elsewhere in this volume of POSIX.1-2017 which reflect +// the actual implementation and which need not be as restrictive. For each of these limits, +// a conforming implementation shall provide a value at least this large or shall have no limit. +// A strictly conforming application must not require a larger value for correct operation. + +_POSIX_AIO_LISTIO_MAX :: 2 +_POSIX_AIO_MAX :: 1 +_POSIX_ARG_MAX :: 4096 +_POSIX_CHILD_MAX :: 25 +_POSIX_DELAYTIMER_MAX :: 32 +_POSIX_HOST_NAME_MAX :: 255 +_POSIX_LINK_MAX :: 8 +_POSIX_MAX_CANON :: 255 +_POSIX_MAX_INPUT :: 255 +_POSIX_MQ_OPEN_MAX :: 8 +_POSIX_MQ_PRIO_MAX :: 32 +_POSIX_NAME_MAX :: 14 +_POSIX_NGROUPS_MAX :: 8 +_POSIX_OPEN_MAX :: 20 +_POSIX_PATH_MAX :: 256 +_POSIX_PIPE_BUF :: 512 +_POSIX_RE_DUP_MAX :: 255 +_POSIX_RTSIG_MAX :: 8 +_POSIX_SEM_NSEMS_MAX :: 256 +_POSIX_SEM_VALUE_MAX :: 32767 +_POSIX_SS_REPL_MAX :: 4 +_POSIX_STREAM_MAX :: 8 +_POSIX_SYMLINK_MAX :: 255 +_POSIX_SYMLOOP_MAX :: 8 +_POSIX_THREAD_DESTRUCTION_ITERATIONS :: 4 +_POSIX_THREAD_KEYS_MAX :: 128 +_POSIX_THREADS_THREADS_MAX :: 64 +_POSIX_TIMER_MAX :: 32 +_POSIX_TRAXE_EVENT_NAME_MAX :: 30 +_POSIX_TRACE_NAME_MAX :: 8 +_POSIX_TRACE_SYS_MAX :: 8 +_POSIX_TRACE_USER_EVENT_MAX :: 32 +_POSIX_TTY_NAME_MAX :: 9 +_POSIX_TZNAME_MAX :: 6 +_POSIX2_BC_BASE_MAX :: 99 +_POSIX2_BC_DIM_MAX :: 2048 +_POSIX2_BC_SCALE_MAX :: 99 +_POSIX2_CHARCLASS_NAME_MAX :: 14 +_POSIX2_COLL_WEIGHTS_MAX :: 2 +_POSIX2_EXPR_NEST_MAX :: 32 +_POSIX2_LINE_MAX :: 2048 +_POSIX2_RE_DUP_MAX :: 255 +_XOPEN_IOV_MAX :: 16 +_XOPEN_NAME_MAX :: 255 +_XOPEN_PATH_MAX :: 1024 + +/* +NOTE: for full portability, usage should look something like: + + page_size: uint + when #defined(posix.PAGESIZE) { + page_size = posix.PAGESIZE + } else { + page_size = posix.sysconf(._PAGESIZE) + } +*/ + +when ODIN_OS == .Darwin { + // A definition of one of the symbolic constants in the following list shall be omitted from + // on specific implementations where the corresponding value is equal to or greater + // than the stated minimum, but is unspecified. + // + // This indetermination might depend on the amount of available memory space on a specific + // instance of a specific implementation. The actual value supported by a specific instance shall + // be provided by the sysconf() function. + + // AIO_LISTIO_MAX :: sysconf(._AIO_LISTIO_MAX) + // AIO_MAX :: sysconf(._AIO_MAX) + // AIO_PRIO_DELTA_MAX :: sysconf(._AIO_PRIO_DELTA_MAX) + ARG_MAX :: 1024 * 1024 + // ATEXIT_MAX :: sysconf(._ATEXIT_MAX) + CHILD_MAX :: 266 + // DELAYTIMER_MAX :: sysconf(._DELAYTIMER_MAX) + // HOST_NAME_MAX :: sysconf(._HOST_NAME_MAX) + IOV_MAX :: 1024 + // LOGIN_NAME_MAX :: sysconf(._LOGIN_NAME_MAX) + // MQ_OPEN_MAX :: sysconf(._MQ_OPEN_MAX) + // MQ_PRIO_MAX :: sysconf(._MQ_PRIO_MAX) + PAGESIZE :: PAGE_SIZE + PAGE_SIZE :: 1 << 12 + PTHREAD_DESTRUCTOR_ITERATIONS :: 4 + PTHREAD_KEYS_MAX :: 512 + PTHREAD_STACK_MIN :: 16384 when ODIN_ARCH == .arm64 else 8192 + // RTSIG_MAX :: sysconf(._RTSIG_MAX) + // SEM_NSEMS_MAX :: sysconf(._SEM_NSEMS_MAX) + // SEM_VALUE_MAX :: sysconf(._SEM_VALUE_MAX) + // SIGQUEUE_MAX :: sysconf(._SIGQUEUE_MAX) + // SS_REPL_MAX :: sysconf(._SS_REPL_MAX) + // STREAM_MAX :: sysconf(._STREAM_MAX) + // SYMLOOP_MAX :: sysconf(._SYMLOOP_MAX) + // TIMER_MAX :: sysconf(._TIMER_MAX) + // TRACE_EVENT_NAME_MAX :: sysconf(._TRACE_EVENT_NAME_MAX) + // TRACE_NAME_MAX :: sysconf(._TRACE_NAME_MAX) + // TRACE_SYS_MAX :: sysconf(._TRACE_SYS_MAX) + // TRACE_USER_EVENT_MAX :: sysconf(._TRACE_USER_EVENT_MAX) + // TTY_NAME_MAX :: sysconf(._TTY_NAME_MAX) + // TZNAME_MAX :: sysconf(._TZNAME_MAX) + + // The values in the following list may be constants within an implementation or may vary from + // one pathname to another. + // For example, file systems or directories may have different characteristics. + // + // A definition of one of the symbolic constants in the following list shall be omitted from the + // header on specific implementations where the corresponding value is equal to or + // greater than the stated minimum, but where the value can vary depending on the file to which + // it is applied. + // The actual value supported for a specific pathname shall be provided by the pathconf() function. + + // FILESIZEBITS :: pathconf(".", ._FILESIZEBITS) + LINK_MAX :: 32767 + MAX_CANON :: 1024 + MAX_INPUT :: 1024 + NAME_MAX :: 255 + PATH_MAX :: 1024 + PIPE_BUF :: 512 + // POSIX_ALLOC_SIZE_MIN :: pathconf("foo.txt", ._POSIX_ALLOC_SIZE_MIN) + // POSIX_REC_INCR_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_INCR_XFER_SIZE) + // POSIX_REC_MAX_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MAX_XFER_SIZE) + // POSIX_REC_MIN_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MIN_XFER_SIZE) + // POSIX_REC_XFER_ALIGN :: pathconf("foo.txt", ._POSIX_REC_XFER_ALIGN) + // SYMLINK_MAX :: pathconf(".", ._SYMLINK_MAX) + + + // The magnitude limitations in the following list shall be fixed by specific implementations. + // An application should assume that the value of the symbolic constant defined by + // in a specific implementation is the minimum that pertains whenever the application is run + // under that implementation. + // A specific instance of a specific implementation may increase the value relative to that + // supplied by for that implementation. + // The actual value supported by a specific instance shall be provided by the sysconf() function. + + BC_BASE_MAX :: 99 + BC_DIM_MAX :: 2048 + BC_SCALE_MAX :: 99 + BC_STRING_MAX :: 1000 + CHARCLASS_NAME_MAX :: 14 + COLL_WEIGHTS_MAX :: 2 + EXPR_NEST_MAX :: 2 + LINE_MAX :: 2048 + NGROUPS_MAX :: 16 + RE_DUP_MAX :: 255 + + // Other limits. + + NL_ARGMAX :: 9 + NL_LANGMAX :: 14 + NL_MSGMAX :: 32767 + NL_SETMAX :: 255 + NL_TEXTMAX :: 2048 + NZERO :: 20 + +} else when ODIN_OS == .FreeBSD { + // A definition of one of the symbolic constants in the following list shall be omitted from + // on specific implementations where the corresponding value is equal to or greater + // than the stated minimum, but is unspecified. + // + // This indetermination might depend on the amount of available memory space on a specific + // instance of a specific implementation. The actual value supported by a specific instance shall + // be provided by the sysconf() function. + + // AIO_LISTIO_MAX :: sysconf(._AIO_LISTIO_MAX) + // AIO_MAX :: sysconf(._AIO_MAX) + // AIO_PRIO_DELTA_MAX :: sysconf(._AIO_PRIO_DELTA_MAX) + ARG_MAX :: 2 * 256 * 1024 + // ATEXIT_MAX :: sysconf(._ATEXIT_MAX) + CHILD_MAX :: 40 + // DELAYTIMER_MAX :: sysconf(._DELAYTIMER_MAX) + // HOST_NAME_MAX :: sysconf(._HOST_NAME_MAX) + IOV_MAX :: 1024 + // LOGIN_NAME_MAX :: sysconf(._LOGIN_NAME_MAX) + // MQ_OPEN_MAX :: sysconf(._MQ_OPEN_MAX) + MQ_PRIO_MAX :: 64 + PAGESIZE :: PAGE_SIZE + PAGE_SIZE :: 1 << 12 + PTHREAD_DESTRUCTOR_ITERATIONS :: 4 + PTHREAD_KEYS_MAX :: 256 + PTHREAD_STACK_MIN :: MINSIGSTKSZ + // RTSIG_MAX :: sysconf(._RTSIG_MAX) + // SEM_NSEMS_MAX :: sysconf(._SEM_NSEMS_MAX) + // SEM_VALUE_MAX :: sysconf(._SEM_VALUE_MAX) + // SIGQUEUE_MAX :: sysconf(._SIGQUEUE_MAX) + // SS_REPL_MAX :: sysconf(._SS_REPL_MAX) + // STREAM_MAX :: sysconf(._STREAM_MAX) + // SYMLOOP_MAX :: sysconf(._SYMLOOP_MAX) + // TIMER_MAX :: sysconf(._TIMER_MAX) + // TRACE_EVENT_NAME_MAX :: sysconf(._TRACE_EVENT_NAME_MAX) + // TRACE_NAME_MAX :: sysconf(._TRACE_NAME_MAX) + // TRACE_SYS_MAX :: sysconf(._TRACE_SYS_MAX) + // TRACE_USER_EVENT_MAX :: sysconf(._TRACE_USER_EVENT_MAX) + // TTY_NAME_MAX :: sysconf(._TTY_NAME_MAX) + // TZNAME_MAX :: sysconf(._TZNAME_MAX) + + // The values in the following list may be constants within an implementation or may vary from + // one pathname to another. + // For example, file systems or directories may have different characteristics. + // + // A definition of one of the symbolic constants in the following list shall be omitted from the + // header on specific implementations where the corresponding value is equal to or + // greater than the stated minimum, but where the value can vary depending on the file to which + // it is applied. + // The actual value supported for a specific pathname shall be provided by the pathconf() function. + + // FILESIZEBITS :: pathconf(".", ._FILESIZEBITS) + // LINK_MAX :: pathconf(foo.txt", ._LINK_MAX) + MAX_CANON :: 255 + MAX_INPUT :: 255 + NAME_MAX :: 255 + PATH_MAX :: 1024 + PIPE_BUF :: 512 + // POSIX_ALLOC_SIZE_MIN :: pathconf("foo.txt", ._POSIX_ALLOC_SIZE_MIN) + // POSIX_REC_INCR_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_INCR_XFER_SIZE) + // POSIX_REC_MAX_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MAX_XFER_SIZE) + // POSIX_REC_MIN_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MIN_XFER_SIZE) + // POSIX_REC_XFER_ALIGN :: pathconf("foo.txt", ._POSIX_REC_XFER_ALIGN) + // SYMLINK_MAX :: pathconf(".", ._SYMLINK_MAX) + + + // The magnitude limitations in the following list shall be fixed by specific implementations. + // An application should assume that the value of the symbolic constant defined by + // in a specific implementation is the minimum that pertains whenever the application is run + // under that implementation. + // A specific instance of a specific implementation may increase the value relative to that + // supplied by for that implementation. + // The actual value supported by a specific instance shall be provided by the sysconf() function. + + BC_BASE_MAX :: 99 + BC_DIM_MAX :: 2048 + BC_SCALE_MAX :: 99 + BC_STRING_MAX :: 1000 + CHARCLASS_NAME_MAX :: 14 + COLL_WEIGHTS_MAX :: 10 + EXPR_NEST_MAX :: 32 + LINE_MAX :: 2048 + NGROUPS_MAX :: 1023 + RE_DUP_MAX :: 255 + + // Other limits. + + NL_ARGMAX :: 4096 + NL_LANGMAX :: 31 + NL_MSGMAX :: 32767 + NL_SETMAX :: 255 + NL_TEXTMAX :: 2048 + NZERO :: 0 + +} else when ODIN_OS == .NetBSD { + + // A definition of one of the symbolic constants in the following list shall be omitted from + // on specific implementations where the corresponding value is equal to or greater + // than the stated minimum, but is unspecified. + // + // This indetermination might depend on the amount of available memory space on a specific + // instance of a specific implementation. The actual value supported by a specific instance shall + // be provided by the sysconf() function. + + // AIO_LISTIO_MAX :: sysconf(._AIO_LISTIO_MAX) + // AIO_MAX :: sysconf(._AIO_MAX) + // AIO_PRIO_DELTA_MAX :: sysconf(._AIO_PRIO_DELTA_MAX) + ARG_MAX :: 256 * 1024 + // ATEXIT_MAX :: sysconf(._ATEXIT_MAX) + CHILD_MAX :: 160 + // DELAYTIMER_MAX :: sysconf(._DELAYTIMER_MAX) + // HOST_NAME_MAX :: sysconf(._HOST_NAME_MAX) + IOV_MAX :: 1024 + LOGIN_NAME_MAX :: 17 + MQ_OPEN_MAX :: 512 + MQ_PRIO_MAX :: 32 + PAGESIZE :: PAGE_SIZE + PAGE_SIZE :: 1 << 12 + PTHREAD_DESTRUCTOR_ITERATIONS :: 4 + PTHREAD_KEYS_MAX :: 256 + // PTHREAD_STACK_MIN :: sysconf(._THREAD_STACK_MIN) + // RTSIG_MAX :: sysconf(._RTSIG_MAX) + // SEM_NSEMS_MAX :: sysconf(._SEM_NSEMS_MAX) + // SEM_VALUE_MAX :: sysconf(._SEM_VALUE_MAX) + // SIGQUEUE_MAX :: sysconf(._SIGQUEUE_MAX) + // SS_REPL_MAX :: sysconf(._SS_REPL_MAX) + // STREAM_MAX :: sysconf(._STREAM_MAX) + // SYMLOOP_MAX :: sysconf(._SYMLOOP_MAX) + // TIMER_MAX :: sysconf(._TIMER_MAX) + // TRACE_EVENT_NAME_MAX :: sysconf(._TRACE_EVENT_NAME_MAX) + // TRACE_NAME_MAX :: sysconf(._TRACE_NAME_MAX) + // TRACE_SYS_MAX :: sysconf(._TRACE_SYS_MAX) + // TRACE_USER_EVENT_MAX :: sysconf(._TRACE_USER_EVENT_MAX) + // TTY_NAME_MAX :: sysconf(._TTY_NAME_MAX) + // TZNAME_MAX :: sysconf(._TZNAME_MAX) + + // The values in the following list may be constants within an implementation or may vary from + // one pathname to another. + // For example, file systems or directories may have different characteristics. + // + // A definition of one of the symbolic constants in the following list shall be omitted from the + // header on specific implementations where the corresponding value is equal to or + // greater than the stated minimum, but where the value can vary depending on the file to which + // it is applied. + // The actual value supported for a specific pathname shall be provided by the pathconf() function. + + // FILESIZEBITS :: pathconf(".", ._FILESIZEBITS) + LINK_MAX :: 32767 + MAX_CANON :: 255 + MAX_INPUT :: 255 + NAME_MAX :: 511 + PATH_MAX :: 1024 + PIPE_BUF :: 512 + // POSIX_ALLOC_SIZE_MIN :: pathconf("foo.txt", ._POSIX_ALLOC_SIZE_MIN) + // POSIX_REC_INCR_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_INCR_XFER_SIZE) + // POSIX_REC_MAX_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MAX_XFER_SIZE) + // POSIX_REC_MIN_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MIN_XFER_SIZE) + // POSIX_REC_XFER_ALIGN :: pathconf("foo.txt", ._POSIX_REC_XFER_ALIGN) + // SYMLINK_MAX :: pathconf(".", ._SYMLINK_MAX) + + + // The magnitude limitations in the following list shall be fixed by specific implementations. + // An application should assume that the value of the symbolic constant defined by + // in a specific implementation is the minimum that pertains whenever the application is run + // under that implementation. + // A specific instance of a specific implementation may increase the value relative to that + // supplied by for that implementation. + // The actual value supported by a specific instance shall be provided by the sysconf() function. + + BC_BASE_MAX :: max(i32) + BC_DIM_MAX :: 65535 + BC_SCALE_MAX :: max(i32) + BC_STRING_MAX :: max(i32) + CHARCLASS_NAME_MAX :: 14 + COLL_WEIGHTS_MAX :: 2 + EXPR_NEST_MAX :: 32 + LINE_MAX :: 2048 + NGROUPS_MAX :: 16 + RE_DUP_MAX :: 255 + + // Other limits. + + NL_ARGMAX :: 9 + NL_LANGMAX :: 14 + NL_MSGMAX :: 32767 + NL_SETMAX :: 255 + NL_TEXTMAX :: 2048 + NZERO :: 20 + +} else when ODIN_OS == .OpenBSD { + + // A definition of one of the symbolic constants in the following list shall be omitted from + // on specific implementations where the corresponding value is equal to or greater + // than the stated minimum, but is unspecified. + // + // This indetermination might depend on the amount of available memory space on a specific + // instance of a specific implementation. The actual value supported by a specific instance shall + // be provided by the sysconf() function. + + // AIO_LISTIO_MAX :: sysconf(._AIO_LISTIO_MAX) + // AIO_MAX :: sysconf(._AIO_MAX) + // AIO_PRIO_DELTA_MAX :: sysconf(._AIO_PRIO_DELTA_MAX) + ARG_MAX :: 512 * 1024 + // ATEXIT_MAX :: sysconf(._ATEXIT_MAX) + CHILD_MAX :: 80 + // DELAYTIMER_MAX :: sysconf(._DELAYTIMER_MAX) + // HOST_NAME_MAX :: sysconf(._HOST_NAME_MAX) + IOV_MAX :: 1024 + LOGIN_NAME_MAX :: 32 + MQ_OPEN_MAX :: 512 + MQ_PRIO_MAX :: 32 + PAGESIZE :: PAGE_SIZE + PAGE_SIZE :: 1 << 12 + PTHREAD_DESTRUCTOR_ITERATIONS :: 4 + PTHREAD_KEYS_MAX :: 256 + PTHREAD_STACK_MIN :: 1 << 12 + // RTSIG_MAX :: sysconf(._RTSIG_MAX) + // SEM_NSEMS_MAX :: sysconf(._SEM_NSEMS_MAX) + SEM_VALUE_MAX :: max(u32) + // SIGQUEUE_MAX :: sysconf(._SIGQUEUE_MAX) + // SS_REPL_MAX :: sysconf(._SS_REPL_MAX) + // STREAM_MAX :: sysconf(._STREAM_MAX) + SYMLOOP_MAX :: 32 + // TIMER_MAX :: sysconf(._TIMER_MAX) + // TRACE_EVENT_NAME_MAX :: sysconf(._TRACE_EVENT_NAME_MAX) + // TRACE_NAME_MAX :: sysconf(._TRACE_NAME_MAX) + // TRACE_SYS_MAX :: sysconf(._TRACE_SYS_MAX) + // TRACE_USER_EVENT_MAX :: sysconf(._TRACE_USER_EVENT_MAX) + // TTY_NAME_MAX :: sysconf(._TTY_NAME_MAX) + // TZNAME_MAX :: sysconf(._TZNAME_MAX) + + // The values in the following list may be constants within an implementation or may vary from + // one pathname to another. + // For example, file systems or directories may have different characteristics. + // + // A definition of one of the symbolic constants in the following list shall be omitted from the + // header on specific implementations where the corresponding value is equal to or + // greater than the stated minimum, but where the value can vary depending on the file to which + // it is applied. + // The actual value supported for a specific pathname shall be provided by the pathconf() function. + + // FILESIZEBITS :: pathconf(".", ._FILESIZEBITS) + LINK_MAX :: 32767 + MAX_CANON :: 255 + MAX_INPUT :: 255 + NAME_MAX :: 255 + PATH_MAX :: 1024 + PIPE_BUF :: 512 + // POSIX_ALLOC_SIZE_MIN :: pathconf("foo.txt", ._POSIX_ALLOC_SIZE_MIN) + // POSIX_REC_INCR_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_INCR_XFER_SIZE) + // POSIX_REC_MAX_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MAX_XFER_SIZE) + // POSIX_REC_MIN_XFER_SIZE :: pathconf("foo.txt", ._POSIX_REC_MIN_XFER_SIZE) + // POSIX_REC_XFER_ALIGN :: pathconf("foo.txt", ._POSIX_REC_XFER_ALIGN) + SYMLINK_MAX :: PATH_MAX + + + // The magnitude limitations in the following list shall be fixed by specific implementations. + // An application should assume that the value of the symbolic constant defined by + // in a specific implementation is the minimum that pertains whenever the application is run + // under that implementation. + // A specific instance of a specific implementation may increase the value relative to that + // supplied by for that implementation. + // The actual value supported by a specific instance shall be provided by the sysconf() function. + + BC_BASE_MAX :: max(i32) + BC_DIM_MAX :: 65535 + BC_SCALE_MAX :: max(i32) + BC_STRING_MAX :: max(i32) + CHARCLASS_NAME_MAX :: 14 + COLL_WEIGHTS_MAX :: 2 + EXPR_NEST_MAX :: 32 + LINE_MAX :: 2048 + NGROUPS_MAX :: 16 + RE_DUP_MAX :: 255 + + // Other limits. + + NL_ARGMAX :: 9 + NL_LANGMAX :: 14 + NL_MSGMAX :: 32767 + NL_SETMAX :: 255 + NL_TEXTMAX :: 255 + NZERO :: 20 + +} else when ODIN_OS == .Linux { + + // A definition of one of the symbolic constants in the following list shall be omitted from + // on specific implementations where the corresponding value is equal to or greater + // than the stated minimum, but is unspecified. + // + // This indetermination might depend on the amount of available memory space on a specific + // instance of a specific implementation. The actual value supported by a specific instance shall + // be provided by the sysconf() function. + + // AIO_LISTIO_MAX :: sysconf(._AIO_LISTIO_MAX) + // AIO_MAX :: sysconf(._AIO_MAX) + // AIO_PRIO_DELTA_MAX :: sysconf(._AIO_PRIO_DELTA_MAX) + ARG_MAX :: 131_072 + // ATEXIT_MAX :: sysconf(._ATEXIT_MAX) + // CHILD_MAX :: sysconf(._POSIX_ARG_MAX) + // DELAYTIMER_MAX :: sysconf(._DELAYTIMER_MAX) + // HOST_NAME_MAX :: sysconf(._HOST_NAME_MAX) + // IOV_MAX :: sysconf(._XOPEN_IOV_MAX) + // LOGIN_NAME_MAX :: sysconf(._LOGIN_NAME_MAX) + // MQ_OPEN_MAX :: sysconf(._MQ_OPEN_MAX) + // MQ_PRIO_MAX :: sysconf(._MQ_PRIO_MAX) + // PAGESIZE :: PAGE_SIZE + // PAGE_SIZE :: sysconf(._PAGE_SIZE) + PTHREAD_DESTRUCTOR_ITERATIONS :: 4 + // PTHREAD_KEYS_MAX :: sysconf(._PTHREAD_KEYS_MAX) + // PTHREAD_STACK_MIN :: sysconf(._PTHREAD_STACK_MIN) + // RTSIG_MAX :: sysconf(._RTSIG_MAX) + // SEM_NSEMS_MAX :: sysconf(._SEM_NSEMS_MAX) + // SEM_VALUE_MAX :: sysconf(._SEM_VALUE_MAX) + // SIGQUEUE_MAX :: sysconf(._SIGQUEUE_MAX) + // SS_REPL_MAX :: sysconf(._SS_REPL_MAX) + // STREAM_MAX :: sysconf(._STREAM_MAX) + // SYMLOOP_MAX :: sysconf(._SYSLOOP_MAX) + // TIMER_MAX :: sysconf(._TIMER_MAX) + // TRACE_EVENT_NAME_MAX :: sysconf(._TRACE_EVENT_NAME_MAX) + // TRACE_NAME_MAX :: sysconf(._TRACE_NAME_MAX) + // TRACE_SYS_MAX :: sysconf(._TRACE_SYS_MAX) + // TRACE_USER_EVENT_MAX :: sysconf(._TRACE_USER_EVENT_MAX) + // TTY_NAME_MAX :: sysconf(._TTY_NAME_MAX) + // TZNAME_MAX :: sysconf(._TZNAME_MAX) + + // The values in the following list may be constants within an implementation or may vary from + // one pathname to another. + // For example, file systems or directories may have different characteristics. + // + // A definition of one of the symbolic constants in the following list shall be omitted from the + // header on specific implementations where the corresponding value is equal to or + // greater than the stated minimum, but where the value can vary depending on the file to which + // it is applied. + // The actual value supported for a specific pathname shall be provided by the pathconf() function. + + // FILESIZEBITS :: pathconf(".", ._FILESIZEBITS) + LINK_MAX :: 127 + MAX_CANON :: 255 + MAX_INPUT :: 255 + NAME_MAX :: 255 + PATH_MAX :: 4096 + PIPE_BUF :: 4096 + // POSIX_ALLOC_SIZE_MIN :: sysconf(._POSIX_ALLOC_SIZE_MIN) + // POSIX_REC_INCR_XFER_SIZE :: sysconf(._POSIX_REC_INCR_XFER_SIZE) + // POSIX_REC_MAX_XFER_SIZE :: sysconf(._POSIX_REC_MAX_XFER_SIZE) + // POSIX_REC_MIN_XFER_SIZE :: sysconf(._POSIX_REC_MIN_XFER_SIZE) + // POSIX_REC_XFER_ALIGN :: sysconf(._POSIX_REC_XFER_ALIGN) + // SYMLINK_MAX :: pathconf(".", ._SYMLINK_MAX) + + + // The magnitude limitations in the following list shall be fixed by specific implementations. + // An application should assume that the value of the symbolic constant defined by + // in a specific implementation is the minimum that pertains whenever the application is run + // under that implementation. + // A specific instance of a specific implementation may increase the value relative to that + // supplied by for that implementation. + // The actual value supported by a specific instance shall be provided by the sysconf() function. + + BC_BASE_MAX :: 99 + BC_DIM_MAX :: 2048 + BC_SCALE_MAX :: 99 + BC_STRING_MAX :: 1000 + CHARCLASS_NAME_MAX :: 14 + COLL_WEIGHTS_MAX :: 2 + EXPR_NEST_MAX :: 32 + // LINE_MAX :: sysconf(._LINE_MAX) + // NGROUPS_MAX :: sysconf(._NGROUPS_MAX) + RE_DUP_MAX :: 255 + + // Other limits. + + NL_ARGMAX :: 9 + NL_LANGMAX :: 32 // 14 on glibc, 32 on musl + NL_MSGMAX :: 32_767 + NL_SETMAX :: 255 + NL_TEXTMAX :: 2048 // 255 on glibc, 2048 on musl + NZERO :: 20 + +} diff --git a/core/sys/posix/locale.odin b/core/sys/posix/locale.odin new file mode 100644 index 000000000..5b8d7c216 --- /dev/null +++ b/core/sys/posix/locale.odin @@ -0,0 +1,11 @@ +#+build windows, linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c/libc" + +localeconv :: libc.localeconv +setlocale :: libc.setlocale + +lconv :: libc.lconv + +Locale_Category :: libc.Locale_Category diff --git a/core/sys/posix/monetary.odin b/core/sys/posix/monetary.odin new file mode 100644 index 000000000..ee342e211 --- /dev/null +++ b/core/sys/posix/monetary.odin @@ -0,0 +1,43 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// monetary.h - monetary types + +foreign lib { + + /* + Places characters into the array pointed to by s as controlled by the string format. + No more than maxsize bytes are placed into the array. + + Returns: -1 (setting errno) on failure, the number of bytes added to s otherwise + + Example: + posix.setlocale(.ALL, "en_US.UTF-8") + value := 123456.789 + buffer: [100]byte + size := posix.strfmon(raw_data(buffer[:]), len(buffer), "%n", value) + if int(size) == -1 { + fmt.panicf("strfmon failure: %s", posix.strerror(posix.errno())) + } + fmt.println(string(buffer[:size])) + + Output: + $123,456.79 + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/strfmon.html ]] + */ + strfmon :: proc( + s: [^]byte, + maxsize: c.size_t, + format: cstring, + #c_vararg args: ..any, + ) -> c.size_t --- +} diff --git a/core/sys/posix/net_if.odin b/core/sys/posix/net_if.odin new file mode 100644 index 000000000..774d11b72 --- /dev/null +++ b/core/sys/posix/net_if.odin @@ -0,0 +1,59 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// net/if.h - sockets local interfaces + +foreign lib { + /* + Retrieve an array of name indexes. Where the last one has an index of 0 and name of nil. + + Returns: nil (setting errno) on failure, an array that should be freed with if_freenameindex otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/if_nameindex.html ]] + */ + if_nameindex :: proc() -> [^]if_nameindex_t --- + + /* + Returns the interface index matching the name or zero. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/if_nametoindex.html ]] + */ + if_nametoindex :: proc(name: cstring) -> c.uint --- + + /* + Returns the name corresponding to the index. + + ifname should be at least IF_NAMESIZE bytes in size. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/if_indextoname.html ]] + */ + if_indextoname :: proc(ifindex: c.uint, ifname: [^]byte) -> cstring --- + + /* + Frees memory allocated by if_nameindex. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/if_freenameindex.html ]] + */ + if_freenameindex :: proc(ptr: ^if_nameindex_t) --- +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { + + // NOTE: `_t` suffix added due to name conflict. + + if_nameindex_t :: struct { + if_index: c.uint, /* [PSX] 1, 2, ... */ + if_name: cstring, /* [PSX] null terminated name: "le0", ... */ + } + + IF_NAMESIZE :: 16 + +} diff --git a/core/sys/posix/netdb.odin b/core/sys/posix/netdb.odin new file mode 100644 index 000000000..79e13a140 --- /dev/null +++ b/core/sys/posix/netdb.odin @@ -0,0 +1,476 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// netdb.h - definitions for network database operations + +foreign lib { + /* + Translate node/serv name and return a set of socket addresses and associated information to be + used in creating a socket with which to address the specified service. + + Example: + // The following (incomplete) program demonstrates the use of getaddrinfo() to obtain the + // socket address structure(s) for the service named in the program's command-line argument. + // The program then loops through each of the address structures attempting to create and bind + // a socket to the address, until it performs a successful bind(). + + args := runtime.args__ + if len(args) != 2 { + fmt.eprintfln("Usage: %s port", args[0]) + posix.exit(1) + } + + hints: posix.addrinfo + hints.ai_socktype = .DGRAM + hints.ai_flags = { .PASSIVE } + + result: ^posix.addrinfo + s := posix.getaddrinfo(nil, args[1], &hints, &result) + if s != .NONE { + fmt.eprintfln("getaddrinfo: %s", posix.gai_strerror(s)) + posix.exit(1) + } + defer posix.freeaddrinfo(result) + + // Try each address until a successful bind(). + rp: ^posix.addrinfo + for rp = result; rp != nil; rp = rp.ai_next { + sfd := posix.socket(rp.ai_family, rp.ai_socktype, rp.ai_protocol) + if sfd == -1 { + continue + } + + if posix.bind(sfd, rp.ai_addr, rp.ai_addrlen) == 0 { + // Success. + break + } + + posix.close(sfd) + } + + if rp == nil { + fmt.eprintln("Could not bind") + posix.exit(1) + } + + // Use the socket... + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getaddrinfo.html ]] + */ + getaddrinfo :: proc( + nodename: cstring, + servname: cstring, + hints: ^addrinfo, + res: ^^addrinfo, + ) -> Info_Errno --- + + /* + Frees the given address info linked list. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getaddrinfo.html ]] + */ + freeaddrinfo :: proc(ai: ^addrinfo) --- + + /* + Translate a socket address to a node name and service location. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getnameinfo.html ]] + */ + getnameinfo :: proc( + sa: ^sockaddr, salen: socklen_t, + node: [^]byte, nodelen: socklen_t, + service: [^]byte, servicelen: socklen_t, + flags: Nameinfo_Flags, + ) -> Info_Errno --- + + /* + Get a textual description for the address info errors. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/gai_strerror.html ]] + */ + gai_strerror :: proc(ecode: Info_Errno) -> cstring --- + + /* + Opens a connection to the database and set the next entry to the first entry in the database. + + This reads /etc/hosts on most systems. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sethostent.html ]] + */ + sethostent :: proc(stayopen: b32) --- + + /* + Reads the next entry in the database, opening and closing a connection as necessary. + + This reads /etc/hosts on most systems. + + Example: + posix.sethostent(true) + defer posix.endhostent() + for ent := posix.gethostent(); ent != nil; ent = posix.gethostent() { + fmt.println(ent) + fmt.println(ent.h_addr_list[0][:ent.h_length]) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sethostent.html ]] + */ + gethostent :: proc() -> ^hostent --- + + /* + Closes the connection to the database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sethostent.html ]] + */ + endhostent :: proc() --- + + /* + Opens and rewinds the database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setnetent.html ]] + */ + setnetent :: proc(stayopen: b32) --- + + /* + Reads the next entry of the database. + + Example: + posix.setnetent(true) + defer posix.endnetent() + for ent := posix.getnetent(); ent != nil; ent = posix.getnetent() { + fmt.println(ent) + fmt.println(transmute([4]byte)ent.n_net) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setnetent.html ]] + */ + getnetent :: proc() -> ^netent --- + + /* + Search the database from the beginning, and find the first entry that matches. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setnetent.html ]] + */ + getnetbyaddr :: proc(net: c.uint32_t, type: AF) -> ^netent --- + + /* + Search the database from the beginning, and find the first entry that matches. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setnetent.html ]] + */ + getnetbyname :: proc(name: cstring) -> ^netent --- + + /* + Closes the database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setnetent.html ]] + */ + endnetent :: proc() --- + + /* + Opens and rewinds the database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setprotoent.html ]] + */ + setprotoent :: proc(stayopen: b32) --- + + /* + Reads the next entry of the database. + + Example: + posix.setprotoent(true) + defer posix.endprotoent() + for ent := posix.getprotoent(); ent != nil; ent = posix.getprotoent() { + fmt.println(ent) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setprotoent.html ]] + */ + getprotoent :: proc() -> ^protoent --- + + /* + Search the database from the beginning, and find the first entry that matches. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setprotoent.html ]] + */ + getprotobyname :: proc(name: cstring) -> ^protoent --- + + /* + Search the database from the beginning, and find the first entry that matches. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setprotoent.html ]] + */ + getprotobynumber :: proc(proto: c.int) -> ^protoent --- + + /* + Closes the database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setprotoent.html ]] + */ + endprotoent :: proc() --- + + /* + Opens and rewinds the database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setservent.html ]] + */ + setservent :: proc(stayopen: b32) --- + + /* + Reads the next entry of the database. + + Example: + posix.setservent(true) + defer posix.endservent() + for ent := posix.getservent(); ent != nil; ent = posix.getservent() { + fmt.println(ent) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setservent.html ]] + */ + getservent :: proc() -> ^servent --- + + /* + Search the database from the beginning, and find the first entry that matches. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setservent.html ]] + */ + getservbyname :: proc(name: cstring, proto: cstring) -> ^servent --- + + /* + Search the database from the beginning, and find the first entry that matches. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setservent.html ]] + */ + getservbyport :: proc(port: c.int, proto: cstring) -> ^servent --- + + /* + Closes the database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setservent.html ]] + */ + endservent :: proc() --- +} + +Addrinfo_Flag_Bits :: enum c.int { + // Socket address is intended for bind(). + PASSIVE = log2(AI_PASSIVE), + // Request for canonical name. + CANONNAME = log2(AI_CANONNAME), + // Return numeric host address as name. + NUMERICHOST = log2(AI_NUMERICHOST), + // Inhibit service name resolution. + NUMERICSERV = log2(AI_NUMERICSERV), + // If no IPv6 addresses are found, query for IPv4 addresses and return them to the + // caller as IPv4-mapped IPv6 addresses. + V4MAPPED = log2(AI_V4MAPPED), + // Query for both IPv4 and IPv6 addresses. + ALL = log2(AI_ALL), + // Query for IPv4 addresses only when an IPv4 address is configured; query for IPv6 addresses + // only when an IPv6 address is configured. + ADDRCONFIG = log2(AI_ADDRCONFIG), +} +Addrinfo_Flags :: bit_set[Addrinfo_Flag_Bits; c.int] + +Nameinfo_Flag_Bits :: enum c.int { + // Only the nodename portion of the FQDN is returned for local hosts. + NOFQDN = log2(NI_NOFQDN), + // The numeric form of the node's address is returned instead of its name. + NUMERICHOST = log2(NI_NUMERICHOST), + // Return an error if the node's name cannot be located in the database. + NAMEREQD = log2(NI_NAMEREQD), + // The numeric form of the service address is returned instead of its name. + NUMERICSERV = log2(NI_NUMERICSERV), + // For IPv6 addresses, the numeric form of the scope identifier is returned instead of its name. + NUMERICSCOPE = log2(NI_NUMERICSCOPE), + // Indicates that the service is a datagram service (SOCK_DGRAM). + DGRAM = log2(NI_DGRAM), +} +Nameinfo_Flags :: bit_set[Nameinfo_Flag_Bits; c.int] + +Info_Errno :: enum c.int { + NONE = 0, + // The name could not be resolved at this time. Future attempts may succeed. + AGAIN = EAI_AGAIN, + // The flags had an invalid value. + BADFLAGS = EAI_BADFLAGS, + // A non-recoverable error ocurred. + FAIL = EAI_FAIL, + // The address family was not recognized or the address length was invald for the specified family. + FAMILY = EAI_FAMILY, + // There was a memory allocation failure. + MEMORY = EAI_MEMORY, + // The name does not resolve for the supplied parameters. + NONAME = EAI_NONAME, + // The service passed was not recognized for the specified socket. + SERVICE = EAI_SERVICE, + // The intended socket type was not recognized. + SOCKTYPE = EAI_SOCKTYPE, + // A system error occurred. The error code can be found in errno. + SYSTEM = EAI_SYSTEM, + // An argument buffer overflowed. + OVERFLOW = EAI_OVERFLOW, +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { + + hostent :: struct { + h_name: cstring, /* [PSX] official name of host */ + h_aliases: [^]cstring `fmt:"v,0"`, /* [PSX] alias list */ + h_addrtype: AF, /* [PSX] host address type */ + h_length: c.int, /* [PSX] length of address */ + h_addr_list: [^][^]byte `fmt:"v,0"`, /* [PSX] list of addresses from name server */ + } + + netent :: struct { + n_name: cstring, /* [PSX] official name of net */ + n_aliases: [^]cstring `fmt:"v,0"`, /* [PSX] alias list */ + n_addrtype: AF, /* [PSX] net address type */ + n_net: c.uint32_t, /* [PSX] network # */ + } + + protoent :: struct { + p_name: cstring, /* [PSX] official protocol name */ + p_aliases: [^]cstring `fmt:"v,0"`, /* [PSX] alias list */ + p_proto: c.int, /* [PSX] protocol # */ + } + + servent :: struct { + s_name: cstring, /* [PSX] official service name */ + s_aliases: [^]cstring `fmt:"v,0"`, /* [PSX] alias list */ + s_port: c.int, /* [PSX] port # */ + s_proto: cstring, /* [PSX] protocol # */ + } + + // The highest reserved port number. + IPPORT_RESERVED :: 1024 + + addrinfo :: struct { + ai_flags: Addrinfo_Flags, /* [PSX] input flags */ + ai_family: AF, /* [PSX] address family of socket */ + ai_socktype: Sock, /* [PSX] socket type */ + ai_protocol: Protocol, /* [PSX] protocol of socket */ + ai_addrlen: socklen_t, /* [PSX] length of socket address */ + ai_canonname: cstring, /* [PSX] canonical name of service location */ + ai_addr: ^sockaddr, /* [PSX] binary address */ + ai_next: ^addrinfo, /* [PSX] pointer to next in list */ + } + + when ODIN_OS == .Darwin { + + AI_PASSIVE :: 0x00000001 + AI_CANONNAME :: 0x00000002 + AI_NUMERICHOST :: 0x00000004 + AI_NUMERICSERV :: 0x00001000 + AI_V4MAPPED :: 0x00000800 + AI_ALL :: 0x00000100 + AI_ADDRCONFIG :: 0x00000400 + + NI_NOFQDN :: 0x00000001 + NI_NUMERICHOST :: 0x00000002 + NI_NAMEREQD :: 0x00000004 + NI_NUMERICSERV :: 0x00000008 + NI_NUMERICSCOPE :: 0x00000100 + NI_DGRAM :: 0x00000010 + + } else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { + + AI_PASSIVE :: 0x00000001 + AI_CANONNAME :: 0x00000002 + AI_NUMERICHOST :: 0x00000004 + AI_NUMERICSERV :: 0x00000008 + AI_V4MAPPED :: 0x00000800 // NOTE: not implemented on netbsd + AI_ALL :: 0x00000100 // NOTE: not implemented on netbsd + AI_ADDRCONFIG :: 0x00000400 + + NI_NOFQDN :: 0x00000001 + NI_NUMERICHOST :: 0x00000002 + NI_NAMEREQD :: 0x00000004 + NI_NUMERICSERV :: 0x00000008 + NI_NUMERICSCOPE :: 0x00000010 + NI_DGRAM :: 0x00000020 + + } else when ODIN_OS == .OpenBSD { + + AI_PASSIVE :: 1 + AI_CANONNAME :: 2 + AI_NUMERICHOST :: 4 + AI_NUMERICSERV :: 16 + AI_V4MAPPED :: 0x00000800 // NOTE: not implemented + AI_ALL :: 0x00000100 // NOTE: not implemented + AI_ADDRCONFIG :: 64 + + NI_NOFQDN :: 4 + NI_NUMERICHOST :: 1 + NI_NAMEREQD :: 8 + NI_NUMERICSERV :: 2 + NI_NUMERICSCOPE :: 32 + NI_DGRAM :: 16 + + } else when ODIN_OS == .Linux { + + AI_PASSIVE :: 0x001 + AI_CANONNAME :: 0x002 + AI_NUMERICHOST :: 0x004 + AI_NUMERICSERV :: 0x400 + AI_V4MAPPED :: 0x008 + AI_ALL :: 0x010 + AI_ADDRCONFIG :: 0x020 + + NI_NOFQDN :: 4 + NI_NUMERICHOST :: 1 + NI_NAMEREQD :: 8 + NI_NUMERICSERV :: 2 + NI_NUMERICSCOPE :: 0x100 + NI_DGRAM :: 16 + + } + + when ODIN_OS == .OpenBSD { + + EAI_AGAIN :: -3 + EAI_BADFLAGS :: -1 + EAI_FAIL :: -4 + EAI_FAMILY :: -6 + EAI_MEMORY :: -10 + EAI_NONAME :: -2 + EAI_SERVICE :: -8 + EAI_SOCKTYPE :: -7 + EAI_SYSTEM :: -11 + EAI_OVERFLOW :: -14 + + } else when ODIN_OS == .Linux { + + EAI_AGAIN :: -3 + EAI_BADFLAGS :: -1 + EAI_FAIL :: -4 + EAI_FAMILY :: -6 + EAI_MEMORY :: -10 + EAI_NONAME :: -2 + EAI_SERVICE :: -8 + EAI_SOCKTYPE :: -7 + EAI_SYSTEM :: -11 + EAI_OVERFLOW :: -12 + + } else { + + EAI_AGAIN :: 2 + EAI_BADFLAGS :: 3 + EAI_FAIL :: 4 + EAI_FAMILY :: 5 + EAI_MEMORY :: 6 + EAI_NONAME :: 8 + EAI_SERVICE :: 9 + EAI_SOCKTYPE :: 10 + EAI_SYSTEM :: 11 + EAI_OVERFLOW :: 14 + } + +} diff --git a/core/sys/posix/netinet_in.odin b/core/sys/posix/netinet_in.odin new file mode 100644 index 000000000..a2cf904ce --- /dev/null +++ b/core/sys/posix/netinet_in.odin @@ -0,0 +1,232 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// netinet/in.h - Internet address family + +foreign lib { + in6addr_any: in6_addr + in6addr_loopback: in6_addr +} + +in_port_t :: u16be +in_addr_t :: u32be + +INET_ADDRSTRLEN :: 16 +INET6_ADDRSTRLEN :: 46 + +Protocol :: enum c.int { + IP = IPPROTO_IP, + ICMP = IPPROTO_ICMP, + IPV6 = IPPROTO_IPV6, + RAW = IPPROTO_RAW, + TCP = IPPROTO_TCP, + UDP = IPPROTO_UDP, +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { + + in_addr :: struct { + s_addr: in_addr_t, /* [PSX] big endian address */ + } + + in6_addr :: struct { + using _: struct #raw_union { + s6_addr: [16]c.uint8_t, /* [PSX] big endian address */ + __u6_addr16: [8]c.uint16_t, + __u6_addr32: [4]c.uint32_t, + }, + } + + when ODIN_OS == .Linux { + + sockaddr_in :: struct { + sin_family: sa_family_t, /* [PSX] AF_INET (but a smaller size) */ + sin_port: in_port_t, /* [PSX] port number */ + sin_addr: in_addr, /* [PSX] IP address */ + sin_zero: [8]c.char, + } + + sockaddr_in6 :: struct { + sin6_family: sa_family_t, /* [PSX] AF_INET6 (but a smaller size) */ + sin6_port: in_port_t, /* [PSX] port number */ + sin6_flowinfo: u32be, /* [PSX] IPv6 traffic class and flow information */ + sin6_addr: in6_addr, /* [PSX] IPv6 address */ + sin6_scope_id: c.uint32_t, /* [PSX] set of interfaces for a scope */ + } + + ipv6_mreq :: struct { + ipv6mr_multiaddr: in6_addr, /* [PSX] IPv6 multicast address */ + ipv6mr_interface: c.uint, /* [PSX] interface index */ + } + + IPV6_MULTICAST_IF :: 17 + IPV6_UNICAST_HOPS :: 16 + IPV6_MULTICAST_HOPS :: 18 + IPV6_MULTICAST_LOOP :: 19 + IPV6_JOIN_GROUP :: 20 + IPV6_LEAVE_GROUP :: 21 + IPV6_V6ONLY :: 26 + + } else { + + sockaddr_in :: struct { + sin_len: c.uint8_t, + sin_family: sa_family_t, /* [PSX] AF_INET (but a smaller size) */ + sin_port: in_port_t, /* [PSX] port number */ + sin_addr: in_addr, /* [PSX] IP address */ + sin_zero: [8]c.char, + } + + sockaddr_in6 :: struct { + sin6_len: c.uint8_t, + sin6_family: sa_family_t, /* [PSX] AF_INET6 (but a smaller size) */ + sin6_port: in_port_t, /* [PSX] port number */ + sin6_flowinfo: c.uint32_t, /* [PSX] IPv6 traffic class and flow information */ + sin6_addr: in6_addr, /* [PSX] IPv6 address */ + sin6_scope_id: c.uint32_t, /* [PSX] set of interfaces for a scope */ + } + + ipv6_mreq :: struct { + ipv6mr_multiaddr: in6_addr, /* [PSX] IPv6 multicast address */ + ipv6mr_interface: c.uint, /* [PSX] interface index */ + } + + IPV6_JOIN_GROUP :: 12 + IPV6_LEAVE_GROUP :: 13 + IPV6_MULTICAST_HOPS :: 10 + IPV6_MULTICAST_IF :: 9 + IPV6_MULTICAST_LOOP :: 11 + IPV6_UNICAST_HOPS :: 4 + IPV6_V6ONLY :: 27 + + } + + IPPROTO_IP :: 0 + IPPROTO_ICMP :: 1 + IPPROTO_IPV6 :: 41 + IPPROTO_RAW :: 255 + IPPROTO_TCP :: 6 + IPPROTO_UDP :: 17 + + INADDR_ANY :: 0x00000000 + INADDR_BROADCAST :: 0xFFFFFFFF + + IN6_IS_ADDR_UNSPECIFIED :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return a.s6_addr == 0 + } + + IN6_IS_ADDR_LOOPBACK :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + a := a + return ( + (^c.uint32_t)(&a.s6_addr[0])^ == 0 && + (^c.uint32_t)(&a.s6_addr[4])^ == 0 && + (^c.uint32_t)(&a.s6_addr[8])^ == 0 && + (^u32be)(&a.s6_addr[12])^ == 1 \ + ) + } + + IN6_IS_ADDR_MULTICAST :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return a.s6_addr[0] == 0xff + } + + IN6_IS_ADDR_LINKLOCAL :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return a.s6_addr[0] == 0xfe && a.s6_addr[1] & 0xc0 == 0x80 + } + + IN6_IS_ADDR_SITELOCAL :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return a.s6_addr[0] == 0xfe && a.s6_addr[1] & 0xc0 == 0xc0 + } + + IN6_IS_ADDR_V4MAPPED :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + a := a + return ( + (^c.uint32_t)(&a.s6_addr[0])^ == 0 && + (^c.uint32_t)(&a.s6_addr[4])^ == 0 && + (^u32be)(&a.s6_addr[8])^ == 0x0000ffff \ + ) + } + + IN6_IS_ADDR_V4COMPAT :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + a := a + return ( + (^c.uint32_t)(&a.s6_addr[0])^ == 0 && + (^c.uint32_t)(&a.s6_addr[4])^ == 0 && + (^c.uint32_t)(&a.s6_addr[8])^ == 0 && + (^c.uint32_t)(&a.s6_addr[12])^ != 0 && + (^u32be)(&a.s6_addr[12])^ != 1 \ + ) + } + + @(private) + __IPV6_ADDR_SCOPE_NODELOCAL :: 0x01 + @(private) + __IPV6_ADDR_SCOPE_LINKLOCAL :: 0x02 + @(private) + __IPV6_ADDR_SCOPE_SITELOCAL :: 0x05 + @(private) + __IPV6_ADDR_SCOPE_ORGLOCAL :: 0x08 + @(private) + __IPV6_ADDR_SCOPE_GLOBAL :: 0x0e + + @(private) + IPV6_ADDR_MC_FLAGS :: #force_inline proc "contextless" (a: in6_addr) -> c.uint8_t { + return a.s6_addr[1] & 0xf0 + } + + @(private) + IPV6_ADDR_MC_FLAGS_TRANSIENT :: 0x10 + @(private) + IPV6_ADDR_MC_FLAGS_PREFIX :: 0x20 + @(private) + IPV6_ADDR_MC_FLAGS_UNICAST_BASED :: IPV6_ADDR_MC_FLAGS_TRANSIENT | IPV6_ADDR_MC_FLAGS_PREFIX + + @(private) + __IPV6_ADDR_MC_SCOPE :: #force_inline proc "contextless" (a: in6_addr) -> c.uint8_t { + return a.s6_addr[1] & 0x0f + } + + IN6_IS_ADDR_MC_NODELOCAL :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return ( + IN6_IS_ADDR_MULTICAST(a) && + (__IPV6_ADDR_MC_SCOPE(a) == __IPV6_ADDR_SCOPE_NODELOCAL) \ + ) + } + + IN6_IS_ADDR_MC_LINKLOCAL :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return ( + IN6_IS_ADDR_MULTICAST(a) && + (IPV6_ADDR_MC_FLAGS(a) != IPV6_ADDR_MC_FLAGS_UNICAST_BASED) && + (__IPV6_ADDR_MC_SCOPE(a) == __IPV6_ADDR_SCOPE_LINKLOCAL) \ + ) + } + + IN6_IS_ADDR_MC_SITELOCAL :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return ( + IN6_IS_ADDR_MULTICAST(a) && + (__IPV6_ADDR_MC_SCOPE(a) == __IPV6_ADDR_SCOPE_SITELOCAL) \ + ) + } + + IN6_IS_ADDR_MC_ORGLOCAL :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return ( + IN6_IS_ADDR_MULTICAST(a) && + (__IPV6_ADDR_MC_SCOPE(a) == __IPV6_ADDR_SCOPE_ORGLOCAL) \ + ) + } + + IN6_IS_ADDR_MC_GLOBAL :: #force_inline proc "contextless" (a: in6_addr) -> b32 { + return ( + IN6_IS_ADDR_MULTICAST(a) && + (__IPV6_ADDR_MC_SCOPE(a) == __IPV6_ADDR_SCOPE_GLOBAL) \ + ) + } + +} diff --git a/core/sys/posix/netinet_tcp.odin b/core/sys/posix/netinet_tcp.odin new file mode 100644 index 000000000..b1da12f5e --- /dev/null +++ b/core/sys/posix/netinet_tcp.odin @@ -0,0 +1,10 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +// netinet/tcp.h - definitions for the Internet Transmission Control Protocol (TCP) + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { + + TCP_NODELAY :: 0x01 + +} diff --git a/core/sys/posix/poll.odin b/core/sys/posix/poll.odin new file mode 100644 index 000000000..9c3b8b081 --- /dev/null +++ b/core/sys/posix/poll.odin @@ -0,0 +1,96 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "base:intrinsics" + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// poll.h - definitions for the poll() function + +foreign lib { + /* + For each pointer in fds, poll() shall examine the given descriptor for the events. + poll will identify on which descriptors writes or reads can be done. + + Returns: -1 (setting errno) on failure, 0 on timeout, the amount of fds that have been changed on success. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/poll.html ]] + */ + poll :: proc(fds: [^]pollfd, nfds: nfds_t, timeout: c.int) -> c.int --- +} + +nfds_t :: c.uint + +Poll_Event_Bits :: enum c.short { + // Data other than high-priority data may be read without blocking. + IN = log2(POLLIN), + // Normal data may be read without blocking. + RDNORM = log2(POLLRDNORM), + // Priority data may be read without blocking. + RDBAND = log2(POLLRDBAND), + // High priority data may be read without blocking. + PRI = log2(POLLPRI), + + // Normal data may be written without blocking. + OUT = log2(POLLOUT), + // Equivalent to POLLOUT. + WRNORM = log2(POLLWRNORM), + // Priority data may be written. + WRBAND = log2(POLLWRBAND), + + // An error has occurred (revents only). + ERR = log2(POLLERR), + // Device hsa been disconnected (revents only). + HUP = log2(POLLHUP), + // Invalid fd member (revents only). + NVAL = log2(POLLNVAL), +} +Poll_Event :: bit_set[Poll_Event_Bits; c.short] + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + pollfd :: struct { + fd: FD, /* [PSX] the following descriptor being polled */ + events: Poll_Event, /* [PSX] the input event flags */ + revents: Poll_Event, /* [PSX] the output event flags */ + } + + POLLIN :: 0x0001 + POLLRDNORM :: 0x0040 + POLLRDBAND :: 0x0080 + POLLPRI :: 0x0002 + POLLOUT :: 0x0004 + POLLWRNORM :: POLLOUT + POLLWRBAND :: 0x0100 + + POLLERR :: 0x0008 + POLLHUP :: 0x0010 + POLLNVAL :: 0x0020 + +} else when ODIN_OS == .Linux { + + pollfd :: struct { + fd: FD, /* [PSX] the following descriptor being polled */ + events: Poll_Event, /* [PSX] the input event flags */ + revents: Poll_Event, /* [PSX] the output event flags */ + } + + POLLIN :: 0x0001 + POLLRDNORM :: 0x0040 + POLLRDBAND :: 0x0080 + POLLPRI :: 0x0002 + POLLOUT :: 0x0004 + POLLWRNORM :: 0x0100 + POLLWRBAND :: 0x0200 + + POLLERR :: 0x0008 + POLLHUP :: 0x0010 + POLLNVAL :: 0x0020 + +} diff --git a/core/sys/posix/posix.odin b/core/sys/posix/posix.odin new file mode 100644 index 000000000..d56217407 --- /dev/null +++ b/core/sys/posix/posix.odin @@ -0,0 +1,75 @@ +/* +Raw bindings for most POSIX APIs. + +Targets glibc and musl compatibility. + +APIs that have been left out are due to not being useful, +being fully replaced (and better) by other Odin packages, +or when one of the targets hasn't implemented the API or option. + +The struct fields that are cross-platform are documented with `[PSX]`. +Accessing these fields on one target should be the same on others. +Other fields are implementation specific. + +The parts of POSIX that Windows implements are also supported here, but +other symbols are undefined on Windows targets. + +Most macros have been reimplemented in Odin with inlined functions. + +Unimplemented headers: +- aio.h +- complex.h | See `core:c/libc` and our own complex types +- cpio.h +- ctype.h | See `core:c/libc` for most of it +- ndbm.h +- fenv.h +- float.h +- fmtmsg.h +- ftw.h +- semaphore.h | See `core:sync` +- inttypes.h | See `core:c` +- iso646.h | Impossible +- math.h | See `core:c/libc` +- mqueue.h | Targets don't seem to have implemented it +- regex.h | See `core:regex` +- search.h | Not useful in Odin +- spawn.h | Use `fork`, `execve`, etc. +- stdarg.h | See `core:c/libc` +- stdint.h | See `core:c` +- stropts.h +- syslog.h +- pthread.h | Only the actual threads API is bound, see `core:sync` for synchronization primitives +- string.h | Most of this is not useful in Odin, only a select few symbols are bound +- tar.h +- tgmath.h +- trace.h +- wchar.h +- wctype.h + +*/ +package posix + +import "base:intrinsics" + +import "core:c" + +result :: enum c.int { + // Use `errno` and `strerror` for more information. + FAIL = -1, + // Operation succeeded. + OK = 0, +} + +FD :: distinct c.int + +@(private) +log2 :: intrinsics.constant_log2 + +when ODIN_OS == .Darwin && ODIN_ARCH == .amd64 { + @(private) + INODE_SUFFIX :: "$INODE64" +} else { + @(private) + INODE_SUFFIX :: "" +} + diff --git a/core/sys/posix/pthread.odin b/core/sys/posix/pthread.odin new file mode 100644 index 000000000..490064da6 --- /dev/null +++ b/core/sys/posix/pthread.odin @@ -0,0 +1,611 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .Linux { + foreign import lib "system:pthread" +} else { + foreign import lib "system:c" +} + +// pthread.h - threads + +// NOTE: mutexes, rwlock, condition variables, once and barriers are left out in favour of `core:sync`. + +foreign lib { + /* + Initializes a thread attributes object. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_init.html ]] + */ + pthread_attr_init :: proc(attr: ^pthread_attr_t) -> Errno --- + + /* + Destroys a thread attributes object. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_init.html ]] + */ + pthread_attr_destroy :: proc(attr: ^pthread_attr_t) -> Errno --- + + /* + The detachstate attribute controls whether the thread is created in a detached state. + If the thread is created detached, then use of the ID of the newly created thread is an error. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getdetachstate.html ]] + */ + pthread_attr_getdetachstate :: proc(attr: ^pthread_attr_t, detachstate: ^Detach_State) -> Errno --- + + /* + The detachstate attribute controls whether the thread is created in a detached state. + If the thread is created detached, then use of the ID of the newly created thread is an error. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getdetachstate.html ]] + */ + pthread_attr_setdetachstate :: proc(attr: ^pthread_attr_t, detachstate: Detach_State) -> Errno --- + + /* + The guardsize attribute controls the size of the guard area for the created thread's stack. + The guardsize attribute provides protection against overflow of the stack pointer. + If a thread's stack is created with guard protection, the implementation allocates extra memory + at the overflow end of the stack as a buffer against stack overflow of the stack pointer. + If an application overflows into this buffer an error shall result (possibly in a SIGSEGV signal being delivered to the thread). + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_setguardsize.html ]] + */ + pthread_attr_getguardsize :: proc(attr: ^pthread_attr_t, guardsize: ^c.size_t) -> Errno --- + + /* + The guardsize attribute controls the size of the guard area for the created thread's stack. + The guardsize attribute provides protection against overflow of the stack pointer. + If a thread's stack is created with guard protection, the implementation allocates extra memory + at the overflow end of the stack as a buffer against stack overflow of the stack pointer. + If an application overflows into this buffer an error shall result (possibly in a SIGSEGV signal being delivered to the thread). + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_setguardsize.html ]] + */ + pthread_attr_setguardsize :: proc(attr: ^pthread_attr_t, guardsize: c.size_t) -> Errno --- + + /* + When the attributes objects are used by pthread_create(), the inheritsched attribute determines + how the other scheduling attributes of the created thread shall be set. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_setinheritsched.html ]] + */ + pthread_attr_getinheritsched :: proc(attr: ^pthread_attr_t, inheritsched: ^Inherit_Sched) -> Errno --- + + /* + When the attributes objects are used by pthread_create(), the inheritsched attribute determines + how the other scheduling attributes of the created thread shall be set. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_setinheritsched.html ]] + */ + pthread_attr_setinheritsched :: proc(attr: ^pthread_attr_t, inheritsched: Inherit_Sched) -> Errno --- + + /* + Gets the scheduling param. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_setschedparam.html ]] + */ + pthread_attr_getschedparam :: proc(attr: ^pthread_attr_t, param: ^sched_param) -> Errno --- + + /* + Sets the scheduling param. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_setschedparam.html ]] + */ + pthread_attr_setschedparam :: proc(attr: ^pthread_attr_t, param: ^sched_param) -> Errno --- + + /* + Gets the scheduling poicy. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getschedpolicy.html ]] + */ + pthread_attr_getschedpolicy :: proc(attr: ^pthread_attr_t, policy: ^Sched_Policy) -> Errno --- + + /* + Sets the scheduling poicy. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getschedpolicy.html ]] + */ + pthread_attr_setschedpolicy :: proc(attr: ^pthread_attr_t, policy: Sched_Policy) -> Errno --- + + /* + Gets the contention scope. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getscope.html ]] + */ + pthread_attr_getscope :: proc(attr: ^pthread_attr_t, contentionscope: ^Thread_Scope) -> Errno --- + + /* + Sets the contention scope. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getscope.html ]] + */ + pthread_attr_setscope :: proc(attr: ^pthread_attr_t, contentionscope: ^Thread_Scope) -> Errno --- + + /* + Get the area of storage to be used for the created thread's stack. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getstack.html ]] + */ + pthread_attr_getstack :: proc(attr: ^pthread_attr_t, stackaddr: ^[^]byte, stacksize: ^c.size_t) -> Errno --- + + /* + Specify the area of storage to be used for the created thread's stack. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getstack.html ]] + */ + pthread_attr_setstack :: proc(attr: ^pthread_attr_t, stackaddr: [^]byte, stacksize: c.size_t) -> Errno --- + + /* + Gets the stack size. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getstacksize.html ]] + */ + pthread_attr_getstacksize :: proc(attr: ^pthread_attr_t, stacksize: ^c.size_t) -> Errno --- + + /* + Sets the stack size. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getstacksize.html ]] + */ + pthread_attr_setstacksize :: proc(attr: ^pthread_attr_t, stacksize: c.size_t) -> Errno --- + + /* + Register fork handlers to be called before and after fork(). + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_atfork.html ]] + */ + pthread_atfork :: proc(prepare: proc "c" (), parent: proc "c" (), child: proc "c" ()) -> Errno --- + + + /* + Cancel the execution of a thread. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cancel.html ]] + */ + pthread_cancel :: proc(thread: pthread_t) -> Errno --- + + /* + Creates a new thread with the given attributes. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_create.html ]] + */ + pthread_create :: proc( + thread: ^pthread_t, + attr: ^pthread_attr_t, + start_routine: proc "c" (arg: rawptr) -> rawptr, + arg: rawptr, + ) -> Errno --- + + + /* + Indicate that storage for the thread can be reclaimed when the thread terminates. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_detach.html ]] + */ + pthread_detach :: proc(thread: pthread_t) -> Errno --- + + /* + Compare thread IDs. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_equal.html ]] + */ + pthread_equal :: proc(t1: pthread_t, t2: pthread_t) -> b32 --- + + /* + Terminates the calling thread and make the given value available to any successfull join calls. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_exit.html ]] + */ + pthread_exit :: proc(value_ptr: rawptr) -> ! --- + + /* + Gets the current concurrency hint. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getconcurrency.html ]] + */ + pthread_getconcurrency :: proc() -> c.int --- + + /* + Sets the current desired concurrency hint. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getconcurrency.html ]] + */ + pthread_setconcurrency :: proc(new_level: c.int) -> Errno --- + + /* + Access a thread CPU-time clock. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getcpuclockid.html ]] + */ + pthread_getcpuclockid :: proc(thread_id: pthread_t, clock_id: ^clockid_t) -> Errno --- + + /* + Gets the scheduling policy and parameters. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getschedparam.html ]] + */ + pthread_getschedparam :: proc(thread: pthread_t, policy: ^Sched_Policy, param: ^sched_param) -> Errno --- + + /* + Sets the scheduling policy and parameters. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getschedparam.html ]] + */ + pthread_setschedparam :: proc(thread: pthread_t, policy: Sched_Policy, param: ^sched_param) -> Errno --- + + /* + Creates a thread-specific data key visible to all threads in the process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_key_create.html ]] + */ + pthread_key_create :: proc(key: ^pthread_key_t, destructor: proc "c" (value: rawptr) = nil) -> Errno --- + + /* + Deletes a thread-specific data key visible to all threads in the process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_key_delete.html ]] + */ + pthread_key_delete :: proc(key: pthread_key_t) -> Errno --- + + /* + Returns the value currently bound to the specified key on behalf of the calling thread. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getspecific.html ]] + */ + pthread_getspecific :: proc(key: pthread_key_t) -> rawptr --- + + /* + Sets the value currently bound to the specified key on behalf of the calling thread. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_getspecific.html ]] + */ + pthread_setspecific :: proc(key: pthread_key_t, value: rawptr) -> Errno --- + + /* + Suspends execution of the calling thread until the target thread terminates. + + Example: + ar: [10_000]i32 + + sb1 := ar[:5_000] + sb2 := ar[5_000:] + + th1, th2: posix.pthread_t + + posix.pthread_create(&th1, nil, incer, &sb1) + posix.pthread_create(&th2, nil, incer, &sb2) + + posix.pthread_join(th1) + posix.pthread_join(th2) + + incer :: proc "c" (arg: rawptr) -> rawptr { + sb := (^[]i32)(arg) + for &val in sb { + val += 1 + } + + return nil + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_join.html ]] + */ + pthread_join :: proc(thread: pthread_t, value_ptr: ^rawptr = nil) -> Errno --- + + /* + Get the calling thread ID. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_self.html ]] + */ + pthread_self :: proc() -> pthread_t --- + + /* + Atomically set the calling thread's cancelability and return the previous value. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_setcancelstate.html ]] + */ + pthread_setcancelstate :: proc(state: Cancel_State, oldstate: ^Cancel_State) -> Errno --- + + /* + Atomically set the calling thread's cancel type and return the previous value. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_setcancelstate.html ]] + */ + pthread_setcanceltype :: proc(type: Cancel_Type, oldtype: ^Cancel_Type) -> Errno --- + + + /* + Creates a cancellation point in the calling thread. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_testcancel.html ]] + */ + pthread_testcancel :: proc() --- + + /* + Sets the scheduling priority for the thread given. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_setschedprio.html ]] + */ + pthread_setschedprio :: proc(thread: pthread_t, prio: c.int) -> Errno --- +} + +Detach_State :: enum c.int { + // Causes all threads to be in the joinable state. + CREATE_JOINABLE = PTHREAD_CREATE_JOINABLE, + // Causes all threads to be in the detached state. + CREATE_DETACHED = PTHREAD_CREATE_DETACHED, +} + +Inherit_Sched :: enum c.int { + // Threads inherit from the creating thread. + INHERIT_SCHED = PTHREAD_INHERIT_SCHED, + // Threads scheduling shall be set to the corresponding values from the attributes object. + EXPLICIT_SCHED = PTHREAD_EXPLICIT_SCHED, +} + +Thread_Scope :: enum c.int { + // System scheduling contention scope. + SYSTEM = PTHREAD_SCOPE_SYSTEM, + // Process scheduling contention scope. + PROCESS = PTHREAD_SCOPE_PROCESS, +} + +Cancel_State :: enum c.int { + // Cancel takes place at next cancellation point. + ENABLE = PTHREAD_CANCEL_ENABLE, + // Cancel postponed. + DISABLE = PTHREAD_CANCEL_DISABLE, +} + +Cancel_Type :: enum c.int { + // Cancel waits until cancellation point. + DEFERRED = PTHREAD_CANCEL_DEFERRED, + // Cancel occurs immediately. + ASYNCHRONOUS = PTHREAD_CANCEL_ASYNCHRONOUS, +} + +when ODIN_OS == .Darwin { + + PTHREAD_CANCEL_ASYNCHRONOUS :: 0x00 + PTHREAD_CANCEL_DEFERRED :: 0x02 + + PTHREAD_CANCEL_DISABLE :: 0x00 + PTHREAD_CANCEL_ENABLE :: 0x01 + + // PTHREAD_CANCEL_ASYNCHRONOUS :: 1 + // PTHREAD_CANCEL_DEFERRED :: 0 + // + // PTHREAD_CANCEL_DISABLE :: 1 + // PTHREAD_CANCEL_ENABLE :: 0 + + PTHREAD_CANCELED :: rawptr(uintptr(1)) + + PTHREAD_CREATE_DETACHED :: 2 + PTHREAD_CREATE_JOINABLE :: 1 + + PTHREAD_EXPLICIT_SCHED :: 2 + PTHREAD_INHERIT_SCHED :: 1 + + PTHREAD_PRIO_INHERIT :: 1 + PTHREAD_PRIO_NONE :: 0 + PTHREAD_PRIO_PROTECT :: 2 + + PTHREAD_PROCESS_SHARED :: 1 + PTHREAD_PROCESS_PRIVATE :: 2 + + PTHREAD_SCOPE_PROCESS :: 2 + PTHREAD_SCOPE_SYSTEM :: 1 + + pthread_t :: distinct u64 + + pthread_attr_t :: struct { + __sig: c.long, + __opaque: [56]c.char, + } + + pthread_key_t :: distinct c.ulong + + pthread_mutex_t :: struct { + __sig: c.long, + __opaque: [56]c.char, + } + + pthread_cond_t :: struct { + __sig: c.long, + __opaque: [40]c.char, + } + + sched_param :: struct { + sched_priority: c.int, /* [PSX] process or thread execution scheduling priority */ + _: [4]c.char, + } + +} else when ODIN_OS == .FreeBSD { + + PTHREAD_CANCEL_ASYNCHRONOUS :: 0x02 + PTHREAD_CANCEL_DEFERRED :: 0x00 + + PTHREAD_CANCEL_DISABLE :: 0x01 + PTHREAD_CANCEL_ENABLE :: 0x00 + + PTHREAD_CANCELED :: rawptr(uintptr(1)) + + PTHREAD_CREATE_DETACHED :: 1 + PTHREAD_CREATE_JOINABLE :: 0 + + PTHREAD_EXPLICIT_SCHED :: 0 + PTHREAD_INHERIT_SCHED :: 4 + + PTHREAD_PRIO_INHERIT :: 1 + PTHREAD_PRIO_NONE :: 0 + PTHREAD_PRIO_PROTECT :: 2 + + PTHREAD_PROCESS_SHARED :: 1 + PTHREAD_PROCESS_PRIVATE :: 0 + + PTHREAD_SCOPE_PROCESS :: 0 + PTHREAD_SCOPE_SYSTEM :: 2 + + pthread_t :: distinct u64 + + pthread_attr_t :: struct #align(8) { + _: [8]byte, + } + + pthread_key_t :: distinct c.int + + pthread_mutex_t :: struct #align(8) { + _: [8]byte, + } + + pthread_cond_t :: struct #align(8) { + _: [8]byte, + } + + sched_param :: struct { + sched_priority: c.int, /* [PSX] process or thread execution scheduling priority */ + } + +} else when ODIN_OS == .NetBSD { + + PTHREAD_CANCEL_ASYNCHRONOUS :: 1 + PTHREAD_CANCEL_DEFERRED :: 0 + + PTHREAD_CANCEL_DISABLE :: 1 + PTHREAD_CANCEL_ENABLE :: 0 + + PTHREAD_CANCELED :: rawptr(uintptr(1)) + + PTHREAD_CREATE_DETACHED :: 1 + PTHREAD_CREATE_JOINABLE :: 0 + + PTHREAD_EXPLICIT_SCHED :: 1 + PTHREAD_INHERIT_SCHED :: 0 + + PTHREAD_PRIO_INHERIT :: 1 + PTHREAD_PRIO_NONE :: 0 + PTHREAD_PRIO_PROTECT :: 2 + + PTHREAD_PROCESS_SHARED :: 1 + PTHREAD_PROCESS_PRIVATE :: 0 + + PTHREAD_SCOPE_PROCESS :: 0 + PTHREAD_SCOPE_SYSTEM :: 1 + + pthread_t :: distinct rawptr + + pthread_attr_t :: struct { + pta_magic: c.uint, + pta_flags: c.int, + pta_private: rawptr, + } + + pthread_key_t :: distinct c.int + + pthread_cond_t :: struct #align(8) { + _: [40]byte, + } + + pthread_mutex_t :: struct #align(8) { + _: [48]byte, + } + + sched_param :: struct { + sched_priority: c.int, /* [PSX] process or thread execution scheduling priority */ + } + +} else when ODIN_OS == .OpenBSD { + + PTHREAD_CANCEL_ASYNCHRONOUS :: 2 + PTHREAD_CANCEL_DEFERRED :: 0 + + PTHREAD_CANCEL_DISABLE :: 1 + PTHREAD_CANCEL_ENABLE :: 0 + + PTHREAD_CANCELED :: rawptr(uintptr(1)) + + PTHREAD_CREATE_DETACHED :: 0x1 + PTHREAD_CREATE_JOINABLE :: 0 + + PTHREAD_EXPLICIT_SCHED :: 0 + PTHREAD_INHERIT_SCHED :: 0x4 + + PTHREAD_PRIO_INHERIT :: 1 + PTHREAD_PRIO_NONE :: 0 + PTHREAD_PRIO_PROTECT :: 2 + + PTHREAD_PROCESS_SHARED :: 0 + PTHREAD_PROCESS_PRIVATE :: 1 + + PTHREAD_SCOPE_PROCESS :: 0 + PTHREAD_SCOPE_SYSTEM :: 0x2 + + pthread_t :: distinct rawptr + pthread_attr_t :: distinct rawptr + pthread_key_t :: distinct c.int + pthread_mutex_t :: distinct rawptr + pthread_cond_t :: distinct rawptr + + sched_param :: struct { + sched_priority: c.int, /* [PSX] process or thread execution scheduling priority */ + } + +} else when ODIN_OS == .Linux { + + PTHREAD_CANCEL_DEFERRED :: 0 + PTHREAD_CANCEL_ASYNCHRONOUS :: 1 + + PTHREAD_CANCEL_ENABLE :: 0 + PTHREAD_CANCEL_DISABLE :: 1 + + PTHREAD_CANCELED :: rawptr(~uintptr(0)) + + PTHREAD_CREATE_JOINABLE :: 0 + PTHREAD_CREATE_DETACHED :: 1 + + PTHREAD_INHERIT_SCHED :: 0 + PTHREAD_EXPLICIT_SCHED :: 1 + + PTHREAD_PRIO_NONE :: 0 + PTHREAD_PRIO_INHERIT :: 1 + PTHREAD_PRIO_PROTECT :: 2 + + PTHREAD_PROCESS_PRIVATE :: 0 + PTHREAD_PROCESS_SHARED :: 1 + + PTHREAD_SCOPE_SYSTEM :: 0 + PTHREAD_SCOPE_PROCESS :: 1 + + pthread_t :: distinct c.ulong + + pthread_attr_t :: struct #raw_union { + __size: [56]c.char, // NOTE: may be smaller depending on libc or arch, but never larger. + __align: c.long, + } + + pthread_key_t :: distinct c.uint + + pthread_cond_t :: struct { + __size: [40]c.char, // NOTE: may be smaller depending on libc or arch, but never larger. + __align: c.long, + } + + pthread_mutex_t :: struct { + __size: [32]c.char, // NOTE: may be smaller depending on libc or arch, but never larger. + __align: c.long, + } + + sched_param :: struct { + sched_priority: c.int, /* [PSX] process or thread execution scheduling priority */ + + // NOTE: may be smaller depending on libc or arch, but never larger. + __reserved1: c.int, + __reserved2: [4]c.long, + __reserved3: c.int, + } + +} diff --git a/core/sys/posix/pwd.odin b/core/sys/posix/pwd.odin new file mode 100644 index 000000000..33cbcd7c5 --- /dev/null +++ b/core/sys/posix/pwd.odin @@ -0,0 +1,179 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// pwd.h - password structure + +foreign lib { + /* + Rewinds the user database so that the next getpwent() returns the first entry. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setpwent.html ]] + */ + setpwent :: proc() --- + + /* + Returns the current entry in the user database. + + Returns: nil (setting errno) on error, nil (not setting errno) on success. + + Example: + posix.setpwent() + defer posix.endpwent() + for e := posix.getpwent(); e != nil; e = posix.getpwent() { + fmt.println(e) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setpwent.html ]] + */ + @(link_name=LGETPWENT) + getpwent :: proc() -> ^passwd --- + + /* + Closes the user database. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setpwent.html ]] + */ + endpwent :: proc() --- + + /* + Searches the database for an entry with a matching name. + + Returns: nil (setting errno) on error, nil (not setting errno) on success. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwnam.html ]] + */ + @(link_name=LGETPWNAM) + getpwnam :: proc(name: cstring) -> ^passwd --- + + /* + Searches the database for an entry with a matching name. + Populating the pwd fields and using the buffer to allocate strings into. + Setting result to nil on failure and to the address of pwd otherwise. + + ERANGE will be returned if there is not enough space in buffer. + sysconf(_SC_GETPW_R_SIZE_MAX) can be called for the suggested size of this buffer, note that it could return -1. + + Example: + length := posix.sysconf(._GETPW_R_SIZE_MAX) + length = length == -1 ? 1024 : length + + buffer: [dynamic]byte + defer delete(buffer) + + result: posix.passwd + resultp: ^posix.passwd + errno: posix.Errno + for { + if err := resize(&buffer, length); err != nil { + fmt.panicf("allocation failure: %v", err) + } + + errno = posix.getpwnam_r("root", &result, raw_data(buffer), len(buffer), &resultp) + if errno != .ERANGE { + break + } + } + + if errno != .NONE { + panic(string(posix.strerror(errno))) + } + + fmt.println(result) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwnam.html ]] + */ + @(link_name=LGETPWNAMR) + getpwnam_r :: proc(name: cstring, pwd: ^passwd, buffer: [^]byte, bufsize: c.size_t, result: ^^passwd) -> Errno --- + + /* + Searches the database for an entry with a matching uid. + + Returns: nil (setting errno) on error, nil (not setting errno) on success. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwuid.html ]] + */ + @(link_name=LGETPWUID) + getpwuid :: proc(uid: uid_t) -> ^passwd --- + + /* + Searches the database for an entry with a matching uid. + Populating the pwd fields and using the buffer to allocate strings into. + Setting result to nil on failure and to the address of pwd otherwise. + + ERANGE will be returned if there is not enough space in buffer. + sysconf(_SC_GETPW_R_SIZE_MAX) can be called for the suggested size of this buffer, note that it could return -1. + + See the example for getpwnam_r. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwuid_r.html ]] + */ + @(link_name=LGETPWUIDR) + getpwuid_r :: proc(uid: uid_t, pwd: ^passwd, buffer: [^]byte, bufsize: c.size_t, result: ^^passwd) -> Errno --- +} + +when ODIN_OS == .NetBSD { + @(private) LGETPWENT :: "__getpwent50" + @(private) LGETPWNAM :: "__getpwnam50" + @(private) LGETPWNAMR :: "__getpwnam_r50" + @(private) LGETPWUID :: "__getpwuid50" + @(private) LGETPWUIDR :: "__getpwuid_r50" +} else { + @(private) LGETPWENT :: "getpwent" + @(private) LGETPWNAM :: "getpwnam" + @(private) LGETPWNAMR :: "getpwnam_r" + @(private) LGETPWUID :: "getpwuid" + @(private) LGETPWUIDR :: "getpwuid_r" +} + +when ODIN_OS == .Darwin || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + passwd :: struct { + pw_name: cstring, /* [PSX] user name */ + pw_passwd: cstring, /* encrypted password */ + pw_uid: uid_t, /* [PSX] user uid */ + pw_gid: gid_t, /* [PSX] user gid */ + pw_change: time_t, /* password change time */ + pw_class: cstring, /* user access class */ + pw_gecos: cstring, /* Honeywell login info */ + pw_dir: cstring, /* [PSX] home directory */ + pw_shell: cstring, /* [PSX] default shell */ + pw_expire: time_t, /* account expiration */ + } + +} else when ODIN_OS == .FreeBSD { + + passwd :: struct { + pw_name: cstring, /* [PSX] user name */ + pw_passwd: cstring, /* encrypted password */ + pw_uid: uid_t, /* [PSX] user uid */ + pw_gid: gid_t, /* [PSX] user gid */ + pw_change: time_t, /* password change time */ + pw_class: cstring, /* user access class */ + pw_gecos: cstring, /* Honeywell login info */ + pw_dir: cstring, /* [PSX] home directory */ + pw_shell: cstring, /* [PSX] default shell */ + pw_expire: time_t, /* account expiration */ + pw_fields: c.int, + } + +} else when ODIN_OS == .Linux { + + passwd :: struct { + pw_name: cstring, /* [PSX] user name */ + pw_passwd: cstring, /* encrypted password */ + pw_uid: uid_t, /* [PSX] user uid */ + pw_gid: gid_t, /* [PSX] user gid */ + pw_gecos: cstring, /* Real name. */ + pw_dir: cstring, /* Home directory. */ + pw_shell: cstring, /* Shell program. */ + } + +} diff --git a/core/sys/posix/sched.odin b/core/sys/posix/sched.odin new file mode 100644 index 000000000..e91178b09 --- /dev/null +++ b/core/sys/posix/sched.odin @@ -0,0 +1,104 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sched.h - execution scheduling + +foreign lib { + /* + Returns the minimum for the given scheduling policy. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_get_priority_max.html ]] + */ + sched_get_priority_max :: proc(policy: Sched_Policy) -> c.int --- + + /* + Returns the maximum for the given scheduling policy. + + Returns: -1 (setting errno) on failure, the maximum on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_get_priority_max.html ]] + */ + sched_get_priority_min :: proc(policy: Sched_Policy) -> c.int --- + + /* + Forces the running thread to relinquish the processor until it again becomes the head of its thread list. + */ + sched_yield :: proc() -> result --- + + /* NOTE: unimplemented on darwin (I think?). + /* + Get the scheduling params of a process, pid of 0 will return that of the current process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_getparam.html ]] + */ + sched_getparam :: proc(pid: pid_t, param: ^sched_param) -> result --- + /* + Sets the scheduling parameters of the given process, pid of 0 will set that of the current process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_setparam.html ]] + */ + sched_setparam :: proc(pid: pid_t, param: ^sched_param) -> result --- + + /* + Returns the scheduling policy of a process, pid of 0 will return that of the current process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_getscheduler.html ]] + */ + sched_getscheduler :: proc(pid: pid_t) -> Sched_Policy --- + + /* + Sets the scheduling policy and parameters of the process, pid 0 will be the current process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_setscheduler.html ]] + */ + sched_setscheduler :: proc(pid: pid_t, policy: Sched_Policy, param: ^sched_param) -> result --- + + /* + Updates the timespec structure to contain the current execution time limit for the process. + pid of 0 will return that of the current process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sched_rr_get_interval.html ]] + */ + sched_rr_get_interval :: proc(pid: pid_t, interval: ^timespec) -> result --- + */ +} + +Sched_Policy :: enum c.int { + // Error condition of sched_getscheduler. + ERROR = -1, + // First in-first out (FIFO) scheduling policy. + FIFO = SCHED_FIFO, + // Round robin scheduling policy. + RR = SCHED_RR, + // Another scheduling policy. + OTHER = SCHED_OTHER, +} + +when ODIN_OS == .Darwin { + + SCHED_FIFO :: 4 + SCHED_RR :: 2 + // SCHED_SPORADIC :: 3 NOTE: not a thing on freebsd, netbsd and probably others, leaving it out + SCHED_OTHER :: 1 + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD { + + SCHED_FIFO :: 1 + SCHED_RR :: 3 + SCHED_OTHER :: 2 + +} else when ODIN_OS == .NetBSD || ODIN_OS == .Linux { + + SCHED_OTHER :: 0 + SCHED_FIFO :: 1 + SCHED_RR :: 2 + +} diff --git a/core/sys/posix/setjmp.odin b/core/sys/posix/setjmp.odin new file mode 100644 index 000000000..926dbd3ad --- /dev/null +++ b/core/sys/posix/setjmp.odin @@ -0,0 +1,54 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// setjmp.h - stack environment declarations + +foreign lib { + /* + Equivalent to longjmp() but must not touch signals. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/_longjmp.html ]] + */ + _longjmp :: proc(env: ^jmp_buf, val: c.int) -> ! --- + + /* + Equivalent to setjmp() but must not touch signals. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/_longjmp.html ]] + */ + _setjmp :: proc(env: ^jmp_buf) -> c.int --- + + /* + Equivalent to longjmp() but restores saved signal masks. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/siglongjump.html ]] + */ + @(link_name=LSIGLONGJMP) + siglongjmp :: proc(env: ^sigjmp_buf, val: c.int) -> ! --- + + /* + Equivalent to setjmp() but restores saved signal masks. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/siglongjump.html ]] + */ + @(link_name=LSIGSETJMP) + sigsetjmp :: proc(env: ^sigjmp_buf, savemask: b32) -> c.int --- +} + +sigjmp_buf :: distinct jmp_buf + +when ODIN_OS == .NetBSD { + @(private) LSIGSETJMP :: "__sigsetjmp14" + @(private) LSIGLONGJMP :: "__siglongjmp14" +} else { + @(private) LSIGSETJMP :: "sigsetjmp" + @(private) LSIGLONGJMP :: "siglongjmp" +} diff --git a/core/sys/posix/setjmp_libc.odin b/core/sys/posix/setjmp_libc.odin new file mode 100644 index 000000000..a69dff09f --- /dev/null +++ b/core/sys/posix/setjmp_libc.odin @@ -0,0 +1,11 @@ +#+build windows, linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c/libc" + +// setjmp.h - stack environment declarations + +jmp_buf :: libc.jmp_buf + +longjmp :: libc.longjmp +setjmp :: libc.setjmp diff --git a/core/sys/posix/signal.odin b/core/sys/posix/signal.odin new file mode 100644 index 000000000..4ba4e9943 --- /dev/null +++ b/core/sys/posix/signal.odin @@ -0,0 +1,1183 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "base:intrinsics" + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// signal.h - signals + +foreign lib { + /* + Raise a signal to the process/group specified by pid. + + If sig is 0, this function can be used to check if the pid is just checked for validity. + + If pid is -1, the signal is sent to all processes that the current process has permission to send. + + If pid is negative (not -1), the signal is sent to all processes in the group identifier by the + absolute value. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/kill.html ]] + */ + kill :: proc(pid: pid_t, sig: Signal) -> result --- + + /* + Shorthand for `kill(-pgrp, sig)` which will kill all processes in the given process group. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/killpg.html ]] + */ + killpg :: proc(pgrp: pid_t, sig: Signal) -> result --- + + /* + Writes a language-dependent message to stderror. + + Example: + posix.psignal(.SIGSEGV, "that didn't go well") + + Possible Output: + that didn't go well: Segmentation fault + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/psignal.html ]] + */ + psignal :: proc(signum: Signal, message: cstring) --- + + /* + Send a signal to a thread. + + As with kill, if sig is 0, only validation (of the pthread_t given) is done and no signal is sent. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_kill.html ]] + */ + pthread_kill :: proc(thread: pthread_t, sig: Signal) -> Errno --- + + /* + Examine and change blocked signals. + + Equivalent to sigprocmask(), without the restriction that the call be made in a single-threaded process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_sigmask.html ]] + */ + pthread_sigmask :: proc(how: Sig, set: ^sigset_t, oset: ^sigset_t) -> Errno --- + + /* + Examine and change blocked signals in a single-threaded process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_sigmask.html ]] + */ + @(link_name=LSIGPROCMASK) + sigprocmask :: proc(how: Sig, set: ^sigset_t, oldset: ^sigset_t) -> result --- + + /* + Examine and change a signal action. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigaction.html ]] + */ + @(link_name=LSIGACTION) + sigaction :: proc(sig: Signal, act: ^sigaction_t, oact: ^sigaction_t) -> result --- + + @(link_name=LSIGADDSET) + sigaddset :: proc(set: ^sigset_t, signo: Signal) -> result --- + @(link_name=LSIGDELSET) + sigdelset :: proc(^sigset_t, Signal) -> c.int --- + @(link_name=LSIGEMPTYSET) + sigemptyset :: proc(^sigset_t) -> c.int --- + @(link_name=LSIGFILLSET) + sigfillset :: proc(^sigset_t) -> c.int --- + + /* + Set and get the signal alternate stack context. + + Example: + sigstk := posix.stack_t { + ss_sp = make([^]byte, posix.SIGSTKSZ) or_else panic("allocation failure"), + ss_size = posix.SIGSTKSZ, + ss_flags = {}, + } + if posix.sigaltstack(&sigstk, nil) != .OK { + fmt.panicf("sigaltstack failure: %v", posix.strerror(posix.errno())) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigaltstack.html ]] + */ + @(link_name=LSIGALTSTACK) + sigaltstack :: proc(ss: ^stack_t, oss: ^stack_t) -> result --- + + /* + Adds sig to the signal mask of the calling process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sighold.html ]] + */ + sighold :: proc(sig: Signal) -> result --- + + /* + Sets the disposition of sig to SIG_IGN. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sighold.html ]] + */ + sigignore :: proc(sig: Signal) -> result --- + + /* + Removes sig from the signal mask of the calling process and suspend the calling process until + a signal is received. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sighold.html ]] + */ + sigpause :: proc(sig: Signal) -> result --- + + /* + Removes sig from the signal mask of the calling process. + + Returns: always -1. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sighold.html ]] + */ + sigrelse :: proc(sig: Signal) -> result --- + + /* + Changes the restart behavior when a function is interrupted by the specified signal. + + If flag is true, SA_RESTART is removed, added otherwise. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/siginterrupt.html ]] + */ + siginterrupt :: proc(sig: Signal, flag: b32) -> result --- + + /* + Test for a signal in a signal set. + + Returns: 1 if it is a member, 0 if not, -1 (setting errno) on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigismember.html ]] + */ + @(link_name=LSIGISMEMBER) + sigismember :: proc(set: ^sigset_t, signo: Signal) -> c.int --- + + /* + Stores the set of signals that are blocked from delivery to the calling thread and that are pending + on the process or the calling thread. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigpending.html ]] + */ + @(link_name=LSIGPENDING) + sigpending :: proc(set: ^sigset_t) -> result --- + + /* + Wait for one of the given signals. + + Returns: always -1 + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigsuspend.html ]] + */ + @(link_name=LSIGSUSPEND) + sigsuspend :: proc(sigmask: ^sigset_t) -> result --- + + /* + Wait for queued signals. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigwait.html ]] + */ + sigwait :: proc(set: ^sigset_t, sig: ^Signal) -> Errno --- + + /* NOTE: unimplemented on darwin. + + void psiginfo(const siginfo_t *, const char *); + int sigqueue(pid_t, int, union sigval); + void (*sigset(int, void (*)(int)))(int); + int sigsuspend(const sigset_t *); + int sigtimedwait(const sigset_t *restrict, siginfo_t *restrict, + const struct timespec *restrict); + int sigwaitinfo(const sigset_t *restrict, siginfo_t *restrict); + */ +} + +sigval :: struct #raw_union { + sigval_int: c.int, /* [PSX] integer signal value */ + sigval_ptr: rawptr, /* [PSX] pointer signal value */ +} + +ILL_Code :: enum c.int { + // Illegal opcode. + ILLOPC = ILL_ILLOPC, + // Illegal operand. + ILLOPN = ILL_ILLOPN, + // Illegal addressing mode. + ILLADR = ILL_ILLADR, + // Illegal trap. + ILLTRP = ILL_ILLTRP, + // Priviledged opcode. + PRVOPC = ILL_PRVOPC, + // Priviledged register. + PRVREG = ILL_PRVREG, + // Coprocessor error. + COPROC = ILL_COPROC, + // Internal stack error. + BADSTK = ILL_BADSTK, +} + +FPE_Code :: enum c.int { + // Integer divide by zero. + INTDIV = FPE_INTDIV, + // Integer overflow. + INTOVF = FPE_INTOVF, + // Floating-point divide by zero. + FLTDIV = FPE_FLTDIV, + // Floating-point overflow. + FLTOVF = FPE_FLTOVF, + // Floating-point underflow. + FLTUND = FPE_FLTUND, + // Floating-point inexact result. + FLTRES = FPE_FLTRES, + // Invalid floating-point operation. + FLTINV = FPE_FLTINV, + // Subscript out of range. + FLTSUB = FPE_FLTSUB, +} + +SEGV_Code :: enum c.int { + // Address not mapped to object. + MAPERR = SEGV_MAPERR, + // Invalid permissions for mapped object. + ACCERR = SEGV_ACCERR, +} + +BUS_Code :: enum c.int { + // Invalid address alignment. + ADRALN = BUS_ADRALN, + // Nonexistent physical address. + ADRERR = BUS_ADRERR, + // Object-specific hardware error. + OBJERR = BUS_OBJERR, +} + +TRAP_Code :: enum c.int { + // Process breakpoint. + BRKPT = TRAP_BRKPT, + // Process trace trap. + TRACE = TRAP_TRACE, +} + +CLD_Code :: enum c.int { + // Child has exited.. + EXITED = CLD_EXITED, + // Child has terminated abnormally and did not create a core file. + KILLED = CLD_KILLED, + // Child has terminated abnormally and created a core file. + DUMPED = CLD_DUMPED, + // Traced child trapped. + TRAPPED = CLD_TRAPPED, + // Child has stopped. + STOPPED = CLD_STOPPED, + // Stopped child has continued. + CONTINUED = CLD_CONTINUED, +} + +POLL_Code :: enum c.int { + // Data input is available. + IN = POLL_IN, + // Output buffers available. + OUT = POLL_OUT, + // Input message available. + MSG = POLL_MSG, + // I/O error. + ERR = POLL_ERR, + // High priority input available. + PRI = POLL_PRI, + // Device disconnected. + HUP = POLL_HUP, +} + +Any_Code :: enum c.int { + // Signal sent by kill(). + USER = SI_USER, + // Signal sent by sigqueue(). + QUEUE = SI_QUEUE, + // Signal generated by expiration of a timer set by timer_settime(). + TIMER = SI_TIMER, + // Signal generated by completion of an asynchronous I/O request. + ASYNCIO = SI_ASYNCIO, + // Signal generated by arrival of a message on an empty message queue. + MESGQ = SI_MESGQ, +} + +SA_Flags_Bits :: enum c.int { + // Do not generate SIGCHLD when children stop or stopped children continue. + NOCLDSTOP = log2(SA_NOCLDSTOP), + // Cause signal delivery to occur on an alternate stack. + ONSTACK = log2(SA_ONSTACK), + // Cause signal disposition to be set to SIG_DFL on entry to signal handlers. + RESETHAND = log2(SA_RESETHAND), + // Cause certain functions to become restartable. + RESTART = log2(SA_RESTART), + // Cause extra information to be passed to signal handlers at the time of receipt of a signal. + SIGINFO = log2(SA_SIGINFO), + // Cause implementation not to create zombie processes or status information on child termination. + NOCLDWAIT = log2(SA_NOCLDWAIT), + // Cause signal not to be automatically blocked on entry to signal handler. + SA_NODEFER = log2(SA_NODEFER), +} +SA_Flags :: bit_set[SA_Flags_Bits; c.int] + +SS_Flag_Bits :: enum c.int { + // Process is executing on an alternate signal stack. + ONSTACK = log2(SS_ONSTACK), + // Alternate signal stack is disabled. + DISABLE = log2(SS_DISABLE), +} +SS_Flags :: bit_set[SS_Flag_Bits; c.int] + +Sig :: enum c.int { + // Resulting set is the union of the current set and the signal set and the complement of + // the signal set pointed to by the argument. + BLOCK = SIG_BLOCK, + // Resulting set is the intersection of the current set and the complement of the signal set + // pointed to by the argument. + UNBLOCK = SIG_UNBLOCK, + // Resulting set is the signal set pointed to by the argument. + SETMASK = SIG_SETMASK, +} + +when ODIN_OS == .NetBSD { + @(private) LSIGPROCMASK :: "__sigprocmask14" + @(private) LSIGACTION :: "__sigaction_siginfo" + @(private) LSIGADDSET :: "__sigaddset14" + @(private) LSIGDELSET :: "__sigdelset14" + @(private) LSIGEMPTYSET :: "__sigemptyset14" + @(private) LSIGFILLSET :: "__sigfillset14" + @(private) LSIGALTSTACK :: "__sigaltstack14" + @(private) LSIGISMEMBER :: "__sigismember14" + @(private) LSIGPENDING :: "__sigpending14" + @(private) LSIGSUSPEND :: "__sigsuspend14" +} else { + @(private) LSIGPROCMASK :: "sigprocmask" + @(private) LSIGACTION :: "sigaction" + @(private) LSIGADDSET :: "sigaddset" + @(private) LSIGDELSET :: "sigdelset" + @(private) LSIGEMPTYSET :: "sigemptyset" + @(private) LSIGFILLSET :: "sigfillset" + @(private) LSIGALTSTACK :: "sigaltstack" + @(private) LSIGISMEMBER :: "sigismember" + @(private) LSIGPENDING :: "sigpending" + @(private) LSIGSUSPEND :: "sigsuspend" +} + +when ODIN_OS == .Darwin { + + // Request that signal be held + SIG_HOLD :: rawptr(uintptr(5)) + + uid_t :: distinct c.uint32_t + sigset_t :: distinct c.uint32_t + + SIGHUP :: 1 + SIGQUIT :: 3 + SIGTRAP :: 5 + SIGPOLL :: 7 + SIGKILL :: 9 + SIGBUS :: 10 + SIGSYS :: 12 + SIGPIPE :: 13 + SIGALRM :: 14 + SIGURG :: 16 + SIGCONT :: 19 + SIGSTOP :: 17 + SIGTSTP :: 18 + SIGCHLD :: 20 + SIGTTIN :: 21 + SIGTTOU :: 22 + SIGXCPU :: 24 + SIGXFSZ :: 25 + SIGVTALRM :: 26 + SIGPROF :: 27 + SIGUSR1 :: 30 + SIGUSR2 :: 31 + + // NOTE: this is actually defined as `sigaction`, but due to the function with the same name + // `_t` has been added. + + sigaction_t :: struct { + using _: struct #raw_union { + sa_handler: proc "c" (Signal), /* [PSX] signal-catching function or one of the SIG_IGN or SIG_DFL */ + sa_sigaction: proc "c" (Signal, ^siginfo_t, rawptr), /* [PSX] signal-catching function */ + }, + sa_mask: sigset_t, /* [PSX] set of signals to be blocked during execution of the signal handling function */ + sa_flags: SA_Flags, /* [PSX] special flags */ + } + + SIG_BLOCK :: 1 + SIG_UNBLOCK :: 2 + SIG_SETMASK :: 3 + + SA_NOCLDSTOP :: 0x0008 + SA_ONSTACK :: 0x0001 + SA_RESETHAND :: 0x0004 + SA_RESTART :: 0x0002 + SA_SIGINFO :: 0x0040 + SA_NOCLDWAIT :: 0x0020 + SA_NODEFER :: 0x0010 + + SS_ONSTACK :: 0x0001 + SS_DISABLE :: 0x0004 + + MINSIGSTKSZ :: 32768 + SIGSTKSZ :: 131072 + + stack_t :: struct { + ss_sp: rawptr, /* [PSX] stack base or pointer */ + ss_size: c.size_t, /* [PSX] stack size */ + ss_flags: SS_Flags, /* [PSX] flags */ + } + + siginfo_t :: struct { + si_signo: Signal, /* [PSX] signal number */ + si_errno: Errno, /* [PSX] errno value associated with this signal */ + si_code: struct #raw_union { /* [PSX] specific more detailed codes per signal */ + ill: ILL_Code, + fpe: FPE_Code, + segv: SEGV_Code, + bus: BUS_Code, + trap: TRAP_Code, + chld: CLD_Code, + poll: POLL_Code, + any: Any_Code, + }, + si_pid: pid_t, /* [PSX] sending process ID */ + si_uid: uid_t, /* [PSX] real user ID of sending process */ + si_status: c.int, /* [PSX] exit value or signal */ + si_addr: rawptr, /* [PSX] address of faulting instruction */ + si_value: sigval, /* [PSX] signal value */ + si_band: c.long, /* [PSX] band event for SIGPOLL */ + __pad: [7]c.ulong, + } + + ILL_ILLOPC :: 1 + ILL_ILLOPN :: 4 + ILL_ILLADR :: 5 + ILL_ILLTRP :: 2 + ILL_PRVOPC :: 3 + ILL_PRVREG :: 6 + ILL_COPROC :: 7 + ILL_BADSTK :: 8 + + FPE_INTDIV :: 7 + FPE_INTOVF :: 8 + FPE_FLTDIV :: 1 + FPE_FLTOVF :: 2 + FPE_FLTUND :: 3 + FPE_FLTRES :: 4 + FPE_FLTINV :: 5 + FPE_FLTSUB :: 6 + + SEGV_MAPERR :: 1 + SEGV_ACCERR :: 2 + + BUS_ADRALN :: 1 + BUS_ADRERR :: 2 + BUS_OBJERR :: 3 + + TRAP_BRKPT :: 1 + TRAP_TRACE :: 2 + + CLD_EXITED :: 1 + CLD_KILLED :: 2 + CLD_DUMPED :: 3 + CLD_TRAPPED :: 4 + CLD_STOPPED :: 5 + CLD_CONTINUED :: 6 + + POLL_IN :: 1 + POLL_OUT :: 2 + POLL_MSG :: 3 + POLL_ERR :: 4 + POLL_PRI :: 5 + POLL_HUP :: 6 + + SI_USER :: 0x10001 + SI_QUEUE :: 0x10002 + SI_TIMER :: 0x10003 + SI_ASYNCIO :: 0x10004 + SI_MESGQ :: 0x10005 + +} else when ODIN_OS == .FreeBSD { + + // Request that signal be held + SIG_HOLD :: rawptr(uintptr(3)) + + uid_t :: distinct c.uint32_t + + sigset_t :: struct { + __bits: [4]c.uint32_t, + } + + SIGHUP :: 1 + SIGQUIT :: 3 + SIGTRAP :: 5 + SIGPOLL :: 7 + SIGKILL :: 9 + SIGBUS :: 10 + SIGSYS :: 12 + SIGPIPE :: 13 + SIGALRM :: 14 + SIGURG :: 16 + SIGCONT :: 19 + SIGSTOP :: 17 + SIGTSTP :: 18 + SIGCHLD :: 20 + SIGTTIN :: 21 + SIGTTOU :: 22 + SIGXCPU :: 24 + SIGXFSZ :: 25 + SIGVTALRM :: 26 + SIGPROF :: 27 + SIGUSR1 :: 30 + SIGUSR2 :: 31 + + // NOTE: this is actually defined as `sigaction`, but due to the function with the same name + // `_t` has been added. + + sigaction_t :: struct { + using _: struct #raw_union { + sa_handler: proc "c" (Signal), /* [PSX] signal-catching function or one of the SIG_IGN or SIG_DFL */ + sa_sigaction: proc "c" (Signal, ^siginfo_t, rawptr), /* [PSX] signal-catching function */ + }, + sa_flags: SA_Flags, /* [PSX] special flags */ + sa_mask: sigset_t, /* [PSX] set of signals to be blocked during execution of the signal handling function */ + } + + SIG_BLOCK :: 1 + SIG_UNBLOCK :: 2 + SIG_SETMASK :: 3 + + SA_NOCLDSTOP :: 0x0008 + SA_ONSTACK :: 0x0001 + SA_RESETHAND :: 0x0004 + SA_RESTART :: 0x0002 + SA_SIGINFO :: 0x0040 + SA_NOCLDWAIT :: 0x0020 + SA_NODEFER :: 0x0010 + + SS_ONSTACK :: 0x0001 + SS_DISABLE :: 0x0004 + + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm32 { + MINSIGSTKSZ :: 1024 * 4 + } else when ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 { + MINSIGSTKSZ :: 512 * 4 + } + + SIGSTKSZ :: MINSIGSTKSZ + 32768 + + stack_t :: struct { + ss_sp: rawptr, /* [PSX] stack base or pointer */ + ss_size: c.size_t, /* [PSX] stack size */ + ss_flags: SS_Flags, /* [PSX] flags */ + } + + siginfo_t :: struct { + si_signo: Signal, /* [PSX] signal number */ + si_errno: Errno, /* [PSX] errno value associated with this signal */ + si_code: struct #raw_union { /* [PSX] specific more detailed codes per signal */ + ill: ILL_Code, + fpe: FPE_Code, + segv: SEGV_Code, + bus: BUS_Code, + trap: TRAP_Code, + chld: CLD_Code, + poll: POLL_Code, + any: Any_Code, + }, + si_pid: pid_t, /* [PSX] sending process ID */ + si_uid: uid_t, /* [PSX] real user ID of sending process */ + si_status: c.int, /* [PSX] exit value or signal */ + si_addr: rawptr, /* [PSX] address of faulting instruction */ + si_value: sigval, /* [PSX] signal value */ + using _reason: struct #raw_union { + _fault: struct { + _trapno: c.int, /* machine specific trap code */ + }, + _timer: struct { + _timerid: c.int, + _overrun: c.int, + }, + _mesgq: struct { + _mqd: c.int, + }, + using _poll: struct { + si_band: c.long, /* [PSX] band event for SIGPOLL */ + }, + _capsicum: struct { + _syscall: c.int, /* syscall number for signals delivered as a result of system calls denied by capsicum */ + }, + __spare__: struct { + __spare1__: c.long, + __spare2__: [7]c.int, + }, + }, + } + + ILL_ILLOPC :: 1 + ILL_ILLOPN :: 2 + ILL_ILLADR :: 3 + ILL_ILLTRP :: 4 + ILL_PRVOPC :: 5 + ILL_PRVREG :: 6 + ILL_COPROC :: 7 + ILL_BADSTK :: 8 + + FPE_INTDIV :: 2 + FPE_INTOVF :: 1 + FPE_FLTDIV :: 3 + FPE_FLTOVF :: 4 + FPE_FLTUND :: 5 + FPE_FLTRES :: 6 + FPE_FLTINV :: 7 + FPE_FLTSUB :: 8 + + SEGV_MAPERR :: 1 + SEGV_ACCERR :: 2 + + BUS_ADRALN :: 1 + BUS_ADRERR :: 2 + BUS_OBJERR :: 3 + + TRAP_BRKPT :: 1 + TRAP_TRACE :: 2 + + CLD_EXITED :: 1 + CLD_KILLED :: 2 + CLD_DUMPED :: 3 + CLD_TRAPPED :: 4 + CLD_STOPPED :: 5 + CLD_CONTINUED :: 6 + + POLL_IN :: 1 + POLL_OUT :: 2 + POLL_MSG :: 3 + POLL_ERR :: 4 + POLL_PRI :: 5 + POLL_HUP :: 6 + + SI_USER :: 0x10001 + SI_QUEUE :: 0x10002 + SI_TIMER :: 0x10003 + SI_ASYNCIO :: 0x10004 + SI_MESGQ :: 0x10005 + +} else when ODIN_OS == .NetBSD { + + // Request that signal be held + SIG_HOLD :: rawptr(uintptr(3)) + + uid_t :: distinct c.uint32_t + sigset_t :: struct { + __bits: [4]c.uint32_t, + } + + SIGHUP :: 1 + SIGQUIT :: 3 + SIGTRAP :: 5 + SIGPOLL :: 7 + SIGKILL :: 9 + SIGBUS :: 10 + SIGSYS :: 12 + SIGPIPE :: 13 + SIGALRM :: 14 + SIGURG :: 16 + SIGCONT :: 19 + SIGSTOP :: 17 + SIGTSTP :: 18 + SIGCHLD :: 20 + SIGTTIN :: 21 + SIGTTOU :: 22 + SIGXCPU :: 24 + SIGXFSZ :: 25 + SIGVTALRM :: 26 + SIGPROF :: 27 + SIGUSR1 :: 30 + SIGUSR2 :: 31 + + // NOTE: this is actually defined as `sigaction`, but due to the function with the same name + // `_t` has been added. + + sigaction_t :: struct { + using _: struct #raw_union { + sa_handler: proc "c" (Signal), /* [PSX] signal-catching function or one of the SIG_IGN or SIG_DFL */ + sa_sigaction: proc "c" (Signal, ^siginfo_t, rawptr), /* [PSX] signal-catching function */ + }, + sa_mask: sigset_t, /* [PSX] set of signals to be blocked during execution of the signal handling function */ + sa_flags: SA_Flags, /* [PSX] special flags */ + } + + SIG_BLOCK :: 1 + SIG_UNBLOCK :: 2 + SIG_SETMASK :: 3 + + SA_NOCLDSTOP :: 0x0008 + SA_ONSTACK :: 0x0001 + SA_RESETHAND :: 0x0004 + SA_RESTART :: 0x0002 + SA_SIGINFO :: 0x0040 + SA_NOCLDWAIT :: 0x0020 + SA_NODEFER :: 0x0010 + + SS_ONSTACK :: 0x0001 + SS_DISABLE :: 0x0004 + + MINSIGSTKSZ :: 8192 + SIGSTKSZ :: MINSIGSTKSZ + 32768 + + stack_t :: struct { + ss_sp: rawptr, /* [PSX] stack base or pointer */ + ss_size: c.size_t, /* [PSX] stack size */ + ss_flags: SS_Flags, /* [PSX] flags */ + } + + @(private) + lwpid_t :: c.int32_t + + siginfo_t :: struct #raw_union { + si_pad: [128]byte, + using _info: struct { + si_signo: Signal, /* [PSX] signal number */ + si_code: struct #raw_union { /* [PSX] specific more detailed codes per signal */ + ill: ILL_Code, + fpe: FPE_Code, + segv: SEGV_Code, + bus: BUS_Code, + trap: TRAP_Code, + chld: CLD_Code, + poll: POLL_Code, + any: Any_Code, + }, + si_errno: Errno, /* [PSX] errno value associated with this signal */ + // #ifdef _LP64 + /* In _LP64 the union starts on an 8-byte boundary. */ + _pad: c.int, + // #endif + using _reason: struct #raw_union { + using _rt: struct { + _pid: pid_t, + _uid: uid_t, + si_value: sigval, /* [PSX] signal value */ + }, + using _child: struct { + si_pid: pid_t, /* [PSX] sending process ID */ + si_uid: uid_t, /* [PSX] real user ID of sending process */ + si_status: c.int, /* [PSX] exit value or signal */ + _utime: clock_t, + _stime: clock_t, + }, + using _fault: struct { + si_addr: rawptr, /* [PSX] address of faulting instruction */ + _trap: c.int, + _trap2: c.int, + _trap3: c.int, + }, + using _poll: struct { + si_band: c.long, /* [PSX] band event for SIGPOLL */ + _fd: FD, + }, + _syscall: struct { + _sysnum: c.int, + _retval: [2]c.int, + _error: c.int, + _args: [8]c.uint64_t, + }, + _ptrace_state: struct { + _pe_report_event: c.int, + _option: struct #raw_union { + _pe_other_pid: pid_t, + _pe_lwp: lwpid_t, + }, + }, + }, + }, + } + + ILL_ILLOPC :: 1 + ILL_ILLOPN :: 2 + ILL_ILLADR :: 3 + ILL_ILLTRP :: 4 + ILL_PRVOPC :: 5 + ILL_PRVREG :: 6 + ILL_COPROC :: 7 + ILL_BADSTK :: 8 + + FPE_INTDIV :: 1 + FPE_INTOVF :: 2 + FPE_FLTDIV :: 3 + FPE_FLTOVF :: 4 + FPE_FLTUND :: 5 + FPE_FLTRES :: 6 + FPE_FLTINV :: 7 + FPE_FLTSUB :: 8 + + SEGV_MAPERR :: 1 + SEGV_ACCERR :: 2 + + BUS_ADRALN :: 1 + BUS_ADRERR :: 2 + BUS_OBJERR :: 3 + + TRAP_BRKPT :: 1 + TRAP_TRACE :: 2 + + CLD_EXITED :: 1 + CLD_KILLED :: 2 + CLD_DUMPED :: 3 + CLD_TRAPPED :: 4 + CLD_STOPPED :: 5 + CLD_CONTINUED :: 6 + + POLL_IN :: 1 + POLL_OUT :: 2 + POLL_MSG :: 3 + POLL_ERR :: 4 + POLL_PRI :: 5 + POLL_HUP :: 6 + + SI_USER :: 0 + SI_QUEUE :: -1 + SI_TIMER :: -2 + SI_ASYNCIO :: -3 + SI_MESGQ :: -4 + +} else when ODIN_OS == .OpenBSD { + + // Request that signal be held + SIG_HOLD :: rawptr(uintptr(3)) + + uid_t :: distinct c.uint32_t + sigset_t :: distinct c.uint32_t + + SIGHUP :: 1 + SIGQUIT :: 3 + SIGTRAP :: 5 + SIGPOLL :: 7 + SIGKILL :: 9 + SIGBUS :: 10 + SIGSYS :: 12 + SIGPIPE :: 13 + SIGALRM :: 14 + SIGURG :: 16 + SIGCONT :: 19 + SIGSTOP :: 17 + SIGTSTP :: 18 + SIGCHLD :: 20 + SIGTTIN :: 21 + SIGTTOU :: 22 + SIGXCPU :: 24 + SIGXFSZ :: 25 + SIGVTALRM :: 26 + SIGPROF :: 27 + SIGUSR1 :: 30 + SIGUSR2 :: 31 + + // NOTE: this is actually defined as `sigaction`, but due to the function with the same name + // `_t` has been added. + + sigaction_t :: struct { + using _: struct #raw_union { + sa_handler: proc "c" (Signal), /* [PSX] signal-catching function or one of the SIG_IGN or SIG_DFL */ + sa_sigaction: proc "c" (Signal, ^siginfo_t, rawptr), /* [PSX] signal-catching function */ + }, + sa_mask: sigset_t, /* [PSX] set of signals to be blocked during execution of the signal handling function */ + sa_flags: SA_Flags, /* [PSX] special flags */ + } + + SIG_BLOCK :: 1 + SIG_UNBLOCK :: 2 + SIG_SETMASK :: 3 + + SA_NOCLDSTOP :: 0x0008 + SA_ONSTACK :: 0x0001 + SA_RESETHAND :: 0x0004 + SA_RESTART :: 0x0002 + SA_SIGINFO :: 0x0040 + SA_NOCLDWAIT :: 0x0020 + SA_NODEFER :: 0x0010 + + SS_ONSTACK :: 0x0001 + SS_DISABLE :: 0x0004 + + MINSIGSTKSZ :: 3 << 12 + SIGSTKSZ :: MINSIGSTKSZ + (1 << 12) * 4 + + stack_t :: struct { + ss_sp: rawptr, /* [PSX] stack base or pointer */ + ss_size: c.size_t, /* [PSX] stack size */ + ss_flags: SS_Flags, /* [PSX] flags */ + } + + SI_MAXSZ :: 128 + SI_PAD :: (SI_MAXSZ / size_of(c.int)) - 3 + + siginfo_t :: struct { + si_signo: Signal, /* [PSX] signal number */ + si_code: struct #raw_union { /* [PSX] specific more detailed codes per signal */ + ill: ILL_Code, + fpe: FPE_Code, + segv: SEGV_Code, + bus: BUS_Code, + trap: TRAP_Code, + chld: CLD_Code, + poll: POLL_Code, + any: Any_Code, + }, + si_errno: Errno, /* [PSX] errno value associated with this signal */ + using _data: struct #raw_union { + _pad: [SI_PAD]c.int, + using _proc: struct { + si_pid: pid_t, /* [PSX] sending process ID */ + si_uid: uid_t, /* [PSX] real user ID of sending process */ + using _pdata: struct #raw_union { + using _kill: struct { + si_value: sigval, + }, + using _cld: struct { + _utime: clock_t, + _stime: clock_t, + si_status: c.int, + }, + }, + }, + using _fault: struct { + si_addr: rawptr, + _trapno: c.int, + }, + using _file: struct { + _fd: FD, + si_band: c.long, /* [PSX] band event for SIGPOLL */ + }, + }, + } + + ILL_ILLOPC :: 1 + ILL_ILLOPN :: 2 + ILL_ILLADR :: 3 + ILL_ILLTRP :: 4 + ILL_PRVOPC :: 5 + ILL_PRVREG :: 6 + ILL_COPROC :: 7 + ILL_BADSTK :: 8 + + FPE_INTDIV :: 1 + FPE_INTOVF :: 2 + FPE_FLTDIV :: 3 + FPE_FLTOVF :: 4 + FPE_FLTUND :: 5 + FPE_FLTRES :: 6 + FPE_FLTINV :: 7 + FPE_FLTSUB :: 8 + + SEGV_MAPERR :: 1 + SEGV_ACCERR :: 2 + + BUS_ADRALN :: 1 + BUS_ADRERR :: 2 + BUS_OBJERR :: 3 + + TRAP_BRKPT :: 1 + TRAP_TRACE :: 2 + + CLD_EXITED :: 1 + CLD_KILLED :: 2 + CLD_DUMPED :: 3 + CLD_TRAPPED :: 4 + CLD_STOPPED :: 5 + CLD_CONTINUED :: 6 + + POLL_IN :: 1 + POLL_OUT :: 2 + POLL_MSG :: 3 + POLL_ERR :: 4 + POLL_PRI :: 5 + POLL_HUP :: 6 + + SI_USER :: 0 + SI_QUEUE :: -2 + SI_TIMER :: -3 + SI_ASYNCIO :: -4 // NOTE: not implemented + SI_MESGQ :: -5 // NOTE: not implemented + +} else when ODIN_OS == .Linux { + + // Request that signal be held + SIG_HOLD :: rawptr(uintptr(2)) + + uid_t :: distinct c.uint32_t + sigset_t :: struct { + __val: [1024/(8 * size_of(c.ulong))]c.ulong, + } + + SIGHUP :: 1 + SIGQUIT :: 3 + SIGTRAP :: 5 + SIGBUS :: 7 + SIGKILL :: 9 + SIGUSR1 :: 10 + SIGUSR2 :: 12 + SIGPIPE :: 13 + SIGALRM :: 14 + SIGCHLD :: 17 + SIGCONT :: 18 + SIGSTOP :: 19 + SIGTSTP :: 20 + SIGTTIN :: 21 + SIGTTOU :: 22 + SIGURG :: 23 + SIGXCPU :: 24 + SIGXFSZ :: 25 + SIGVTALRM :: 26 + SIGPROF :: 27 + SIGPOLL :: 29 + SIGSYS :: 31 + + // NOTE: this is actually defined as `sigaction`, but due to the function with the same name + // `_t` has been added. + + sigaction_t :: struct { + using _: struct #raw_union { + sa_handler: proc "c" (Signal), /* [PSX] signal-catching function or one of the SIG_IGN or SIG_DFL */ + sa_sigaction: proc "c" (Signal, ^siginfo_t, rawptr), /* [PSX] signal-catching function */ + }, + sa_mask: sigset_t, /* [PSX] set of signals to be blocked during execution of the signal handling function */ + sa_flags: SA_Flags, /* [PSX] special flags */ + sa_restorer: proc "c" (), + } + + SIG_BLOCK :: 0 + SIG_UNBLOCK :: 1 + SIG_SETMASK :: 2 + + SA_NOCLDSTOP :: 1 + SA_NOCLDWAIT :: 2 + SA_SIGINFO :: 4 + SA_ONSTACK :: 0x08000000 + SA_RESTART :: 0x10000000 + SA_NODEFER :: 0x40000000 + SA_RESETHAND :: 0x80000000 + + SS_ONSTACK :: 1 + SS_DISABLE :: 2 + + when ODIN_ARCH == .arm64 { + MINSIGSTKSZ :: 6144 + SIGSTKSZ :: 12288 + } else { + MINSIGSTKSZ :: 2048 + SIGSTKSZ :: 8192 + } + + stack_t :: struct { + ss_sp: rawptr, /* [PSX] stack base or pointer */ + ss_flags: SS_Flags, /* [PSX] flags */ + ss_size: c.size_t, /* [PSX] stack size */ + } + + @(private) + __SI_MAX_SIZE :: 128 + + when size_of(int) == 8 { + @(private) + _pad0 :: struct { + _pad0: c.int, + } + @(private) + __SI_PAD_SIZE :: (__SI_MAX_SIZE / size_of(c.int)) - 4 + + } else { + @(private) + _pad0 :: struct {} + @(private) + __SI_PAD_SIZE :: (__SI_MAX_SIZE / size_of(c.int)) - 3 + } + + siginfo_t :: struct #align(8) { + si_signo: Signal, /* [PSX] signal number */ + si_errno: Errno, /* [PSX] errno value associated with this signal */ + si_code: struct #raw_union { /* [PSX] specific more detailed codes per signal */ + ill: ILL_Code, + fpe: FPE_Code, + segv: SEGV_Code, + bus: BUS_Code, + trap: TRAP_Code, + chld: CLD_Code, + poll: POLL_Code, + any: Any_Code, + }, + __pad0: _pad0, + using _sifields: struct #raw_union { + _pad: [__SI_PAD_SIZE]c.int, + + using _: struct { + si_pid: pid_t, /* [PSX] sending process ID */ + si_uid: uid_t, /* [PSX] real user ID of sending process */ + using _: struct #raw_union { + si_status: c.int, /* [PSX] exit value or signal */ + si_value: sigval, /* [PSX] signal value */ + }, + }, + using _: struct { + si_addr: rawptr, /* [PSX] address of faulting instruction */ + }, + using _: struct { + si_band: c.long, /* [PSX] band event for SIGPOLL */ + }, + }, + } + + ILL_ILLOPC :: 1 + ILL_ILLOPN :: 2 + ILL_ILLADR :: 3 + ILL_ILLTRP :: 4 + ILL_PRVOPC :: 5 + ILL_PRVREG :: 6 + ILL_COPROC :: 7 + ILL_BADSTK :: 8 + + FPE_INTDIV :: 1 + FPE_INTOVF :: 2 + FPE_FLTDIV :: 3 + FPE_FLTOVF :: 4 + FPE_FLTUND :: 5 + FPE_FLTRES :: 6 + FPE_FLTINV :: 7 + FPE_FLTSUB :: 8 + + SEGV_MAPERR :: 1 + SEGV_ACCERR :: 2 + + BUS_ADRALN :: 1 + BUS_ADRERR :: 2 + BUS_OBJERR :: 3 + + TRAP_BRKPT :: 1 + TRAP_TRACE :: 2 + + CLD_EXITED :: 1 + CLD_KILLED :: 2 + CLD_DUMPED :: 3 + CLD_TRAPPED :: 4 + CLD_STOPPED :: 5 + CLD_CONTINUED :: 6 + + POLL_IN :: 1 + POLL_OUT :: 2 + POLL_MSG :: 3 + POLL_ERR :: 4 + POLL_PRI :: 5 + POLL_HUP :: 6 + + SI_USER :: 0 + SI_QUEUE :: -1 + SI_TIMER :: -2 + SI_MESGQ :: -3 + SI_ASYNCIO :: -4 +} diff --git a/core/sys/posix/signal_libc.odin b/core/sys/posix/signal_libc.odin new file mode 100644 index 000000000..aef22da29 --- /dev/null +++ b/core/sys/posix/signal_libc.odin @@ -0,0 +1,145 @@ +#+build linux, windows, darwin, netbsd, openbsd, freebsd +package posix + +import "base:intrinsics" + +import "core:c" +import "core:c/libc" + +when ODIN_OS == .Windows { + foreign import lib "system:libucrt.lib" +} else when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// signal.h - signals + +foreign lib { + /* + Set a signal handler. + + func can either be: + - `auto_cast posix.SIG_DFL` setting the default handler for that specific signal + - `auto_cast posix.SIG_IGN` causing the specific signal to be ignored + - a custom signal handler + + Returns: SIG_ERR (setting errno), the last value of func on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/signal.html ]] + */ + signal :: proc(sig: Signal, func: proc "c" (Signal)) -> proc "c" (Signal) --- + + /* + Raises a signal, calling its handler and then returning. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/raise.html ]] + */ + raise :: proc(sig: Signal) -> result --- +} + +Signal :: enum c.int { + NONE, + + // LIBC: + + // Process abort signal. + SIGABRT = SIGABRT, + // Erronous arithemtic operation. + SIGFPE = SIGFPE, + // Illegal instruction. + SIGILL = SIGILL, + // Terminal interrupt signal. + SIGINT = SIGINT, + // Invalid memory reference. + SIGSEGV = SIGSEGV, + // Termination signal. + SIGTERM = SIGTERM, + + // POSIX: + + // Process abort signal. + SIGALRM = SIGALRM, + // Access to an undefined portion of a memory object. + SIGBUS = SIGBUS, + // Child process terminated, stopped, or continued. + SIGCHLD = SIGCHLD, + // Continue execution, if stopped. + SIGCONT = SIGCONT, + // Hangup. + SIGHUP = SIGHUP, + // Kill (cannot be caught or ignored). + SIGKILL = SIGKILL, + // Write on a pipe with no one to read it. + SIGPIPE = SIGPIPE, + // Terminal quit signal. + SIGQUIT = SIGQUIT, + // Stop executing (cannot be caught or ignored). + SIGSTOP = SIGSTOP, + // Terminal stop process. + SIGTSTP = SIGTSTP, + // Background process attempting read. + SIGTTIN = SIGTTIN, + // Background process attempting write. + SIGTTOU = SIGTTOU, + // User-defined signal 1. + SIGUSR1 = SIGUSR1, + // User-defined signal 2. + SIGUSR2 = SIGUSR2, + // Pollable event. + SIGPOLL = SIGPOLL, + // Profiling timer expired. + SIGPROF = SIGPROF, + // Bad system call. + SIGSYS = SIGSYS, + // Trace/breakpoint trap. + SIGTRAP = SIGTRAP, + // High bandwidth data is available at a socket. + SIGURG = SIGURG, + // Virtual timer expired. + SIGVTALRM = SIGVTALRM, + // CPU time limit exceeded. + SIGXCPU = SIGXCPU, + // File size limit exceeded. + SIGXFSZ = SIGXFSZ, +} + +// Request for default signal handling. +SIG_DFL :: libc.SIG_DFL +// Return value from signal() in case of error. +SIG_ERR :: libc.SIG_ERR +// Request that signal be ignored. +SIG_IGN :: libc.SIG_IGN + +SIGABRT :: libc.SIGABRT +SIGFPE :: libc.SIGFPE +SIGILL :: libc.SIGILL +SIGINT :: libc.SIGINT +SIGSEGV :: libc.SIGSEGV +SIGTERM :: libc.SIGTERM + +when ODIN_OS == .Windows { + SIGALRM :: -1 + SIGBUS :: -1 + SIGCHLD :: -1 + SIGCONT :: -1 + SIGHUP :: -1 + SIGKILL :: -1 + SIGPIPE :: -1 + SIGQUIT :: -1 + SIGSTOP :: -1 + SIGTSTP :: -1 + SIGTTIN :: -1 + SIGTTOU :: -1 + SIGUSR1 :: -1 + SIGUSR2 :: -1 + SIGPOLL :: -1 + SIGPROF :: -1 + SIGSYS :: -1 + SIGTRAP :: -1 + SIGURG :: -1 + SIGVTALRM :: -1 + SIGXCPU :: -1 + SIGXFSZ :: -1 +} diff --git a/core/sys/posix/stdio.odin b/core/sys/posix/stdio.odin new file mode 100644 index 000000000..24464dfd8 --- /dev/null +++ b/core/sys/posix/stdio.odin @@ -0,0 +1,175 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// stdio.h - standard buffered input/output + +foreign lib { + /* + Generates a string that, when used as a pathname, + refers to the current controlling terminal for the current process. + + If s is nil, the returned string might be static and overwritten by subsequent calls or other factors. + If s is not nil, s is assumed len(s) >= L_ctermid. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ctermid.html ]] + */ + ctermid :: proc(s: [^]byte) -> cstring --- + + /* + Equivalent to fprintf but output is written to the file descriptor. + + Return: number of bytes written, negative (setting errno) on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dprintf.html ]] + */ + dprintf :: proc(fildse: FD, format: cstring, #c_vararg args: ..any) -> c.int --- + + /* + Associate a stream with a file descriptor. + + Returns: nil (setting errno) on failure, the stream on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fdopen.html ]] + */ + fdopen :: proc(fildes: FD, mode: cstring) -> ^FILE --- + + /* + Map a stream pointer to a file descriptor. + + Returns: the file descriptor or -1 (setting errno) on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fileno.html ]] + */ + fileno :: proc(stream: ^FILE) -> FD --- + + /* + Locks a file. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/flockfile.html ]] + */ + flockfile :: proc(file: ^FILE) --- + + /* + Tries to lock a file. + + Returns: 0 if it could be locked, non-zero if it couldn't + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/flockfile.html ]] + */ + ftrylockfile :: proc(file: ^FILE) -> c.int --- + + /* + Unlocks a file. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/flockfile.html ]] + */ + funlockfile :: proc(file: ^FILE) --- + + /* + Open a memory buffer stream. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fmemopen.html ]] + */ + fmemopen :: proc(buf: [^]byte, size: c.size_t, mode: cstring) -> ^FILE --- + + /* + Reposition a file-position indicator in a stream. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fseeko.html ]] + */ + fseeko :: proc(stream: ^FILE, offset: off_t, whence: Whence) -> result --- + + /* + Return the file offset in a stream. + + Returns: the current file offset, -1 (setting errno) on error + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ftello.html ]] + */ + ftello :: proc(^FILE) -> off_t --- + + /* + Open a dynamic memory buffer stream. + + Returns: nil (setting errno) on failure, the stream on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/open_memstream.html ]] + */ + open_memstream :: proc(bufp: ^[^]byte, sizep: ^c.size_t) -> ^FILE --- + + /* + Read a delimited record from the stream. + + Returns: the number of bytes written or -1 on failure/EOF + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getdelim.html ]] + */ + getdelim :: proc(lineptr: ^cstring, n: ^c.size_t, delimiter: c.int, stream: ^FILE) -> c.ssize_t --- + + /* + Read a line delimited record from the stream. + + Returns: the number of bytes written or -1 on failure/EOF + + Example: + fp := posix.fopen(#file, "r") + if fp == nil { + posix.exit(1) + } + + line: cstring + length: uint + for { + read := posix.getline(&line, &length, fp) + if read == -1 do break + posix.printf("Retrieved line of length %zu :\n", read) + posix.printf("%s", line) + } + if posix.ferror(fp) != 0 { + /* handle error */ + } + posix.free(rawptr(line)) + posix.fclose(fp) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getdelim.html ]] + */ + getline :: proc(lineptr: ^cstring, n: ^c.size_t, stream: ^FILE) -> c.ssize_t --- + + /* + Equivalent to rename but relative directories are resolved from their respective fds. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/renameat.html ]] + */ + renameat :: proc(oldfd: FD, old: cstring, newfd: FD, new: cstring) -> result --- +} + +when ODIN_OS == .Darwin { + + L_ctermid :: 1024 + L_tmpnam :: 1024 + + P_tmpdir :: "/var/tmp/" + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + L_ctermid :: 1024 + L_tmpnam :: 1024 + + P_tmpdir :: "/tmp/" + +} else when ODIN_OS == .Linux { + + L_ctermid :: 20 // 20 on musl, 9 on glibc + L_tmpnam :: 20 + + P_tmpdir :: "/tmp/" + +} diff --git a/core/sys/posix/stdio_libc.odin b/core/sys/posix/stdio_libc.odin new file mode 100644 index 000000000..fbd949b2c --- /dev/null +++ b/core/sys/posix/stdio_libc.odin @@ -0,0 +1,207 @@ +#+build linux, windows, linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" +import "core:c/libc" + +when ODIN_OS == .Windows { + foreign import lib { + "system:libucrt.lib", + "system:legacy_stdio_definitions.lib", + } +} else when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// stdio.h - standard buffered input/output + +when ODIN_OS == .Windows { + @(private) LGETC_UNLOCKED :: "_getc_nolock" + @(private) LGETCHAR_UNLOCKED :: "_getchar_nolock" + @(private) LPUTC_UNLOCKED :: "_putc_nolock" + @(private) LPUTCHAR_UNLOCKED :: "_putchar_nolock" + @(private) LTEMPNAM :: "_tempnam" + @(private) LPOPEN :: "_popen" + @(private) LPCLOSE :: "_pclose" +} else { + @(private) LGETC_UNLOCKED :: "getc_unlocked" + @(private) LGETCHAR_UNLOCKED :: "getchar_unlocked" + @(private) LPUTC_UNLOCKED :: "putc_unlocked" + @(private) LPUTCHAR_UNLOCKED :: "putchar_unlocked" + @(private) LTEMPNAM :: "tempnam" + @(private) LPOPEN :: "popen" + @(private) LPCLOSE :: "pclose" +} + +foreign lib { + /* + Equivalent to fprintf but output is written to s, it is the user's responsibility to + ensure there is enough space. + + Return: number of bytes written, negative (setting errno) on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dprintf.html ]] + */ + sprintf :: proc(s: [^]byte, format: cstring, #c_vararg args: ..any) -> c.int --- + + /* + Equivalent to getc but unaffected by locks. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getc_unlocked.html ]] + */ + @(link_name=LGETC_UNLOCKED) + getc_unlocked :: proc(stream: ^FILE) -> c.int --- + + /* + Equivalent to getchar but unaffected by locks. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getc_unlocked.html ]] + */ + @(link_name=LGETCHAR_UNLOCKED) + getchar_unlocked :: proc() -> c.int --- + + /* + Equivalent to putc but unaffected by locks. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getc_unlocked.html ]] + */ + @(link_name=LPUTC_UNLOCKED) + putc_unlocked :: proc(ch: c.int, stream: ^FILE) -> c.int --- + + /* + Equivalent to putchar but unaffected by locks. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getc_unlocked.html ]] + */ + @(link_name=LPUTCHAR_UNLOCKED) + putchar_unlocked :: proc(ch: c.int) -> c.int --- + + /* + Get a string from the stdin stream. + + It is up to the user to make sure s is big enough. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/gets.html ]] + */ + gets :: proc(s: [^]byte) -> cstring --- + + /* + Create a name for a temporary file. + + Returns: an allocated cstring that needs to be freed, nil on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tempnam.html ]] + */ + @(link_name=LTEMPNAM) + tempnam :: proc(dir: cstring, pfx: cstring) -> cstring --- + + /* + Executes the command specified, creating a pipe and returning a pointer to a stream that can + read or write from/to the pipe. + + Returns: nil (setting errno) on failure or a pointer to the stream + + Example: + fp := posix.popen("ls *", "r") + if fp == nil { + /* Handle error */ + } + + path: [1024]byte + for posix.fgets(raw_data(path[:]), len(path), fp) != nil { + posix.printf("%s", &path) + } + + status := posix.pclose(fp) + if status == -1 { + /* Error reported by pclose() */ + } else { + /* Use functions described under wait() to inspect `status` in order + to determine success/failure of the command executed by popen() */ + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/popen.html ]] + */ + @(link_name=LPOPEN) + popen :: proc(command: cstring, mode: cstring) -> ^FILE --- + + /* + Closes a pipe stream to or from a process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pclose.html ]] + */ + @(link_name=LPCLOSE) + pclose :: proc(stream: ^FILE) -> c.int --- +} + +clearerr :: libc.clearerr +fclose :: libc.fclose +feof :: libc.feof +ferror :: libc.ferror +fflush :: libc.fflush +fgetc :: libc.fgetc +fgetpos :: libc.fgetpos +fgets :: libc.fgets +fopen :: libc.fopen +fprintf :: libc.fprintf +fputc :: libc.fputc +fread :: libc.fread +freopen :: libc.freopen +fscanf :: libc.fscanf +fseek :: libc.fseek +fsetpos :: libc.fsetpos +ftell :: libc.ftell +fwrite :: libc.fwrite +getc :: libc.getc +getchar :: libc.getchar +perror :: libc.perror +printf :: libc.printf +putc :: libc.puts +putchar :: libc.putchar +puts :: libc.puts +remove :: libc.remove +rename :: libc.rename +rewind :: libc.rewind +scanf :: libc.scanf +setbuf :: libc.setbuf +setvbuf :: libc.setvbuf +snprintf :: libc.snprintf +sscanf :: libc.sscanf +tmpfile :: libc.tmpfile +tmpnam :: libc.tmpnam +vfprintf :: libc.vfprintf +vfscanf :: libc.vfscanf +vprintf :: libc.vprintf +vscanf :: libc.vscanf +vsnprintf :: libc.vsnprintf +vsprintf :: libc.vsprintf +vsscanf :: libc.vsscanf +ungetc :: libc.ungetc + +to_stream :: libc.to_stream + +Whence :: libc.Whence +FILE :: libc.FILE +fpos_t :: libc.fpos_t + +BUFSIZ :: libc.BUFSIZ + +_IOFBF :: libc._IOFBF +_IOLBF :: libc._IOLBF +_IONBF :: libc._IONBF + +SEEK_CUR :: libc.SEEK_CUR +SEEK_END :: libc.SEEK_END +SEEK_SET :: libc.SEEK_SET + +FILENAME_MAX :: libc.FILENAME_MAX +FOPEN_MAX :: libc.FOPEN_MAX +TMP_MAX :: libc.TMP_MAX + +EOF :: libc.EOF + +stderr := libc.stderr +stdin := libc.stdin +stdout := libc.stdout diff --git a/core/sys/posix/stdlib.odin b/core/sys/posix/stdlib.odin new file mode 100644 index 000000000..640c70b5a --- /dev/null +++ b/core/sys/posix/stdlib.odin @@ -0,0 +1,373 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "base:intrinsics" + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +foreign lib { + /* + Takes a pointer to a radix-64 representation, in which the first digit is the least significant, + and return the corresponding long value. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/a64l.html ]] + */ + a64l :: proc(s: cstring) -> c.long --- + + /* + The l64a() function shall take a long argument and return a pointer to the corresponding + radix-64 representation. + + Returns: a string that may be invalidated by subsequent calls + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/a64l.html ]] + */ + l64a :: proc(value: c.long) -> cstring --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + Returns: non-negative, double-precision, floating-point values, uniformly distributed over the interval [0.0,1.0) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + drand48 :: proc() -> c.double --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + Returns: non-negative, double-precision, floating-point values, uniformly distributed over the interval [0.0,1.0) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + erand48 :: proc(xsubi: ^[3]c.ushort) -> c.double --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + Returns: return signed long integers uniformly distributed over the interval [-231,231) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + mrand48 :: proc() -> c.long --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + Returns: return signed long integers uniformly distributed over the interval [-231,231) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + jrand48 :: proc(xsubi: ^[3]c.ushort) -> c.long --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + Returns: non-negative, long integers, uniformly distributed over the interval [0,231) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + lrand48 :: proc() -> c.long --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + Returns: non-negative, long integers, uniformly distributed over the interval [0,231) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + nrand48 :: proc(xsubi: ^[3]c.ushort) -> c.long --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + The srand48(), seed48(), and lcong48() functions are initialization entry points, one of which should be invoked before either drand48(), lrand48(), or mrand48() is called. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + srand48 :: proc(seedval: c.long) --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + The srand48(), seed48(), and lcong48() functions are initialization entry points, one of which should be invoked before either drand48(), lrand48(), or mrand48() is called. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + lcong48 :: proc(param: ^[7]c.ushort) --- + + /* + This family of functions shall generate pseudo-random numbers using a linear congruential algorithm and 48-bit integer arithmetic. + + The srand48(), seed48(), and lcong48() functions are initialization entry points, one of which should be invoked before either drand48(), lrand48(), or mrand48() is called. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/drand48.html ]] + */ + seed48 :: proc(seed16v: ^[3]c.ushort) -> ^[3]c.ushort --- + + /* + Parses suboption arguments in a flag argument. + + Returns: the index of the matched token string, or -1 if no token strings were matched + + Example: + args := runtime.args__ + + Opt :: enum { + RO, + RW, + NAME, + NIL, + } + token := [Opt]cstring{ + .RO = "ro", + .RW = "rw", + .NAME = "name", + .NIL = nil, + } + + Options :: struct { + readonly, readwrite: bool, + name: cstring, + + } + opts: Options + + errfnd: bool + for { + opt := posix.getopt(i32(len(args)), raw_data(args), "o:") + if opt == -1 { + break + } + + switch opt { + case 'o': + subopt := posix.optarg + value: cstring + for subopt != "" && !errfnd { + o := posix.getsubopt(&subopt, &token[.RO], &value) + switch Opt(o) { + case .RO: opts.readonly = true + case .RW: opts.readwrite = true + case .NAME: + if value == nil { + fmt.eprintfln("missing value for suboption %s", token[.NAME]) + errfnd = true + continue + } + + opts.name = value + case .NIL: + fallthrough + case: + fmt.eprintfln("no match found for token: %s", value) + errfnd = true + } + } + if opts.readwrite && opts.readonly { + fmt.eprintfln("Only one of %s and %s can be specified", token[.RO], token[.RW]) + errfnd = true + } + case: + errfnd = true + } + } + + if errfnd || len(args) == 1 { + fmt.eprintfln("\nUsage: %s -o ", args[0]) + fmt.eprintfln("suboptions are 'ro', 'rw', and 'name='") + posix.exit(1) + } + + fmt.println(opts) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsubopt.html ]] + */ + getsubopt :: proc(optionp: ^cstring, keylistp: [^]cstring, valuep: ^cstring) -> c.int --- + + /* + Changes the mode and ownership of the slave pseudo-terminal device associated with its master pseudo-terminal counterpart. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/grantpt.html ]] + */ + grantpt :: proc(fildes: FD) -> result --- + + /* + Allows a state array, pointed to by the state argument, to be initialized for future use. + + Returns: the previous state array or nil on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/initstate.html ]] + */ + @(link_name=LINITSTATE) + initstate :: proc(seed: c.uint, state: [^]byte, size: c.size_t) -> [^]byte --- + + /* + Sets the state array of the random number generator. + + Returns: the previous state array or nil on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/initstate.html ]] + */ + setstate :: proc(state: [^]byte) -> [^]byte --- + + /* + Use a non-linear additive feedback random-number generator employing a default state array + size of 31 long integers to return successive pseudo-random numbers in the range from 0 to 231-1. + The period of this random-number generator is approximately 16 x (231-1). + The size of the state array determines the period of the random-number generator. + Increasing the state array size shall increase the period. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/initstate.html ]] + */ + random :: proc() -> c.long --- + + /* + Initializes the current state array using the value of seed. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/initstate.html ]] + */ + @(link_name=LSRANDOM) + srandom :: proc(seed: c.uint) --- + + /* + Creates a directory with a unique name derived from template. + The application shall ensure that the string provided in template is a pathname ending + with at least six trailing 'X' characters. + + Returns: nil (setting errno) on failure, template on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdtemp.html ]] + */ + mkdtemp :: proc(template: [^]byte) -> cstring --- + + /* + Creates a regular file with a unique name derived from template and return a file descriptor + for the file open for reading and writing. + The application shall ensure that the string provided in template is a pathname ending with + at least six trailing 'X' characters. + + Returns: -1 (setting errno) on failure, an open file descriptor on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdtemp.html ]] + */ + mkstemp :: proc(template: cstring) -> FD --- + + /* + Allocates size bytes aligned on a boundary specified by alignment, and shall return a pointer + to the allocated memory in memptr. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_memalign.html ]] + */ + posix_memalign :: proc(memptr: ^[^]byte, alignment: c.size_t, size: c.size_t) -> Errno --- + + /* + Establishes a connection between a master device for a pseudo-terminal and a file descriptor. + + Returns: -1 (setting errno) on failure, an open file descriptor otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_openpt.html ]] + */ + posix_openpt :: proc(oflag: O_Flags) -> FD --- + + /* + Returns the name of the slave pseudo-terminal device associated with a master pseudo-terminal device. + + Returns: nil (setting errno) on failure, the name on success, which may be invalidated on subsequent calls + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ptsname.html ]] + */ + ptsname :: proc(fildes: FD) -> cstring --- + + /* + Unlocks the slave pseudo-terminal device associated with the master to which fildes refers. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlockpt.html ]] + */ + unlockpt :: proc(fildes: FD) -> result --- + + /* + Updates or add a variable in the environment of the calling process. + + Example: + if posix.setenv("HOME", "/usr/home") != .OK { + fmt.panicf("putenv failure: %v", posix.strerror(posix.errno())) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setenv.html ]] + */ + setenv :: proc(envname: cstring, envval: cstring, overwrite: b32) -> result --- + + /* + Removes an environment variable from the environment of the calling process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/unsetenv.html ]] + */ + @(link_name=LUNSETENV) + unsetenv :: proc(name: cstring) -> result --- + + /* + Computes a sequence of pseudo-random integers in the range [0, {RAND_MAX}]. + (The value of the {RAND_MAX} macro shall be at least 32767.) + + If rand_r() is called with the same initial value for the object pointed to by seed and that object is not modified between successive returns and calls to rand_r(), the same sequence shall be generated. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/rand_r.html ]] + */ + rand_r :: proc(seed: ^c.uint) -> c.int --- + + /* + Derive, from the pathname file_name, an absolute pathname that resolves to the same directory entry, + whose resolution does not involve '.', '..', or symbolic links. + + If resolved_name is not `nil` it should be larger than `PATH_MAX` and the result will use it as a backing buffer. + If resolved_name is `nil` the returned string is allocated by `malloc`. + + Returns: `nil` (setting errno) on failure, the "real path" otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/realpath.html ]] + */ + realpath :: proc(file_name: cstring, resolved_name: [^]byte = nil) -> cstring --- + + /* + Provides access to an implementation-defined encoding algorithm. + The argument of setkey() is an array of length 64 bytes containing only the bytes with numerical + value of 0 and 1. + + If this string is divided into groups of 8, the low-order bit in each group is ignored; this gives a 56-bit key which is used by the algorithm. + This is the key that shall be used with the algorithm to encode a string block passed to encrypt(). + + The setkey() function shall not change the setting of errno if successful. + An application wishing to check for error situations should set errno to 0 before calling setkey(). + If errno is non-zero on return, an error has occurred. + + Example: + key: [64]byte + // set key bytes... + + posix.set_errno(.NONE) + posix.setkey(raw_data(key)) + if errno := posix.errno(); errno != .NONE { + fmt.panicf("setkey failure: %s", posix.strerror(errno)) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setkey.html ]] + */ + setkey :: proc(key: [^]byte) --- +} + +when ODIN_OS == .NetBSD { + @(private) LINITSTATE :: "__initstate60" + @(private) LSRANDOM :: "__srandom60" + @(private) LUNSETENV :: "__unsetenv13" +} else { + @(private) LINITSTATE :: "initstate" + @(private) LSRANDOM :: "srandom" + @(private) LUNSETENV :: "unsetenv" +} diff --git a/core/sys/posix/stdlib_libc.odin b/core/sys/posix/stdlib_libc.odin new file mode 100644 index 000000000..fa4d925b2 --- /dev/null +++ b/core/sys/posix/stdlib_libc.odin @@ -0,0 +1,101 @@ +#+build linux, windows, darwin, netbsd, openbsd, freebsd +package posix + +import "base:intrinsics" + +import "core:c" +import "core:c/libc" + +when ODIN_OS == .Windows { + foreign import lib "system:libucrt.lib" +} else when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// stdlib.h - standard library definitions + +atof :: libc.atof +atoi :: libc.atoi +atol :: libc.atol +atoll :: libc.atoll +strtod :: libc.strtod +strtof :: libc.strtof +strtol :: libc.strtol +strtoll :: libc.strtoll +strtoul :: libc.strtoul +strtoull :: libc.strtoull + +rand :: libc.rand +srand :: libc.srand + +calloc :: libc.calloc +malloc :: libc.malloc +realloc :: libc.realloc + +abort :: libc.abort +atexit :: libc.atexit +at_quick_exit :: libc.at_quick_exit +exit :: libc.exit +_Exit :: libc._Exit +getenv :: libc.getenv +quick_exit :: libc.quick_exit +system :: libc.system + +bsearch :: libc.bsearch +qsort :: libc.qsort + +abs :: libc.abs +labs :: libc.labs +llabs :: libc.llabs +div :: libc.div +ldiv :: libc.ldiv +lldiv :: libc.lldiv + +mblen :: libc.mblen +mbtowc :: libc.mbtowc +wctomb :: libc.wctomb + +mbstowcs :: libc.mbstowcs +wcstombs :: libc.wcstombs + +free :: #force_inline proc(ptr: $T) where intrinsics.type_is_pointer(T) || intrinsics.type_is_multi_pointer(T) || T == cstring { + libc.free(rawptr(ptr)) +} + +foreign lib { + + /* + Uses the string argument to set environment variable values. + + Returns: 0 on success, non-zero (setting errno) on failure + + Example: + if posix.putenv("HOME=/usr/home") != 0 { + fmt.panicf("putenv failure: %v", posix.strerror(posix.errno())) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/putenv.html ]] + */ + @(link_name=LPUTENV) + putenv :: proc(string: cstring) -> c.int --- +} + +EXIT_FAILURE :: libc.EXIT_FAILURE +EXIT_SUCCESS :: libc.EXIT_SUCCESS + +RAND_MAX :: libc.RAND_MAX +MB_CUR_MAX :: libc.MB_CUR_MAX + +div_t :: libc.div_t +ldiv_t :: libc.ldiv_t +lldiv_t :: libc.lldiv_t + +when ODIN_OS == .Windows { + @(private) LPUTENV :: "_putenv" +} else when ODIN_OS == .NetBSD { + @(private) LPUTENV :: "__putenv50" +} else { + @(private) LPUTENV :: "putenv" +} diff --git a/core/sys/posix/string.odin b/core/sys/posix/string.odin new file mode 100644 index 000000000..96b6a9007 --- /dev/null +++ b/core/sys/posix/string.odin @@ -0,0 +1,34 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// string.h - string operations + +// NOTE: most of the symbols in this header are not useful in Odin and have been left out. + +foreign lib { + /* + Map the error number to a locale-dependent error message string and put it in the buffer. + + Returns: ERANGE if the buffer is not big enough + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/strerror_r.html ]] + */ + strerror_r :: proc(errnum: Errno, strerrbuf: [^]byte, buflen: c.size_t) -> Errno --- + + /* + Map the signal number to an implementation-defined string. + + Returns: a string that may be invalidated by subsequent calls + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/strsignal.html ]] + */ + strsignal :: proc(sig: Signal) -> cstring --- +} diff --git a/core/sys/posix/string_libc.odin b/core/sys/posix/string_libc.odin new file mode 100644 index 000000000..336352cbc --- /dev/null +++ b/core/sys/posix/string_libc.odin @@ -0,0 +1,30 @@ +#+build linux, windows, darwin, netbsd, openbsd, freebsd +package posix + +when ODIN_OS == .Windows { + foreign import lib "system:libucrt.lib" +} else when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// string.h - string operations + +// NOTE: most of the symbols in this header are not useful in Odin and have been left out. + +foreign lib { + /* + Map the error number to a locale-dependent error message string. + + Returns: a string that may be invalidated by subsequent calls + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/strerror.html ]] + */ + @(link_name="strerror") + _strerror :: proc(errnum: Errno) -> cstring --- +} + +strerror :: #force_inline proc "contextless" (errnum: Maybe(Errno) = nil) -> cstring { + return _strerror(errnum.? or_else errno()) +} diff --git a/core/sys/posix/sys_ipc.odin b/core/sys/posix/sys_ipc.odin new file mode 100644 index 000000000..0f7ec06c5 --- /dev/null +++ b/core/sys/posix/sys_ipc.odin @@ -0,0 +1,114 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/ipc.h = XSI interprocess communication access structure + +foreign lib { + /* + Generate an IPC key. + + Returns: -1 (setting errno) on failure, the key otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ftok.html ]] + */ + ftok :: proc(path: cstring, id: c.int) -> key_t --- +} + +IPC_Cmd :: enum c.int { + RMID = IPC_RMID, + SET = IPC_SET, + STAT = IPC_STAT, +} + +IPC_Flag_Bits :: enum c.int { + CREAT = log2(IPC_CREAT), + EXCL = log2(IPC_EXCL), + NOWAIT = log2(IPC_NOWAIT), + + MSG_NOERROR = log2(MSG_NOERROR), +} +IPC_Flags :: bit_set[IPC_Flag_Bits; c.int] + +when ODIN_OS == .Darwin { + + key_t :: distinct c.int32_t + + ipc_perm :: struct { + uid: uid_t, /* [PSX] owner's user ID */ + gid: gid_t, /* [PSX] owner's group ID */ + cuid: uid_t, /* [PSX] creator's user ID */ + cgid: gid_t, /* [PSX] creator's group ID */ + mode: mode_t, /* [PSX] read/write perms */ + _seq: c.ushort, + _key: key_t, + } + + IPC_CREAT :: 0o01000 + IPC_EXCL :: 0o02000 + IPC_NOWAIT :: 0o04000 + + IPC_PRIVATE :: key_t(0) + + IPC_RMID :: 0 + IPC_SET :: 1 + IPC_STAT :: 2 + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + key_t :: distinct c.long + + ipc_perm :: struct { + cuid: uid_t, /* [PSX] creator's user ID */ + cgid: gid_t, /* [PSX] creator's group ID */ + uid: uid_t, /* [PSX] owner's user ID */ + gid: gid_t, /* [PSX] owner's group ID */ + mode: mode_t, /* [PSX] read/write perms */ + _seq: c.ushort, + _key: key_t, + } + + IPC_CREAT :: 0o01000 + IPC_EXCL :: 0o02000 + IPC_NOWAIT :: 0o04000 + + IPC_PRIVATE :: key_t(0) + + IPC_RMID :: 0 + IPC_SET :: 1 + IPC_STAT :: 2 + +} else when ODIN_OS == .Linux { + + key_t :: distinct c.int32_t + + ipc_perm :: struct { + __ipc_perm_key: key_t, + uid: uid_t, /* [PSX] owner's user ID */ + gid: gid_t, /* [PSX] owner's group ID */ + cuid: uid_t, /* [PSX] creator's user ID */ + cgid: gid_t, /* [PSX] creator's group ID */ + mode: mode_t, /* [PSX] read/write perms */ + __ipc_perm_seq: c.int, + __pad1: c.long, + __pad2: c.long, + } + + IPC_CREAT :: 0o01000 + IPC_EXCL :: 0o02000 + IPC_NOWAIT :: 0o04000 + + IPC_PRIVATE :: key_t(0) + + IPC_RMID :: 0 + IPC_SET :: 1 + IPC_STAT :: 2 + +} diff --git a/core/sys/posix/sys_mman.odin b/core/sys/posix/sys_mman.odin new file mode 100644 index 000000000..0594672ae --- /dev/null +++ b/core/sys/posix/sys_mman.odin @@ -0,0 +1,233 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// mman.h - memory management declarations + +foreign lib { + /* + Establish a mapping between an address space of a process and a memory object. + + Returns: MAP_FAILED (setting errno) on failure, the address in memory otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mmap.html ]] + */ + mmap :: proc( + addr: rawptr, + len: c.size_t, + prot: Prot_Flags, + flags: Map_Flags, + fd: FD = -1, + off: off_t = 0, + ) -> rawptr --- + + /* + Unmaps pages of memory. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/munmap.html ]] + */ + munmap :: proc(addr: rawptr, len: c.size_t) -> result --- + + /* + Locks a range of the process address space. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mlock.html ]] + */ + mlock :: proc(addr: rawptr, len: c.size_t) -> result --- + + /* + Unlocks a range of the process address space. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mlock.html ]] + */ + munlock :: proc(addr: rawptr, len: c.size_t) -> result --- + + /* + Locks all pages of the process address space. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mlockall.html ]] + */ + mlockall :: proc(flags: Lock_Flags) -> result --- + + /* + Unlocks all pages of the process address space. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mlockall.html ]] + */ + munlockall :: proc() -> result --- + + /* + Set protection of a memory mapping. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mprotect.html ]] + */ + mprotect :: proc(addr: rawptr, len: c.size_t, prot: Prot_Flags) -> result --- + + /* + Write all modified data to permanent storage locations. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/msync.html ]] + */ + @(link_name=LMSYNC) + msync :: proc(addr: rawptr, len: c.size_t, flags: Sync_Flags) -> result --- + + /* + Advise the implementation of expected behavior of the application. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_madvise.html ]] + */ + posix_madvise :: proc(addr: rawptr, len: c.size_t, advice: MAdvice) -> Errno --- + + /* + Open a shared memory object. + + Returns: -1 (setting errno) on failure, an open file descriptor otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_open.html ]] + */ + shm_open :: proc(name: cstring, oflag: O_Flags, mode: mode_t) -> FD --- + + /* + Removes a shared memory object. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_unlink.html ]] + */ + shm_unlink :: proc(name: cstring) -> result --- +} + +#assert(_PROT_NONE == 0) +PROT_NONE :: Prot_Flags{} + +Prot_Flag_Bits :: enum c.int { + // Data can be executed. + EXEC = log2(PROT_EXEC), + // Data can be read. + READ = log2(PROT_READ), + // Data can be written. + WRITE = log2(PROT_WRITE), +} +Prot_Flags :: bit_set[Prot_Flag_Bits; c.int] + +Map_Flag_Bits :: enum c.int { + // Map anonymous memory. + ANONYMOUS = log2(MAP_ANONYMOUS), + // Interpret addr exactly. + FIXED = log2(MAP_FIXED), + // Changes are private. + PRIVATE = log2(MAP_PRIVATE), + // Changes are shared. + SHARED = log2(MAP_SHARED), +} +Map_Flags :: bit_set[Map_Flag_Bits; c.int] + +Lock_Flag_Bits :: enum c.int { + // Lock all pages currently mapped into the address space of the process. + CURRENT = log2(MCL_CURRENT), + // Lock all pages that become mapped into the address space of the process in the future, + // when those mappings are established. + FUTURE = log2(MCL_FUTURE), +} +Lock_Flags :: bit_set[Lock_Flag_Bits; c.int] + +Sync_Flags_Bits :: enum c.int { + // Perform asynchronous writes. + ASYNC = log2(MS_ASYNC), + // Invalidate cached data. + INVALIDATE = log2(MS_INVALIDATE), + + // Perform synchronous writes. + // NOTE: use with `posix.MS_SYNC + { .OTHER_FLAG, .OTHER_FLAG }`, unfortunately can't be in + // this bit set enum because it is 0 on some platforms and a value on others. + // LOCAL = RTLD_LOCAL + // SYNC = MS_SYNC, + + _MAX = 31, +} +Sync_Flags :: bit_set[Sync_Flags_Bits; c.int] + +MAdvice :: enum c.int { + DONTNEED = POSIX_MADV_DONTNEED, + NORMAL = POSIX_MADV_NORMAL, + RANDOM = POSIX_MADV_RANDOM, + SEQUENTIAL = POSIX_MADV_SEQUENTIAL, + WILLNEED = POSIX_MADV_WILLNEED, +} + +when ODIN_OS == .NetBSD { + @(private) LMSYNC :: "__msync13" +} else { + @(private) LMSYNC :: "msync" +} + +when ODIN_OS == .Darwin || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { + + PROT_EXEC :: 0x04 + _PROT_NONE :: 0x00 + PROT_READ :: 0x01 + PROT_WRITE :: 0x02 + + MAP_FIXED :: 0x0010 + MAP_PRIVATE :: 0x0002 + MAP_SHARED :: 0x0001 + MAP_ANONYMOUS :: 0x0020 when ODIN_OS == .Linux else 0x1000 + + when ODIN_OS == .Darwin || ODIN_OS == .Linux { + MS_INVALIDATE :: 0x0002 + _MS_SYNC :: 0x0010 + } else when ODIN_OS == .NetBSD { + MS_INVALIDATE :: 0x0002 + _MS_SYNC :: 0x0004 + } else when ODIN_OS == .OpenBSD { + MS_INVALIDATE :: 0x0004 + _MS_SYNC :: 0x0002 + } + + MS_ASYNC :: 0x0001 + MS_SYNC :: Sync_Flags{Sync_Flags_Bits(log2(_MS_SYNC))} + + MCL_CURRENT :: 0x0001 + MCL_FUTURE :: 0x0002 + + MAP_FAILED :: rawptr(~uintptr(0)) + + POSIX_MADV_DONTNEED :: 4 + POSIX_MADV_NORMAL :: 0 + POSIX_MADV_RANDOM :: 1 + POSIX_MADV_SEQUENTIAL :: 2 + POSIX_MADV_WILLNEED :: 3 + +} else when ODIN_OS == .FreeBSD { + + PROT_EXEC :: 0x04 + _PROT_NONE :: 0x00 + PROT_READ :: 0x01 + PROT_WRITE :: 0x02 + + MAP_FIXED :: 0x0010 + MAP_PRIVATE :: 0x0002 + MAP_SHARED :: 0x0001 + MAP_ANONYMOUS :: 0x1000 + + MS_ASYNC :: 0x0001 + MS_INVALIDATE :: 0x0002 + MS_SYNC :: Sync_Flags{} + + MCL_CURRENT :: 0x0001 + MCL_FUTURE :: 0x0002 + + MAP_FAILED :: rawptr(~uintptr(0)) + + POSIX_MADV_DONTNEED :: 4 + POSIX_MADV_NORMAL :: 0 + POSIX_MADV_RANDOM :: 1 + POSIX_MADV_SEQUENTIAL :: 2 + POSIX_MADV_WILLNEED :: 3 + +} diff --git a/core/sys/posix/sys_msg.odin b/core/sys/posix/sys_msg.odin new file mode 100644 index 000000000..0e78777f9 --- /dev/null +++ b/core/sys/posix/sys_msg.odin @@ -0,0 +1,174 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/msg.h = XSI message queue structures + +foreign lib { + /* + Provides various operation as specified by the given cmd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/msgctl.html ]] + */ + @(link_name=LMSGCTL) + msgctl :: proc(msqid: FD, cmd: IPC_Cmd, buf: ^msqid_ds) -> result --- + + /* + Returns the message queue identifier associated with the argument key. + + Returns: -1 (setting errno) on failure, the identifier otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/msgget.html ]] + */ + msgget :: proc(key: key_t, msgflg: IPC_Flags) -> FD --- + + /* + Read a message from the queue. + + Returns: -1 (setting errno) on failure, the bytes received otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/msgrcv.html ]] + */ + msgrcv :: proc( + msgid: FD, + msgp: rawptr, + msgsz: c.size_t, + msgtyp: c.long, + msgflg: IPC_Flags, + ) -> c.ssize_t --- + + /* + Send a message on the queue. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/msgsnd.html ]] + */ + msgsnd :: proc(msgid: FD, msgp: rawptr, msgsz: c.size_t, msgflg: IPC_Flags) -> result --- +} + +when ODIN_OS == .NetBSD { + @(private) LMSGCTL :: "__msgctl50" +} else { + @(private) LMSGCTL :: "msgctl" +} + +when ODIN_OS == .Darwin { + + msgqnum_t :: distinct c.ulong + msglen_t :: distinct c.ulong + + MSG_NOERROR :: 0o10000 + + msqid_ds :: struct #max_field_align(4) { + msg_perm: ipc_perm, /* [PSX] operation permission structure */ + msg_first: c.int32_t, + msg_last: c.int32_t, + msg_cbytes: msglen_t, + msg_qnum: msgqnum_t, /* [PSX] number of messages currently on queue */ + msg_qbytes: msglen_t, /* [PSX] maximum number of bytes allowed on queue */ + msg_lspid: pid_t, /* [PSX] process ID of last msgsnd() */ + msg_lrpid: pid_t, /* [PSX] process ID of last msgrcv() */ + msg_stime: time_t, /* [PSX] time of last msgsnd() */ + msg_pad1: c.int32_t, + msg_rtime: time_t, /* [PSX] time of last msgrcv() */ + msg_pad2: c.int32_t, + msg_ctime: time_t, /* [PSX] time of last change */ + msg_pad3: c.int32_t, + msg_pad4: [4]c.int32_t, + } + +} else when ODIN_OS == .FreeBSD { + + msgqnum_t :: distinct c.ulong + msglen_t :: distinct c.ulong + + MSG_NOERROR :: 0o10000 + + msqid_ds :: struct { + msg_perm: ipc_perm, /* [PSX] operation permission structure */ + __msg_first: rawptr, + __msg_last: rawptr, + msg_cbytes: msglen_t, + msg_qnum: msgqnum_t, /* [PSX] number of messages currently on queue */ + msg_qbytes: msglen_t, /* [PSX] maximum number of bytes allowed on queue */ + msg_lspid: pid_t, /* [PSX] process ID of last msgsnd() */ + msg_lrpid: pid_t, /* [PSX] process ID of last msgrcv() */ + msg_stime: time_t, /* [PSX] time of last msgsnd() */ + msg_rtime: time_t, /* [PSX] time of last msgrcv() */ + msg_ctime: time_t, /* [PSX] time of last change */ + } + +} else when ODIN_OS == .NetBSD { + + msgqnum_t :: distinct c.ulong + msglen_t :: distinct c.size_t + + MSG_NOERROR :: 0o10000 + + msqid_ds :: struct { + msg_perm: ipc_perm, /* [PSX] operation permission structure */ + msg_qnum: msgqnum_t, /* [PSX] number of messages currently on queue */ + msg_qbytes: msglen_t, /* [PSX] maximum number of bytes allowed on queue */ + msg_lspid: pid_t, /* [PSX] process ID of last msgsnd() */ + msg_lrpid: pid_t, /* [PSX] process ID of last msgrcv() */ + msg_stime: time_t, /* [PSX] time of last msgsnd() */ + msg_rtime: time_t, /* [PSX] time of last msgrcv() */ + msg_ctime: time_t, /* [PSX] time of last change */ + + _msg_first: rawptr, + _msg_last: rawptr, + _msg_cbytes: msglen_t, + } + +} else when ODIN_OS == .OpenBSD { + + msgqnum_t :: distinct c.ulong + msglen_t :: distinct c.ulong + + MSG_NOERROR :: 0o10000 + + msqid_ds :: struct { + msg_perm: ipc_perm, /* [PSX] operation permission structure */ + __msg_first: rawptr, + __msg_last: rawptr, + msg_cbytes: msglen_t, + msg_qnum: msgqnum_t, /* [PSX] number of messages currently on queue */ + msg_qbytes: msglen_t, /* [PSX] maximum number of bytes allowed on queue */ + msg_lspid: pid_t, /* [PSX] process ID of last msgsnd() */ + msg_lrpid: pid_t, /* [PSX] process ID of last msgrcv() */ + msg_stime: time_t, /* [PSX] time of last msgsnd() */ + msg_pad1: c.long, + msg_rtime: time_t, /* [PSX] time of last msgrcv() */ + msg_pad2: c.long, + msg_ctime: time_t, /* [PSX] time of last change */ + msg_pad3: c.long, + msg_pad4: [4]c.long, + } + +} else when ODIN_OS == .Linux { + + msgqnum_t :: distinct c.ulong + msglen_t :: distinct c.ulong + + MSG_NOERROR :: 0o10000 + + msqid_ds :: struct { + msg_perm: ipc_perm, /* [PSX] operation permission structure */ + msg_stime: time_t, /* [PSX] time of last msgsnd() */ + msg_rtime: time_t, /* [PSX] time of last msgrcv() */ + msg_ctime: time_t, /* [PSX] time of last change */ + msg_cbytes: c.ulong, + msg_qnum: msgqnum_t, /* [PSX] number of messages currently on queue */ + msg_qbytes: msglen_t, /* [PSX] maximum number of bytes allowed on queue */ + msg_lspid: pid_t, /* [PSX] process ID of last msgsnd() */ + msg_lrpid: pid_t, /* [PSX] process ID of last msgrcv() */ + __unused: [2]c.ulong, + } + +} diff --git a/core/sys/posix/sys_resource.odin b/core/sys/posix/sys_resource.odin new file mode 100644 index 000000000..9af2a929b --- /dev/null +++ b/core/sys/posix/sys_resource.odin @@ -0,0 +1,158 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/resource.h - definitions XSI resource operations + +foreign lib { + /* + Gets the nice value of the process, process group or user given. + + Note that a nice value can be -1, so checking for an error would mean clearing errno, doing the + call and then checking that this returns -1 and it has an errno. + + Returns: -1 (setting errno) on failure, the value otherwise + + Example: + pid := posix.getpid() + posix.set_errno(.NONE) + prio := posix.getpriority(.PROCESS, pid) + if err := posix.errno(); prio == -1 && err != .NONE { + // Handle error... + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpriority.html ]] + */ + getpriority :: proc(which: Which_Prio, who: id_t) -> c.int --- + + /* + Sets the nice value of the process, process group or user given. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpriority.html ]] + */ + setpriority :: proc(which: Which_Prio, who: id_t, value: c.int) -> result --- + + /* + Get a resource limit. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getrlimit.html ]] + */ + getrlimit :: proc(resource: Resource, rlp: ^rlimit) -> result --- + + /* + Set a resource limit. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getrlimit.html ]] + */ + setrlimit :: proc(resource: Resource, rlp: ^rlimit) -> result --- + + /* + Get resource usage. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getrusage.html ]] + */ + @(link_name=LGETRUSAGE) + getrusage :: proc(who: Which_Usage, rusage: ^rusage) -> result --- +} + +Which_Prio :: enum c.int { + PROCESS = PRIO_PROCESS, + PGRP = PRIO_PGRP, + USER = PRIO_USER, +} + +Which_Usage :: enum c.int { + SELF = RUSAGE_SELF, + CHILDREN = RUSAGE_CHILDREN, +} + +Resource :: enum c.int { + // Maximum byte size of a core file that may be created by a process. + CORE = RLIMIT_CORE, + // Maximum amount of CPU time, in seconds, used by a process. + CPU = RLIMIT_CPU, + // Maximum size of data segment of the process, in bytes. + DATA = RLIMIT_DATA, + // Maximum size of a file, in bytes, that may be created by a process. + FSIZE = RLIMIT_FSIZE, + // A number one greater than the maximum value that the system may assign to a newly-created descriptor. + NOFILE = RLIMIT_NOFILE, + // The maximum size of the initial thread's stack, in bytes. + STACK = RLIMIT_STACK, + // Maximum size of total available memory of the process, in bytes. + AS = RLIMIT_AS, +} + +when ODIN_OS == .NetBSD { + @(private) LGETRUSAGE :: "__getrusage50" +} else { + @(private) LGETRUSAGE :: "getrusage" +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { + + PRIO_PROCESS :: 0 + PRIO_PGRP :: 1 + PRIO_USER :: 2 + + rlim_t :: distinct c.uint64_t + + RLIM_INFINITY :: ~rlim_t(0) when ODIN_OS == .Linux else (rlim_t(1) << 63) - 1 + RLIM_SAVED_MAX :: RLIM_INFINITY + RLIM_SAVED_CUR :: RLIM_INFINITY + + RUSAGE_SELF :: 0 + RUSAGE_CHILDREN :: -1 + + rlimit :: struct { + rlim_cur: rlim_t, /* [PSX] the current (soft) limit */ + rlim_max: rlim_t, /* [PSX] the hard limit */ + } + + rusage :: struct { + ru_utime: timeval, /* [PSX] user time used */ + ru_stime: timeval, /* [PSX] system time used */ + + // Informational aliases for source compatibility with programs + // that need more information than that provided by standards, + // and which do not mind being OS-dependent. + + ru_maxrss: c.long, /* max resident set size (PL) */ + ru_ixrss: c.long, /* integral shared memory size (NU) */ + ru_idrss: c.long, /* integral unshared data (NU) */ + ru_isrss: c.long, /* integral unshared stack (NU) */ + ru_minflt: c.long, /* page reclaims (NU) */ + ru_majflt: c.long, /* page faults (NU) */ + ru_nswap: c.long, /* swaps (NU) */ + ru_inblock: c.long, /* block input operations (atomic) */ + ru_outblock: c.long, /* block output operations (atomic) */ + ru_msgsnd: c.long, /* messages sent (atomic) */ + ru_msgrcv: c.long, /* messages received (atomic) */ + ru_nsignals: c.long, /* signals received (atomic) */ + ru_nvcsw: c.long, /* voluntary context switches (atomic) */ + ru_nivcsw: c.long, /* involuntary " */ + } + + RLIMIT_CORE :: 4 + RLIMIT_CPU :: 0 + RLIMIT_DATA :: 2 + RLIMIT_FSIZE :: 1 + RLIMIT_NOFILE :: 7 when ODIN_OS == .Linux else 8 + RLIMIT_STACK :: 3 + + when ODIN_OS == .Linux { + RLIMIT_AS :: 9 + } else when ODIN_OS == .Darwin || ODIN_OS == .OpenBSD { + RLIMIT_AS :: 5 + } else { + RLIMIT_AS :: 10 + } + +} diff --git a/core/sys/posix/sys_select.odin b/core/sys/posix/sys_select.odin new file mode 100644 index 000000000..2058ee777 --- /dev/null +++ b/core/sys/posix/sys_select.odin @@ -0,0 +1,119 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "base:intrinsics" + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/select.h - select types + +foreign lib { + /* + Examines the file descriptor sets to see whether some of their descriptors are ready for writing, + or have an exceptional condition pending, respectively. + + Returns: -1 (setting errno) on failure, total amount of bits set in the bit masks otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pselect.html ]] + */ + @(link_name=LPSELECT) + pselect :: proc( + nfds: c.int, + readfds: ^fd_set, + writefds: ^fd_set, + errorfds: ^fd_set, + timeout: ^timespec, + sigmask: ^sigset_t, + ) -> c.int --- + + /* + Equivalent to pselect() except a more specific timeout resolution (nanoseconds), + does not have a signal mask, and may modify the timeout. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pselect.html ]] + */ + @(link_name=LSELECT) + select :: proc( + nfds: c.int, + readfds: ^fd_set, + writefds: ^fd_set, + errorfds: ^fd_set, + timeout: ^timeval, + ) -> c.int --- +} + +when ODIN_OS == .NetBSD { + LPSELECT :: "__pselect50" + LSELECT :: "__select50" +} else { + LPSELECT :: "pselect" + LSELECT :: "select" +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { + + suseconds_t :: distinct (c.int32_t when ODIN_OS == .Darwin || ODIN_OS == .NetBSD else c.long) + + timeval :: struct { + tv_sec: time_t, /* [PSX] seconds */ + tv_usec: suseconds_t, /* [PSX] microseconds */ + } + + // Maximum number of file descriptors in the fd_set structure. + FD_SETSIZE :: #config(POSIX_FD_SETSIZE, 256 when ODIN_OS == .NetBSD else 1024) + + @(private) + __NFDBITS :: size_of(c.int32_t) * 8 + + // NOTE: this seems correct for FreeBSD but they do use a set backed by the long type themselves (thus the align change). + @(private) + ALIGN :: align_of(c.long) when ODIN_OS == .FreeBSD || ODIN_OS == .Linux else align_of(c.int32_t) + + fd_set :: struct #align(ALIGN) { + fds_bits: [(FD_SETSIZE / __NFDBITS) when (FD_SETSIZE % __NFDBITS) == 0 else (FD_SETSIZE / __NFDBITS) + 1]c.int32_t, + } + + @(private) + __check_fd_set :: #force_inline proc "contextless" (_a: FD, _b: rawptr) -> bool { + if _a < 0 { + set_errno(.EINVAL) + } + + if _a >= FD_SETSIZE { + set_errno(.EINVAL) + } + + return true + } + + FD_CLR :: #force_inline proc "contextless" (_fd: FD, _p: ^fd_set) { + if __check_fd_set(_fd, _p) { + _p.fds_bits[cast(c.ulong)_fd / __NFDBITS] &= ~cast(c.int32_t)((cast(c.ulong)1) << (cast(c.ulong)_fd % __NFDBITS)) + } + } + + FD_ISSET :: #force_inline proc "contextless" (_fd: FD, _p: ^fd_set) -> bool { + if __check_fd_set(_fd, _p) { + return bool(_p.fds_bits[cast(c.ulong)_fd / __NFDBITS] & cast(c.int32_t)((cast(c.ulong)1) << (cast(c.ulong)_fd % __NFDBITS))) + } + + return false + } + + FD_SET :: #force_inline proc "contextless" (_fd: FD, _p: ^fd_set) { + if __check_fd_set(_fd, _p) { + _p.fds_bits[cast(c.ulong)_fd / __NFDBITS] |= cast(c.int32_t)((cast(c.ulong)1) << (cast(c.ulong)_fd % __NFDBITS)) + } + } + + FD_ZERO :: #force_inline proc "contextless" (_p: ^fd_set) { + intrinsics.mem_zero(_p, size_of(fd_set)) + } + +} diff --git a/core/sys/posix/sys_sem.odin b/core/sys/posix/sys_sem.odin new file mode 100644 index 000000000..6b695e766 --- /dev/null +++ b/core/sys/posix/sys_sem.odin @@ -0,0 +1,157 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/sem.h - XSI semaphore facility + +foreign lib { + /* + Provides various semaphore control operation as specified by cmd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/semctl.html ]] + */ + @(link_name=LSEMCTL) + semctl :: proc(semid: FD, semnum: c.int, cmd: Sem_Cmd, arg: ^semun = nil) -> c.int --- + + /* + Returns the semaphore identifier associated with key. + + Returns: -1 (setting errno) on failure, a semaphore file descriptor otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/semget.html ]] + */ + semget :: proc(key: key_t, nsems: c.int, semflg: IPC_Flags) -> FD --- + + /* + Perform atomically a user-defined array of semaphore operations in array order on the set of + semaphores associated with the semaphore identifier specified by the argument semid. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/semop.html ]] + */ + semop :: proc(semid: FD, sops: [^]sembuf, nsops: c.size_t) -> result --- +} + +Sem_Cmd :: enum c.int { + // Returns the value of semncnt. + GETNCNT = GETNCNT, + // Returns the value of sempid. + GETPID = GETPID, + // Return the value of semval. + GETVAL = GETVAL, + // Returns the value of semval for each semaphore in the semaphore set. + GETALL = GETALL, + // Returns the value of semzcnt. + GETZCNT = GETZCNT, + // Sets the value of semval to arg.val. + SETVAL = SETVAL, + // Sets the value of semval for each semaphore in the set. + SETALL = SETALL, +} + +semun :: struct #raw_union { + val: c.int, + buf: ^semid_ds, + array: [^]c.ushort, +} + +when ODIN_OS == .NetBSD { + @(private) LSEMCTL :: "__semctl50" +} else { + @(private) LSEMCTL :: "semctl" +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + SEM_UNDO :: 0o10000 + + GETNCNT :: 3 + GETPID :: 4 + GETVAL :: 5 + GETALL :: 6 + GETZCNT :: 7 + SETVAL :: 8 + SETALL :: 9 + + when ODIN_OS == .Darwin { + semid_ds :: struct #max_field_align(4) { + sem_perm: ipc_perm, /* [PSX] operation permission structure */ + sem_base: c.int32_t, /* 32 bit base ptr for semaphore set */ + sem_nsems: c.ushort, /* [PSX] number of semaphores in set */ + sem_otime: time_t, /* [PSX] last semop() */ + sem_pad1: c.int32_t, + sem_ctime: time_t, /* [PSX] last time changed by semctl() */ + sem_pad2: c.int32_t, + sem_pad3: [4]c.int32_t, + } + } else when ODIN_OS == .FreeBSD { + semid_ds :: struct { + sem_perm: ipc_perm, /* [PSX] operation permission structure */ + sem_base: rawptr, /* 32 bit base ptr for semaphore set */ + sem_nsems: c.ushort, /* [PSX] number of semaphores in set */ + sem_otime: time_t, /* [PSX] last semop() */ + sem_ctime: time_t, /* [PSX] last time changed by semctl() */ + } + } else when ODIN_OS == .NetBSD { + semid_ds :: struct { + sem_perm: ipc_perm, /* [PSX] operation permission structure */ + sem_nsems: c.ushort, /* [PSX] number of semaphores in set */ + sem_otime: time_t, /* [PSX] last semop() */ + sem_ctime: time_t, /* [PSX] last time changed by semctl() */ + _sem_base: rawptr, /* 32 bit base ptr for semaphore set */ + } + } else when ODIN_OS == .OpenBSD { + semid_ds :: struct { + sem_perm: ipc_perm, /* [PSX] operation permission structure */ + sem_nsems: c.ushort, /* [PSX] number of semaphores in set */ + sem_otime: time_t, /* [PSX] last semop() */ + sem_pad1: c.long, + sem_ctime: time_t, /* [PSX] last time changed by semctl() */ + sem_pad2: c.long, + sem_pad3: [4]c.long, + } + } + + sembuf :: struct { + sem_num: c.ushort, /* [PSX] semaphore number */ + sem_op: c.short, /* [PSX] semaphore operation */ + sem_flg: c.short, /* [PSX] operation flags */ + } + +} else when ODIN_OS == .Linux { + + SEM_UNDO :: 0x1000 // undo the operation on exit + + // Commands for `semctl'. + GETPID :: 11 + GETVAL :: 12 + GETALL :: 13 + GETNCNT :: 14 + GETZCNT :: 15 + SETVAL :: 16 + SETALL :: 17 + + semid_ds :: struct { + sem_perm: ipc_perm, // [PSX] operation permission structure + sem_otime: time_t, // [PSX] last semop() + __sem_otime_high: c.ulong, + sem_ctime: time_t, // [PSX] last time changed by semctl() + __sem_ctime_high: c.ulong, + sem_nsems: c.ulong, // [PSX] number of semaphores in set + __glibc_reserved3: c.ulong, + __glibc_reserved4: c.ulong, + } + + sembuf :: struct { + sem_num: c.ushort, /* [PSX] semaphore number */ + sem_op: c.short, /* [PSX] semaphore operation */ + sem_flg: c.short, /* [PSX] operation flags */ + } + +} diff --git a/core/sys/posix/sys_shm.odin b/core/sys/posix/sys_shm.odin new file mode 100644 index 000000000..8f3c56b9c --- /dev/null +++ b/core/sys/posix/sys_shm.odin @@ -0,0 +1,161 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/shm.h = XSI shared memory facility + +foreign lib { + /* + Attaches the shared memory segment associated with the identifier + into the address space of the calling process. + + Returns: nil (setting errno) on failure, the address otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shmat.html ]] + */ + shmat :: proc(shmid: FD, shmaddr: rawptr, shmflag: SHM_Flags) -> rawptr --- + + /* + Provides various shared memory operation as specified by the given cmd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shmctl.html ]] + */ + @(link_name=LSHMCTL) + shmctl :: proc(shmid: FD, cmd: IPC_Cmd, buf: ^shmid_ds) -> result --- + + /* + Detaches the shared memory segment located at the address specified. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shmdt.html ]] + */ + shmdt :: proc(shmaddr: rawptr) -> result --- + + /* + Returns the shared memory identifier associated with key. + + Returns: -1 (setting errno) on failure, the shared memory ID otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shmget.html ]] + */ + shmget :: proc(key: key_t, size: c.size_t, shmflag: SHM_Flags) -> FD --- +} + +SHM_Flag_Bits :: enum c.int { + RDONLY = log2(SHM_RDONLY), + RND = log2(SHM_RND), +} +SHM_Flags :: bit_set[SHM_Flag_Bits; c.int] + +when ODIN_OS == .NetBSD { + @(private) LSHMCTL :: "__shmctl50" +} else { + @(private) LSHMCTL :: "shmctl" +} + +when ODIN_OS == .Darwin { + + SHM_RDONLY :: 0o10000 + SHM_RND :: 0o20000 + + SHMLBA :: 16 * 1024 when ODIN_ARCH == .arm64 else 4096 + + shmatt_t :: distinct c.ushort + + shmid_ds :: struct #max_field_align(4) { + shm_perm: ipc_perm, /* [PSX] operation permission structure */ + shm_segsz: c.size_t, /* [PSX] size of segment in bytes */ + shm_lpid: pid_t, /* [PSX] process ID of last shared memory operation */ + shm_cpid: pid_t, /* [PSX] process ID of creator */ + shm_nattch: shmatt_t, /* [PSX] number of current attaches */ + shm_atime: time_t, /* [PSX] time of last shmat() */ + shm_dtime: time_t, /* [PSX] time of last shmdt() */ + shm_ctime: time_t, /* [PSX] time of last change by shmctl() */ + shm_internal: rawptr, + } + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { + + SHM_RDONLY :: 0o10000 + SHM_RND :: 0o20000 + + SHMLBA :: PAGESIZE + + shmatt_t :: distinct c.uint + + when ODIN_OS == .FreeBSD { + shmid_ds :: struct { + shm_perm: ipc_perm, /* [PSX] operation permission structure */ + shm_segsz: c.size_t, /* [PSX] size of segment in bytes */ + shm_lpid: pid_t, /* [PSX] process ID of last shared memory operation */ + shm_cpid: pid_t, /* [PSX] process ID of creator */ + shm_nattch: shmatt_t, /* [PSX] number of current attaches */ + shm_atime: time_t, /* [PSX] time of last shmat() */ + shm_dtime: time_t, /* [PSX] time of last shmdt() */ + shm_ctime: time_t, /* [PSX] time of last change by shmctl() */ + } + } else { + shmid_ds :: struct { + shm_perm: ipc_perm, /* [PSX] operation permission structure */ + shm_segsz: c.size_t, /* [PSX] size of segment in bytes */ + shm_lpid: pid_t, /* [PSX] process ID of last shared memory operation */ + shm_cpid: pid_t, /* [PSX] process ID of creator */ + shm_nattch: shmatt_t, /* [PSX] number of current attaches */ + shm_atime: time_t, /* [PSX] time of last shmat() */ + shm_dtime: time_t, /* [PSX] time of last shmdt() */ + shm_ctime: time_t, /* [PSX] time of last change by shmctl() */ + _shm_internal: rawptr, + } + } + +} else when ODIN_OS == .OpenBSD { + + SHM_RDONLY :: 0o10000 + SHM_RND :: 0o20000 + + SHMLBA :: 1 << 12 + + shmatt_t :: distinct c.short + + shmid_ds :: struct { + shm_perm: ipc_perm, /* [PSX] operation permission structure */ + shm_segsz: c.int, /* [PSX] size of segment in bytes */ + shm_lpid: pid_t, /* [PSX] process ID of last shared memory operation */ + shm_cpid: pid_t, /* [PSX] process ID of creator */ + shm_nattch: shmatt_t, /* [PSX] number of current attaches */ + shm_atime: time_t, /* [PSX] time of last shmat() */ + __shm_atimensec: c.long, + shm_dtime: time_t, /* [PSX] time of last shmdt() */ + __shm_dtimensec: c.long, + shm_ctime: time_t, /* [PSX] time of last change by shmctl() */ + __shm_ctimensec: c.long, + _shm_internal: rawptr, + } + +} else when ODIN_OS == .Linux { + + SHM_RDONLY :: 0o10000 + SHM_RND :: 0o20000 + + SHMLBA :: 4096 + + shmatt_t :: distinct c.ulong + + shmid_ds :: struct { + shm_perm: ipc_perm, /* [PSX] operation permission structure */ + shm_segsz: c.size_t, /* [PSX] size of segment in bytes */ + shm_atime: time_t, /* [PSX] time of last shmat() */ + shm_dtime: time_t, /* [PSX] time of last shmdt() */ + shm_ctime: time_t, /* [PSX] time of last change by shmctl() */ + shm_cpid: pid_t, /* [PSX] process ID of creator */ + shm_lpid: pid_t, /* [PSX] process ID of last shared memory operation */ + shm_nattch: shmatt_t, /* [PSX] number of current attaches */ + _: [2]c.ulong, + } +} diff --git a/core/sys/posix/sys_socket.odin b/core/sys/posix/sys_socket.odin new file mode 100644 index 000000000..4dd6074a3 --- /dev/null +++ b/core/sys/posix/sys_socket.odin @@ -0,0 +1,572 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import libc "system:System.framework" +} else { + foreign import libc "system:c" +} + +// sys/socket.h - main sockets header + +#assert(Protocol.IP == Protocol(0), "socket() assumes this") + +foreign libc { + /* + Creates a socket. + + Returns: -1 (setting errno) on failure, file descriptor of socket otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html ]] + */ + @(link_name=LSOCKET) + socket :: proc(domain: AF, type: Sock, protocol: Protocol = .IP) -> FD --- + + /* + Extracts the first connection on the queue of pending connections. + + Blocks (if not O_NONBLOCK) if there is no pending connection. + + Returns: -1 (setting errno) on failure, file descriptor of accepted socket otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/accept.html ]] + */ + accept :: proc(socket: FD, address: ^sockaddr, address_len: ^socklen_t) -> FD --- + + /* + Assigns a local socket address to the socket. + + Example: + sfd := posix.socket(.UNIX, .STREAM) + if sfd == -1 { + /* Handle error */ + } + + addr: posix.sockaddr_un + addr.sun_family = .UNIX + copy(addr.sun_path[:], "/somepath\x00") + + /* + unlink the socket before binding in case + of previous runs not cleaning up the socket + */ + posix.unlink("/somepath") + + if posix.bind(sfd, (^posix.sockaddr)(&addr), size_of(addr)) != .OK { + /* Handle error */ + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html ]] + */ + bind :: proc(socket: FD, address: ^sockaddr, address_len: socklen_t) -> result --- + + /* + Attempt to make a connection. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html ]] + */ + connect :: proc(socket: FD, address: ^sockaddr, address_len: socklen_t) -> result --- + + /* + Get the peer address of the specified socket. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpeername.html ]] + */ + getpeername :: proc(socket: FD, address: ^sockaddr, address_len: ^socklen_t) -> result --- + + /* + Get the socket name. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockname.html ]] + */ + getsockname :: proc(socket: FD, address: ^sockaddr, address_len: ^socklen_t) -> result --- + + /* + Retrieves the value for the option specified by option_name. + + level: either `c.int(posix.Protocol(...))` to specify a protocol level or `posix.SOL_SOCKET` + to specify the socket local level. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockopt.html ]] + */ + getsockopt :: proc( + socket: FD, + level: c.int, + option_name: Sock_Option, + option_value: rawptr, + option_len: ^socklen_t, + ) -> result --- + + /* + Sets the specified option. + + level: either `c.int(posix.Protocol(...))` to specify a protocol level or `posix.SOL_SOCKET` + to specify the socket local level. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setsockopt.html ]] + */ + setsockopt :: proc( + socket: FD, + level: c.int, + option_name: Sock_Option, + option_value: rawptr, + option_len: socklen_t, + ) -> result --- + + /* + Mark the socket as a socket accepting connections. + + backlog provides a hint to limit the number of connections on the listen queue. + Implementation may silently reduce the backlog, additionally `SOMAXCONN` specifies the maximum + an implementation has to support. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/listen.html ]] + */ + listen :: proc(socket: FD, backlog: c.int) -> result --- + + /* + Receives a message from a socket. + + Blocks (besides with O_NONBLOCK) if there is nothing to receive. + + Returns: 0 when the peer shutdown with no more messages, -1 (setting errno) on failure, the amount of bytes received on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/recv.html ]] + */ + recv :: proc(socket: FD, buffer: rawptr, length: c.size_t, flags: Msg_Flags) -> c.ssize_t --- + + /* + Receives a message from a socket. + + Equivalent to recv() but retrieves the source address too. + + Returns: 0 when the peer shutdown with no more messages, -1 (setting errno) on failure, the amount of bytes received on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvfrom.html ]] + */ + recvfrom :: proc( + socket: FD, + buffer: rawptr, + length: c.size_t, + flags: Msg_Flags, + address: ^sockaddr, + address_len: ^socklen_t, + ) -> c.ssize_t --- + + /* + Receives a message from a socket. + + Returns: 0 when the peer shutdown with no more messages, -1 (setting errno) on failure, the amount of bytes received on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/recvmsg.html ]] + */ + recvmsg :: proc(socket: FD, message: ^msghdr, flags: Msg_Flags) -> c.ssize_t --- + + /* + Sends a message on a socket. + + Returns: -1 (setting errno) on failure, the amount of bytes received on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/send.html ]] + */ + send :: proc(socket: FD, buffer: rawptr, length: c.size_t, flags: Msg_Flags) -> c.ssize_t --- + + /* + Sends a message on a socket. + + Returns: -1 (setting errno) on failure, the amount of bytes received on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendmsg.html ]] + */ + sendmsg :: proc(socket: FD, message: ^msghdr, flags: Msg_Flags) -> c.ssize_t --- + + /* + Sends a message on a socket. + + If the socket is connectionless, the dest_addr is used to send to. + + Returns: -1 (setting errno) on failure, the amount of bytes received on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sendto.html ]] + */ + sendto :: proc( + socket: FD, + message: rawptr, + length: c.size_t, + flags: Msg_Flags, + dest_addr: ^sockaddr, + dest_len: socklen_t, + ) -> c.ssize_t --- + + /* + Shuts down a socket end or both. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shutdown.html ]] + */ + shutdown :: proc(socket: FD, how: Shut) -> result --- + + /* + Determine wheter a socket is at the out-of-band mark. + + Returns: -1 (setting errno) on failure, 0 if not at the mark, 1 if it is + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sockatmark.html ]] + */ + sockatmark :: proc(socket: FD) -> c.int --- + + /* + Create a pair of connected sockets. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/socketpair.html ]] + */ + socketpair :: proc(domain: AF, type: Sock, protocol: Protocol, socket_vector: ^[2]FD) -> result --- +} + +AF_UNSPEC :: 0 + +AF :: enum c.int { + // Unspecified. + UNSPEC = AF_UNSPEC, + // Internet domain sockets for use with IPv4 addresses. + INET = AF_INET, + // Internet domain sockets for use with IPv6 addresses. + INET6 = AF_INET6, + // UNIX domain sockets. + UNIX = AF_UNIX, +} + +sa_family_t :: enum _sa_family_t { + // Unspecified. + UNSPEC = AF_UNSPEC, + // Internet domain sockets for use with IPv4 addresses. + INET = AF_INET, + // Internet domain sockets for use with IPv6 addresses. + INET6 = AF_INET6, + // UNIX domain sockets. + UNIX = AF_UNIX, +} + +Sock :: enum c.int { + // Datagram socket. + DGRAM = SOCK_DGRAM, + // Raw Protocol Interface. + RAW = SOCK_RAW, + // Sequenced-packet socket. + SEQPACKET = SOCK_SEQPACKET, + // Byte-stream socket. + STREAM = SOCK_STREAM, +} + +Shut :: enum c.int { + // Disables further receive operations. + RD = SHUT_RD, + // Disables further send and receive operations. + RDWR = SHUT_RDWR, + // Disables further send operations. + WR = SHUT_WR, +} + +Msg_Flag_Bits :: enum c.int { + // Control data truncated. + CTRUNC = log2(MSG_CTRUNC), + // Send without using routing table. + DONTROUTE = log2(MSG_DONTROUTE), + // Terminates a record (if supported by protocol). + EOR = log2(MSG_EOR), + // Out-of-band data. + OOB = log2(MSG_OOB), + // No SIGPIPE is generated when an attempt to send is made on a stream-oriented socket that is + // no longer connected. + NOSIGNAL = log2(MSG_NOSIGNAL), + // Leave received data in queue. + PEEK = log2(MSG_PEEK), + // Normal data truncated. + TRUNC = log2(MSG_TRUNC), + // Attempt to fill the read buffer. + WAITALL = log2(MSG_WAITALL), +} +Msg_Flags :: bit_set[Msg_Flag_Bits; c.int] + +Sock_Option :: enum c.int { + // Transmission of broadcast message is supported. + BROADCAST = SO_BROADCAST, + // Debugging information is being recorded. + DEBUG = SO_DEBUG, + // Bypass normal routing. + DONTROUTE = SO_DONTROUTE, + // Socket error status. + ERROR = SO_ERROR, + // Connections are kept alive with periodic messages. + KEEPALIVE = SO_KEEPALIVE, + // Socket lingers on close. + LINGER = SO_LINGER, + // Out-of-band data is transmitted in line. + OOBINLINE = SO_OOBINLINE, + // Receive buffer size. + RCVBUF = SO_RCVBUF, + // Receive low water mark. + RCVLOWAT = SO_RCVLOWAT, + // Receive timeout. + RCVTIMEO = SO_RCVTIMEO, + // Reuse of local addresses is supported. + REUSEADDR = SO_REUSEADDR, + // Send buffer size. + SNDBUF = SO_SNDBUF, + // Send low water mark. + SNDLOWAT = SO_SNDLOWAT, + // Send timeout. + SNDTIMEO = SO_SNDTIMEO, + // Socket type. + TYPE = SO_TYPE, +} + +when ODIN_OS == .NetBSD { + @(private) LSOCKET :: "__socket30" +} else { + @(private) LSOCKET :: "socket" +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { + + socklen_t :: distinct c.uint + + when ODIN_OS == .Linux { + _sa_family_t :: distinct c.ushort + + sockaddr :: struct { + sa_family: sa_family_t, /* [PSX] address family */ + sa_data: [14]c.char, /* [PSX] socket address */ + } + } else { + _sa_family_t :: distinct c.uint8_t + + sockaddr :: struct { + sa_len: c.uint8_t, /* total length */ + sa_family: sa_family_t, /* [PSX] address family */ + sa_data: [14]c.char, /* [PSX] socket address */ + } + } + + + when ODIN_OS == .OpenBSD { + @(private) + _SS_PAD1SIZE :: 6 + @(private) + _SS_PAD2SIZE :: 240 + } else when ODIN_OS == .Linux { + @(private) + _SS_SIZE :: 128 + @(private) + _SS_PADSIZE :: _SS_SIZE - size_of(c.uint16_t) - size_of(c.uint64_t) + } else { + @(private) + _SS_MAXSIZE :: 128 + @(private) + _SS_ALIGNSIZE :: size_of(c.int64_t) + @(private) + _SS_PAD1SIZE :: _SS_ALIGNSIZE - size_of(c.uint8_t) - size_of(sa_family_t) + @(private) + _SS_PAD2SIZE :: _SS_MAXSIZE - size_of(c.uint8_t) - size_of(sa_family_t) - _SS_PAD1SIZE - _SS_ALIGNSIZE + } + + when ODIN_OS == .Linux { + sockaddr_storage :: struct { + ss_family: sa_family_t, /* [PSX] address family */ + __ss_padding: [_SS_PADSIZE]c.char, + __ss_align: c.uint64_t, /* force structure storage alignment */ + } + + msghdr :: struct { + msg_name: rawptr, /* [PSX] optional address */ + msg_namelen: socklen_t, /* [PSX] size of address */ + msg_iov: [^]iovec, /* [PSX] scatter/gather array */ + msg_iovlen: c.size_t, /* [PSX] members in msg_iov */ + msg_control: rawptr, /* [PSX] ancillary data */ + msg_controllen: c.size_t, /* [PSX] ancillary data buffer length */ + msg_flags: Msg_Flags, /* [PSX] flags on received message */ + } + + cmsghdr :: struct { + cmsg_len: c.size_t, /* [PSX] data byte count, including cmsghdr */ + cmsg_level: c.int, /* [PSX] originating protocol */ + cmsg_type: c.int, /* [PSX] protocol-specific type */ + } + } else { + sockaddr_storage :: struct { + ss_len: c.uint8_t, /* address length */ + ss_family: sa_family_t, /* [PSX] address family */ + __ss_pad1: [_SS_PAD1SIZE]c.char, + __ss_align: c.int64_t, /* force structure storage alignment */ + __ss_pad2: [_SS_PAD2SIZE]c.char, + } + + msghdr :: struct { + msg_name: rawptr, /* [PSX] optional address */ + msg_namelen: socklen_t, /* [PSX] size of address */ + msg_iov: [^]iovec, /* [PSX] scatter/gather array */ + msg_iovlen: c.int, /* [PSX] members in msg_iov */ + msg_control: rawptr, /* [PSX] ancillary data */ + msg_controllen: socklen_t, /* [PSX] ancillary data buffer length */ + msg_flags: Msg_Flags, /* [PSX] flags on received message */ + } + + cmsghdr :: struct { + cmsg_len: socklen_t, /* [PSX] data byte count, including cmsghdr */ + cmsg_level: c.int, /* [PSX] originating protocol */ + cmsg_type: c.int, /* [PSX] protocol-specific type */ + } + } + + SCM_RIGHTS :: 0x01 + + @(private) + __ALIGN32 :: #force_inline proc "contextless" (p: uintptr) -> uintptr { + __ALIGNBYTES32 :: size_of(c.uint32_t) - 1 + return (p + __ALIGNBYTES32) &~ __ALIGNBYTES32 + } + + // Returns a pointer to the data array. + CMSG_DATA :: #force_inline proc "contextless" (cmsg: ^cmsghdr) -> [^]c.uchar { + return ([^]c.uchar)(uintptr(cmsg) + __ALIGN32(size_of(cmsghdr))) + } + + // Returns a pointer to the next cmsghdr or nil. + CMSG_NXTHDR :: #force_inline proc "contextless" (mhdr: ^msghdr, cmsg: ^cmsghdr) -> ^cmsghdr { + if cmsg == nil { + return CMSG_FIRSTHDR(mhdr) + } + + ptr := uintptr(cmsg) + __ALIGN32(uintptr(cmsg.cmsg_len)) + if ptr + __ALIGN32(size_of(cmsghdr)) > uintptr(mhdr.msg_control) + uintptr(mhdr.msg_controllen) { + return nil + } + + return (^cmsghdr)(ptr) + } + + // Returns a pointer to the first cmsghdr or nil. + CMSG_FIRSTHDR :: #force_inline proc "contextless" (mhdr: ^msghdr) -> ^cmsghdr { + if mhdr.msg_controllen >= size_of(cmsghdr) { + return (^cmsghdr)(mhdr.msg_control) + } + + return nil + } + + linger :: struct { + l_onoff: c.int, /* [PSX] indicates whether linger option is enabled */ + l_linger: c.int, /* [PSX] linger time in seconds */ + } + + SOCK_DGRAM :: 2 + SOCK_RAW :: 3 + SOCK_SEQPACKET :: 5 + SOCK_STREAM :: 1 + + // Options to be accessed at socket level, not protocol level. + when ODIN_OS == .Linux { + SOL_SOCKET :: 1 + + SO_ACCEPTCONN :: 30 + SO_BROADCAST :: 6 + SO_DEBUG :: 1 + SO_DONTROUTE :: 5 + SO_ERROR :: 4 + SO_KEEPALIVE :: 9 + SO_OOBINLINE :: 10 + SO_RCVBUF :: 8 + SO_RCVLOWAT :: 18 + SO_REUSEADDR :: 2 + SO_SNDBUF :: 7 + SO_SNDLOWAT :: 19 + SO_TYPE :: 3 + SO_LINGER :: 13 + + SO_RCVTIMEO :: 66 + SO_SNDTIMEO :: 67 + } else { + SOL_SOCKET :: 0xffff + + SO_ACCEPTCONN :: 0x0002 + SO_BROADCAST :: 0x0020 + SO_DEBUG :: 0x0001 + SO_DONTROUTE :: 0x0010 + SO_ERROR :: 0x1007 + SO_KEEPALIVE :: 0x0008 + SO_OOBINLINE :: 0x0100 + SO_RCVBUF :: 0x1002 + SO_RCVLOWAT :: 0x1004 + SO_REUSEADDR :: 0x0004 + SO_SNDBUF :: 0x1001 + SO_SNDLOWAT :: 0x1003 + SO_TYPE :: 0x1008 + + when ODIN_OS == .Darwin { + SO_LINGER :: 0x1080 + SO_RCVTIMEO :: 0x1006 + SO_SNDTIMEO :: 0x1005 + } else when ODIN_OS == .FreeBSD { + SO_LINGER :: 0x0080 + SO_RCVTIMEO :: 0x1006 + SO_SNDTIMEO :: 0x1005 + } else when ODIN_OS == .NetBSD { + SO_LINGER :: 0x0080 + SO_RCVTIMEO :: 0x100c + SO_SNDTIMEO :: 0x100b + } else when ODIN_OS == .OpenBSD { + SO_LINGER :: 0x0080 + SO_RCVTIMEO :: 0x1006 + SO_SNDTIMEO :: 0x1005 + } + } + + // The maximum backlog queue length for listen(). + SOMAXCONN :: 128 + + when ODIN_OS == .Linux { + MSG_CTRUNC :: 0x008 + MSG_DONTROUTE :: 0x004 + MSG_EOR :: 0x080 + MSG_OOB :: 0x001 + MSG_PEEK :: 0x002 + MSG_TRUNC :: 0x020 + MSG_WAITALL :: 0x100 + MSG_NOSIGNAL :: 0x4000 + } else { + MSG_CTRUNC :: 0x20 + MSG_DONTROUTE :: 0x4 + MSG_EOR :: 0x8 + MSG_OOB :: 0x1 + MSG_PEEK :: 0x2 + MSG_TRUNC :: 0x10 + MSG_WAITALL :: 0x40 + + when ODIN_OS == .Darwin { + MSG_NOSIGNAL :: 0x80000 + } else when ODIN_OS == .FreeBSD { + MSG_NOSIGNAL :: 0x00020000 + } else when ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + MSG_NOSIGNAL :: 0x0400 + } + } + + AF_INET :: 2 + AF_UNIX :: 1 + + when ODIN_OS == .Darwin { + AF_INET6 :: 30 + } else when ODIN_OS == .FreeBSD { + AF_INET6 :: 28 + } else when ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + AF_INET6 :: 24 + } else when ODIN_OS == .Linux { + AF_INET6 :: 10 + } + + SHUT_RD :: 0 + SHUT_RDWR :: 2 + SHUT_WR :: 1 + +} diff --git a/core/sys/posix/sys_stat.odin b/core/sys/posix/sys_stat.odin new file mode 100644 index 000000000..61b98ef35 --- /dev/null +++ b/core/sys/posix/sys_stat.odin @@ -0,0 +1,485 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/stat.h - data returned by the stat() function + +foreign lib { + + /* + Equivalent to either stat or lstat (based on the SYMLINK_NOFOLLOW bit in flags) + but resolves relative paths based on the given fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatat.html ]] + */ + @(link_name="fstatat" + INODE_SUFFIX) + fstatat :: proc(fd: FD, path: cstring, buf: ^stat_t, flag: AT_Flags) -> result --- + + /* + Obtain information about a "file" at the given path. + + Follows symbolic links. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatat.html ]] + */ + @(link_name=LSTAT) + stat :: proc(path: cstring, buf: ^stat_t) -> result --- + + /* + Obtain information about an open file. + + Follows symbol links. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstat.html ]] + */ + @(link_name=LFSTAT) + fstat :: proc(fildes: FD, buf: ^stat_t) -> result --- + + /* + Obtain information about a "file" at the given path. + + Does not follow symlinks (will stat the symlink itself). + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatat.html ]] + */ + @(link_name=LLSTAT) + lstat :: proc(path: cstring, buf: ^stat_t) -> result --- + + /* + Change the mode of a file. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/chmod.html ]] + */ + chmod :: proc(path: cstring, mode: mode_t) -> result --- + + /* + Equivalent to chmod but takes an open file descriptor. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchmod.html ]] + */ + fchmod :: proc(fd: FD, mode: mode_t) -> result --- + + /* + Equivalent to chmod but follows (or doesn't) symlinks based on the flag and resolves + relative paths from the given fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/chmod.html ]] + */ + fchmodat :: proc(fd: FD, path: cstring, mode: mode_t, flag: AT_Flags) -> result --- + + /* + Make a directory. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdir.html ]] + */ + mkdir :: proc(path: cstring, mode: mode_t) -> result --- + + /* + Equivalent to mkdir but relative paths are relative to fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdir.html ]] + */ + mkdirat :: proc(fd: FD, path: cstring, mode: mode_t) -> result --- + + /* + Make a FIFO special file. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkfifo.html ]] + */ + mkfifo :: proc(path: cstring, mode: mode_t) -> result --- + + /* + Equivalent to mkfifo but relative paths are relative to fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkfifo.html ]] + */ + mkfifoat :: proc(fd: FD, path: cstring, mode: mode_t) -> result --- + + /* + Make directory, special file, or regular file. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mknodat.html ]] + */ + @(link_name=LMKNOD) + mknod :: proc(path: cstring, mode: mode_t, dev: dev_t) -> result --- + + /* + Equivalent to mknod but relative paths are relative to fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/mknodat.html ]] + */ + mknodat :: proc(fd: FD, path: cstring, mode: mode_t, dev: dev_t) -> result --- + + /* + Sets the file access and modification time of the given file descriptor. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html ]] + */ + futimens :: proc(fd: FD, times: ^[2]timespec) -> result --- + + /* + Equivalent to futimens. + Relative directories are based on fd. + Symlinks may or may not be followed based on the flags. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html ]] + */ + utimensat :: proc(fd: FD, path: cstring, times: ^[2]timespec, flag: AT_Flags) -> result --- + + /* + Set and get the file mode creation flags. + + Makes the file mode permissions bits in cmask the new default for the process. + + Returns: the previous value + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/umask.html ]] + */ + umask :: proc(cmask: mode_t) -> mode_t --- +} + +// Read, write, execute user. +S_IRWXU :: mode_t{ .IRUSR, .IWUSR, .IXUSR } +// Read, write, execute group. +S_IRWXG :: mode_t{ .IRGRP, .IWGRP, .IXGRP } +// Read, write, execute other. +S_IRWXO :: mode_t{ .IROTH, .IWOTH, .IXOTH } + +Mode_Bits :: enum c.int { + // File type: + + IFCHR = log2(_S_IFCHR), /* Character special */ + IFIFO = log2(_S_IFIFO), /* FIFO special */ + IFREG = log2(_S_IFREG), /* Regular */ + IFDIR = log2(_S_IFDIR), /* Directory */ + + // Permissions: + + IRUSR = log2(_S_IRUSR), /* R for owner */ + IWUSR = log2(_S_IWUSR), /* W for owner */ + IXUSR = log2(_S_IXUSR), /* X for owner */ + + IRGRP = log2(_S_IRGRP), /* R for group */ + IWGRP = log2(_S_IWGRP), /* W for group */ + IXGRP = log2(_S_IXGRP), /* X for group */ + + IROTH = log2(_S_IROTH), /* R for other */ + IWOTH = log2(_S_IWOTH), /* W for other */ + IXOTH = log2(_S_IXOTH), /* X for other */ + + ISUID = log2(_S_ISUID), /* Set user ID on execution */ + ISGID = log2(_S_ISGID), /* Set group ID on execution */ + ISVXT = log2(_S_ISVTX), /* On directories, restricted deletion flag */ +} +mode_t :: bit_set[Mode_Bits; _mode_t] +#assert(size_of(mode_t) == size_of(_mode_t)) + +S_IFMT :: mode_t{ .IFCHR, .IFREG, .IFDIR, .IFIFO } +S_IFSOCK :: mode_t{ .IFREG, .IFDIR } +S_IFLNK :: mode_t{ .IFREG, .IFCHR } +S_IFBLK :: mode_t{ .IFDIR, .IFCHR } +S_IFIFO :: mode_t{ .IFIFO } +S_IFCHR :: mode_t{ .IFCHR } +S_IFDIR :: mode_t{ .IFDIR } +S_IFREG :: mode_t{ .IFREG } + +#assert(_S_IFMT == _S_IFCHR|_S_IFREG|_S_IFDIR|_S_IFIFO) +#assert(_S_IFSOCK == _S_IFREG|_S_IFDIR) +#assert(_S_IFLNK == _S_IFREG|_S_IFCHR) +#assert(_S_IFBLK == _S_IFDIR|_S_IFCHR) + +// Test for a block special file. +S_ISBLK :: #force_inline proc "contextless" (m: mode_t) -> bool { + return (m & S_IFMT) == S_IFBLK +} + +// Test for a character special file. +S_ISCHR :: #force_inline proc "contextless" (m: mode_t) -> bool { + return (m & S_IFMT) == S_IFCHR +} + +// Test for a pipe or FIFO special file. +S_ISFIFO :: #force_inline proc "contextless" (m: mode_t) -> bool { + return (m & S_IFMT) == S_IFIFO +} + +// Test for a regular file. +S_ISREG :: #force_inline proc "contextless" (m: mode_t) -> bool { + return (m & S_IFMT) == S_IFREG +} + +// Test for a directory. +S_ISDIR :: #force_inline proc "contextless" (m: mode_t) -> bool { + return (m & S_IFMT) == S_IFDIR +} + +// Test for a symbolic link. +S_ISLNK :: #force_inline proc "contextless" (m: mode_t) -> bool { + return (m & S_IFMT) == S_IFLNK +} + +// Test for a socket. +S_ISSOCK :: #force_inline proc "contextless" (m: mode_t) -> bool { + return (m & S_IFMT) == S_IFSOCK +} + +_S_IRWXU :: 0o000700 +_S_IRUSR :: 0o000400 +_S_IWUSR :: 0o000200 +_S_IXUSR :: 0o000100 + +_S_IRWXG :: 0o000070 +_S_IRGRP :: 0o000040 +_S_IWGRP :: 0o000020 +_S_IXGRP :: 0o000010 + +_S_IRWXO :: 0o000007 +_S_IROTH :: 0o000004 +_S_IWOTH :: 0o000002 +_S_IXOTH :: 0o000001 + +_S_ISUID :: 0o004000 +_S_ISGID :: 0o002000 +_S_ISVTX :: 0o001000 + +_S_IFBLK :: 0o060000 +_S_IFCHR :: 0o020000 +_S_IFIFO :: 0o010000 +_S_IFREG :: 0o100000 +_S_IFDIR :: 0o040000 +_S_IFLNK :: 0o120000 +_S_IFSOCK :: 0o140000 + +_S_IFMT :: 0o170000 + +when ODIN_OS == .NetBSD { + @(private) LSTAT :: "__stat50" + @(private) LFSTAT :: "__fstat50" + @(private) LLSTAT :: "__lstat50" + @(private) LMKNOD :: "__mknod50" +} else { + @(private) LSTAT :: "stat" + INODE_SUFFIX + @(private) LFSTAT :: "fstat" + INODE_SUFFIX + @(private) LLSTAT :: "lstat" + INODE_SUFFIX + @(private) LMKNOD :: "mknod" +} + +when ODIN_OS == .Darwin { + + dev_t :: distinct c.int32_t + nlink_t :: distinct c.uint16_t + _mode_t :: distinct c.uint16_t + blkcnt_t :: distinct c.int64_t + blksize_t :: distinct c.int32_t + ino_t :: distinct c.uint64_t + + stat_t :: struct { + st_dev: dev_t, /* [PSX] ID of device containing file */ + st_mode: mode_t, /* [PSX] mode of file */ + st_nlink: nlink_t, /* [PSX] number of hard links */ + st_ino: ino_t, /* [PSX] file serial number */ + st_uid: uid_t, /* [PSX] user ID of the file */ + st_gid: gid_t, /* [PSX] group ID of the file */ + st_rdev: dev_t, /* [PSX] device ID */ + st_atim: timespec, /* [PSX] time of last access */ + st_mtim: timespec, /* [PSX] time of last data modification */ + st_ctim: timespec, /* [PSX] time of last status change */ + st_birthtimespec: timespec, /* time of file creation(birth) */ + st_size: off_t, /* [PSX] file size, in bytes */ + st_blocks: blkcnt_t, /* [PSX] blocks allocated for file */ + st_blksize: blksize_t, /* [PSX] optimal blocksize for I/O */ + st_flags: c.uint32_t, /* user defined flags for file */ + st_gen: c.uint32_t, /* file generation number */ + st_lspare: c.int32_t, /* RESERVED */ + st_qspare: [2]c.int64_t, /* RESERVED */ + } + + UTIME_NOW :: -1 + UTIME_OMIT :: -2 + +} else when ODIN_OS == .FreeBSD { + + dev_t :: distinct c.uint64_t + nlink_t :: distinct c.uint64_t + _mode_t :: distinct c.uint16_t + blkcnt_t :: distinct c.int64_t + blksize_t :: distinct c.int32_t + ino_t :: distinct c.uint64_t + + when ODIN_ARCH == .i386 { + stat_t :: struct { + st_dev: dev_t, /* [PSX] ID of device containing file */ + st_ino: ino_t, /* [PSX] file serial number */ + st_nlink: nlink_t, /* [PSX] number of hard links */ + st_mode: mode_t, /* [PSX] mode of file */ + st_padding0: c.int16_t, + st_uid: uid_t, /* [PSX] user ID of the file */ + st_gid: gid_t, /* [PSX] group ID of the file */ + st_padding1: c.int32_t, + st_rdev: dev_t, /* [PSX] device ID */ + st_atim_ext: c.int32_t, + st_atim: timespec, /* [PSX] time of last access */ + st_mtim_ext: c.int32_t, + st_mtim: timespec, /* [PSX] time of last data modification */ + st_ctim_ext: c.int32_t, + st_ctim: timespec, /* [PSX] time of last status change */ + st_birthtimespec: timespec, /* time of file creation(birth) */ + st_size: off_t, /* [PSX] file size, in bytes */ + st_blocks: blkcnt_t, /* [PSX] blocks allocated for file */ + st_blksize: blksize_t, /* [PSX] optimal blocksize for I/O */ + st_flags: c.uint32_t, /* user defined flags for file */ + st_gen: c.uint64_t, + st_spare: [10]c.uint64_t, + } + } else { + stat_t :: struct { + st_dev: dev_t, /* [PSX] ID of device containing file */ + st_ino: ino_t, /* [PSX] file serial number */ + st_nlink: nlink_t, /* [PSX] number of hard links */ + st_mode: mode_t, /* [PSX] mode of file */ + st_padding0: c.int16_t, + st_uid: uid_t, /* [PSX] user ID of the file */ + st_gid: gid_t, /* [PSX] group ID of the file */ + st_padding1: c.int32_t, + st_rdev: dev_t, /* [PSX] device ID */ + st_atim: timespec, /* [PSX] time of last access */ + st_mtim: timespec, /* [PSX] time of last data modification */ + st_ctim: timespec, /* [PSX] time of last status change */ + st_birthtimespec: timespec, /* time of file creation(birth) */ + st_size: off_t, /* [PSX] file size, in bytes */ + st_blocks: blkcnt_t, /* [PSX] blocks allocated for file */ + st_blksize: blksize_t, /* [PSX] optimal blocksize for I/O */ + st_flags: c.uint32_t, /* user defined flags for file */ + st_gen: c.uint64_t, + st_spare: [10]c.uint64_t, + } + } + + UTIME_NOW :: -1 + UTIME_OMIT :: -2 + +} else when ODIN_OS == .NetBSD { + + dev_t :: distinct c.uint64_t + nlink_t :: distinct c.uint32_t + _mode_t :: distinct c.uint32_t + blkcnt_t :: distinct c.int64_t + blksize_t :: distinct c.int32_t + ino_t :: distinct c.uint64_t + + stat_t :: struct { + st_dev: dev_t, /* [PSX] ID of device containing file */ + st_mode: mode_t, /* [PSX] mode of file */ + st_ino: ino_t, /* [PSX] file serial number */ + st_nlink: nlink_t, /* [PSX] number of hard links */ + st_uid: uid_t, /* [PSX] user ID of the file */ + st_gid: gid_t, /* [PSX] group ID of the file */ + st_rdev: dev_t, /* [PSX] device ID */ + st_atim: timespec, /* [PSX] time of last access */ + st_mtim: timespec, /* [PSX] time of last data modification */ + st_ctim: timespec, /* [PSX] time of last status change */ + st_birthtimespec: timespec, /* time of file creation(birth) */ + st_size: off_t, /* [PSX] file size, in bytes */ + st_blocks: blkcnt_t, /* [PSX] blocks allocated for file */ + st_blksize: blksize_t, /* [PSX] optimal blocksize for I/O */ + st_flags: c.uint32_t, /* user defined flags for file */ + st_gen: c.uint64_t, + st_spare: [2]c.uint32_t, + } + + UTIME_NOW :: (1 << 30) - 1 + UTIME_OMIT :: (1 << 30) - 2 + +} else when ODIN_OS == .OpenBSD { + + dev_t :: distinct c.int32_t + nlink_t :: distinct c.uint32_t + _mode_t :: distinct c.uint32_t + blkcnt_t :: distinct c.int64_t + blksize_t :: distinct c.int32_t + ino_t :: distinct c.uint64_t + + stat_t :: struct { + st_mode: mode_t, /* [PSX] mode of file */ + st_dev: dev_t, /* [PSX] ID of device containing file */ + st_ino: ino_t, /* [PSX] file serial number */ + st_nlink: nlink_t, /* [PSX] number of hard links */ + st_uid: uid_t, /* [PSX] user ID of the file */ + st_gid: gid_t, /* [PSX] group ID of the file */ + st_rdev: dev_t, /* [PSX] device ID */ + st_atim: timespec, /* [PSX] time of last access */ + st_mtim: timespec, /* [PSX] time of last data modification */ + st_ctim: timespec, /* [PSX] time of last status change */ + st_size: off_t, /* [PSX] file size, in bytes */ + st_blocks: blkcnt_t, /* [PSX] blocks allocated for file */ + st_blksize: blksize_t, /* [PSX] optimal blocksize for I/O */ + st_flags: c.uint32_t, /* user defined flags for file */ + st_gen: c.int32_t, + st_birthtimespec: timespec, + } + + UTIME_NOW :: -2 + UTIME_OMIT :: -1 + +} else when ODIN_OS == .Linux { + + dev_t :: distinct u64 + _mode_t :: distinct c.uint + blkcnt_t :: distinct i64 + + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { + nlink_t :: distinct c.uint + blksize_t :: distinct c.int + } else { + nlink_t :: distinct c.size_t + blksize_t :: distinct c.long + } + + ino_t :: distinct u64 + + when ODIN_ARCH == .amd64 { + stat_t :: struct { + st_dev: dev_t, /* [PSX] ID of device containing file */ + st_ino: ino_t, /* [PSX] file serial number */ + st_nlink: nlink_t, /* [PSX] number of hard links */ + st_mode: mode_t, /* [PSX] mode of file */ + st_uid: uid_t, /* [PSX] user ID of the file */ + st_gid: gid_t, /* [PSX] group ID of the file */ + _pad0: c.uint, + st_rdev: dev_t, /* [PSX] device ID */ + st_size: off_t, /* [PSX] file size, in bytes */ + st_blksize: blksize_t, /* [PSX] optimal blocksize for I/O */ + st_blocks: blkcnt_t, /* [PSX] blocks allocated for file */ + st_atim: timespec, /* [PSX] time of last access */ + st_mtim: timespec, /* [PSX] time of last data modification */ + st_ctim: timespec, /* [PSX] time of last status change */ + __unused: [3]c.long, + } + } else { + stat_t :: struct { + st_dev: dev_t, /* [PSX] ID of device containing file */ + st_ino: ino_t, /* [PSX] file serial number */ + st_mode: mode_t, /* [PSX] mode of file */ + st_nlink: nlink_t, /* [PSX] number of hard links */ + st_uid: uid_t, /* [PSX] user ID of the file */ + st_gid: gid_t, /* [PSX] group ID of the file */ + st_rdev: dev_t, /* [PSX] device ID */ + __pad: c.ulonglong, + st_size: off_t, /* [PSX] file size, in bytes */ + st_blksize: blksize_t, /* [PSX] optimal blocksize for I/O */ + __pad2: c.int, + st_blocks: blkcnt_t, /* [PSX] blocks allocated for file */ + st_atim: timespec, /* [PSX] time of last access */ + st_mtim: timespec, /* [PSX] time of last data modification */ + st_ctim: timespec, /* [PSX] time of last status change */ + __unused: [2]c.uint, + } + } +} diff --git a/core/sys/posix/sys_statvfs.odin b/core/sys/posix/sys_statvfs.odin new file mode 100644 index 000000000..47c810135 --- /dev/null +++ b/core/sys/posix/sys_statvfs.odin @@ -0,0 +1,157 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/statvfs.h - VFS File System information structure + +foreign lib { + + /* + Obtains information about the file system containing the fildes. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/statvfs.html ]] + */ + @(link_name=LFSTATVFS) + fstatvfs :: proc(fildes: FD, buf: ^statvfs_t) -> result --- + + /* + Obtains information about the file system containing the file named by path. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/statvfs.html ]] + */ + @(link_name=LSTATVFS) + statvfs :: proc(path: cstring, buf: ^statvfs_t) -> result --- +} + +VFS_Flag_Bits :: enum c.ulong { + // Read-only file system. + RDONLY = log2(ST_RDONLY), + // Does not support the semantics of the ST_ISUID and ST_ISGID file mode bits. + NOSUID = log2(ST_NOSUID), +} +VFS_Flags :: bit_set[VFS_Flag_Bits; c.ulong] + +when ODIN_OS == .NetBSD { + @(private) LFSTATVFS :: "__fstatvfs90" + @(private) LSTATVFS :: "__statvfs90" +} else { + @(private) LFSTATVFS :: "fstatvfs" + @(private) LSTATVFS :: "statvfs" +} + +when ODIN_OS == .Darwin || ODIN_OS == .OpenBSD { + + fsblkcnt_t :: distinct c.uint + + statvfs_t :: struct { + f_bsize: c.ulong, /* [PSX] file system block size */ + f_frsize: c.ulong, /* [PSX] fundamental file system block size */ + f_blocks: fsblkcnt_t, /* [PSX] total number of blocks on file system in units of f_frsize */ + f_bfree: fsblkcnt_t, /* [PSX] total number of free blocks */ + f_bavail: fsblkcnt_t, /* [PSX] number of free blocks available to non-privileged process */ + f_files: fsblkcnt_t, /* [PSX] total number of file serial numbers */ + f_ffree: fsblkcnt_t, /* [PSX] total number of free file serial numbers */ + f_favail: fsblkcnt_t, /* [PSX] number of file serial numbers available to non-privileged process */ + f_fsid: c.ulong, /* [PSX] file system ID */ + f_flag: VFS_Flags, /* [PSX] bit mask of f_flag values */ + f_namemax: c.ulong, /* [PSX] maximum filename length */ + } + + ST_RDONLY :: 0x00000001 + ST_NOSUID :: 0x00000002 + +} else when ODIN_OS == .FreeBSD { + + fsblkcnt_t :: distinct c.uint64_t + + statvfs_t :: struct { + f_bavail: fsblkcnt_t, /* [PSX] number of free blocks available to non-privileged process */ + f_bfree: fsblkcnt_t, /* [PSX] total number of free blocks */ + f_blocks: fsblkcnt_t, /* [PSX] total number of blocks on file system in units of f_frsize */ + f_favail: fsblkcnt_t, /* [PSX] number of file serial numbers available to non-privileged process */ + f_ffree: fsblkcnt_t, /* [PSX] total number of free file serial numbers */ + f_files: fsblkcnt_t, /* [PSX] total number of file serial numbers */ + f_bsize: c.ulong, /* [PSX] file system block size */ + f_flag: VFS_Flags, /* [PSX] bit mask of f_flag values */ + f_frsize: c.ulong, /* [PSX] fundamental file system block size */ + f_fsid: c.ulong, /* [PSX] file system ID */ + f_namemax: c.ulong, /* [PSX] maximum filename length */ + } + + ST_RDONLY :: 0x00000001 + ST_NOSUID :: 0x00000002 + +} else when ODIN_OS == .NetBSD { + + fsblkcnt_t :: distinct c.uint64_t + + @(private) + _VFS_NAMELEN :: 1024 + + @(private) + fsid_t :: struct { + __fsid_val: [2]c.int, + } + + statvfs_t :: struct { + f_flag: VFS_Flags, /* [PSX] bit mask of f_flag values */ + f_bsize: c.ulong, /* [PSX] file system block size */ + f_frsize: c.ulong, /* [PSX] fundamental file system block size */ + f_iosize: c.ulong, + f_blocks: fsblkcnt_t, /* [PSX] total number of blocks on file system in units of f_frsize */ + f_bfree: fsblkcnt_t, /* [PSX] total number of free blocks */ + f_bavail: fsblkcnt_t, /* [PSX] number of free blocks available to non-privileged process */ + f_bresvd: fsblkcnt_t, + f_files: fsblkcnt_t, /* [PSX] total number of file serial numbers */ + f_ffree: fsblkcnt_t, /* [PSX] total number of free file serial numbers */ + f_favail: fsblkcnt_t, /* [PSX] number of file serial numbers available to non-privileged process */ + f_fresvd: fsblkcnt_t, + f_syncreads: c.uint64_t, + f_syncwrites: c.uint64_t, + f_asyncreads: c.uint64_t, + f_asyncwrites: c.uint64_t, + f_fsidx: fsid_t, + f_fsid: c.ulong, /* [PSX] file system ID */ + f_namemax: c.ulong, /* [PSX] maximum filename length */ + f_owner: uid_t, + f_spare: [4]c.uint64_t, + f_fstypename: [_VFS_NAMELEN]c.char `fmt:"s,0"`, + f_mntonname: [_VFS_NAMELEN]c.char `fmt:"s,0"`, + f_mntfromname: [_VFS_NAMELEN]c.char `fmt:"s,0"`, + f_mntfromlabel: [_VFS_NAMELEN]c.char `fmt:"s,0"`, + } + + ST_RDONLY :: 0x00000001 + ST_NOSUID :: 0x00000008 + +} else when ODIN_OS == .Linux { + + fsblkcnt_t :: distinct c.uint64_t + + statvfs_t :: struct { + f_bsize: c.ulong, /* [PSX] file system block size */ + f_frsize: c.ulong, /* [PSX] fundamental file system block size */ + f_blocks: fsblkcnt_t, /* [PSX] total number of blocks on file system in units of f_frsize */ + f_bfree: fsblkcnt_t, /* [PSX] total number of free blocks */ + f_bavail: fsblkcnt_t, /* [PSX] number of free blocks available to non-privileged process */ + f_files: fsblkcnt_t, /* [PSX] total number of file serial numbers */ + f_ffree: fsblkcnt_t, /* [PSX] total number of free file serial numbers */ + f_favail: fsblkcnt_t, /* [PSX] number of file serial numbers available to non-privileged process */ + f_fsid: c.ulong, /* [PSX] file system ID */ + _: [2*size_of(c.int)-size_of(c.long)]byte, + f_flag: VFS_Flags, /* [PSX] bit mask of f_flag values */ + f_namemax: c.ulong, /* [PSX] maximum filename length */ + f_type: c.uint, + __reserved: [5]c.int, + } + + ST_RDONLY :: 0x00000001 + ST_NOSUID :: 0x00000002 +} diff --git a/core/sys/posix/sys_time.odin b/core/sys/posix/sys_time.odin new file mode 100644 index 000000000..3036352aa --- /dev/null +++ b/core/sys/posix/sys_time.odin @@ -0,0 +1,81 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/time.h - time types + +foreign lib { + /* + Store the current value of timer into value. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getitimer.html ]] + */ + @(link_name=LGETITIMER) + getitimer :: proc(which: ITimer, value: ^itimerval) -> result --- + + /* + Set the timer to the value given, and store the previous value in ovalue if it is not nil. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getitimer.html ]] + */ + @(link_name=LSETITIMER) + setitimer :: proc(which: ITimer, value: ^itimerval, ovalue: ^itimerval) -> result --- + + /* + Obtains the current time. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/gettimeofday.html ]] + */ + @(link_name=LGETTIMEOFDAY) + gettimeofday :: proc(tp: ^timeval, tzp: rawptr = nil) -> result --- + + /* + Sets the access and modification times of the file at the given path. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/utimes.html ]] + */ + @(link_name=LUTIMES) + utimes :: proc(path: cstring, times: ^[2]timeval) -> result --- +} + +ITimer :: enum c.int { + // Decrements in real time. + REAL = ITIMER_REAL, + // Decrements in process virtual time, only when the process is executing. + VIRTUAL = ITIMER_VIRTUAL, + // Decrements both in process virtual time and when the system is running on + // behalf of the process. + PROF = ITIMER_PROF, +} + +when ODIN_OS == .NetBSD { + @(private) LGETITIMER :: "__getitimer50" + @(private) LSETITIMER :: "__setitimer50" + @(private) LGETTIMEOFDAY :: "__gettimeofday50" + @(private) LUTIMES :: "__utimes50" +} else { + @(private) LGETITIMER :: "getitimer" + @(private) LSETITIMER :: "setitimer" + @(private) LGETTIMEOFDAY :: "gettimeofday" + @(private) LUTIMES :: "utimes" +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { + + itimerval :: struct { + it_interval: timeval, /* [PSX] timer interval */ + it_value: timeval, /* [PSX] current value */ + } + + ITIMER_REAL :: 0 + ITIMER_VIRTUAL :: 1 + ITIMER_PROF :: 2 + +} diff --git a/core/sys/posix/sys_times.odin b/core/sys/posix/sys_times.odin new file mode 100644 index 000000000..113e3f963 --- /dev/null +++ b/core/sys/posix/sys_times.odin @@ -0,0 +1,37 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/times.h - file access and modification times structure + +foreign lib { + /* + Get time accounting information. + + Returns: -1 (setting errno) on failure, the elapsed real time, since an arbitrary point in the past + */ + @(link_name=LTIMES) + times :: proc(buffer: ^tms) -> clock_t --- +} + +when ODIN_OS == .NetBSD { + @(private) LTIMES :: "__times13" +} else { + @(private) LTIMES :: "times" +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { + + tms :: struct { + tms_utime: clock_t, /* [PSX] user CPU time */ + tms_stime: clock_t, /* [PSX] system CPU time */ + tms_cutime: clock_t, /* [PSX] terminated children user CPU time */ + tms_cstime: clock_t, /* [PSX] terminated children system CPU time */ + } + +} diff --git a/core/sys/posix/sys_uio.odin b/core/sys/posix/sys_uio.odin new file mode 100644 index 000000000..a0ad2934e --- /dev/null +++ b/core/sys/posix/sys_uio.odin @@ -0,0 +1,41 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import libc "system:System.framework" +} else { + foreign import libc "system:c" +} + +// sys/uio.h - definitions for vector I/O operations + +foreign libc { + /* + Equivalent to read() but takes a vector of inputs. + + iovcnt can be 0..=IOV_MAX in length. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/readv.html ]] + */ + readv :: proc(fildes: FD, iov: [^]iovec, iovcnt: c.int) -> c.ssize_t --- + + /* + Equivalent to write() but takes a vector of inputs. + + iovcnt can be 0..=IOV_MAX in length. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/readv.html ]] + */ + writev :: proc(fildes: FD, iov: [^]iovec, iovcnt: c.int) -> c.ssize_t --- +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { + + iovec :: struct { + iov_base: rawptr, /* [PSX] base address of I/O memory region */ + iov_len: c.size_t, /* [PSX] size of the region iov_base points to */ + } + +} diff --git a/core/sys/posix/sys_un.odin b/core/sys/posix/sys_un.odin new file mode 100644 index 000000000..ca5c4ee31 --- /dev/null +++ b/core/sys/posix/sys_un.odin @@ -0,0 +1,23 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +// sys/un.h = definitions for UNIX domain sockets + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + sockaddr_un :: struct { + sun_len: c.uchar, /* sockaddr len including nil */ + sun_family: sa_family_t, /* [PSX] address family */ + sun_path: [104]c.char, /* [PSX] socket pathname */ + } + +} else when ODIN_OS == .Linux { + + sockaddr_un :: struct { + sun_family: sa_family_t, /* [PSX] address family */ + sun_path: [108]c.char, /* [PSX] socket pathname */ + } + +} diff --git a/core/sys/posix/sys_utsname.odin b/core/sys/posix/sys_utsname.odin new file mode 100644 index 000000000..64930160f --- /dev/null +++ b/core/sys/posix/sys_utsname.odin @@ -0,0 +1,67 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/utsname.h = system name structure + +foreign lib { + /* + Stores information identifying the current system in the given structure. + + Returns: non-negative on success, -1 (setting errno) on failure + + NOTE: have a look at `core:sys/info` for similar/better system information. + + Example: + uname: posix.utsname + posix.uname(&uname) + fmt.printfln("%#v", uname) + + Possible Output: + utsname{ + sysname = Darwin, + nodename = Laytans-MacBook-Pro.local, + release = 23.5.0, + version = Darwin Kernel Version 23.5.0: Wed May 1 20:16:51 PDT 2024; root:xnu-11331.111.3~1/RELEASE_ARM64_T8103, + machine = arm64, + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/uname.html ]] + */ + uname :: proc(uname: ^utsname) -> c.int --- +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + @(private) + _SYS_NAMELEN :: 256 + + utsname :: struct { + sysname: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] name of OS */ + nodename: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] name of this network node */ + release: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] release level */ + version: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] version level */ + machine: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] hardware type */ + } + +} else when ODIN_OS == .Linux { + + @(private) + _SYS_NAMELEN :: 65 + + utsname :: struct { + sysname: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] name of OS */ + nodename: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] name of this network node */ + release: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] release level */ + version: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] version level */ + machine: [_SYS_NAMELEN]c.char `fmt:"s,0"`, /* [PSX] hardware type */ + __domainname: [_SYS_NAMELEN]c.char `fmt:"s,0"`, + } +} diff --git a/core/sys/posix/sys_wait.odin b/core/sys/posix/sys_wait.odin new file mode 100644 index 000000000..812bd8c62 --- /dev/null +++ b/core/sys/posix/sys_wait.odin @@ -0,0 +1,445 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// sys/wait.h - declarations for waiting + +foreign lib { + /* + Obtains status information pertaining to one of the caller's child processes. + + Returns: -1 (setting errno) on failure or signal on calling process, the pid of the process that caused the return otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html ]] + */ + wait :: proc(stat_loc: ^c.int) -> pid_t --- + + /* + Obtains status information pertaining to the given pid specifier. + + If pid is -1, status is requested for any child process. + If pid is greater than 0, it specifies the process ID of a single child process. + If pid is 0, it specifies any child process whose process group ID is equal to that of the call. + If pid is < -1, status is requested for any child whose process group ID is the absolute value of pid. + + Returns: -1 (setting errno) on failure or signal on calling process, 0 if NOHANG and status is not available, the pid of the process that caused the return otherwise + + Example: + // The following example demonstrates the use of waitpid(), fork(), and the macros used to + // interpret the status value returned by waitpid() (and wait()). The code segment creates a + // child process which does some unspecified work. Meanwhile the parent loops performing calls + // to waitpid() to monitor the status of the child. The loop terminates when child termination + // is detected. + + child_pid := posix.fork(); switch child_pid { + case -1: // `fork` failed. + panic("fork failed") + + case 0: // This is the child. + + // Do some work... + + case: + for { + status: i32 + wpid := posix.waitpid(child_pid, &status, { .UNTRACED, .CONTINUED }) + if wpid == -1 { + panic("waitpid failure") + } + + switch { + case posix.WIFEXITED(status): + fmt.printfln("child exited, status=%v", posix.WEXITSTATUS(status)) + case posix.WIFSIGNALED(status): + fmt.printfln("child killed (signal %v)", posix.WTERMSIG(status)) + case posix.WIFSTOPPED(status): + fmt.printfln("child stopped (signal %v", posix.WSTOPSIG(status)) + case posix.WIFCONTINUED(status): + fmt.println("child continued") + case: + // Should never happen. + fmt.println("unexpected status (%x)", status) + } + + if posix.WIFEXITED(status) || posix.WIFSIGNALED(status) { + break + } + } + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html ]] + */ + waitpid :: proc(pid: pid_t, stat_loc: ^c.int, options: Wait_Flags) -> pid_t --- + + /* + Obtains status information pertaining to the given idtype_t and id specifier. + + Returns: 0 if WNOHANG and no status available, 0 if child changed state, -1 (setting errno) on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/waitid.html ]] + */ + waitid :: proc(idtype: idtype_t, id: id_t, infop: ^siginfo_t, options: Wait_Flags) -> c.int --- +} + +// If terminated normally. +WIFEXITED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WIFEXITED(x) +} + +// If WIFEXITED is true, returns the exit status. +WEXITSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return _WEXITSTATUS(x) +} + +// If terminated due to an uncaught signal. +WIFSIGNALED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WIFSIGNALED(x) +} + +// If WIFSIGNALED is true, returns the signal. +WTERMSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return _WTERMSIG(x) +} + +// If status was returned for a child process that is currently stopped. +WIFSTOPPED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WIFSTOPPED(x) +} + +// If WIFSTOPPED, the signal that caused the child process to stop. +WSTOPSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return _WSTOPSIG(x) +} + +// If status was returned for a child process that has continued from a job control stop. +WIFCONTINUED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WIFCONTINUED(x) +} + +idtype_t :: enum c.int { + // Wait for any children and `id` is ignored. + P_ALL = _P_ALL, + // Wait for any child wiith a process group ID equal to `id`. + P_PID = _P_PID, + // Wait for any child with a process group ID equal to `id`. + P_PGID = _P_PGID, +} + +Wait_Flag_Bits :: enum c.int { + // Report the status of any continued child process specified by pid whose status has not been + // reported since it continued from a job control stop. + CONTINUED = log2(WCONTINUED), + // Don't suspend execution of the calling thread if status is not immediately available for one + // of the child processes specified by pid. + NOHANG = log2(WNOHANG), + // The status of any child process specified by pid that are stopped, and whose status has not + // yet been reported since they stopped, shall also be reported to the requesting process. + UNTRACED = log2(WUNTRACED), + + // Following are only available on `waitid`, not `waitpid`. + + // Wait for processes that have exited. + EXITED = log2(WEXITED), + // Keep the process whose status is returned in a waitable state, so it may be waited on again. + NOWAIT = log2(WNOWAIT), + // Children that have stopped upon receipt of a signal, and whose status either hasn't been reported + // or has been reported but that report was called with NOWAIT. + STOPPED = log2(WSTOPPED), +} +Wait_Flags :: bit_set[Wait_Flag_Bits; c.int] + +when ODIN_OS == .Darwin { + + id_t :: distinct c.uint + + WCONTINUED :: 0x00000010 + WNOHANG :: 0x00000001 + WUNTRACED :: 0x00000002 + + WEXITED :: 0x00000004 + WNOWAIT :: 0x00000020 + WSTOPPED :: 0x00000008 + + _P_ALL :: 0 + _P_PID :: 1 + _P_PGID :: 2 + + @(private) + _WSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return x & 0o177 + } + + @(private) + _WSTOPPED :: 0o177 + + @(private) + _WIFEXITED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == 0 + } + + @(private) + _WEXITSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return x >> 8 + } + + @(private) + _WIFSIGNALED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) != _WSTOPPED && _WSTATUS(x) != 0 + } + + @(private) + _WTERMSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(_WSTATUS(x)) + } + + @(private) + _WIFSTOPPED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == _WSTOPPED && WSTOPSIG(x) != .SIGCONT + } + + @(private) + _WSTOPSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(x >> 8) + } + + @(private) + _WIFCONTINUED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == _WSTOPPED && WSTOPSIG(x) == .SIGCONT + } + +} else when ODIN_OS == .FreeBSD { + + id_t :: distinct c.int64_t + + WCONTINUED :: 4 + WNOHANG :: 1 + WUNTRACED :: 2 + + WEXITED :: 16 + WNOWAIT :: 8 + WSTOPPED :: 2 + + _P_ALL :: 7 + _P_PID :: 0 + _P_PGID :: 2 + + @(private) + _WSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return x & 0o177 + } + + @(private) + _WSTOPPED :: 0o177 + + @(private) + _WIFEXITED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == 0 + } + + @(private) + _WEXITSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return x >> 8 + } + + @(private) + _WIFSIGNALED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) != _WSTOPPED && _WSTATUS(x) != 0 && x != c.int(Signal.SIGCONT) + } + + @(private) + _WTERMSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(_WSTATUS(x)) + } + + @(private) + _WIFSTOPPED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == _WSTOPPED + } + + @(private) + _WSTOPSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(x >> 8) + } + + @(private) + _WIFCONTINUED :: #force_inline proc "contextless" (x: c.int) -> bool { + return x == c.int(Signal.SIGCONT) + } +} else when ODIN_OS == .NetBSD { + + id_t :: distinct c.uint32_t + + WCONTINUED :: 0x00000010 + WNOHANG :: 0x00000001 + WUNTRACED :: 0x00000002 + + WEXITED :: 0x00000020 + WNOWAIT :: 0x00010000 + WSTOPPED :: 0x00000002 + + _P_ALL :: 0 + _P_PID :: 1 + _P_PGID :: 2 + + @(private) + _WSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return x & 0o177 + } + + @(private) + _WSTOPPED :: 0o177 + + @(private) + _WIFEXITED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == 0 + } + + @(private) + _WEXITSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return c.int((c.uint(x) >> 8) & 0xff) + } + + @(private) + _WIFSIGNALED :: #force_inline proc "contextless" (x: c.int) -> bool { + return !WIFSTOPPED(x) && !WIFCONTINUED(x) && !WIFEXITED(x) + } + + @(private) + _WTERMSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(_WSTATUS(x)) + } + + @(private) + _WIFSTOPPED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == _WSTOPPED && !WIFCONTINUED(x) + } + + @(private) + _WSTOPSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(c.int((c.uint(x) >> 8) & 0xff)) + } + + @(private) + _WIFCONTINUED :: #force_inline proc "contextless" (x: c.int) -> bool { + return x == 0xffff + } + +} else when ODIN_OS == .OpenBSD { + + id_t :: distinct c.uint32_t + + WCONTINUED :: 0x00000010 + WNOHANG :: 0x00000001 + WUNTRACED :: 0x00000002 + + WEXITED :: 0x00000020 + WNOWAIT :: 0x00010000 + WSTOPPED :: 0x00000002 + + _P_ALL :: 0 + _P_PID :: 2 + _P_PGID :: 1 + + @(private) + _WSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return x & 0o177 + } + + @(private) + _WSTOPPED :: 0o177 + @(private) + _WCONTINUED :: 0o177777 + + @(private) + _WIFEXITED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) == 0 + } + + @(private) + _WEXITSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return (x >> 8) & 0x000000ff + } + + @(private) + _WIFSIGNALED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WSTATUS(x) != _WSTOPPED && _WSTATUS(x) != 0 + } + + @(private) + _WTERMSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(_WSTATUS(x)) + } + + @(private) + _WIFSTOPPED :: #force_inline proc "contextless" (x: c.int) -> bool { + return (x & 0xff) == _WSTOPPED + } + + @(private) + _WSTOPSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal((x >> 8) & 0xff) + } + + @(private) + _WIFCONTINUED :: #force_inline proc "contextless" (x: c.int) -> bool { + return (x & _WCONTINUED) == _WCONTINUED + } + +} else when ODIN_OS == .Linux { + + id_t :: distinct c.uint + + WCONTINUED :: 8 + WNOHANG :: 1 + WUNTRACED :: 2 + + WEXITED :: 4 + WNOWAIT :: 0x1000000 + WSTOPPED :: 2 + + _P_ALL :: 0 + _P_PID :: 1 + _P_PGID :: 2 + + @(private) + _WIFEXITED :: #force_inline proc "contextless" (x: c.int) -> bool { + return _WTERMSIG(x) == nil + } + + @(private) + _WEXITSTATUS :: #force_inline proc "contextless" (x: c.int) -> c.int { + return (x & 0xff00) >> 8 + } + + @(private) + _WIFSIGNALED :: #force_inline proc "contextless" (x: c.int) -> bool { + return (x & 0xffff) - 1 < 0xff + } + + @(private) + _WTERMSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(x & 0x7f) + } + + @(private) + _WIFSTOPPED :: #force_inline proc "contextless" (x: c.int) -> bool { + return ((x & 0xffff) * 0x10001) >> 8 > 0x7f00 + } + + @(private) + _WSTOPSIG :: #force_inline proc "contextless" (x: c.int) -> Signal { + return Signal(_WEXITSTATUS(x)) + } + + @(private) + _WIFCONTINUED :: #force_inline proc "contextless" (x: c.int) -> bool { + return x == 0xffff + } +} diff --git a/core/sys/posix/termios.odin b/core/sys/posix/termios.odin new file mode 100644 index 000000000..0c07eceb9 --- /dev/null +++ b/core/sys/posix/termios.odin @@ -0,0 +1,600 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// termios.h - define values for termios + +foreign lib { + /* + Get the input baud rate. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfgetispeed.html ]] + */ + cfgetispeed :: proc(termios_p: ^termios) -> speed_t --- + + /* + Set the input baud rate. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfsetispeed.html ]] + */ + cfsetispeed :: proc(termios_p: ^termios, rate: speed_t) -> result --- + + /* + Get the output baud rate. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfgetospeed.html ]] + */ + cfgetospeed :: proc(termios_p: ^termios) -> speed_t --- + + /* + Set the output baud rate. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/cfsetospeed.html ]] + */ + cfsetospeed :: proc(termios_p: ^termios, rate: speed_t) -> result --- + + /* + Wait for transmission of output. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcdrain.html ]] + */ + tcdrain :: proc(fildes: FD) -> result --- + + /* + Suspend or restart the transmission or reception of data. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcflow.html ]] + */ + tcflow :: proc(fildes: FD, action: TC_Action) -> result --- + + /* + Flush non-transmitted output data, non-read input data, or both. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcflush.html ]] + */ + tcflush :: proc(fildes: FD, queue_selector: TC_Queue) -> result --- + + /* + Get the parameters associated with the terminal. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcgetattr.html ]] + */ + tcgetattr :: proc(fildes: FD, termios_p: ^termios) -> result --- + + /* + Get the process group ID for the session leader for the controlling terminal. + + Returns: -1 (setting errno) on failure, the pid otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcgetsid.html ]] + */ + tcgetsid :: proc(fildes: FD) -> pid_t --- + + /* + Send a break for a specific duration. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsendbreak.html ]] + */ + tcsendbreak :: proc(fildes: FD, duration: c.int) -> result --- + + /* + Set the parameters associated with the terminal. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsetattr.html ]] + */ + tcsetattr :: proc(fildes: FD, optional_actions: TC_Optional_Action, termios_p: ^termios) -> result --- +} + +Control_Char :: enum c.int { + VEOF = VEOF, + VEOL = VEOL, + VERASE = VERASE, + VINTR = VINTR, + VKILL = VKILL, + VMIN = VMIN, + VQUIT = VQUIT, + VSTART = VSTART, + VSTOP = VSTOP, + VSUSP = VSUSP, + VTIME = VTIME, + + NCCS = NCCS-1, +} +#assert(len(#sparse [Control_Char]cc_t) == NCCS) + +CInput_Flag_Bits :: enum tcflag_t { + IGNBRK = log2(IGNBRK), /* ignore BREAK condition */ + BRKINT = log2(BRKINT), /* map BREAK to SIGINTR */ + IGNPAR = log2(IGNPAR), /* ignore (discard) parity errors */ + PARMRK = log2(PARMRK), /* mark parity and framing errors */ + INPCK = log2(INPCK), /* enable checking of parity errors */ + ISTRIP = log2(ISTRIP), /* strip 8th bit off chars */ + INLCR = log2(INLCR), /* map NL into CR */ + IGNCR = log2(IGNCR), /* ignore CR */ + ICRNL = log2(ICRNL), /* map CR to NL (ala CRMOD) */ + IXON = log2(IXON), /* enable output flow control */ + IXOFF = log2(IXOFF), /* enable input flow control */ + IXANY = log2(IXANY), /* any char will restart after stop */ +} +CInput_Flags :: bit_set[CInput_Flag_Bits; tcflag_t] + +CLocal_Flag_Bits :: enum tcflag_t { + ECHO = log2(ECHO), /* visual erase for line kill */ + ECHOE = log2(ECHOE), /* visually erase chars */ + ECHOK = log2(ECHOK), /* echo NL after line kill */ + ECHONL = log2(ECHONL), /* echo NL even if ECHO is off */ + ICANON = log2(ICANON), /* canonicalize input lines */ + IEXTEN = log2(IEXTEN), /* enable DISCARD and LNEXT */ + ISIG = log2(ISIG), /* enable signals INTR, QUIT, [D]SUSP */ + NOFLSH = log2(NOFLSH), /* don't flush after interrupt */ + TOSTOP = log2(TOSTOP), /* stop background jobs from output */ +} +CLocal_Flags :: bit_set[CLocal_Flag_Bits; tcflag_t] + +CControl_Flag_Bits :: enum tcflag_t { + // CS5 = log2(CS5), /* 5 bits (pseudo) (default) */ + CS6 = log2(CS6), /* 6 bits */ + CS7 = log2(CS7), /* 7 bits */ + CS8 = log2(CS8), /* 8 bits */ + CSTOPB = log2(CSTOPB), /* send 2 stop bits */ + CREAD = log2(CREAD), /* enable receiver */ + PARENB = log2(PARENB), /* parity enable */ + PARODD = log2(PARODD), /* odd parity, else even */ + HUPCL = log2(HUPCL), /* hang up on last close */ + CLOCAL = log2(CLOCAL), /* ignore modem status lines */ +} +CControl_Flags :: bit_set[CControl_Flag_Bits; tcflag_t] + +// character size mask +CSIZE :: transmute(CControl_Flags)tcflag_t(_CSIZE) + +COutput_Flag_Bits :: enum tcflag_t { + OPOST = log2(OPOST), /* enable following output processing */ + ONLCR = log2(ONLCR), /* map NL to CR-NL (ala CRMOD) */ + OCRNL = log2(OCRNL), /* map CR to NL on output */ + ONOCR = log2(ONOCR), /* no CR output at column 0 */ + ONLRET = log2(ONLRET), /* NL performs CR function */ + OFDEL = log2(OFDEL), /* fill is DEL, else NUL */ + OFILL = log2(OFILL), /* use fill characters for delay */ + // NL0 = log2(NL0), /* \n delay 0 (default) */ + NL1 = log2(NL1), /* \n delay 1 */ + // CR0 = log2(CR0), /* \r delay 0 (default) */ + CR1 = log2(CR1), /* \r delay 1 */ + CR2 = log2(CR2), /* \r delay 2 */ + CR3 = log2(CR3), /* \r delay 3 */ + // TAB0 = log2(TAB0),/* horizontal tab delay 0 (default) */ + TAB1 = log2(TAB1), /* horizontal tab delay 1 */ + TAB3 = log2(TAB3), /* horizontal tab delay 3 */ + // BS0 = log2(BS0), /* \b delay 0 (default) */ + BS1 = log2(BS1), /* \b delay 1 */ + // VT0 = log2(VT0), /* vertical tab delay 0 (default) */ + VT1 = log2(VT1), /* vertical tab delay 1 */ + // FF0 = log2(FF0), /* form feed delay 0 (default) */ + FF1 = log2(FF1), /* form feed delay 1 */ +} +COutput_Flags :: bit_set[COutput_Flag_Bits; tcflag_t] + +// \n delay mask +NLDLY :: transmute(COutput_Flags)tcflag_t(_NLDLY) +// \r delay mask +CRDLY :: transmute(COutput_Flags)tcflag_t(_CRDLY) +// horizontal tab delay mask +TABDLY :: transmute(COutput_Flags)tcflag_t(_TABDLY) +// \b delay mask +BSDLY :: transmute(COutput_Flags)tcflag_t(_BSDLY) +// vertical tab delay mask +VTDLY :: transmute(COutput_Flags)tcflag_t(_VTDLY) +// form feed delay mask +FFDLY :: transmute(COutput_Flags)tcflag_t(_FFDLY) + +speed_t :: enum _speed_t { + B0 = B0, + B50 = B50, + B75 = B75, + B110 = B110, + B134 = B134, + B150 = B150, + B200 = B200, + B300 = B300, + B600 = B600, + B1200 = B1200, + B1800 = B1800, + B2400 = B2400, + B4800 = B4800, + B9600 = B9600, + B19200 = B19200, + B38400 = B38400, +} + +TC_Action :: enum c.int { + TCIOFF = TCIOFF, + TCION = TCION, + TCOOFF = TCOOFF, + TCOON = TCOON, +} + +TC_Optional_Action :: enum c.int { + TCSANOW, + TCSADRAIN, + TCSAFLUSH, +} + +TC_Queue :: enum c.int { + TCIFLUSH = TCIFLUSH, + TCOFLUSH = TCOFLUSH, + TCIOFLUSH = TCIOFLUSH, +} + +when ODIN_OS == .Darwin { + + cc_t :: distinct c.uchar + _speed_t :: distinct c.ulong + tcflag_t :: distinct c.ulong + + termios :: struct { + c_iflag: CInput_Flags, /* [XBD] input flags */ + c_oflag: COutput_Flags, /* [XBD] output flags */ + c_cflag: CControl_Flags, /* [XBD] control flags */ + c_lflag: CLocal_Flags, /* [XBD] local flag */ + c_cc: #sparse [Control_Char]cc_t, /* [XBD] control chars */ + c_ispeed: speed_t, /* input speed */ + c_ospeed: speed_t, /* output speed */ + } + + NCCS :: 20 + + VEOF :: 0 + VEOL :: 1 + VERASE :: 3 + VINTR :: 8 + VKILL :: 5 + VMIN :: 16 + VQUIT :: 9 + VSTART :: 12 + VSTOP :: 13 + VSUSP :: 10 + VTIME :: 17 + + IGNBRK :: 0x00000001 + BRKINT :: 0x00000002 + IGNPAR :: 0x00000004 + PARMRK :: 0x00000008 + INPCK :: 0x00000010 + ISTRIP :: 0x00000020 + INLCR :: 0x00000040 + IGNCR :: 0x00000080 + ICRNL :: 0x00000100 + IXON :: 0x00000200 + IXOFF :: 0x00000400 + IXANY :: 0x00000800 + + OPOST :: 0x00000001 + ONLCR :: 0x00000002 + OCRNL :: 0x00000010 + ONOCR :: 0x00000020 + ONLRET :: 0x00000040 + OFDEL :: 0x00020000 + OFILL :: 0x00000080 + _NLDLY :: 0x00000300 + NL0 :: 0x00000000 + NL1 :: 0x00000100 + _CRDLY :: 0x00003000 + CR0 :: 0x00000000 + CR1 :: 0x00001000 + CR2 :: 0x00002000 + CR3 :: 0x00003000 + _TABDLY :: 0x00000c04 + TAB0 :: 0x00000000 + TAB1 :: 0x00000400 + TAB3 :: 0x00000800 + _BSDLY :: 0x00008000 + BS0 :: 0x00000000 + BS1 :: 0x00008000 + _VTDLY :: 0x00010000 + VT0 :: 0x00000000 + VT1 :: 0x00010000 + _FFDLY :: 0x00004000 + FF0 :: 0x00000000 + FF1 :: 0x00004000 + + B0 :: 0 + B50 :: 50 + B75 :: 75 + B110 :: 110 + B134 :: 134 + B150 :: 150 + B200 :: 200 + B300 :: 300 + B600 :: 600 + B1200 :: 1200 + B1800 :: 1800 + B2400 :: 2400 + B4800 :: 4800 + B9600 :: 9600 + B19200 :: 19200 + B38400 :: 38400 + + _CSIZE :: 0x00000300 + CS5 :: 0x00000000 + CS6 :: 0x00000100 + CS7 :: 0x00000200 + CS8 :: 0x00000300 + CSTOPB :: 0x00000400 + CREAD :: 0x00000800 + PARENB :: 0x00001000 + PARODD :: 0x00002000 + HUPCL :: 0x00004000 + CLOCAL :: 0x00008000 + + ECHO :: 0x00000008 + ECHOE :: 0x00000002 + ECHOK :: 0x00000004 + ECHONL :: 0x00000010 + ICANON :: 0x00000100 + IEXTEN :: 0x00000400 + ISIG :: 0x00000080 + NOFLSH :: 0x80000000 + TOSTOP :: 0x00400000 + + TCIFLUSH :: 1 + TCOFLUSH :: 2 + TCIOFLUSH :: 3 + + TCIOFF :: 3 + TCION :: 4 + TCOOFF :: 1 + TCOON :: 2 + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + cc_t :: distinct c.uchar + _speed_t :: distinct c.uint + tcflag_t :: distinct c.uint + + termios :: struct { + c_iflag: CInput_Flags, /* [XBD] input flags */ + c_oflag: COutput_Flags, /* [XBD] output flags */ + c_cflag: CControl_Flags, /* [XBD] control flags */ + c_lflag: CLocal_Flags, /* [XBD] local flag */ + c_cc: #sparse [Control_Char]cc_t, /* [XBD] control chars */ + c_ispeed: speed_t, /* input speed */ + c_ospeed: speed_t, /* output speed */ + } + + NCCS :: 20 + + VEOF :: 0 + VEOL :: 1 + VERASE :: 3 + VINTR :: 8 + VKILL :: 5 + VMIN :: 16 + VQUIT :: 9 + VSTART :: 12 + VSTOP :: 13 + VSUSP :: 10 + VTIME :: 17 + + IGNBRK :: 0x00000001 + BRKINT :: 0x00000002 + IGNPAR :: 0x00000004 + PARMRK :: 0x00000008 + INPCK :: 0x00000010 + ISTRIP :: 0x00000020 + INLCR :: 0x00000040 + IGNCR :: 0x00000080 + ICRNL :: 0x00000100 + IXON :: 0x00000200 + IXOFF :: 0x00000400 + IXANY :: 0x00000800 + + OPOST :: 0x00000001 + ONLCR :: 0x00000002 + OCRNL :: 0x00000010 + when ODIN_OS == .OpenBSD { + ONOCR :: 0x00000040 + ONLRET :: 0x00000080 + } else { + ONOCR :: 0x00000020 + ONLRET :: 0x00000040 + } + OFDEL :: 0x00020000 // NOTE: not in headers + OFILL :: 0x00000080 // NOTE: not in headers + _NLDLY :: 0x00000300 // NOTE: not in headers + NL0 :: 0x00000000 // NOTE: not in headers + NL1 :: 0x00000100 // NOTE: not in headers + _CRDLY :: 0x00003000 // NOTE: not in headers + CR0 :: 0x00000000 // NOTE: not in headers + CR1 :: 0x00001000 // NOTE: not in headers + CR2 :: 0x00002000 // NOTE: not in headers + CR3 :: 0x00003000 // NOTE: not in headers + _TABDLY :: 0x00000004 // NOTE: not in headers (netbsd) + TAB0 :: 0x00000000 // NOTE: not in headers (netbsd) + TAB1 :: 0x00000004 // NOTE: not in headers + TAB3 :: 0x00000004 // NOTE: not in headers (netbsd) + _BSDLY :: 0x00008000 // NOTE: not in headers + BS0 :: 0x00000000 // NOTE: not in headers + BS1 :: 0x00008000 // NOTE: not in headers + _VTDLY :: 0x00010000 // NOTE: not in headers + VT0 :: 0x00000000 // NOTE: not in headers + VT1 :: 0x00010000 // NOTE: not in headers + _FFDLY :: 0x00004000 // NOTE: not in headers + FF0 :: 0x00000000 // NOTE: not in headers + FF1 :: 0x00004000 // NOTE: not in headers + + B0 :: 0 + B50 :: 50 + B75 :: 75 + B110 :: 110 + B134 :: 134 + B150 :: 150 + B200 :: 200 + B300 :: 300 + B600 :: 600 + B1200 :: 1200 + B1800 :: 1800 + B2400 :: 2400 + B4800 :: 4800 + B9600 :: 9600 + B19200 :: 19200 + B38400 :: 38400 + + _CSIZE :: 0x00000300 + CS5 :: 0x00000000 + CS6 :: 0x00000100 + CS7 :: 0x00000200 + CS8 :: 0x00000300 + CSTOPB :: 0x00000400 + CREAD :: 0x00000800 + PARENB :: 0x00001000 + PARODD :: 0x00002000 + HUPCL :: 0x00004000 + CLOCAL :: 0x00008000 + + ECHO :: 0x00000008 + ECHOE :: 0x00000002 + ECHOK :: 0x00000004 + ECHONL :: 0x00000010 + ICANON :: 0x00000100 + IEXTEN :: 0x00000400 + ISIG :: 0x00000080 + NOFLSH :: 0x80000000 + TOSTOP :: 0x00400000 + + TCIFLUSH :: 1 + TCOFLUSH :: 2 + TCIOFLUSH :: 3 + + TCIOFF :: 3 + TCION :: 4 + TCOOFF :: 1 + TCOON :: 2 + +} else when ODIN_OS == .Linux { + cc_t :: distinct c.uchar + _speed_t :: distinct c.uint + tcflag_t :: distinct c.uint + + termios :: struct { + c_iflag: CInput_Flags, /* [XBD] input flags */ + c_oflag: COutput_Flags, /* [XBD] output flags */ + c_cflag: CControl_Flags, /* [XBD] control flags */ + c_lflag: CLocal_Flags, /* [XBD] local flag */ + c_line: cc_t, /* control characters */ + c_cc: #sparse [Control_Char]cc_t, /* [XBD] control chars */ + c_ispeed: speed_t, /* input speed */ + c_ospeed: speed_t, /* output speed */ + } + + NCCS :: 32 + + VINTR :: 0 + VQUIT :: 1 + VERASE :: 2 + VKILL :: 3 + VEOF :: 4 + VTIME :: 5 + VMIN :: 6 + VSTART :: 8 + VSTOP :: 9 + VSUSP :: 10 + VEOL :: 11 + + IGNBRK :: 0x00000001 + BRKINT :: 0x00000002 + IGNPAR :: 0x00000004 + PARMRK :: 0x00000008 + INPCK :: 0x00000010 + ISTRIP :: 0x00000020 + INLCR :: 0x00000040 + IGNCR :: 0x00000080 + ICRNL :: 0x00000100 + IXON :: 0x00000400 + IXOFF :: 0x00001000 + IXANY :: 0x00000800 + + OPOST :: 0x00000001 + ONLCR :: 0x00000004 + OCRNL :: 0x00000008 + ONOCR :: 0x00000010 + ONLRET :: 0x00000020 + OFDEL :: 0x00000080 + OFILL :: 0x00000040 + _NLDLY :: 0x00000100 + NL0 :: 0x00000000 + NL1 :: 0x00000100 + _CRDLY :: 0x00000600 + CR0 :: 0x00000000 + CR1 :: 0x00000200 + CR2 :: 0x00000400 + CR3 :: 0x00000600 + _TABDLY :: 0x00001800 + TAB0 :: 0x00000000 + TAB1 :: 0x00000800 + TAB3 :: 0x00001800 + _BSDLY :: 0x00002000 + BS0 :: 0x00000000 + BS1 :: 0x00002000 + _VTDLY :: 0x00004000 + VT0 :: 0x00000000 + VT1 :: 0x00004000 + _FFDLY :: 0x00008000 + FF0 :: 0x00000000 + FF1 :: 0x00008000 + + B0 :: 0x00000000 + B50 :: 0x00000001 + B75 :: 0x00000002 + B110 :: 0x00000003 + B134 :: 0x00000004 + B150 :: 0x00000005 + B200 :: 0x00000006 + B300 :: 0x00000007 + B600 :: 0x00000008 + B1200 :: 0x00000009 + B1800 :: 0x0000000a + B2400 :: 0x0000000b + B4800 :: 0x0000000c + B9600 :: 0x0000000d + B19200 :: 0x0000000e + B38400 :: 0x0000000f + + _CSIZE :: 0x00000030 + CS5 :: 0x00000000 + CS6 :: 0x00000010 + CS7 :: 0x00000020 + CS8 :: 0x00000030 + CSTOPB :: 0x00000040 + CREAD :: 0x00000080 + PARENB :: 0x00000100 + PARODD :: 0x00000200 + HUPCL :: 0x00000400 + CLOCAL :: 0x00000800 + + ECHO :: 0x00000008 + ECHOE :: 0x00000010 + ECHOK :: 0x00000020 + ECHONL :: 0x00000040 + ICANON :: 0x00000002 + IEXTEN :: 0x00008000 + ISIG :: 0x00000001 + NOFLSH :: 0x80000080 + TOSTOP :: 0x00000100 + + TCIFLUSH :: 0 + TCOFLUSH :: 1 + TCIOFLUSH :: 2 + + TCIOFF :: 2 + TCION :: 3 + TCOOFF :: 0 + TCOON :: 1 + +} diff --git a/core/sys/posix/time.odin b/core/sys/posix/time.odin new file mode 100644 index 000000000..f9c51c63c --- /dev/null +++ b/core/sys/posix/time.odin @@ -0,0 +1,245 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" +import "core:c/libc" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// time.h - time types + +foreign lib { + /* + Convert the broken down time in the structure to a string form: Sun Sep 16 01:03:52 1973. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/asctime_r.html ]] + */ + asctime_r :: proc(tm: ^tm, buf: [^]c.char) -> cstring --- + + /* + Convert a time value to a date and time string in the same format as asctime(). + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ctime_r.html ]] + */ + @(link_name=LCTIMER) + ctime_r :: proc(clock: ^time_t, buf: [^]c.char) -> cstring --- + + /* + Converts the time in seconds since epoch to a broken-down tm struct. + + Returns: nil (setting errno) on failure, the result pointer on success. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/gmtime_r.html ]] + */ + @(link_name=LGMTIMER) + gmtime_r :: proc(timer: ^time_t, result: ^tm) -> ^tm --- + + /* + Convert the time in seconds since epoch to a broken-down tm struct in local time. + + Returns: nil (setting errno) on failure, the result pointer on success. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/localtime_r.html ]] + */ + @(link_name=LLOCALTIMER) + localtime_r :: proc(timer: ^time_t, result: ^tm) -> ^tm --- + + /* + Returns the resolution of any clock. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html ]] + */ + @(link_name=LCLOCKGETRES) + clock_getres :: proc(clock_id: Clock, res: ^timespec) -> result --- + + /* + Returns the current value tp for the specified clock, clock_id. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html ]] + */ + @(link_name=LCLOCKGETTIME) + clock_gettime :: proc(clock_id: Clock, tp: ^timespec) -> result --- + + /* + Sets the specified clock's time. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_getres.html ]] + */ + @(link_name=LCLOCKSETTIME) + clock_settime :: proc(clock_id: Clock, tp: ^timespec) -> result --- + + /* + Converts a string representation of a date or time into a broken-down time. + + Returns: nil (setting getdate_err) on failure, the broken-down time otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getdate.html ]] + */ + getdate :: proc(string: cstring) -> ^tm --- + + /* + Causes the current thread to be suspended from execution until either the time interval + specified by rqtp has elapsed or a signal is delivered. + + Returns: -1 on failure (setting errno), if it was due to a signal, rmtp will be filled with the + remaining time, 0 if all time has been slept + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/nanosleep.html ]] + */ + @(link_name=LNANOSLEEP) + nanosleep :: proc(rqtp: ^timespec, rmtp: ^timespec) -> result --- + + /* + Converts the character string to values which are stored in tm, using the specified format. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/strptime.html ]] + */ + strptime :: proc(buf: [^]c.char, format: cstring, tm: ^tm) -> cstring --- + + /* + Uses the value of the environment variable TZ (or default) to set time conversion info. + + `daylight` is set to whether daylight saving time conversion should be done. + `timezone` is set to the difference, in seconds, between UTC and local standard time. + `tzname` is set by `tzname[0] = "std"` and `tzname[1] = "dst"` + + Example: + posix.tzset() + fmt.println(posix.tzname) + fmt.println(posix.daylight) + fmt.println(posix.timezone) + + Possible Output: + ["CET", "CEST"] + true + -3600 + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tzset.html ]] + */ + tzset :: proc() --- + + // Whether daylight saving conversion should be done. + daylight: b32 + // The time in seconds between UTC and local standard time. + @(link_name=LTIMEZONE) + timezone: c.long + tzname: [2]cstring +} + +time_t :: libc.time_t +clock_t :: libc.clock_t + +tm :: libc.tm +timespec :: libc.timespec + +CLOCKS_PER_SEC :: libc.CLOCKS_PER_SEC + +asctime :: libc.asctime +clock :: libc.clock +ctime :: libc.ctime +difftime :: libc.difftime +gmtime :: libc.gmtime +localtime :: libc.localtime +mktime :: libc.mktime +strftime :: libc.strftime +time :: libc.time + +Clock :: enum clockid_t { + // system-wide monotonic clock, defined as clock measuring real time, + // can be set with clock_settime() and cannot have negative clock jumps. + MONOTONIC = CLOCK_MONOTONIC, + // CPU-time clock associated with the process making a clock() function call. + PROCESS_CPUTIME_ID = CLOCK_PROCESS_CPUTIME_ID, + // system-wide clock measuring real time. + REALTIME = CLOCK_REALTIME, + // CPU-time clock associated with the thread making a clock() function call. + THREAD_CPUTIME_ID = CLOCK_THREAD_CPUTIME_ID, +} + +when ODIN_OS == .NetBSD { + @(private) LCTIMER :: "__ctime_r50" + @(private) LGMTIMER :: "__gmtime_r50" + @(private) LLOCALTIMER :: "__localtime_r50" + @(private) LCLOCKGETRES :: "__clock_getres50" + @(private) LCLOCKGETTIME :: "__clock_gettime50" + @(private) LCLOCKSETTIME :: "__clock_settime50" + @(private) LNANOSLEEP :: "__nanosleep50" + @(private) LTIMEZONE :: "__timezone13" +} else { + @(private) LCTIMER :: "ctime_r" + @(private) LGMTIMER :: "gmtime_r" + @(private) LLOCALTIMER :: "localtime_r" + @(private) LCLOCKGETRES :: "clock_getres" + @(private) LCLOCKGETTIME :: "clock_gettime" + @(private) LCLOCKSETTIME :: "clock_settime" + @(private) LNANOSLEEP :: "nanosleep" + @(private) LTIMEZONE :: "timezone" +} + +when ODIN_OS == .Darwin { + + clockid_t :: distinct c.int + + CLOCK_MONOTONIC :: 6 + CLOCK_PROCESS_CPUTIME_ID :: 12 + CLOCK_REALTIME :: 0 + CLOCK_THREAD_CPUTIME_ID :: 16 + + foreign lib { + getdate_err: Errno + } + +} else when ODIN_OS == .FreeBSD { + + clockid_t :: distinct c.int + + CLOCK_MONOTONIC :: 4 + CLOCK_PROCESS_CPUTIME_ID :: 15 + CLOCK_REALTIME :: 0 + CLOCK_THREAD_CPUTIME_ID :: 14 + + foreign lib { + getdate_err: Errno + } + +} else when ODIN_OS == .NetBSD { + + clockid_t :: distinct c.uint + + CLOCK_MONOTONIC :: 3 + CLOCK_PROCESS_CPUTIME_ID :: 0x40000000 + CLOCK_REALTIME :: 0 + CLOCK_THREAD_CPUTIME_ID :: 0x20000000 + + foreign lib { + getdate_err: Errno + } + +} else when ODIN_OS == .OpenBSD { + + clockid_t :: distinct c.uint + + CLOCK_MONOTONIC :: 3 + CLOCK_PROCESS_CPUTIME_ID :: 2 + CLOCK_REALTIME :: 0 + CLOCK_THREAD_CPUTIME_ID :: 4 + + getdate_err: Errno = .ENOSYS // NOTE: looks like it's not a thing on OpenBSD. + +} else when ODIN_OS == .Linux { + + clockid_t :: distinct c.int + + CLOCK_MONOTONIC :: 1 + CLOCK_PROCESS_CPUTIME_ID :: 2 + CLOCK_REALTIME :: 0 + CLOCK_THREAD_CPUTIME_ID :: 3 + + foreign lib { + getdate_err: Errno + } +} diff --git a/core/sys/posix/ulimit.odin b/core/sys/posix/ulimit.odin new file mode 100644 index 000000000..0f87641fa --- /dev/null +++ b/core/sys/posix/ulimit.odin @@ -0,0 +1,42 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// ulimit.h - ulimit commands + +foreign lib { + /* + Control process limits. + + Note that -1 is a valid return value, applications should clear errno, do this call and then + check both -1 and the errno to determine status. + + Returns: -1 (setting errno) on failure. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ulimit.html ]] + */ + ulimit :: proc(i: c.int, #c_vararg arg: ..c.long) -> c.long --- +} + +Ulimit_Cmd :: enum c.int { + // Returns the file size limit of the process in units of 512-byte blocks inherited by children. + GETFSIZE = UL_GETFSIZE, + // Set the file size limit for output operations, taken as a long, multiplied by 512. + SETFSIZE = UL_SETFSIZE, +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { + + UL_GETFSIZE :: 1 + UL_SETFSIZE :: 2 + + // NOTE: I don't think OpenBSD implements this API. + +} diff --git a/core/sys/posix/unistd.odin b/core/sys/posix/unistd.odin new file mode 100644 index 000000000..0526b3235 --- /dev/null +++ b/core/sys/posix/unistd.odin @@ -0,0 +1,1995 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// unistd.h - standard symbolic constants and types + +foreign lib { + /* + Equivalent to `access` but relative paths are resolved based on `fd`. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/access.html ]] + */ + faccessat :: proc(fd: FD, path: cstring, amode: Mode_Flags, flag: AT_Flags) -> result --- + + /* + The alarm() function shall cause the system to generate a SIGALRM signal for the process after the number of realtime seconds specified by seconds have elapsed. Processor scheduling delays may prevent the process from handling the signal as soon as it is generated. + + If seconds is 0, a pending alarm request, if any, is canceled. + + Returns: the time left on the previous alarm() or 0 + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/alarm.html ]] + */ + alarm :: proc(seconds: c.uint) -> c.uint --- + + /* + Equivalent to chdir but instead of a path the fildes is resolved to a directory. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchdir.html ]] + */ + fchdir :: proc(fildes: FD) -> result --- + + /* + Changes the user and group ownership of a file. + + If owner or group is specified as (uid_t)-1 or (gid_t)-1, respectively, the corresponding ID of the file shall not be changed. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/chown.html ]] + */ + @(link_name=LCHOWN) + chown :: proc(path: cstring, owner: uid_t, group: gid_t) -> result --- + + /* + Equivalent to chown expect that it takes a file descriptor. + + Example: + fildes := posix.open("/home/cnd/mod1", {.RDWR}) + pwd := posix.getpwnam("jones") + grp := posix.getgrnam("cnd") + posix.fchown(fildes, pwd.pw_uid, grp.gr_gid) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchown.html ]] + */ + @(link_name=LFCHOWN) + fchown :: proc(fildes: FD, owner: uid_t, mode: gid_t) -> result --- + + /* + Equivalent to fchown except that relative paths are based on the given fildes. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/chown.html ]] + */ + fchownat :: proc(fildes: FD, path: cstring, owner: uid_t, group: gid_t, flag: AT_Flags) -> result --- + + /* + If path points to a symbolic link, the owner and group of the link itself is changed. + Equivalent to chown on normal files. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/lchown.html ]] + */ + @(link_name=LLCHOWN) + lchown :: proc(path: cstring, owner: uid_t, group: gid_t) -> result --- + + /* + Deallocates the file descriptor indicated by fildes. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html ]] + */ + close :: proc(fildes: FD) -> result --- + + /* + Return configuration-defined string values. + Its use and purpose are similar to sysconf(), but it is used where string values rather than numeric values are returned. + + Returns: 0 (setting errno) if `name` is invalid, need `buf` of `len` bytes if `buf` is `nil`, amount of bytes added to buf otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/confstr.html ]] + */ + confstr :: proc(name: CS, buf: [^]c.char, len: c.size_t) -> c.size_t --- + + /* + Determines the current value of a configurable limit or option that is associated with a file or directory. + + Returns: value on success, -1 (setting errno) on failure, -1 (no errno) if the variable should be taken from limits + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fpathconf.html ]] + */ + pathconf :: proc(path: cstring, name: PC) -> c.long --- + + /* + Equivalent to pathconf but takes a file descriptor instead of a path. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fpathconf.html ]] + */ + fpathconf :: proc(fildes: FD, name: PC) -> c.long --- + + /* + Determines the current value of configurable system limit or options. + + Returns: value on success, -1 (setting errno) on failure, -1 (no errno) if the variable should be taken from limits + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sysconf.html ]] + */ + sysconf :: proc(name: SC) -> c.long --- + + /* + A string encoding function. The algorithm is implementation-defined. + + The use of crypt() for anything other than password hashing is not recommended. + + Returns: a static string overwritten by subsequent calls, `nil` (setting errno) on failure + */ + crypt :: proc(key: cstring, salt: cstring) -> cstring --- + + /* + An implementation-defined encoding algorithm. + The key generated by setkey() is used to encrypt the string block with encrypt(). + + block must be 64 bytes. + + decode controls if the block is encoded or decoded. + + May set errno to ENOSYS if the functionality is not supported. + + Example: + block: [64]byte + copy(block[:], "Hello, World!") + + posix.set_errno(.NONE) + posix.encrypt(raw_data(block[:]), decode=false) + assert(posix.errno() == .NONE, "encrypt not supported") + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/encrypt.html ]] + */ + encrypt :: proc(block: [^]c.char, decode: b32) --- + + /* + Returns a new file descriptor referring to the one given, sharing locks, clearing CLOEXEC. + + Returns: -1 (setting errno) on failure, the new file descriptor on success + + Example: + // Redirecting stdout to a file: + file := posix.open("/tmp/out", { .RDWR }) + posix.close(1) + posix.dup(file) + posix.close(file) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dup.html ]] + */ + dup :: proc(fildes: FD) -> FD --- + + /* + Causes the file descriptor fildes2 to refer to the same open file description as + the file descriptor fildes and to share any locks, and shall return fildes2. + + Returns: -1 (setting errno) on failure, fildes2 on success + + Example: + // Redirecting stderr to stdout: + posix.dup2(1, 2) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/dup.html ]] + */ + dup2 :: proc(fildes, fildes2: FD) -> FD --- + + /* + The exec family of functions shall replace the current process image with a new process image. + The new image shall be constructed from a regular, executable file called the new process image file. + There shall be no return from a successful exec, + because the calling process image is overlaid by the new process image. + + Takes arguments as varargs and the last of them must be nil. + + Example: + ret := posix.execl("/bin/ls", "ls", "-l", nil) + fmt.panicf("could not execute: %v %v", ret, posix.strerror(posix.errno())) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + execl :: proc(path: cstring, arg0: cstring, #c_vararg args: ..cstring) -> c.int --- + + /* + The exec family of functions shall replace the current process image with a new process image. + The new image shall be constructed from a regular, executable file called the new process image file. + There shall be no return from a successful exec, + because the calling process image is overlaid by the new process image. + + Takes arguments as varargs and the last of them must be nil. + After the arguments an array of environment strings (also nil terminated) is expected. + + Example: + env := []cstring{ + "HOME=/usr/home", + "LOGNAME=home", + nil, + } + ret := posix.execle("/bin/ls", "ls", cstring("-l"), cstring(nil), raw_data(env)) + fmt.panicf("could not execute: %v", posix.strerror(posix.errno())) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + execle :: proc(path: cstring, arg0: cstring, #c_vararg args: ..any) -> c.int --- + + /* + The exec family of functions shall replace the current process image with a new process image. + The new image shall be constructed from a regular, executable file called the new process image file. + There shall be no return from a successful exec, + because the calling process image is overlaid by the new process image. + + If file does not contain a slash the PATH environment variable is searched for a matching file. + Takes arguments as varargs and the last of them must be nil. + + Example: + ret := posix.execlp("ls", "-l", cstring(nil)) + fmt.panicf("could not execute: %v, %v", ret, posix.strerror(posix.errno())) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + execlp :: proc(file: cstring, arg0: cstring, #c_vararg args: ..cstring) -> c.int --- + + /* + The exec family of functions shall replace the current process image with a new process image. + The new image shall be constructed from a regular, executable file called the new process image file. + There shall be no return from a successful exec, + because the calling process image is overlaid by the new process image. + + Takes arguments as an array which should be nil terminated. + + Example: + args := []cstring{ "ls", "-l", nil } + ret := posix.execv("/bin/ls", raw_data(args)) + fmt.panicf("could not execute: %v, %v", ret, posix.strerror(posix.errno())) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + execv :: proc(path: cstring, argv: [^]cstring) -> c.int --- + + /* + The exec family of functions shall replace the current process image with a new process image. + The new image shall be constructed from a regular, executable file called the new process image file. + There shall be no return from a successful exec, + because the calling process image is overlaid by the new process image. + + If file does not contain a slash the PATH environment variable is searched for a matching file. + Takes arguments as an array which should be nil terminated. + + Example: + cmd := []cstring{ "ls", "-l", nil } + ret := posix.execvp("ls", raw_data(cmd)) + fmt.panicf("could not execute: %v, %v", ret, posix.strerror(posix.errno())) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + execvp :: proc(file: cstring, argv: [^]cstring) -> c.int --- + + /* + The exec family of functions shall replace the current process image with a new process image. + The new image shall be constructed from a regular, executable file called the new process image file. + There shall be no return from a successful exec, + because the calling process image is overlaid by the new process image. + + Takes arguments as an array which should be nil terminated. + Takes environment variables as an array which should be nil terminated. + + Example: + cmd := []cstring{ "ls", "-l", nil } + env := []cstring{ "HOME=/usr/home", "LOGNAME=home", nil } + ret := posix.execve("/bin/ls", raw_data(cmd), raw_data(env)) + fmt.panicf("could not execute: %v, %v", ret, posix.strerror(posix.errno())) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + execve :: proc(path: cstring, argv: [^]cstring, envp: [^]cstring) -> c.int --- + + /* + The exec family of functions shall replace the current process image with a new process image. + The new image shall be constructed from a regular, executable file called the new process image file. + There shall be no return from a successful exec, + because the calling process image is overlaid by the new process image. + + Equivalent to execve but takes a file descriptor instead of a path. + + Example: + ls := posix.open("/bin/ls", { .EXEC }) + cmd := []cstring{ "ls", "-l", nil } + env := []cstring{ "HOME=/usr/home", "LOGNAME=home", nil } + ret := posix.fexecve(ls, raw_data(cmd), raw_data(env)) + fmt.panicf("could not execute: %v, %v", ret, posix.strerror(posix.errno())) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + fexecve :: proc(fd: FD, argv: [^]cstring, envp: [^]cstring) -> c.int --- + + /* + Example: + for i, entry := 0, posix.environ[0]; entry != nil; i, entry = i+1, posix.environ[i] { + fmt.println(entry) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/execl.html ]] + */ + environ: [^]cstring + + /* + Forcec all currently queued I/O operations associated with the file indicated by file descriptor + fildes to the synchronized I/O completion state. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fdatasync.html ]] + */ + fdatasync :: proc(fd: FD) -> result --- + + /* + The fork() function shall create a new process. + The new process (child process) shall be an exact copy of the calling process (parent process). + With some exceptions outlined below. + + Result: -1 (setting errno) on failure, otherwise 0 to the child process and the child process id to the parent process. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html ]] + */ + fork :: proc() -> pid_t --- + + /* + Requests that all data for the open file descriptor named by fildes is to be transferred + to the storage device associated with the file described by fildes. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/fsync.html ]] + */ + fsync :: proc(fildes: FD) -> result --- + + /* + Truncates a file to the specified length. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/truncate.html ]] + */ + truncate :: proc(path: cstring, length: off_t) -> result --- + + /* + Truncates a file to the specified length. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ftruncate.html ]] + */ + ftruncate :: proc(fildes: FD, length: off_t) -> result --- + + /* + Returns the effective group ID of the calling process. + + Returns: the ID, no failure is defined + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getegid.html ]] + */ + getegid :: proc() -> gid_t --- + + /* + Returns the effective user ID of the calling process. + + Returns: the ID, no failure is defined + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/geteuid.html ]] + */ + geteuid :: proc() -> uid_t --- + + /* + Returns the real group ID of the calling process. + + Returns: the ID, no failure is defined + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getgid.html ]] + */ + getgid :: proc() -> gid_t --- + + /* + Fills the grouplist array with the current supplementary group IDs of the calling process. + + Returns: -1 (setting errno) on failure, desired grouplist length if gidsetsize is 0, amount of IDs added otherwise + + Example: + length := posix.getgroups(0, nil) + if length == -1 { + fmt.panicf("getgroups failure: %v", posix.strerror(posix.errno())) + } + + groups := make([]posix.gid_t, length) or_else panic("allocation failure") + if posix.getgroups(length, raw_data(groups)) != length { + fmt.panicf("getgroups failure: %v", posix.strerror(posix.errno())) + } + + fmt.println(groups) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getgroups.html ]] + */ + getgroups :: proc(gidsetsize: c.int, grouplist: [^]gid_t) -> c.int --- + + /* + Retrieves a 32-bit identifier for the current host. + + Returns: the ID, no failure is defined + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/gethostid.html ]] + */ + gethostid :: proc() -> c.long --- + + /* + Returns the standard host name for the current machine. + + Host names are limited to HOST_NAME_MAX bytes. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/gethostname.html ]] + */ + gethostname :: proc(name: [^]c.char, namelen: c.size_t) -> result --- + + /* + Returns a string containing the user name associated by the login activity. + + Returns: nil (setting errno) on failure, the login name otherwise in a potentially static buffer overwritten by subsequent calls + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getlogin.html ]] + */ + getlogin :: proc() -> cstring --- + + /* + Equivalent to getlogin but puts the name in the name buffer given. + + The name is limited to LOGIN_NAME_MAX bytes. + + Example: + max := posix.sysconf(posix._SC_LOGIN_NAME_MAX)+1 + buf := make([]byte, max) + posix.getlogin_r(raw_data(buf), uint(len(max))) + fmt.printfln("login: %v", cstring(buf)) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getlogin.html ]] + */ + getlogin_r :: proc(name: [^]c.char, namelen: c.size_t) -> Errno --- + + /* + A command-line parser, see linked docs. + + Example: + // The following code fragment shows how you might process the arguments for a utility that + // can take the mutually-exclusive options a and b and the options f and o, both of which + // require arguments. + + bflg, aflg, errflg: bool + ifile: string + ofile: string + + for { + c := posix.getopt(i32(len(runtime.args__)), raw_data(runtime.args__), ":abf:o:") + (c != -1) or_break + + switch c { + case 'a': + if bflg { + errflg = true + } else { + aflg = true + } + case 'b': + if aflg { + errflg = true + } else { + bflg = true + } + case 'f': + ifile = string(posix.optarg) + case 'o': + ofile = string(posix.optarg) + case ':': /* -f or -o without operand */ + fmt.eprintfln("Option -%c requires an operand", posix.optopt) + errflg = true + case '?': + fmt.eprintfln("Unrecognized option: '-%c'", posix.optopt) + errflg = true + } + } + + if errflg { + fmt.eprintfln("usage: . . . ") + posix.exit(2) + } + + // Loop through remaining arguments: + for ; posix.optind < i32(len(runtime.args__)); posix.optind += 1 { + fmt.println(runtime.args__[posix.optind]) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getopt.html ]] + */ + getopt :: proc(argc: c.int, argv: [^]cstring, optstring: cstring) -> c.int --- + + optarg: cstring + opterr: c.int + optind: c.int + optopt: c.int + + /* + Returns the process group ID of the process whose process ID is equal to pid. + If pid is 0, it returns the process group ID of the calling process. + + Returns: -1 on failure, the ID otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpgid.html ]] + */ + getpgid :: proc(pid: pid_t) -> pid_t --- + + /* + Returns the process group ID of the calling process. + + Returns: no failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpgrp.html ]] + */ + getpgrp :: proc() -> pid_t --- + + /* + Returns the ID of the calling process. + + Returns: no failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpid.html ]] + */ + getpid :: proc() -> pid_t --- + + /* + Returns the parent process ID. + + Returns: no failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getppid.html ]] + */ + getppid :: proc() -> pid_t --- + + + /* + Get the process group ID of the session leader. + If pid is 0, it is the current process. + + Returns: -1 (setting errno) on failure, the pid otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsid.html ]] + */ + getsid :: proc(pid: pid_t) -> pid_t --- + + /* + Returns the real user ID of the calling process. + + Returns: no failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getuid.html ]] + */ + getuid :: proc() -> uid_t --- + + /* + Tests whether fildes is associated with a terminal device. + + Returns: false (setting errno) if fildes is invalid or not a terminal, true otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/isatty.html ]] + */ + isatty :: proc(fildes: FD) -> b32 --- + + /* + Creates a new link for the existing file path1 to path2. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/link.html ]] + */ + link :: proc(path1: cstring, path2: cstring) -> result --- + + /* + If path1 is relative it is relative to directory fd1. + If path2 is relative it is relative to directory fd2. + If flag is { .SYMLINK_FOLLOW } path1 is resolved to its link if it is a link. + Equivalent to link otherwise. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/link.html ]] + */ + linkat :: proc(fd1: FD, path1: cstring, fd2: FD, path2: cstring, flag: AT_Flags) -> result --- + + /* + Creates a symbolic link called path2 that contains a link to path1. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlink.html ]] + */ + symlink :: proc(path1: cstring, path2: cstring) -> result --- + + /* + Equivalent to symlink but relative paths are resolved to dir fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlink.html ]] + */ + symlinkat :: proc(path1: cstring, fd: FD, path2: cstring) -> result --- + + /* + Locks sections of a file with advisory-mode locks. + + Example: + fildes := posix.open("/home/cnd/mod1", { .RDWR }) + if posix.lockf(fildes, .TLOCK, 10000) != .OK { + errno := posix.errno(); #partial switch errno { + case .EACCES, .EAGAIN: + // File is already locked. + case: + // Other error. + fmt.panicf("lockf failure: %v", posix.strerror(errno)) + } + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/lockf.html ]] + */ + lockf :: proc(fildes: FD, function: Lock_Function, size: off_t) -> result --- + + /* + Sets the file offset of the given file descriptor. + + If whence is .SET, the offset is set + If whence is .CUR, the offset is the current offset + given offset + If whence is .END, the offset is set to the size of the file + given offset + + Returns: the resulting offset or -1 (setting errno) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/lseek.html ]] + */ + lseek :: proc(fildes: FD, offset: off_t, whence: Whence) -> off_t --- + + /* + Changes the nice value of a process. + + Higher values result in less favorable scheduling. + + Because -1 is a valid nice value, checking failure would be done by first setting errno to .NONE + and then calling nice. + + Returns: the new nice value, or -1 (setting) errno on failure + + Example: + posix.set_errno(.NONE) + niceness := posix.nice(-20) + if errno := posix.errno(); niceness == -1 && errno != .NONE { + fmt.panicf("nice failure: %v", posix.strerror(errno)) + } + fmt.printfln("Niceness is now: %v", niceness) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/nice.html ]] + */ + nice :: proc(incr: c.int) -> c.int --- + + /* + Suspend the thread until a signal is received. + + Returns: -1 (setting errno to EINTR) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pause.html ]] + */ + pause :: proc() -> c.int --- + + /* + Create an interprocess channel. + + Example: + fildes: [2]posix.FD + if posix.pipe(&fildes) != .OK { + // Handle error ... + } + + switch posix.fork() { + case -1: + // Handle error ... + + case 0: /* Child - reads from pipe */ + BSIZE :: 100 + buf: [BSIZE]byte + nbytes: int + + posix.close(fildes[1]) /* Write end is unused */ + nbytes = posix.read(fildes[0], raw_data(buf[:]), BSIZE) /* Get data from pipe */ + /* At this point, a further read would see end-of-file ... */ + posix.close(fildes[0]) /* Finished with pipe */ + + fmt.println(string(buf[:nbytes])) + + posix.exit(0) + + case: /* Parent - write to pipe */ + msg := raw_data(transmute([]byte)string("Hello world\n")) + posix.close(fildes[0]) /* Read end is unused */ + posix.write(fildes[1], msg, 12); /* Write data on pipe */ + posix.close(fildes[1]) + posix.exit(0) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pipe.html ]] + */ + pipe :: proc(fildes: ^[2]FD) -> result --- + + /* + Read from a file. + + Returns: the amount of bytes read or -1 (setting errno) on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pread.html ]] + */ + read :: proc(fd: FD, buf: [^]byte, nbyte: c.size_t) -> c.ssize_t --- + + /* + Equivalent to read on a specified offset instead of the internal offset. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pread.html ]] + */ + pread :: proc(fd: FD, buf: [^]byte, nbyte: c.size_t, offset: off_t) -> c.ssize_t --- + + /* + Write on a file. + + Returns: the amount of bytes written or -1 (setting errno) on failure. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html ]] + */ + write :: proc(fd: FD, buf: [^]byte, buflen: c.size_t) -> c.ssize_t --- + + /* + Equivalent to write on a specified offset instead of the internal offset. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html ]] + */ + pwrite :: proc(fd: FD, buf: [^]byte, buflen: c.size_t, offset: off_t) -> c.ssize_t --- + + /* + Read the contents of a symbolic link. + + Returns: the amount of bytes read or -1 (setting errno) on failure. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlink.html ]] + */ + readlink :: proc(path: cstring, buf: [^]byte, bufsize: c.size_t) -> c.ssize_t --- + + /* + Equivalent to readlink but relative paths are resolved based on the dir fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlink.html ]] + */ + readlinkat :: proc(fd: FD, path: cstring, buf: [^]byte, bufsize: c.size_t) -> c.ssize_t --- + + /* + Set the effective group ID. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setegid.html ]] + */ + setegid :: proc(gid: gid_t) -> result --- + + /* + Sets the effective user ID. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/seteuid.html ]] + */ + seteuid :: proc(uid: uid_t) -> result --- + + /* + Sets the group ID. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setgid.html ]] + */ + setgid :: proc(gid: gid_t) -> result --- + + /* + Set process group ID. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setpgid.html ]] + */ + setpgid :: proc(pid: pid_t, pgid: pid_t) -> result --- + + /* + Set the process group ID to that of the process. + + Returns: the process group id, no failures are defined + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setpgrp.html ]] + */ + setpgrp :: proc() -> pid_t --- + + /* + Set the real and effective group IDs. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setregid.html ]] + */ + setregid :: proc(rgid: gid_t, egid: gid_t) -> result --- + + /* + Set real and effective user IDs. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setreuid.html ]] + */ + setreuid :: proc(ruid: uid_t, euid: uid_t) -> result --- + + /* + Create session and set process group ID. + + Returns: the new process group ID or -1 (setting errno) on failure + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setsid.html ]] + */ + setsid :: proc() -> pid_t --- + + /* + Set user ID. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/setuid.html ]] + */ + setuid :: proc(uid: uid_t) -> result --- + + /* + Suspend execution for an interval of time. + + Returns: the time left to sleep (may be > 0 in case of signals) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sleep.html ]] + */ + sleep :: proc(seconds: c.uint) -> c.uint --- + + /* + Schedule file system updates. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sync.html ]] + */ + sync :: proc() --- + + /* + Get the foreground process group ID. + + Returns: -1 (setting errno) on failure, the id otherwise + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcgetpgrp.html ]] + */ + tcgetpgrp :: proc(fildes: FD) -> pid_t --- + + /* + Set the foreground process group ID. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsetpgrp.html ]] + */ + tcsetpgrp :: proc(fildes: FD, pgid_id: pid_t) -> result --- + + /* + Find the path name of a terminal. + + Returns: nil (setting errno) on failure, the name, which may be invalidated by subsequent calls on success + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ttyname.html ]] + */ + ttyname :: proc(fildes: FD) -> cstring --- + + /* + Equivalent to ttyname but name is placed into the buf. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/ttyname.html ]] + */ + ttyname_r :: proc(fildes: FD, name: [^]byte, namesize: c.size_t) -> Errno --- + + /* + Equivalent to unlink or rmdir (if flag is .REMOVEDIR) but relative paths are relative to the dir fd. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlink.html ]] + */ + unlinkat :: proc(fd: FD, path: cstring, flag: AT_Flags) -> result --- +} + +CS :: enum c.int { + _PATH = _CS_PATH, + _POSIX_V6_ILP32_OFF32_CFLAGS = _CS_POSIX_V6_ILP32_OFF32_CFLAGS, + _POSIX_V6_ILP32_OFF32_LDFLAGS = _CS_POSIX_V6_ILP32_OFF32_LDFLAGS, + _POSIX_V6_ILP32_OFF32_LIBS = _CS_POSIX_V6_ILP32_OFF32_LIBS, + _POSIX_V6_ILP32_OFFBIG_CFLAGS = _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS, + _POSIX_V6_ILP32_OFFBIG_LDFLAGS = _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS, + _POSIX_V6_ILP32_OFFBIG_LIBS = _CS_POSIX_V6_ILP32_OFFBIG_LIBS, + _POSIX_V6_LP64_OFF64_CFLAGS = _CS_POSIX_V6_LP64_OFF64_CFLAGS, + _POSIX_V6_LP64_OFF64_LDFLAGS = _CS_POSIX_V6_LP64_OFF64_LDFLAGS, + _POSIX_V6_LP64_OFF64_LIBS = _CS_POSIX_V6_LP64_OFF64_LIBS, + _POSIX_V6_LPBIG_OFFBIG_CFLAGS = _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS, + _POSIX_V6_LPBIG_OFFBIG_LDFLAGS = _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS, + _POSIX_V6_LPBIG_OFFBIG_LIBS = _CS_POSIX_V6_LPBIG_OFFBIG_LIBS, + _POSIX_V6_WIDTH_RESTRICTED_ENVS = _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS, +} + +PC :: enum c.int { + _2_SYMLINK = _PC_2_SYMLINK, + _ALLOC_SIZE_MIN = _PC_ALLOC_SIZE_MIN, + _ASYNC_IO = _PC_ASYNC_IO, + _CHOWN_RESTRICTED = _PC_CHOWN_RESTRICTED, + _FILESIZEBITS = _PC_FILESIZEBITS, + _LINK_MAX = _PC_LINK_MAX, + _MAX_CANON = _PC_MAX_CANON, + _MAX_INPUT = _PC_MAX_INPUT, + _NAME_MAX = _PC_NAME_MAX, + _NO_TRUNC = _PC_NO_TRUNC, + _PATH_MAX = _PC_PATH_MAX, + _PIPE_BUF = _PC_PIPE_BUF, + _PRIO_IO = _PC_PRIO_IO, + _REC_INCR_XFER_SIZE = _PC_REC_INCR_XFER_SIZE, + _REC_MAX_XFER_SIZE = _PC_REC_MAX_XFER_SIZE, + _REC_MIN_XFER_SIZE = _PC_REC_MIN_XFER_SIZE, + _REC_XFER_ALIGN = _PC_REC_XFER_ALIGN, + _SYMLINK_MAX = _PC_SYMLINK_MAX, + _SYNC_IO = _PC_SYNC_IO, + _VDISABLE = _PC_VDISABLE, +} + +SC :: enum c.int { + _2_C_BIND = _SC_2_C_BIND, + _2_C_DEV = _SC_2_C_DEV, + _2_CHAR_TERM = _SC_2_CHAR_TERM, + _2_FORT_DEV = _SC_2_FORT_DEV, + _2_FORT_RUN = _SC_2_FORT_RUN, + _2_LOCALEDEF = _SC_2_LOCALEDEF, + _2_PBS = _SC_2_PBS, + _2_PBS_ACCOUNTING = _SC_2_PBS_ACCOUNTING, + _2_PBS_CHECKPOINT = _SC_2_PBS_CHECKPOINT, + _2_PBS_LOCATE = _SC_2_PBS_LOCATE, + _2_PBS_MESSAGE = _SC_2_PBS_MESSAGE, + _2_PBS_TRACK = _SC_2_PBS_TRACK, + _2_SW_DEV = _SC_2_SW_DEV, + _2_UPE = _SC_2_UPE, + _2_VERSION = _SC_2_VERSION, + _ADVISORY_INFO = _SC_ADVISORY_INFO, + _AIO_LISTIO_MAX = _SC_AIO_LISTIO_MAX, + _AIO_MAX = _SC_AIO_MAX, + _AIO_PRIO_DELTA_MAX = _SC_AIO_PRIO_DELTA_MAX, + _ARG_MAX = _SC_ARG_MAX, + _ASYNCHRONOUS_IO = _SC_ASYNCHRONOUS_IO, + _ATEXIT_MAX = _SC_ATEXIT_MAX, + _BARRIERS = _SC_BARRIERS, + _BC_BASE_MAX = _SC_BC_BASE_MAX, + _BC_DIM_MAX = _SC_BC_DIM_MAX, + _BC_SCALE_MAX = _SC_BC_SCALE_MAX, + _BC_STRING_MAX = _SC_BC_STRING_MAX, + _CHILD_MAX = _SC_CHILD_MAX, + _CLK_TCK = _SC_CLK_TCK, + _CLOCK_SELECTION = _SC_CLOCK_SELECTION, + _COLL_WEIGHTS_MAX = _SC_COLL_WEIGHTS_MAX, + _CPUTIME = _SC_CPUTIME, + _DELAYTIMER_MAX = _SC_DELAYTIMER_MAX, + _EXPR_NEST_MAX = _SC_EXPR_NEST_MAX, + _FSYNC = _SC_FSYNC, + _GETGR_R_SIZE_MAX = _SC_GETGR_R_SIZE_MAX, + _GETPW_R_SIZE_MAX = _SC_GETPW_R_SIZE_MAX, + _HOST_NAME_MAX = _SC_HOST_NAME_MAX, + _IOV_MAX = _SC_IOV_MAX, + _IPV6 = _SC_IPV6, + _JOB_CONTROL = _SC_JOB_CONTROL, + _LINE_MAX = _SC_LINE_MAX, + _LOGIN_NAME_MAX = _SC_LOGIN_NAME_MAX, + _MAPPED_FILES = _SC_MAPPED_FILES, + _MEMLOCK = _SC_MEMLOCK, + _MEMLOCK_RANGE = _SC_MEMLOCK_RANGE, + _MEMORY_PROTECTION = _SC_MEMORY_PROTECTION, + _MESSAGE_PASSING = _SC_MESSAGE_PASSING, + _MONOTONIC_CLOCK = _SC_MONOTONIC_CLOCK, + _MQ_OPEN_MAX = _SC_MQ_OPEN_MAX, + _MQ_PRIO_MAX = _SC_MQ_PRIO_MAX, + _NGROUPS_MAX = _SC_NGROUPS_MAX, + _OPEN_MAX = _SC_OPEN_MAX, + _PAGE_SIZE = _SC_PAGE_SIZE, + _PAGESIZE = _SC_PAGESIZE, + _PRIORITIZED_IO = _SC_PRIORITIZED_IO, + _PRIORITY_SCHEDULING = _SC_PRIORITY_SCHEDULING, + _RAW_SOCKETS = _SC_RAW_SOCKETS, + _RE_DUP_MAX = _SC_RE_DUP_MAX, + _READER_WRITER_LOCKS = _SC_READER_WRITER_LOCKS, + _REALTIME_SIGNALS = _SC_REALTIME_SIGNALS, + _REGEXP = _SC_REGEXP, + _RTSIG_MAX = _SC_RTSIG_MAX, + _SAVED_IDS = _SC_SAVED_IDS, + _SEM_NSEMS_MAX = _SC_SEM_NSEMS_MAX, + _SEM_VALUE_MAX = _SC_SEM_VALUE_MAX, + _SEMAPHORES = _SC_SEMAPHORES, + _SHARED_MEMORY_OBJECTS = _SC_SHARED_MEMORY_OBJECTS, + _SHELL = _SC_SHELL, + _SIGQUEUE_MAX = _SC_SIGQUEUE_MAX, + _SPAWN = _SC_SPAWN, + _SPIN_LOCKS = _SC_SPIN_LOCKS, + _SPORADIC_SERVER = _SC_SPORADIC_SERVER, + _SS_REPL_MAX = _SC_SS_REPL_MAX, + _STREAM_MAX = _SC_STREAM_MAX, + _SYMLOOP_MAX = _SC_SYMLOOP_MAX, + _SYNCHRONIZED_IO = _SC_SYNCHRONIZED_IO, + _THREAD_ATTR_STACKADDR = _SC_THREAD_ATTR_STACKADDR, + _THREAD_ATTR_STACKSIZE = _SC_THREAD_ATTR_STACKSIZE, + _THREAD_CPUTIME = _SC_THREAD_CPUTIME, + _THREAD_DESTRUCTOR_ITERATIONS = _SC_THREAD_DESTRUCTOR_ITERATIONS, + _THREAD_KEYS_MAX = _SC_THREAD_KEYS_MAX, + _THREAD_PRIO_INHERIT = _SC_THREAD_PRIO_INHERIT, + _THREAD_PRIO_PROTECT = _SC_THREAD_PRIO_PROTECT, + _THREAD_PRIORITY_SCHEDULING = _SC_THREAD_PRIORITY_SCHEDULING, + _THREAD_PROCESS_SHARED = _SC_THREAD_PROCESS_SHARED, + _THREAD_SAFE_FUNCTIONS = _SC_THREAD_SAFE_FUNCTIONS, + _THREAD_SPORADIC_SERVER = _SC_THREAD_SPORADIC_SERVER, + _THREAD_STACK_MIN = _SC_THREAD_STACK_MIN, + _THREAD_THREADS_MAX = _SC_THREAD_THREADS_MAX, + _THREADS = _SC_THREADS, + _TIMEOUTS = _SC_TIMEOUTS, + _TIMER_MAX = _SC_TIMER_MAX, + _TIMERS = _SC_TIMERS, + _TRACE = _SC_TRACE, + _TRACE_EVENT_FILTER = _SC_TRACE_EVENT_FILTER, + _TRACE_EVENT_NAME_MAX = _SC_TRACE_EVENT_NAME_MAX, + _TRACE_INHERIT = _SC_TRACE_INHERIT, + _TRACE_LOG = _SC_TRACE_LOG, + _TRACE_NAME_MAX = _SC_TRACE_NAME_MAX, + _TRACE_SYS_MAX = _SC_TRACE_SYS_MAX, + _TRACE_USER_EVENT_MAX = _SC_TRACE_USER_EVENT_MAX, + _TTY_NAME_MAX = _SC_TTY_NAME_MAX, + _TYPED_MEMORY_OBJECTS = _SC_TYPED_MEMORY_OBJECTS, + _TZNAME_MAX = _SC_TZNAME_MAX, + _V6_ILP32_OFF32 = _SC_V6_ILP32_OFF32, + _V6_ILP32_OFFBIG = _SC_V6_ILP32_OFFBIG, + _V6_LP64_OFF64 = _SC_V6_LP64_OFF64, + _V6_LPBIG_OFFBIG = _SC_V6_LPBIG_OFFBIG, + _VERSION = _SC_VERSION, + _XOPEN_CRYPT = _SC_XOPEN_CRYPT, + _XOPEN_ENH_I18N = _SC_XOPEN_ENH_I18N, + _XOPEN_REALTIME = _SC_XOPEN_REALTIME, + _XOPEN_REALTIME_THREADS = _SC_XOPEN_REALTIME_THREADS, + _XOPEN_SHM = _SC_XOPEN_SHM, + _XOPEN_STREAMS = _SC_XOPEN_STREAMS, + _XOPEN_UNIX = _SC_XOPEN_UNIX, + _XOPEN_VERSION = _SC_XOPEN_VERSION, +} + +Lock_Function :: enum c.int { + // Lock a section for exclusive use. + LOCK = F_LOCK, + // Test a section for locks by other processes. + TEST = F_TEST, + // Test and lock a section for exclusive use. + TLOCK = F_TLOCK, + // Unlock locked sections. + ULOCK = F_ULOCK, +} + +when ODIN_OS == .NetBSD { + @(private) LCHOWN :: "__posix_chown" + @(private) LFCHOWN :: "__posix_fchown" + @(private) LLCHOWN :: "__posix_lchown" +} else { + @(private) LCHOWN :: "chown" + @(private) LFCHOWN :: "fchown" + @(private) LLCHOWN :: "lchown" +} + +when ODIN_OS == .Darwin { + + _F_OK :: 0 + X_OK :: (1<<0) + W_OK :: (1<<1) + R_OK :: (1<<2) + + F_LOCK :: 1 + F_TEST :: 3 + F_TLOCK :: 2 + F_ULOCK :: 0 + + _CS_PATH :: 1 + _CS_POSIX_V6_ILP32_OFF32_CFLAGS :: 2 + _CS_POSIX_V6_ILP32_OFF32_LDFLAGS :: 3 + _CS_POSIX_V6_ILP32_OFF32_LIBS :: 4 + _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS :: 5 + _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS :: 6 + _CS_POSIX_V6_ILP32_OFFBIG_LIBS :: 7 + _CS_POSIX_V6_LP64_OFF64_CFLAGS :: 8 + _CS_POSIX_V6_LP64_OFF64_LDFLAGS :: 9 + _CS_POSIX_V6_LP64_OFF64_LIBS :: 10 + _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS :: 11 + _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS :: 12 + _CS_POSIX_V6_LPBIG_OFFBIG_LIBS :: 13 + _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS :: 14 + + _PC_LINK_MAX :: 1 + _PC_MAX_CANON :: 2 + _PC_MAX_INPUT :: 3 + _PC_NAME_MAX :: 4 + _PC_PATH_MAX :: 5 + _PC_PIPE_BUF :: 6 + _PC_CHOWN_RESTRICTED :: 7 + _PC_NO_TRUNC :: 8 + _PC_VDISABLE :: 9 + _PC_2_SYMLINK :: 15 + _PC_ALLOC_SIZE_MIN :: 16 + _PC_ASYNC_IO :: 17 + _PC_FILESIZEBITS :: 18 + _PC_PRIO_IO :: 19 + _PC_REC_INCR_XFER_SIZE :: 20 + _PC_REC_MAX_XFER_SIZE :: 21 + _PC_REC_MIN_XFER_SIZE :: 22 + _PC_REC_XFER_ALIGN :: 23 + _PC_SYMLINK_MAX :: 24 + _PC_SYNC_IO :: 25 + + _SC_ARG_MAX :: 1 + _SC_CHILD_MAX :: 2 + _SC_CLK_TCK :: 3 + _SC_NGROUPS_MAX :: 4 + _SC_OPEN_MAX :: 5 + _SC_JOB_CONTROL :: 6 + _SC_SAVED_IDS :: 7 + _SC_VERSION :: 8 + _SC_BC_BASE_MAX :: 9 + + _SC_BC_DIM_MAX :: 10 + _SC_BC_SCALE_MAX :: 11 + _SC_BC_STRING_MAX :: 12 + _SC_COLL_WEIGHTS_MAX :: 13 + _SC_EXPR_NEST_MAX :: 14 + _SC_LINE_MAX :: 15 + _SC_RE_DUP_MAX :: 16 + _SC_2_VERSION :: 17 + _SC_2_C_BIND :: 18 + _SC_2_C_DEV :: 19 + + _SC_2_CHAR_TERM :: 20 + _SC_2_FORT_DEV :: 21 + _SC_2_FORT_RUN :: 22 + _SC_2_LOCALEDEF :: 23 + _SC_2_SW_DEV :: 24 + _SC_2_UPE :: 25 + _SC_STREAM_MAX :: 26 + _SC_TZNAME_MAX :: 27 + _SC_ASYNCHRONOUS_IO :: 28 + _SC_PAGE_SIZE :: 29 + _SC_PAGESIZE :: _SC_PAGE_SIZE + + _SC_MEMLOCK :: 30 + _SC_MEMLOCK_RANGE :: 31 + _SC_MEMORY_PROTECTION :: 32 + _SC_MESSAGE_PASSING :: 33 + _SC_PRIORITIZED_IO :: 34 + _SC_PRIORITY_SCHEDULING :: 35 + _SC_REALTIME_SIGNALS :: 36 + _SC_SEMAPHORES :: 37 + _SC_FSYNC :: 38 + _SC_SHARED_MEMORY_OBJECTS :: 39 + + _SC_SYNCHRONIZED_IO :: 40 + _SC_TIMERS :: 41 + _SC_AIO_LISTIO_MAX :: 42 + _SC_AIO_MAX :: 43 + _SC_AIO_PRIO_DELTA_MAX :: 44 + _SC_DELAYTIMER_MAX :: 45 + _SC_MQ_OPEN_MAX :: 46 + _SC_MAPPED_FILES :: 47 + _SC_RTSIG_MAX :: 48 + _SC_SEM_NSEMS_MAX :: 49 + + _SC_SEM_VALUE_MAX :: 50 + _SC_SIGQUEUE_MAX :: 51 + _SC_TIMER_MAX :: 52 + _SC_IOV_MAX :: 56 + _SC_2_PBS :: 59 + + _SC_2_PBS_ACCOUNTING :: 60 + _SC_2_PBS_CHECKPOINT :: 61 + _SC_2_PBS_LOCATE :: 62 + _SC_2_PBS_MESSAGE :: 63 + _SC_2_PBS_TRACK :: 64 + _SC_ADVISORY_INFO :: 65 + _SC_BARRIERS :: 66 + _SC_CLOCK_SELECTION :: 67 + _SC_CPUTIME :: 68 + + _SC_GETGR_R_SIZE_MAX :: 70 + _SC_GETPW_R_SIZE_MAX :: 71 + _SC_HOST_NAME_MAX :: 72 + _SC_LOGIN_NAME_MAX :: 73 + _SC_MONOTONIC_CLOCK :: 74 + _SC_MQ_PRIO_MAX :: 75 + _SC_READER_WRITER_LOCKS :: 76 + _SC_REGEXP :: 77 + _SC_SHELL :: 78 + _SC_SPAWN :: 79 + + _SC_SPIN_LOCKS :: 80 + _SC_SPORADIC_SERVER :: 81 + _SC_THREAD_ATTR_STACKADDR :: 82 + _SC_THREAD_ATTR_STACKSIZE :: 83 + _SC_THREAD_CPUTIME :: 84 + _SC_THREAD_DESTRUCTOR_ITERATIONS :: 85 + _SC_THREAD_KEYS_MAX :: 86 + _SC_THREAD_PRIO_INHERIT :: 87 + _SC_THREAD_PRIO_PROTECT :: 88 + _SC_THREAD_PRIORITY_SCHEDULING :: 89 + + _SC_THREAD_PROCESS_SHARED :: 90 + _SC_THREAD_SAFE_FUNCTIONS :: 91 + _SC_THREAD_SPORADIC_SERVER :: 92 + _SC_THREAD_STACK_MIN :: 93 + _SC_THREAD_THREADS_MAX :: 94 + _SC_TIMEOUTS :: 95 + _SC_THREADS :: 96 + _SC_TRACE :: 97 + _SC_TRACE_EVENT_FILTER :: 98 + _SC_TRACE_INHERIT :: 99 + + _SC_TRACE_LOG :: 100 + _SC_TTY_NAME_MAX :: 101 + _SC_TYPED_MEMORY_OBJECTS :: 102 + _SC_V6_ILP32_OFF32 :: 103 + _SC_V6_ILP32_OFFBIG :: 104 + _SC_V6_LP64_OFF64 :: 105 + _SC_V6_LPBIG_OFFBIG :: 106 + _SC_ATEXIT_MAX :: 107 + _SC_XOPEN_CRYPT :: 108 + _SC_XOPEN_ENH_I18N :: 109 + + _SC_XOPEN_REALTIME :: 111 + _SC_XOPEN_REALTIME_THREADS :: 112 + _SC_XOPEN_SHM :: 113 + _SC_XOPEN_STREAMS :: 114 + _SC_XOPEN_UNIX :: 115 + _SC_XOPEN_VERSION :: 116 + _SC_IPV6 :: 118 + _SC_RAW_SOCKETS :: 119 + + _SC_SYMLOOP_MAX :: 120 + _SC_SS_REPL_MAX :: 126 + _SC_TRACE_EVENT_NAME_MAX :: 127 + _SC_TRACE_NAME_MAX :: 128 + _SC_TRACE_SYS_MAX :: 129 + _SC_TRACE_USER_EVENT_MAX :: 130 + + _POSIX_VDISABLE :: '\377' + +} else when ODIN_OS == .FreeBSD { + + _F_OK :: 0 + X_OK :: 0x01 + W_OK :: 0x02 + R_OK :: 0x04 + + F_LOCK :: 1 + F_TEST :: 3 + F_TLOCK :: 2 + F_ULOCK :: 0 + + _CS_PATH :: 1 + _CS_POSIX_V6_ILP32_OFF32_CFLAGS :: 2 + _CS_POSIX_V6_ILP32_OFF32_LDFLAGS :: 3 + _CS_POSIX_V6_ILP32_OFF32_LIBS :: 4 + _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS :: 5 + _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS :: 6 + _CS_POSIX_V6_ILP32_OFFBIG_LIBS :: 7 + _CS_POSIX_V6_LP64_OFF64_CFLAGS :: 8 + _CS_POSIX_V6_LP64_OFF64_LDFLAGS :: 9 + _CS_POSIX_V6_LP64_OFF64_LIBS :: 10 + _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS :: 11 + _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS :: 12 + _CS_POSIX_V6_LPBIG_OFFBIG_LIBS :: 13 + _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS :: 14 + + _PC_LINK_MAX :: 1 + _PC_MAX_CANON :: 2 + _PC_MAX_INPUT :: 3 + _PC_NAME_MAX :: 4 + _PC_PATH_MAX :: 5 + _PC_PIPE_BUF :: 6 + _PC_CHOWN_RESTRICTED :: 7 + _PC_NO_TRUNC :: 8 + _PC_VDISABLE :: 9 + _PC_2_SYMLINK :: 13 // NOTE: not in headers (freebsd) + _PC_ALLOC_SIZE_MIN :: 10 + _PC_ASYNC_IO :: 53 + _PC_FILESIZEBITS :: 12 + _PC_PRIO_IO :: 54 + _PC_REC_INCR_XFER_SIZE :: 14 + _PC_REC_MAX_XFER_SIZE :: 15 + _PC_REC_MIN_XFER_SIZE :: 16 + _PC_REC_XFER_ALIGN :: 17 + _PC_SYMLINK_MAX :: 18 + _PC_SYNC_IO :: 55 + + _SC_ARG_MAX :: 1 + _SC_CHILD_MAX :: 2 + _SC_CLK_TCK :: 3 + _SC_NGROUPS_MAX :: 4 + _SC_OPEN_MAX :: 5 + _SC_JOB_CONTROL :: 6 + _SC_SAVED_IDS :: 7 + _SC_VERSION :: 8 + _SC_BC_BASE_MAX :: 9 + + _SC_BC_DIM_MAX :: 10 + _SC_BC_SCALE_MAX :: 11 + _SC_BC_STRING_MAX :: 12 + _SC_COLL_WEIGHTS_MAX :: 13 + _SC_EXPR_NEST_MAX :: 14 + _SC_LINE_MAX :: 15 + _SC_RE_DUP_MAX :: 16 + _SC_2_VERSION :: 17 + _SC_2_C_BIND :: 18 + _SC_2_C_DEV :: 19 + + _SC_2_CHAR_TERM :: 20 + _SC_2_FORT_DEV :: 21 + _SC_2_FORT_RUN :: 22 + _SC_2_LOCALEDEF :: 23 + _SC_2_SW_DEV :: 24 + _SC_2_UPE :: 25 + _SC_STREAM_MAX :: 26 + _SC_TZNAME_MAX :: 27 + _SC_ASYNCHRONOUS_IO :: 28 + _SC_MAPPED_FILES :: 29 + + _SC_MEMLOCK :: 30 + _SC_MEMLOCK_RANGE :: 31 + _SC_MEMORY_PROTECTION :: 32 + _SC_MESSAGE_PASSING :: 33 + _SC_PRIORITIZED_IO :: 34 + _SC_PRIORITY_SCHEDULING :: 35 + _SC_REALTIME_SIGNALS :: 36 + _SC_SEMAPHORES :: 37 + _SC_FSYNC :: 38 + _SC_SHARED_MEMORY_OBJECTS :: 39 + + _SC_SYNCHRONIZED_IO :: 40 + _SC_TIMERS :: 41 + _SC_AIO_LISTIO_MAX :: 42 + _SC_AIO_MAX :: 43 + _SC_AIO_PRIO_DELTA_MAX :: 44 + _SC_DELAYTIMER_MAX :: 45 + _SC_MQ_OPEN_MAX :: 46 + _SC_PAGE_SIZE :: 47 + _SC_PAGESIZE :: _SC_PAGE_SIZE + _SC_RTSIG_MAX :: 48 + _SC_SEM_NSEMS_MAX :: 49 + + _SC_SEM_VALUE_MAX :: 50 + _SC_SIGQUEUE_MAX :: 51 + _SC_TIMER_MAX :: 52 + _SC_IOV_MAX :: 56 + _SC_2_PBS :: 59 + + _SC_2_PBS_ACCOUNTING :: 60 + _SC_2_PBS_CHECKPOINT :: 61 + _SC_2_PBS_LOCATE :: 62 + _SC_2_PBS_MESSAGE :: 63 + _SC_2_PBS_TRACK :: 64 + _SC_ADVISORY_INFO :: 65 + _SC_BARRIERS :: 66 + _SC_CLOCK_SELECTION :: 67 + _SC_CPUTIME :: 68 + + _SC_GETGR_R_SIZE_MAX :: 70 + _SC_GETPW_R_SIZE_MAX :: 71 + _SC_HOST_NAME_MAX :: 72 + _SC_LOGIN_NAME_MAX :: 73 + _SC_MONOTONIC_CLOCK :: 74 + _SC_MQ_PRIO_MAX :: 75 + _SC_READER_WRITER_LOCKS :: 76 + _SC_REGEXP :: 77 + _SC_SHELL :: 78 + _SC_SPAWN :: 79 + + _SC_SPIN_LOCKS :: 80 + _SC_SPORADIC_SERVER :: 81 + _SC_THREAD_ATTR_STACKADDR :: 82 + _SC_THREAD_ATTR_STACKSIZE :: 83 + _SC_THREAD_CPUTIME :: 84 + _SC_THREAD_DESTRUCTOR_ITERATIONS :: 85 + _SC_THREAD_KEYS_MAX :: 86 + _SC_THREAD_PRIO_INHERIT :: 87 + _SC_THREAD_PRIO_PROTECT :: 88 + _SC_THREAD_PRIORITY_SCHEDULING :: 89 + + _SC_THREAD_PROCESS_SHARED :: 90 + _SC_THREAD_SAFE_FUNCTIONS :: 91 + _SC_THREAD_SPORADIC_SERVER :: 92 + _SC_THREAD_STACK_MIN :: 93 + _SC_THREAD_THREADS_MAX :: 94 + _SC_TIMEOUTS :: 95 + _SC_THREADS :: 96 + _SC_TRACE :: 97 + _SC_TRACE_EVENT_FILTER :: 98 + _SC_TRACE_INHERIT :: 99 + + _SC_TRACE_LOG :: 100 + _SC_TTY_NAME_MAX :: 101 + _SC_TYPED_MEMORY_OBJECTS :: 102 + _SC_V6_ILP32_OFF32 :: 103 + _SC_V6_ILP32_OFFBIG :: 104 + _SC_V6_LP64_OFF64 :: 105 + _SC_V6_LPBIG_OFFBIG :: 106 + _SC_ATEXIT_MAX :: 107 + _SC_XOPEN_CRYPT :: 108 + _SC_XOPEN_ENH_I18N :: 109 + + _SC_XOPEN_REALTIME :: 111 + _SC_XOPEN_REALTIME_THREADS :: 112 + _SC_XOPEN_SHM :: 113 + _SC_XOPEN_STREAMS :: 114 + _SC_XOPEN_UNIX :: 115 + _SC_XOPEN_VERSION :: 116 + _SC_IPV6 :: 118 + _SC_RAW_SOCKETS :: 119 + + _SC_SYMLOOP_MAX :: 120 + _SC_SS_REPL_MAX :: 126 // NOTE: not in headers + _SC_TRACE_EVENT_NAME_MAX :: 127 // NOTE: not in headers + _SC_TRACE_NAME_MAX :: 128 // NOTE: not in headers + _SC_TRACE_SYS_MAX :: 129 // NOTE: not in headers + _SC_TRACE_USER_EVENT_MAX :: 130 // NOTE: not in headers + + _POSIX_VDISABLE :: 0xff + +} else when ODIN_OS == .NetBSD { + + _F_OK :: 0 + X_OK :: 0x01 + W_OK :: 0x02 + R_OK :: 0x04 + + F_LOCK :: 1 + F_TEST :: 3 + F_TLOCK :: 2 + F_ULOCK :: 0 + + _CS_PATH :: 1 + _CS_POSIX_V6_ILP32_OFF32_CFLAGS :: 2 + _CS_POSIX_V6_ILP32_OFF32_LDFLAGS :: 3 + _CS_POSIX_V6_ILP32_OFF32_LIBS :: 4 + _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS :: 5 + _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS :: 6 + _CS_POSIX_V6_ILP32_OFFBIG_LIBS :: 7 + _CS_POSIX_V6_LP64_OFF64_CFLAGS :: 8 + _CS_POSIX_V6_LP64_OFF64_LDFLAGS :: 9 + _CS_POSIX_V6_LP64_OFF64_LIBS :: 10 + _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS :: 11 + _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS :: 12 + _CS_POSIX_V6_LPBIG_OFFBIG_LIBS :: 13 + _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS :: 14 + + _PC_LINK_MAX :: 1 + _PC_MAX_CANON :: 2 + _PC_MAX_INPUT :: 3 + _PC_NAME_MAX :: 4 + _PC_PATH_MAX :: 5 + _PC_PIPE_BUF :: 6 + _PC_CHOWN_RESTRICTED :: 7 + _PC_NO_TRUNC :: 8 + _PC_VDISABLE :: 9 + _PC_2_SYMLINK :: 13 // NOTE: not in headers + _PC_ALLOC_SIZE_MIN :: 10 // NOTE: not in headers + _PC_ASYNC_IO :: 53 // NOTE: not in headers + _PC_FILESIZEBITS :: 11 + _PC_PRIO_IO :: 54 // NOTE: not in headers + _PC_REC_INCR_XFER_SIZE :: 14 // NOTE: not in headers + _PC_REC_MAX_XFER_SIZE :: 15 // NOTE: not in headers + _PC_REC_MIN_XFER_SIZE :: 16 // NOTE: not in headers + _PC_REC_XFER_ALIGN :: 17 // NOTE: not in headers + _PC_SYMLINK_MAX :: 12 + _PC_SYNC_IO :: 10 + + _SC_ARG_MAX :: 1 + _SC_CHILD_MAX :: 2 + _SC_NGROUPS_MAX :: 4 + _SC_OPEN_MAX :: 5 + _SC_JOB_CONTROL :: 6 + _SC_SAVED_IDS :: 7 + _SC_VERSION :: 8 + _SC_BC_BASE_MAX :: 9 + + _SC_BC_DIM_MAX :: 10 + _SC_BC_SCALE_MAX :: 11 + _SC_BC_STRING_MAX :: 12 + _SC_COLL_WEIGHTS_MAX :: 13 + _SC_EXPR_NEST_MAX :: 14 + _SC_LINE_MAX :: 15 + _SC_RE_DUP_MAX :: 16 + _SC_2_VERSION :: 17 + _SC_2_C_BIND :: 18 + _SC_2_C_DEV :: 19 + + _SC_2_CHAR_TERM :: 20 + _SC_2_FORT_DEV :: 21 + _SC_2_FORT_RUN :: 22 + _SC_2_LOCALEDEF :: 23 + _SC_2_SW_DEV :: 24 + _SC_2_UPE :: 25 + _SC_STREAM_MAX :: 26 + _SC_TZNAME_MAX :: 27 + _SC_PAGE_SIZE :: 28 + _SC_PAGESIZE :: _SC_PAGE_SIZE + _SC_FSYNC :: 29 + + _SC_XOPEN_SHM :: 30 + _SC_SYNCHRONIZED_IO :: 31 + _SC_IOV_MAX :: 32 + _SC_MAPPED_FILES :: 33 + _SC_MEMLOCK :: 34 + _SC_MEMLOCK_RANGE :: 35 + _SC_MEMORY_PROTECTION :: 36 + _SC_LOGIN_NAME_MAX :: 37 + _SC_MONOTONIC_CLOCK :: 38 + _SC_CLK_TCK :: 39 + + _SC_ATEXIT_MAX :: 40 + _SC_THREADS :: 41 + _SC_SEMAPHORES :: 42 + _SC_BARRIERS :: 43 + _SC_TIMERS :: 44 + _SC_SPIN_LOCKS :: 45 + _SC_READER_WRITER_LOCKS :: 46 + _SC_GETGR_R_SIZE_MAX :: 47 + _SC_GETPW_R_SIZE_MAX :: 48 + _SC_CLOCK_SELECTION :: 49 + + _SC_ASYNCHRONOUS_IO :: 50 + _SC_AIO_LISTIO_MAX :: 51 + _SC_AIO_MAX :: 52 + _SC_MESSAGE_PASSING :: 53 + _SC_MQ_OPEN_MAX :: 54 + _SC_MQ_PRIO_MAX :: 55 + _SC_PRIORITY_SCHEDULING :: 56 + _SC_THREAD_DESTRUCTOR_ITERATIONS :: 57 + _SC_THREAD_KEYS_MAX :: 58 + _SC_THREAD_STACK_MIN :: 59 + + _SC_THREAD_THREADS_MAX :: 60 + _SC_THREAD_ATTR_STACKADDR :: 61 + _SC_THREAD_ATTR_STACKSIZE :: 62 + _SC_THREAD_PRIORITY_SCHEDULING :: 63 + _SC_THREAD_PRIO_INHERIT :: 64 + _SC_THREAD_PRIO_PROTECT :: 65 + _SC_THREAD_PROCESS_SHARED :: 66 + _SC_THREAD_SAFE_FUNCTIONS :: 67 + _SC_TTY_NAME_MAX :: 68 + _SC_HOST_NAME_MAX :: 69 + + _SC_REGEXP :: 71 + _SC_SHELL :: 72 + _SC_SYMLOOP_MAX :: 73 + _SC_V6_ILP32_OFF32 :: 74 + _SC_V6_ILP32_OFFBIG :: 75 + _SC_V6_LP64_OFF64 :: 76 + _SC_V6_LPBIG_OFFBIG :: 77 + + _SC_2_PBS :: 80 + _SC_2_PBS_ACCOUNTING :: 81 + _SC_2_PBS_CHECKPOINT :: 82 + _SC_2_PBS_LOCATE :: 83 + _SC_2_PBS_MESSAGE :: 84 + _SC_2_PBS_TRACK :: 85 + _SC_SPAWN :: 86 + _SC_SHARED_MEMORY_OBJECTS :: 87 + _SC_TIMER_MAX :: 88 + _SC_SEM_NSEMS_MAX :: 89 + + _SC_CPUTIME :: 90 + _SC_THREAD_CPUTIME :: 91 + _SC_DELAYTIMER_MAX :: 92 + _SC_SIGQUEUE_MAX :: 93 + _SC_REALTIME_SIGNALS :: 94 + _SC_RTSIG_MAX :: 95 + + _POSIX_VDISABLE :: '\377' + + // NOTE: following are not defined in netbsd headers. + + _SC_SPORADIC_SERVER :: 81 + _SC_SEM_VALUE_MAX :: 50 + + _SC_TRACE :: 97 + _SC_TRACE_EVENT_FILTER :: 98 + _SC_TRACE_INHERIT :: 99 + _SC_TRACE_LOG :: 100 + _SC_TYPED_MEMORY_OBJECTS :: 102 + + _SC_THREAD_SPORADIC_SERVER :: 92 + _SC_TIMEOUTS :: 95 + + _SC_XOPEN_CRYPT :: 108 + _SC_XOPEN_ENH_I18N :: 109 + _SC_XOPEN_REALTIME :: 111 + _SC_XOPEN_REALTIME_THREADS :: 112 + _SC_XOPEN_STREAMS :: 114 + _SC_XOPEN_UNIX :: 115 + _SC_XOPEN_VERSION :: 116 + _SC_IPV6 :: 118 + _SC_RAW_SOCKETS :: 119 + + _SC_PRIORITIZED_IO :: 34 + _SC_AIO_PRIO_DELTA_MAX :: 44 + _SC_ADVISORY_INFO :: 65 + _SC_SS_REPL_MAX :: 126 + _SC_TRACE_EVENT_NAME_MAX :: 127 + _SC_TRACE_NAME_MAX :: 128 + _SC_TRACE_SYS_MAX :: 129 + _SC_TRACE_USER_EVENT_MAX :: 130 + +} else when ODIN_OS == .OpenBSD { + + _F_OK :: 0 + X_OK :: 0x01 + W_OK :: 0x02 + R_OK :: 0x04 + + F_LOCK :: 1 + F_TEST :: 3 + F_TLOCK :: 2 + F_ULOCK :: 0 + + _CS_PATH :: 1 + _CS_POSIX_V6_ILP32_OFF32_CFLAGS :: 2 + _CS_POSIX_V6_ILP32_OFF32_LDFLAGS :: 3 + _CS_POSIX_V6_ILP32_OFF32_LIBS :: 4 + _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS :: 5 + _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS :: 6 + _CS_POSIX_V6_ILP32_OFFBIG_LIBS :: 7 + _CS_POSIX_V6_LP64_OFF64_CFLAGS :: 8 + _CS_POSIX_V6_LP64_OFF64_LDFLAGS :: 9 + _CS_POSIX_V6_LP64_OFF64_LIBS :: 10 + _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS :: 11 + _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS :: 12 + _CS_POSIX_V6_LPBIG_OFFBIG_LIBS :: 13 + _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS :: 14 + + _PC_LINK_MAX :: 1 + _PC_MAX_CANON :: 2 + _PC_MAX_INPUT :: 3 + _PC_NAME_MAX :: 4 + _PC_PATH_MAX :: 5 + _PC_PIPE_BUF :: 6 + _PC_CHOWN_RESTRICTED :: 7 + _PC_NO_TRUNC :: 8 + _PC_VDISABLE :: 9 + _PC_2_SYMLINK :: 10 + _PC_ALLOC_SIZE_MIN :: 11 + _PC_ASYNC_IO :: 12 + _PC_FILESIZEBITS :: 13 + _PC_PRIO_IO :: 14 + _PC_REC_INCR_XFER_SIZE :: 15 + _PC_REC_MAX_XFER_SIZE :: 16 + _PC_REC_MIN_XFER_SIZE :: 17 + _PC_REC_XFER_ALIGN :: 18 + _PC_SYMLINK_MAX :: 19 + _PC_SYNC_IO :: 20 + + _SC_ARG_MAX :: 1 + _SC_CHILD_MAX :: 2 + _SC_CLK_TCK :: 3 + _SC_NGROUPS_MAX :: 4 + _SC_OPEN_MAX :: 5 + _SC_JOB_CONTROL :: 6 + _SC_SAVED_IDS :: 7 + _SC_VERSION :: 8 + _SC_BC_BASE_MAX :: 9 + + _SC_BC_DIM_MAX :: 10 + _SC_BC_SCALE_MAX :: 11 + _SC_BC_STRING_MAX :: 12 + _SC_COLL_WEIGHTS_MAX :: 13 + _SC_EXPR_NEST_MAX :: 14 + _SC_LINE_MAX :: 15 + _SC_RE_DUP_MAX :: 16 + _SC_2_VERSION :: 17 + _SC_2_C_BIND :: 18 + _SC_2_C_DEV :: 19 + + _SC_2_CHAR_TERM :: 20 + _SC_2_FORT_DEV :: 21 + _SC_2_FORT_RUN :: 22 + _SC_2_LOCALEDEF :: 23 + _SC_2_SW_DEV :: 24 + _SC_2_UPE :: 25 + _SC_STREAM_MAX :: 26 + _SC_TZNAME_MAX :: 27 + _SC_PAGESIZE :: 28 + _SC_PAGE_SIZE :: _SC_PAGESIZE + _SC_FSYNC :: 29 + + _SC_XOPEN_SHM :: 30 + _SC_SEM_NSEMS_MAX :: 31 + _SC_SEM_VALUE_MAX :: 32 + _SC_HOST_NAME_MAX :: 33 + _SC_MONOTONIC_CLOCK :: 34 + _SC_2_PBS :: 35 + _SC_2_PBS_ACCOUNTING :: 36 + _SC_2_PBS_CHECKPOINT :: 37 + _SC_2_PBS_LOCATE :: 38 + _SC_2_PBS_MESSAGE :: 39 + + _SC_2_PBS_TRACK :: 40 + _SC_ADVISORY_INFO :: 41 + _SC_AIO_LISTIO_MAX :: 42 + _SC_AIO_MAX :: 43 + _SC_AIO_PRIO_DELTA_MAX :: 44 + _SC_ASYNCHRONOUS_IO :: 45 + _SC_ATEXIT_MAX :: 46 + _SC_BARRIERS :: 47 + _SC_CLOCK_SELECTION :: 48 + _SC_CPUTIME :: 49 + + _SC_DELAYTIMER_MAX :: 50 + _SC_IOV_MAX :: 51 + _SC_IPV6 :: 52 + _SC_MAPPED_FILES :: 53 + _SC_MEMLOCK :: 54 + _SC_MEMLOCK_RANGE :: 55 + _SC_MEMORY_PROTECTION :: 56 + _SC_MESSAGE_PASSING :: 57 + _SC_MQ_OPEN_MAX :: 58 + _SC_MQ_PRIO_MAX :: 59 + + _SC_PRIORITIZED_IO :: 60 + _SC_PRIORITY_SCHEDULING :: 61 + _SC_RAW_SOCKETS :: 62 + _SC_READER_WRITER_LOCKS :: 63 + _SC_REALTIME_SIGNALS :: 64 + _SC_REGEXP :: 65 + _SC_RTSIG_MAX :: 66 + _SC_SEMAPHORES :: 67 + _SC_SHARED_MEMORY_OBJECTS :: 68 + _SC_SHELL :: 69 + + _SC_SIGQUEUE_MAX :: 70 + _SC_SPAWN :: 71 + _SC_SPIN_LOCKS :: 72 + _SC_SPORADIC_SERVER :: 73 + _SC_SS_REPL_MAX :: 74 + _SC_SYNCHRONIZED_IO :: 75 + _SC_SYMLOOP_MAX :: 76 + _SC_THREAD_ATTR_STACKADDR :: 77 + _SC_THREAD_ATTR_STACKSIZE :: 78 + _SC_THREAD_CPUTIME :: 79 + + _SC_THREAD_DESTRUCTOR_ITERATIONS :: 80 + _SC_THREAD_KEYS_MAX :: 81 + _SC_THREAD_PRIO_INHERIT :: 82 + _SC_THREAD_PRIO_PROTECT :: 83 + _SC_THREAD_PRIORITY_SCHEDULING :: 84 + _SC_THREAD_PROCESS_SHARED :: 85 + _SC_THREAD_ROBUST_PRIO_INHERIT :: 86 + _SC_THREAD_ROBUST_PRIO_PROTECT :: 87 + _SC_THREAD_SPORADIC_SERVER :: 88 + _SC_THREAD_STACK_MIN :: 89 + + _SC_THREAD_THREADS_MAX :: 90 + _SC_THREADS :: 91 + _SC_TIMEOUTS :: 92 + _SC_TIMER_MAX :: 93 + _SC_TIMERS :: 94 + _SC_TRACE :: 95 + _SC_TRACE_EVENT_FILTER :: 96 + _SC_TRACE_EVENT_NAME_MAX :: 97 + _SC_TRACE_INHERIT :: 98 + _SC_TRACE_LOG :: 99 + + _SC_GETGR_R_SIZE_MAX :: 100 + _SC_GETPW_R_SIZE_MAX :: 101 + _SC_LOGIN_NAME_MAX :: 102 + _SC_THREAD_SAFE_FUNCTIONS :: 103 + _SC_TRACE_NAME_MAX :: 104 + _SC_TRACE_SYS_MAX :: 105 + _SC_TRACE_USER_EVENT_MAX :: 106 + _SC_TTY_NAME_MAX :: 107 + _SC_TYPED_MEMORY_OBJECTS :: 108 + _SC_V6_ILP32_OFF32 :: 109 + + _SC_V6_ILP32_OFFBIG :: 110 + _SC_V6_LP64_OFF64 :: 111 + _SC_V6_LPBIG_OFFBIG :: 112 + _SC_V7_ILP32_OFF32 :: 113 + _SC_V7_ILP32_OFFBIG :: 114 + _SC_V7_LP64_OFF64 :: 115 + _SC_V7_LPBIG_OFFBIG :: 116 + _SC_XOPEN_CRYPT :: 117 + _SC_XOPEN_ENH_I18N :: 118 + _SC_XOPEN_LEGACY :: 119 + + _SC_XOPEN_REALTIME :: 120 + _SC_XOPEN_REALTIME_THREADS :: 121 + _SC_XOPEN_STREAMS :: 122 + _SC_XOPEN_UNIX :: 123 + _SC_XOPEN_UUCP :: 124 + _SC_XOPEN_VERSION :: 125 + + _SC_PHYS_PAGES :: 500 + _SC_AVPHYS_PAGES :: 501 + _SC_NPROCESSORS_CONF :: 502 + _SC_NPROCESSORS_ONLN :: 503 + + _POSIX_VDISABLE :: '\377' + +} else when ODIN_OS == .Linux { + + _F_OK :: 0 + X_OK :: 1 + W_OK :: 2 + R_OK :: 4 + + F_LOCK :: 1 + F_TEST :: 3 + F_TLOCK :: 2 + F_ULOCK :: 0 + + _CS_PATH :: 1 + _CS_POSIX_V6_WIDTH_RESTRICTED_ENVS :: 2 + + _CS_POSIX_V6_ILP32_OFF32_CFLAGS :: 1116 + _CS_POSIX_V6_ILP32_OFF32_LDFLAGS :: 1117 + _CS_POSIX_V6_ILP32_OFF32_LIBS :: 1118 + _CS_POSIX_V6_ILP32_OFFBIG_CFLAGS :: 1120 + _CS_POSIX_V6_ILP32_OFFBIG_LDFLAGS :: 1121 + _CS_POSIX_V6_ILP32_OFFBIG_LIBS :: 1122 + _CS_POSIX_V6_LP64_OFF64_CFLAGS :: 1124 + _CS_POSIX_V6_LP64_OFF64_LDFLAGS :: 1125 + _CS_POSIX_V6_LP64_OFF64_LIBS :: 1126 + _CS_POSIX_V6_LPBIG_OFFBIG_CFLAGS :: 1128 + _CS_POSIX_V6_LPBIG_OFFBIG_LDFLAGS :: 1129 + _CS_POSIX_V6_LPBIG_OFFBIG_LIBS :: 1130 + + _PC_LINK_MAX :: 1 + _PC_MAX_CANON :: 2 + _PC_MAX_INPUT :: 3 + _PC_NAME_MAX :: 4 + _PC_PATH_MAX :: 5 + _PC_PIPE_BUF :: 6 + _PC_CHOWN_RESTRICTED :: 7 + _PC_NO_TRUNC :: 8 + _PC_VDISABLE :: 9 + _PC_SYNC_IO :: 10 + _PC_ASYNC_IO :: 11 + _PC_PRIO_IO :: 12 + _PC_FILESIZEBITS :: 14 + _PC_REC_INCR_XFER_SIZE :: 15 + _PC_REC_MAX_XFER_SIZE :: 16 + _PC_REC_MIN_XFER_SIZE :: 17 + _PC_REC_XFER_ALIGN :: 18 + _PC_ALLOC_SIZE_MIN :: 19 + _PC_SYMLINK_MAX :: 20 + _PC_2_SYMLINK :: 21 + + _SC_ARG_MAX :: 1 + _SC_CHILD_MAX :: 2 + _SC_CLK_TCK :: 3 + _SC_NGROUPS_MAX :: 4 + _SC_OPEN_MAX :: 5 + _SC_STREAM_MAX :: 6 + _SC_TZNAME_MAX :: 7 + _SC_JOB_CONTROL :: 8 + _SC_SAVED_IDS :: 9 + _SC_REALTIME_SIGNALS :: 10 + _SC_PRIORITY_SCHEDULING :: 11 + _SC_TIMERS :: 12 + _SC_ASYNCHRONOUS_IO :: 13 + _SC_PRIORITIZED_IO :: 14 + _SC_SYNCHRONIZED_IO :: 15 + _SC_FSYNC :: 16 + _SC_MAPPED_FILES :: 17 + _SC_MEMLOCK :: 18 + _SC_MEMLOCK_RANGE :: 19 + _SC_MEMORY_PROTECTION :: 20 + _SC_MESSAGE_PASSING :: 21 + _SC_SEMAPHORES :: 22 + _SC_SHARED_MEMORY_OBJECTS :: 23 + _SC_AIO_LISTIO_MAX :: 24 + _SC_AIO_MAX :: 25 + _SC_AIO_PRIO_DELTA_MAX :: 26 + _SC_DELAYTIMER_MAX :: 27 + _SC_MQ_OPEN_MAX :: 28 + _SC_MQ_PRIO_MAX :: 29 + _SC_VERSION :: 30 + _SC_PAGESIZE :: 31 + _SC_PAGE_SIZE :: _SC_PAGESIZE + _SC_RTSIG_MAX :: 32 + _SC_SEM_NSEMS_MAX :: 33 + _SC_SEM_VALUE_MAX :: 34 + _SC_SIGQUEUE_MAX :: 35 + _SC_TIMER_MAX :: 36 + _SC_BC_BASE_MAX :: 37 + _SC_BC_DIM_MAX :: 38 + _SC_BC_SCALE_MAX :: 39 + _SC_BC_STRING_MAX :: 40 + _SC_COLL_WEIGHTS_MAX :: 41 + _SC_EXPR_NEST_MAX :: 43 + _SC_LINE_MAX :: 44 + _SC_RE_DUP_MAX :: 45 + _SC_2_VERSION :: 47 + _SC_2_C_BIND :: 48 + _SC_2_C_DEV :: 49 + _SC_2_FORT_DEV :: 50 + _SC_2_FORT_RUN :: 51 + _SC_2_SW_DEV :: 52 + _SC_2_LOCALEDEF :: 53 + + _SC_IOV_MAX :: 62 + _SC_THREADS :: 69 + _SC_THREAD_SAFE_FUNCTIONS :: 70 + _SC_GETGR_R_SIZE_MAX :: 71 + _SC_GETPW_R_SIZE_MAX :: 72 + _SC_LOGIN_NAME_MAX :: 73 + _SC_TTY_NAME_MAX :: 74 + _SC_THREAD_DESTRUCTOR_ITERATIONS :: 75 + _SC_THREAD_KEYS_MAX :: 76 + _SC_THREAD_STACK_MIN :: 77 + _SC_THREAD_THREADS_MAX :: 78 + _SC_THREAD_ATTR_STACKADDR :: 79 + _SC_THREAD_ATTR_STACKSIZE :: 80 + _SC_THREAD_PRIORITY_SCHEDULING :: 81 + _SC_THREAD_PRIO_INHERIT :: 82 + _SC_THREAD_PRIO_PROTECT :: 83 + _SC_THREAD_PROCESS_SHARED :: 84 + _SC_NPROCESSORS_CONF :: 85 + _SC_NPROCESSORS_ONLN :: 86 + _SC_PHYS_PAGES :: 87 + _SC_AVPHYS_PAGES :: 88 + _SC_ATEXIT_MAX :: 89 + _SC_PASS_MAX :: 90 + _SC_XOPEN_VERSION :: 91 + _SC_XOPEN_UNIX :: 92 + _SC_XOPEN_CRYPT :: 93 + _SC_XOPEN_ENH_I18N :: 94 + _SC_XOPEN_SHM :: 95 + _SC_2_CHAR_TERM :: 96 + _SC_2_UPE :: 97 + + _SC_XOPEN_LEGACY :: 129 + _SC_XOPEN_REALTIME :: 130 + _SC_XOPEN_REALTIME_THREADS :: 131 + _SC_ADVISORY_INFO :: 132 + _SC_BARRIERS :: 133 + _SC_CLOCK_SELECTION :: 137 + _SC_CPUTIME :: 138 + _SC_THREAD_CPUTIME :: 139 + _SC_MONOTONIC_CLOCK :: 149 + _SC_READER_WRITER_LOCKS :: 153 + _SC_SPIN_LOCKS :: 154 + _SC_REGEXP :: 155 + _SC_SHELL :: 157 + _SC_SPAWN :: 159 + _SC_SPORADIC_SERVER :: 160 + _SC_THREAD_SPORADIC_SERVER :: 161 + _SC_TIMEOUTS :: 164 + _SC_TYPED_MEMORY_OBJECTS :: 165 + _SC_2_PBS :: 168 + _SC_2_PBS_ACCOUNTING :: 169 + _SC_2_PBS_LOCATE :: 170 + _SC_2_PBS_MESSAGE :: 171 + _SC_2_PBS_TRACK :: 172 + _SC_SYMLOOP_MAX :: 173 + _SC_2_PBS_CHECKPOINT :: 174 + _SC_V6_ILP32_OFF32 :: 175 + _SC_V6_ILP32_OFFBIG :: 176 + _SC_V6_LP64_OFF64 :: 177 + _SC_V6_LPBIG_OFFBIG :: 178 + _SC_HOST_NAME_MAX :: 179 + _SC_TRACE :: 180 + _SC_TRACE_EVENT_FILTER :: 181 + _SC_TRACE_INHERIT :: 182 + _SC_TRACE_LOG :: 183 + + _SC_IPV6 :: 234 + _SC_RAW_SOCKETS :: 235 + _SC_V7_ILP32_OFF32 :: 236 + _SC_V7_ILP32_OFFBIG :: 237 + _SC_V7_LP64_OFF64 :: 238 + _SC_V7_LPBIG_OFFBIG :: 239 + _SC_SS_REPL_MAX :: 240 + _SC_TRACE_EVENT_NAME_MAX :: 241 + _SC_TRACE_NAME_MAX :: 242 + _SC_TRACE_SYS_MAX :: 243 + _SC_TRACE_USER_EVENT_MAX :: 244 + _SC_XOPEN_STREAMS :: 245 + _SC_THREAD_ROBUST_PRIO_INHERIT :: 246 + _SC_THREAD_ROBUST_PRIO_PROTECT :: 247 + + // NOTE: Not implemented. + _SC_XOPEN_UUCP :: 0 + // NOTE: Not implemented. + _POSIX_VDISABLE :: 0 + +} diff --git a/core/sys/posix/unistd_libc.odin b/core/sys/posix/unistd_libc.odin new file mode 100644 index 000000000..bbfe3d59d --- /dev/null +++ b/core/sys/posix/unistd_libc.odin @@ -0,0 +1,153 @@ +#+build linux, windows, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Windows { + foreign import lib "system:libucrt.lib" +} else when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// unistd.h - standard symbolic constants and types + +foreign lib { + /* + Checks the file named by the pathname pointed to by the path argument for + accessibility according to the bit pattern contained in amode. + + Example: + if (posix.access("/tmp/myfile", posix.F_OK) != .OK) { + fmt.printfln("/tmp/myfile access check failed: %v", posix.strerror(posix.errno())) + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/access.html ]] + */ + @(link_name=LACCESS) + access :: proc(path: cstring, amode: Mode_Flags = F_OK) -> result --- + + /* + Causes the directory named by path to become the current working directory. + + Example: + if (posix.chdir("/tmp") == .OK) { + fmt.println("changed current directory to /tmp") + } + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/chdir.html ]] + */ + @(link_name=LCHDIR) + chdir :: proc(path: cstring) -> result --- + + /* + Exits but, shall not call functions registered with atexit() nor any registered signal handlers. + Open streams shall not be flushed. + Whether open streams are closed (without flushing) is implementation-defined. Finally, the calling process shall be terminated with the consequences described below. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/_exit.html ]] + */ + _exit :: proc(status: c.int) -> ! --- + + /* + Places an absolute pathname of the current working directory into buf. + + Returns: buf as a cstring on success, nil (setting errno) on failure + + Example: + size: int + path_max := posix.pathconf(".", ._PATH_MAX) + if path_max == -1 { + size = 1024 + } else if path_max > 10240 { + size = 10240 + } else { + size = int(path_max) + } + + buf: [dynamic]byte + cwd: cstring + for ; cwd == nil; size *= 2 { + if err := resize(&buf, size); err != nil { + fmt.panicf("allocation failure: %v", err) + } + + cwd = posix.getcwd(raw_data(buf), len(buf)) + if cwd == nil { + errno := posix.errno() + if errno != .ERANGE { + fmt.panicf("getcwd failure: %v", posix.strerror(errno)) + } + } + } + + fmt.println(path_max, cwd) + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/getcwd.html ]] + */ + @(link_name=LGETCWD) + getcwd :: proc(buf: [^]c.char, size: c.size_t) -> cstring --- + + /* + Remove an (empty) directory. + + ]] More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/rmdir.html ]] + */ + @(link_name=LRMDIR) + rmdir :: proc(path: cstring) -> result --- + + /* + Copy nbyte bytes, from src, to dest, exchanging adjecent bytes. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/swab.html ]] + */ + @(link_name=LSWAB) + swab :: proc(src: [^]byte, dest: [^]byte, nbytes: c.ssize_t) --- + + /* + Remove a directory entry. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlink.html ]] + */ + @(link_name=LUNLINK) + unlink :: proc(path: cstring) -> result --- +} + +when ODIN_OS == .Windows { + @(private) LACCESS :: "_access" + @(private) LCHDIR :: "_chdir" + @(private) LGETCWD :: "_getcwd" + @(private) LRMDIR :: "_rmdir" + @(private) LSWAB :: "_swab" + @(private) LUNLINK :: "_unlink" +} else { + @(private) LACCESS :: "access" + @(private) LCHDIR :: "chdir" + @(private) LGETCWD :: "getcwd" + @(private) LRMDIR :: "rmdir" + @(private) LSWAB :: "swab" + @(private) LUNLINK :: "unlink" +} + +STDERR_FILENO :: 2 +STDIN_FILENO :: 0 +STDOUT_FILENO :: 1 + +Mode_Flag_Bits :: enum c.int { + X_OK = log2(X_OK), + W_OK = log2(W_OK), + R_OK = log2(R_OK), +} +Mode_Flags :: bit_set[Mode_Flag_Bits; c.int] + +#assert(_F_OK == 0) +F_OK :: Mode_Flags{} + +when ODIN_OS == .Windows { + _F_OK :: 0 + X_OK :: 1 + W_OK :: 2 + R_OK :: 4 + #assert(W_OK|R_OK == 6) +} diff --git a/core/sys/posix/utime.odin b/core/sys/posix/utime.odin new file mode 100644 index 000000000..e884eb1a3 --- /dev/null +++ b/core/sys/posix/utime.odin @@ -0,0 +1,35 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// utime.h - access and modification time structure + +foreign lib { + /* + Set file access and modification times. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/utime.html ]] + */ + @(link_name=LUTIME) + utime :: proc(path: cstring, times: ^utimbuf) -> result --- +} + +when ODIN_OS == .NetBSD { + @(private) LUTIME :: "__utime50" +} else { + @(private) LUTIME :: "utime" +} + +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Linux { + + utimbuf :: struct { + actime: time_t, /* [PSX] access time (seconds since epoch) */ + modtime: time_t, /* [PSX] modification time (seconds since epoch) */ + } + +} diff --git a/core/sys/posix/wordexp.odin b/core/sys/posix/wordexp.odin new file mode 100644 index 000000000..a9e6f39a7 --- /dev/null +++ b/core/sys/posix/wordexp.odin @@ -0,0 +1,127 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +package posix + +import "core:c" + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +// wordexp.h - word-expansion type + +foreign lib { + /* + Perform word expansion. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/wordexp.html ]] + */ + wordexp :: proc(words: cstring, pwordexp: ^wordexp_t, flags: WRDE_Flags) -> WRDE_Errno --- + + /* + Free the space allocated during word expansion. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/wordexp.html ]] + */ + wordfree :: proc(pwordexp: ^wordexp_t) --- +} + +WRDE_Flag_Bits :: enum c.int { + // Appends words to those previously generated. + APPEND = log2(WRDE_APPEND), + // Number of null pointers to prepend to we_wordv. + DOOFFS = log2(WRDE_DOOFFS), + // Fail if command substitution is requested. + NOCMD = log2(WRDE_NOCMD), + // The pwordexp argument was passed to a previous successful call to wordexp(), + // and has not been passed to wordfree(). + REUSE = log2(WRDE_REUSE), + // Do not redirect stderr to /dev/null. + SHOWERR = log2(WRDE_SHOWERR), + // Report error on attempt to expand an undefined shell variable. + UNDEF = log2(WRDE_UNDEF), +} +WRDE_Flags :: bit_set[WRDE_Flag_Bits; c.int] + +WRDE_Errno :: enum c.int { + OK = 0, + // One of the unquoted characters- , '|', '&', ';', '<', '>', '(', ')', '{', '}' - + // appears in words in an inappropriate context. + BADCHAR = WRDE_BADCHAR, + // Reference to undefined shell variable when WRDE_UNDEF is set in flags. + BADVAL = WRDE_BADVAL, + // Command substitution requested when WRDE_NOCMD was set in flags. + CMDSUB = WRDE_CMDSUB, + // Attempt to allocate memory failed. + NOSPACE = WRDE_NOSPACE, + // Shell syntax error, such as unbalanced parentheses or an unterminated string. + SYNTAX = WRDE_SYNTAX, +} + +when ODIN_OS == .Darwin { + + wordexp_t :: struct { + we_wordc: c.size_t, /* [PSX] count of words matched by words */ + we_wordv: [^]cstring, /* [PSX] pointer to list of expanded words */ + we_offs: c.size_t, /* [PSX] slots to reserve at the beginning of we_wordv */ + } + + WRDE_APPEND :: 0x01 + WRDE_DOOFFS :: 0x02 + WRDE_NOCMD :: 0x04 + WRDE_REUSE :: 0x08 + WRDE_SHOWERR :: 0x10 + WRDE_UNDEF :: 0x20 + + WRDE_BADCHAR :: 1 + WRDE_BADVAL :: 2 + WRDE_CMDSUB :: 3 + WRDE_NOSPACE :: 4 + WRDE_SYNTAX :: 6 + +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD { + + wordexp_t :: struct { + we_wordc: c.size_t, /* [PSX] count of words matched by words */ + we_wordv: [^]cstring, /* [PSX] pointer to list of expanded words */ + we_offs: c.size_t, /* [PSX] slots to reserve at the beginning of we_wordv */ + we_strings: [^]byte, /* storage for wordv strings */ + we_nbytes: c.size_t, /* size of we_strings */ + } + + WRDE_APPEND :: 0x01 + WRDE_DOOFFS :: 0x02 + WRDE_NOCMD :: 0x04 + WRDE_REUSE :: 0x08 + WRDE_SHOWERR :: 0x10 + WRDE_UNDEF :: 0x20 + + WRDE_BADCHAR :: 1 + WRDE_BADVAL :: 2 + WRDE_CMDSUB :: 3 + WRDE_NOSPACE :: 4 + WRDE_SYNTAX :: 6 + +} else when ODIN_OS == .Linux { + + wordexp_t :: struct { + we_wordc: c.size_t, /* [PSX] count of words matched by words */ + we_wordv: [^]cstring, /* [PSX] pointer to list of expanded words */ + we_offs: c.size_t, /* [PSX] slots to reserve at the beginning of we_wordv */ + } + + WRDE_DOOFFS :: 1 << 0 /* Insert PWORDEXP->we_offs NULLs. */ + WRDE_APPEND :: 1 << 1 /* Append to results of a previous call. */ + WRDE_NOCMD :: 1 << 2 /* Don't do command substitution. */ + WRDE_REUSE :: 1 << 3 /* Reuse storage in PWORDEXP. */ + WRDE_SHOWERR :: 1 << 4 /* Don't redirect stderr to /dev/null. */ + WRDE_UNDEF :: 1 << 5 /* Error for expanding undefined variables. */ + + WRDE_NOSPACE :: 1 + WRDE_BADCHAR :: 2 + WRDE_BADVAL :: 3 + WRDE_CMDSUB :: 4 + WRDE_SYNTAX :: 5 + +} diff --git a/core/sys/unix/pthread_darwin.odin b/core/sys/unix/pthread_darwin.odin deleted file mode 100644 index a28de4ad0..000000000 --- a/core/sys/unix/pthread_darwin.odin +++ /dev/null @@ -1,96 +0,0 @@ -//+build darwin -package unix - -import "core:c" - -// NOTE(tetra): No 32-bit Macs. -// Source: _pthread_types.h on my Mac. -PTHREAD_SIZE :: 8176 -PTHREAD_ATTR_SIZE :: 56 -PTHREAD_MUTEXATTR_SIZE :: 8 -PTHREAD_MUTEX_SIZE :: 56 -PTHREAD_CONDATTR_SIZE :: 8 -PTHREAD_COND_SIZE :: 40 -PTHREAD_ONCE_SIZE :: 8 -PTHREAD_RWLOCK_SIZE :: 192 -PTHREAD_RWLOCKATTR_SIZE :: 16 - -pthread_t :: distinct u64 - -pthread_attr_t :: struct #align(16) { - sig: c.long, - _: [PTHREAD_ATTR_SIZE] c.char, -} - -pthread_cond_t :: struct #align(16) { - sig: c.long, - _: [PTHREAD_COND_SIZE] c.char, -} - -pthread_condattr_t :: struct #align(16) { - sig: c.long, - _: [PTHREAD_CONDATTR_SIZE] c.char, -} - -pthread_mutex_t :: struct #align(16) { - sig: c.long, - _: [PTHREAD_MUTEX_SIZE] c.char, -} - -pthread_mutexattr_t :: struct #align(16) { - sig: c.long, - _: [PTHREAD_MUTEXATTR_SIZE] c.char, -} - -pthread_once_t :: struct #align(16) { - sig: c.long, - _: [PTHREAD_ONCE_SIZE] c.char, -} - -pthread_rwlock_t :: struct #align(16) { - sig: c.long, - _: [PTHREAD_RWLOCK_SIZE] c.char, -} - -pthread_rwlockattr_t :: struct #align(16) { - sig: c.long, - _: [PTHREAD_RWLOCKATTR_SIZE] c.char, -} - -SCHED_OTHER :: 1 // Avoid if you are writing portable software. -SCHED_FIFO :: 4 -SCHED_RR :: 2 // Round robin. - -SCHED_PARAM_SIZE :: 4 - -sched_param :: struct { - sched_priority: c.int, - _: [SCHED_PARAM_SIZE] c.char, -} - -// Source: https://github.com/apple/darwin-libpthread/blob/03c4628c8940cca6fd6a82957f683af804f62e7f/pthread/pthread.h#L138 -PTHREAD_CREATE_JOINABLE :: 1 -PTHREAD_CREATE_DETACHED :: 2 -PTHREAD_INHERIT_SCHED :: 1 -PTHREAD_EXPLICIT_SCHED :: 2 -PTHREAD_PROCESS_SHARED :: 1 -PTHREAD_PROCESS_PRIVATE :: 2 - - -PTHREAD_MUTEX_NORMAL :: 0 -PTHREAD_MUTEX_RECURSIVE :: 1 -PTHREAD_MUTEX_ERRORCHECK :: 2 - -PTHREAD_CANCEL_ENABLE :: 0 -PTHREAD_CANCEL_DISABLE :: 1 -PTHREAD_CANCEL_DEFERRED :: 0 -PTHREAD_CANCEL_ASYNCHRONOUS :: 1 - -foreign import pthread "System.framework" - -@(default_calling_convention="c") -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_freebsd.odin b/core/sys/unix/pthread_freebsd.odin deleted file mode 100644 index 3417d3943..000000000 --- a/core/sys/unix/pthread_freebsd.odin +++ /dev/null @@ -1,122 +0,0 @@ -//+build freebsd -package unix - -import "core:c" - -pthread_t :: distinct u64 -// pthread_t :: struct #align(16) { x: u64 } - -PTHREAD_COND_T_SIZE :: 8 - -PTHREAD_MUTEXATTR_T_SIZE :: 8 -PTHREAD_CONDATTR_T_SIZE :: 8 -PTHREAD_RWLOCKATTR_T_SIZE :: 8 -PTHREAD_BARRIERATTR_T_SIZE :: 8 - -// WARNING: The sizes of these things are different yet again -// on non-X86! -when size_of(int) == 8 { - PTHREAD_ATTR_T_SIZE :: 8 - PTHREAD_MUTEX_T_SIZE :: 8 - PTHREAD_RWLOCK_T_SIZE :: 8 - PTHREAD_BARRIER_T_SIZE :: 8 -} else when size_of(int) == 4 { // TODO - PTHREAD_ATTR_T_SIZE :: 32 - PTHREAD_MUTEX_T_SIZE :: 32 - PTHREAD_RWLOCK_T_SIZE :: 44 - PTHREAD_BARRIER_T_SIZE :: 20 -} - -pthread_cond_t :: struct #align(16) { - _: [PTHREAD_COND_T_SIZE] c.char, -} -pthread_mutex_t :: struct #align(16) { - _: [PTHREAD_MUTEX_T_SIZE] c.char, -} -pthread_rwlock_t :: struct #align(16) { - _: [PTHREAD_RWLOCK_T_SIZE] c.char, -} -pthread_barrier_t :: struct #align(16) { - _: [PTHREAD_BARRIER_T_SIZE] c.char, -} - -pthread_attr_t :: struct #align(16) { - _: [PTHREAD_ATTR_T_SIZE] c.char, -} -pthread_condattr_t :: struct #align(16) { - _: [PTHREAD_CONDATTR_T_SIZE] c.char, -} -pthread_mutexattr_t :: struct #align(16) { - _: [PTHREAD_MUTEXATTR_T_SIZE] c.char, -} -pthread_rwlockattr_t :: struct #align(16) { - _: [PTHREAD_RWLOCKATTR_T_SIZE] c.char, -} -pthread_barrierattr_t :: struct #align(16) { - _: [PTHREAD_BARRIERATTR_T_SIZE] c.char, -} - -PTHREAD_MUTEX_ERRORCHECK :: 1 -PTHREAD_MUTEX_RECURSIVE :: 2 -PTHREAD_MUTEX_NORMAL :: 3 - - -PTHREAD_CREATE_JOINABLE :: 0 -PTHREAD_CREATE_DETACHED :: 1 -PTHREAD_INHERIT_SCHED :: 4 -PTHREAD_EXPLICIT_SCHED :: 0 -PTHREAD_PROCESS_PRIVATE :: 0 -PTHREAD_PROCESS_SHARED :: 1 - -SCHED_FIFO :: 1 -SCHED_OTHER :: 2 -SCHED_RR :: 3 // Round robin. - - -sched_param :: struct { - sched_priority: c.int, -} - -_usem :: struct { - _has_waiters: u32, - _count: u32, - _flags: u32, -} -_usem2 :: struct { - _count: u32, - _flags: u32, -} -sem_t :: struct { - _magic: u32, - _kern: _usem2, - _padding: u32, -} - -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 { - // create named semaphore. - // used in process-shared semaphores. - 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 --- - // sem_timedwait :: proc(sem: ^sem_t, timeout: time.TimeSpec) -> c.int --- - - // NOTE: unclear whether pthread_yield is well-supported on Linux systems, - // see https://linux.die.net/man/3/pthread_yield - 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 --- -} \ No newline at end of file diff --git a/core/sys/unix/pthread_linux.odin b/core/sys/unix/pthread_linux.odin deleted file mode 100644 index f4ded7464..000000000 --- a/core/sys/unix/pthread_linux.odin +++ /dev/null @@ -1,124 +0,0 @@ -//+build linux -package unix - -import "core:c" - -// TODO(tetra): For robustness, I'd like to mark this with align 16. -// I cannot currently do this. -// And at the time of writing there is a bug with putting it -// as the only field in a struct. -pthread_t :: distinct u64 -// pthread_t :: struct #align(16) { x: u64 }; - -// NOTE(tetra): Got all the size constants from pthreadtypes-arch.h on my -// Linux machine. - -PTHREAD_COND_T_SIZE :: 48 - -PTHREAD_MUTEXATTR_T_SIZE :: 4 -PTHREAD_CONDATTR_T_SIZE :: 4 -PTHREAD_RWLOCKATTR_T_SIZE :: 8 -PTHREAD_BARRIERATTR_T_SIZE :: 4 - -// WARNING: The sizes of these things are different yet again -// on non-X86! -when size_of(int) == 8 { - PTHREAD_ATTR_T_SIZE :: 56 - PTHREAD_MUTEX_T_SIZE :: 40 - PTHREAD_RWLOCK_T_SIZE :: 56 - PTHREAD_BARRIER_T_SIZE :: 32 -} else when size_of(int) == 4 { - PTHREAD_ATTR_T_SIZE :: 32 - PTHREAD_MUTEX_T_SIZE :: 32 - PTHREAD_RWLOCK_T_SIZE :: 44 - PTHREAD_BARRIER_T_SIZE :: 20 -} - -pthread_cond_t :: struct #align(16) { - _: [PTHREAD_COND_T_SIZE] c.char, -} -pthread_mutex_t :: struct #align(16) { - _: [PTHREAD_MUTEX_T_SIZE] c.char, -} -pthread_rwlock_t :: struct #align(16) { - _: [PTHREAD_RWLOCK_T_SIZE] c.char, -} -pthread_barrier_t :: struct #align(16) { - _: [PTHREAD_BARRIER_T_SIZE] c.char, -} - -pthread_attr_t :: struct #align(16) { - _: [PTHREAD_ATTR_T_SIZE] c.char, -} -pthread_condattr_t :: struct #align(16) { - _: [PTHREAD_CONDATTR_T_SIZE] c.char, -} -pthread_mutexattr_t :: struct #align(16) { - _: [PTHREAD_MUTEXATTR_T_SIZE] c.char, -} -pthread_rwlockattr_t :: struct #align(16) { - _: [PTHREAD_RWLOCKATTR_T_SIZE] c.char, -} -pthread_barrierattr_t :: struct #align(16) { - _: [PTHREAD_BARRIERATTR_T_SIZE] c.char, -} - -PTHREAD_MUTEX_NORMAL :: 0 -PTHREAD_MUTEX_RECURSIVE :: 1 -PTHREAD_MUTEX_ERRORCHECK :: 2 - - -// TODO(tetra, 2019-11-01): Maybe make `enum c.int`s for these? -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_OTHER :: 0 -SCHED_FIFO :: 1 -SCHED_RR :: 2 // Round robin. - -sched_param :: struct { - sched_priority: c.int, -} - -sem_t :: struct #align(16) { - _: [SEM_T_SIZE] c.char, -} - -when size_of(int) == 8 { - SEM_T_SIZE :: 32 -} else when size_of(int) == 4 { - SEM_T_SIZE :: 16 -} - -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 { - // create named semaphore. - // used in process-shared semaphores. - 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 --- - // sem_timedwait :: proc(sem: ^sem_t, timeout: time.TimeSpec) -> c.int ---; - - // NOTE: unclear whether pthread_yield is well-supported on Linux systems, - // see https://linux.die.net/man/3/pthread_yield - pthread_yield :: proc() -> c.int --- - - 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 deleted file mode 100644 index 7ae82e662..000000000 --- a/core/sys/unix/pthread_openbsd.odin +++ /dev/null @@ -1,74 +0,0 @@ -//+build openbsd -package unix - -import "core:c" - -pthread_t :: distinct rawptr -pthread_attr_t :: distinct rawptr -pthread_mutex_t :: distinct rawptr -pthread_mutexattr_t :: distinct rawptr -pthread_cond_t :: distinct rawptr -pthread_condattr_t :: distinct rawptr -pthread_rwlock_t :: distinct rawptr -pthread_rwlockattr_t :: distinct rawptr -pthread_barrier_t :: distinct rawptr -pthread_barrierattr_t :: distinct rawptr -pthread_spinlock_t :: distinct rawptr - -pthread_key_t :: distinct c.int -pthread_once_t :: struct { - state: c.int, - mutex: pthread_mutex_t, -} - -PTHREAD_MUTEX_ERRORCHECK :: 1 -PTHREAD_MUTEX_RECURSIVE :: 2 -PTHREAD_MUTEX_NORMAL :: 3 -PTHREAD_MUTEX_STRICT_NP :: 4 - -PTHREAD_DETACHED :: 0x1 -PTHREAD_SCOPE_SYSTEM :: 0x2 -PTHREAD_INHERIT_SCHED :: 0x4 -PTHREAD_NOFLOAT :: 0x8 - -PTHREAD_CREATE_DETACHED :: PTHREAD_DETACHED -PTHREAD_CREATE_JOINABLE :: 0 -PTHREAD_SCOPE_PROCESS :: 0 -PTHREAD_EXPLICIT_SCHED :: 0 - -SCHED_FIFO :: 1 -SCHED_OTHER :: 2 -SCHED_RR :: 3 - -sched_param :: struct { - sched_priority: c.int, -} - -sem_t :: distinct rawptr - -PTHREAD_CANCEL_ENABLE :: 0 -PTHREAD_CANCEL_DISABLE :: 1 -PTHREAD_CANCEL_DEFERRED :: 0 -PTHREAD_CANCEL_ASYNCHRONOUS :: 1 - -foreign import libc "system:c" - -@(default_calling_convention="c") -foreign libc { - 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 --- - //sem_timedwait :: proc(sem: ^sem_t, timeout: time.TimeSpec) -> c.int --- - - // NOTE: unclear whether pthread_yield is well-supported on Linux systems, - // see https://linux.die.net/man/3/pthread_yield - 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 --- -} \ No newline at end of file diff --git a/core/sys/unix/pthread_unix.odin b/core/sys/unix/pthread_unix.odin deleted file mode 100644 index 8bf397647..000000000 --- a/core/sys/unix/pthread_unix.odin +++ /dev/null @@ -1,111 +0,0 @@ -//+build linux, darwin, freebsd, openbsd -package unix - -foreign import "system:pthread" - -import "core:c" - -// -// On success, these functions return 0. -// - -@(default_calling_convention="c") -foreign pthread { - pthread_create :: proc(t: ^pthread_t, attrs: ^pthread_attr_t, routine: proc(data: rawptr) -> rawptr, arg: rawptr) -> c.int --- - - // retval is a pointer to a location to put the return value of the thread proc. - pthread_join :: proc(t: pthread_t, retval: ^rawptr) -> c.int --- - - pthread_self :: proc() -> pthread_t --- - - pthread_equal :: proc(a, b: pthread_t) -> b32 --- - - sched_get_priority_min :: proc(policy: c.int) -> c.int --- - sched_get_priority_max :: proc(policy: c.int) -> c.int --- - - // NOTE: POSIX says this can fail with OOM. - pthread_attr_init :: proc(attrs: ^pthread_attr_t) -> c.int --- - - pthread_attr_destroy :: proc(attrs: ^pthread_attr_t) -> c.int --- - - pthread_attr_getschedparam :: proc(attrs: ^pthread_attr_t, param: ^sched_param) -> c.int --- - pthread_attr_setschedparam :: proc(attrs: ^pthread_attr_t, param: ^sched_param) -> c.int --- - - pthread_attr_getschedpolicy :: proc(t: ^pthread_attr_t, policy: ^c.int) -> c.int --- - pthread_attr_setschedpolicy :: proc(t: ^pthread_attr_t, policy: c.int) -> c.int --- - - // states: PTHREAD_CREATE_DETACHED, PTHREAD_CREATE_JOINABLE - pthread_attr_setdetachstate :: proc(attrs: ^pthread_attr_t, detach_state: c.int) -> c.int --- - - // scheds: PTHREAD_INHERIT_SCHED, PTHREAD_EXPLICIT_SCHED - pthread_attr_setinheritsched :: proc(attrs: ^pthread_attr_t, sched: c.int) -> c.int --- - - // NOTE(tetra, 2019-11-06): WARNING: Different systems have different alignment requirements. - // For maximum usefulness, use the OS's page size. - // ALSO VERY MAJOR WARNING: `stack_ptr` must be the LAST byte of the stack on systems - // where the stack grows downwards, which is the common case, so far as I know. - // On systems where it grows upwards, give the FIRST byte instead. - // ALSO SLIGHTLY LESS MAJOR WARNING: Using this procedure DISABLES automatically-provided - // guard pages. If you are using this procedure, YOU must set them up manually. - // If you forget to do this, you WILL get stack corruption bugs if you do not EXTREMELY - // know what you are doing! - pthread_attr_setstack :: proc(attrs: ^pthread_attr_t, stack_ptr: rawptr, stack_size: u64) -> c.int --- - pthread_attr_getstack :: proc(attrs: ^pthread_attr_t, stack_ptr: ^rawptr, stack_size: ^u64) -> c.int --- - - sched_yield :: proc() -> c.int --- - -} - -@(default_calling_convention="c") -foreign pthread { - // NOTE: POSIX says this can fail with OOM. - pthread_cond_init :: proc(cond: ^pthread_cond_t, attrs: ^pthread_condattr_t) -> c.int --- - - pthread_cond_destroy :: proc(cond: ^pthread_cond_t) -> c.int --- - - pthread_cond_signal :: proc(cond: ^pthread_cond_t) -> c.int --- - - // same as signal, but wakes up _all_ threads that are waiting - pthread_cond_broadcast :: proc(cond: ^pthread_cond_t) -> c.int --- - - - // assumes the mutex is pre-locked - pthread_cond_wait :: proc(cond: ^pthread_cond_t, mutex: ^pthread_mutex_t) -> c.int --- - pthread_cond_timedwait :: proc(cond: ^pthread_cond_t, mutex: ^pthread_mutex_t, timeout: ^timespec) -> c.int --- - - pthread_condattr_init :: proc(attrs: ^pthread_condattr_t) -> c.int --- - pthread_condattr_destroy :: proc(attrs: ^pthread_condattr_t) -> c.int --- - - // p-shared = "process-shared" - i.e: is this condition shared among multiple processes? - // values: PTHREAD_PROCESS_PRIVATE, PTHREAD_PROCESS_SHARED - pthread_condattr_setpshared :: proc(attrs: ^pthread_condattr_t, value: c.int) -> c.int --- - pthread_condattr_getpshared :: proc(attrs: ^pthread_condattr_t, result: ^c.int) -> c.int --- - -} - -@(default_calling_convention="c") -foreign pthread { - // NOTE: POSIX says this can fail with OOM. - pthread_mutex_init :: proc(mutex: ^pthread_mutex_t, attrs: ^pthread_mutexattr_t) -> c.int --- - - pthread_mutex_destroy :: proc(mutex: ^pthread_mutex_t) -> c.int --- - - pthread_mutex_trylock :: proc(mutex: ^pthread_mutex_t) -> c.int --- - - pthread_mutex_lock :: proc(mutex: ^pthread_mutex_t) -> c.int --- - - pthread_mutex_timedlock :: proc(mutex: ^pthread_mutex_t, timeout: ^timespec) -> c.int --- - - pthread_mutex_unlock :: proc(mutex: ^pthread_mutex_t) -> c.int --- - - - pthread_mutexattr_init :: proc(attrs: ^pthread_mutexattr_t) -> c.int --- - pthread_mutexattr_destroy :: proc(attrs: ^pthread_mutexattr_t) -> c.int --- - pthread_mutexattr_settype :: proc(attrs: ^pthread_mutexattr_t, type: c.int) -> c.int --- - - // p-shared = "process-shared" - i.e: is this mutex shared among multiple processes? - // values: PTHREAD_PROCESS_PRIVATE, PTHREAD_PROCESS_SHARED - 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 --- - -} diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 3083c084b..89ad2661f 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -1,6 +1,6 @@ package unix -import "core:intrinsics" +import "base:intrinsics" // Linux has inconsistent system call numbering across architectures, // for largely historical reasons. This attempts to provide a unified @@ -1514,6 +1514,338 @@ when ODIN_ARCH == .amd64 { SYS_landlock_create_ruleset : uintptr : 444 SYS_landlock_add_rule : uintptr : 445 SYS_landlock_restrict_self : uintptr : 446 +} else when ODIN_ARCH == .riscv64 { + SYS_io_setup :: uintptr(0) + SYS_io_destroy :: uintptr(1) + SYS_io_submit :: uintptr(2) + SYS_io_cancel :: uintptr(3) + SYS_io_getevents :: uintptr(4) + SYS_setxattr :: uintptr(5) + SYS_lsetxattr :: uintptr(6) + SYS_fsetxattr :: uintptr(7) + SYS_getxattr :: uintptr(8) + SYS_lgetxattr :: uintptr(9) + SYS_fgetxattr :: uintptr(10) + SYS_listxattr :: uintptr(11) + SYS_llistxattr :: uintptr(12) + SYS_flistxattr :: uintptr(13) + SYS_removexattr :: uintptr(14) + SYS_lremovexattr :: uintptr(15) + SYS_fremovexattr :: uintptr(16) + SYS_getcwd :: uintptr(17) + SYS_lookup_dcookie :: uintptr(18) + SYS_eventfd2 :: uintptr(19) + SYS_epoll_create1 :: uintptr(20) + SYS_epoll_ctl :: uintptr(21) + SYS_epoll_pwait :: uintptr(22) + SYS_dup :: uintptr(23) + SYS_dup3 :: uintptr(24) + SYS_fcntl :: uintptr(25) + SYS_inotify_init1 :: uintptr(26) + SYS_inotify_add_watch :: uintptr(27) + SYS_inotify_rm_watch :: uintptr(28) + SYS_ioctl :: uintptr(29) + SYS_ioprio_set :: uintptr(30) + SYS_ioprio_get :: uintptr(31) + SYS_flock :: uintptr(32) + SYS_mknodat :: uintptr(33) + SYS_mkdirat :: uintptr(34) + SYS_unlinkat :: uintptr(35) + SYS_symlinkat :: uintptr(36) + SYS_linkat :: uintptr(37) + SYS_renameat :: uintptr(38) + SYS_umount2 :: uintptr(39) + SYS_mount :: uintptr(40) + SYS_pivot_root :: uintptr(41) + SYS_nfsservctl :: uintptr(42) + SYS_statfs :: uintptr(43) + SYS_fstatfs :: uintptr(44) + SYS_truncate :: uintptr(45) + SYS_ftruncate :: uintptr(46) + SYS_fallocate :: uintptr(47) + SYS_faccessat :: uintptr(48) + SYS_chdir :: uintptr(49) + SYS_fchdir :: uintptr(50) + SYS_chroot :: uintptr(51) + SYS_fchmod :: uintptr(52) + SYS_fchmodat :: uintptr(53) + SYS_fchownat :: uintptr(54) + SYS_fchown :: uintptr(55) + SYS_openat :: uintptr(56) + SYS_close :: uintptr(57) + SYS_vhangup :: uintptr(58) + SYS_pipe2 :: uintptr(59) + SYS_quotactl :: uintptr(60) + SYS_getdents64 :: uintptr(61) + SYS_lseek :: uintptr(62) + SYS_read :: uintptr(63) + SYS_write :: uintptr(64) + SYS_readv :: uintptr(65) + SYS_writev :: uintptr(66) + SYS_pread64 :: uintptr(67) + SYS_pwrite64 :: uintptr(68) + SYS_preadv :: uintptr(69) + SYS_pwritev :: uintptr(70) + SYS_sendfile :: uintptr(71) + SYS_pselect6 :: uintptr(72) + SYS_ppoll :: uintptr(73) + SYS_signalfd4 :: uintptr(74) + SYS_vmsplice :: uintptr(75) + SYS_splice :: uintptr(76) + SYS_tee :: uintptr(77) + SYS_readlinkat :: uintptr(78) + SYS_fstatat :: uintptr(79) + SYS_fstat :: uintptr(80) + SYS_sync :: uintptr(81) + SYS_fsync :: uintptr(82) + SYS_fdatasync :: uintptr(83) + SYS_sync_file_range2 :: uintptr(84) + SYS_sync_file_range :: uintptr(84) + SYS_timerfd_create :: uintptr(85) + SYS_timerfd_settime :: uintptr(86) + SYS_timerfd_gettime :: uintptr(87) + SYS_utimensat :: uintptr(88) + SYS_acct :: uintptr(89) + SYS_capget :: uintptr(90) + SYS_capset :: uintptr(91) + SYS_personality :: uintptr(92) + SYS_exit :: uintptr(93) + SYS_exit_group :: uintptr(94) + SYS_waitid :: uintptr(95) + SYS_set_tid_address :: uintptr(96) + SYS_unshare :: uintptr(97) + SYS_futex :: uintptr(98) + SYS_set_robust_list :: uintptr(99) + SYS_get_robust_list :: uintptr(100) + SYS_nanosleep :: uintptr(101) + SYS_getitimer :: uintptr(102) + SYS_setitimer :: uintptr(103) + SYS_kexec_load :: uintptr(104) + SYS_init_module :: uintptr(105) + SYS_delete_module :: uintptr(106) + SYS_timer_create :: uintptr(107) + SYS_timer_gettime :: uintptr(108) + SYS_timer_getoverrun :: uintptr(109) + SYS_timer_settime :: uintptr(110) + SYS_timer_delete :: uintptr(111) + SYS_clock_settime :: uintptr(112) + SYS_clock_gettime :: uintptr(113) + SYS_clock_getres :: uintptr(114) + SYS_clock_nanosleep :: uintptr(115) + SYS_syslog :: uintptr(116) + SYS_ptrace :: uintptr(117) + SYS_sched_setparam :: uintptr(118) + SYS_sched_setscheduler :: uintptr(119) + SYS_sched_getscheduler :: uintptr(120) + SYS_sched_getparam :: uintptr(121) + SYS_sched_setaffinity :: uintptr(122) + SYS_sched_getaffinity :: uintptr(123) + SYS_sched_yield :: uintptr(124) + SYS_sched_get_priority_max :: uintptr(125) + SYS_sched_get_priority_min :: uintptr(126) + SYS_sched_rr_get_interval :: uintptr(127) + SYS_restart_syscall :: uintptr(128) + SYS_kill :: uintptr(129) + SYS_tkill :: uintptr(130) + SYS_tgkill :: uintptr(131) + SYS_sigaltstack :: uintptr(132) + SYS_rt_sigsuspend :: uintptr(133) + SYS_rt_sigaction :: uintptr(134) + SYS_rt_sigprocmask :: uintptr(135) + SYS_rt_sigpending :: uintptr(136) + SYS_rt_sigtimedwait :: uintptr(137) + SYS_rt_sigqueueinfo :: uintptr(138) + SYS_rt_sigreturn :: uintptr(139) + SYS_setpriority :: uintptr(140) + SYS_getpriority :: uintptr(141) + SYS_reboot :: uintptr(142) + SYS_setregid :: uintptr(143) + SYS_setgid :: uintptr(144) + SYS_setreuid :: uintptr(145) + SYS_setuid :: uintptr(146) + SYS_setresuid :: uintptr(147) + SYS_getresuid :: uintptr(148) + SYS_setresgid :: uintptr(149) + SYS_getresgid :: uintptr(150) + SYS_setfsuid :: uintptr(151) + SYS_setfsgid :: uintptr(152) + SYS_times :: uintptr(153) + SYS_setpgid :: uintptr(154) + SYS_getpgid :: uintptr(155) + SYS_getsid :: uintptr(156) + SYS_setsid :: uintptr(157) + SYS_getgroups :: uintptr(158) + SYS_setgroups :: uintptr(159) + SYS_uname :: uintptr(160) + SYS_sethostname :: uintptr(161) + SYS_setdomainname :: uintptr(162) + SYS_getrlimit :: uintptr(163) + SYS_setrlimit :: uintptr(164) + SYS_getrusage :: uintptr(165) + SYS_umask :: uintptr(166) + SYS_prctl :: uintptr(167) + SYS_getcpu :: uintptr(168) + SYS_gettimeofday :: uintptr(169) + SYS_settimeofday :: uintptr(170) + SYS_adjtimex :: uintptr(171) + SYS_getpid :: uintptr(172) + SYS_getppid :: uintptr(173) + SYS_getuid :: uintptr(174) + SYS_geteuid :: uintptr(175) + SYS_getgid :: uintptr(176) + SYS_getegid :: uintptr(177) + SYS_gettid :: uintptr(178) + SYS_sysinfo :: uintptr(179) + SYS_mq_open :: uintptr(180) + SYS_mq_unlink :: uintptr(181) + SYS_mq_timedsend :: uintptr(182) + SYS_mq_timedreceive :: uintptr(183) + SYS_mq_notify :: uintptr(184) + SYS_mq_getsetattr :: uintptr(185) + SYS_msgget :: uintptr(186) + SYS_msgctl :: uintptr(187) + SYS_msgrcv :: uintptr(188) + SYS_msgsnd :: uintptr(189) + SYS_semget :: uintptr(190) + SYS_semctl :: uintptr(191) + SYS_semtimedop :: uintptr(192) + SYS_semop :: uintptr(193) + SYS_shmget :: uintptr(194) + SYS_shmctl :: uintptr(195) + SYS_shmat :: uintptr(196) + SYS_shmdt :: uintptr(197) + SYS_socket :: uintptr(198) + SYS_socketpair :: uintptr(199) + SYS_bind :: uintptr(200) + SYS_listen :: uintptr(201) + SYS_accept :: uintptr(202) + SYS_connect :: uintptr(203) + SYS_getsockname :: uintptr(204) + SYS_getpeername :: uintptr(205) + SYS_sendto :: uintptr(206) + SYS_recvfrom :: uintptr(207) + SYS_setsockopt :: uintptr(208) + SYS_getsockopt :: uintptr(209) + SYS_shutdown :: uintptr(210) + SYS_sendmsg :: uintptr(211) + SYS_recvmsg :: uintptr(212) + SYS_readahead :: uintptr(213) + SYS_brk :: uintptr(214) + SYS_munmap :: uintptr(215) + SYS_mremap :: uintptr(216) + SYS_add_key :: uintptr(217) + SYS_request_key :: uintptr(218) + SYS_keyctl :: uintptr(219) + SYS_clone :: uintptr(220) + SYS_execve :: uintptr(221) + SYS_mmap :: uintptr(222) + SYS_fadvise64 :: uintptr(223) + SYS_swapon :: uintptr(224) + SYS_swapoff :: uintptr(225) + SYS_mprotect :: uintptr(226) + SYS_msync :: uintptr(227) + SYS_mlock :: uintptr(228) + SYS_munlock :: uintptr(229) + SYS_mlockall :: uintptr(230) + SYS_munlockall :: uintptr(231) + SYS_mincore :: uintptr(232) + SYS_madvise :: uintptr(233) + SYS_remap_file_pages :: uintptr(234) + SYS_mbind :: uintptr(235) + SYS_get_mempolicy :: uintptr(236) + SYS_set_mempolicy :: uintptr(237) + SYS_migrate_pages :: uintptr(238) + SYS_move_pages :: uintptr(239) + SYS_rt_tgsigqueueinfo :: uintptr(240) + SYS_perf_event_open :: uintptr(241) + SYS_accept4 :: uintptr(242) + SYS_recvmmsg :: uintptr(243) + SYS_wait4 :: uintptr(260) + SYS_prlimit64 :: uintptr(261) + SYS_fanotify_init :: uintptr(262) + SYS_fanotify_mark :: uintptr(263) + SYS_name_to_handle_at :: uintptr(264) + SYS_open_by_handle_at :: uintptr(265) + SYS_clock_adjtime :: uintptr(266) + SYS_syncfs :: uintptr(267) + SYS_setns :: uintptr(268) + SYS_sendmmsg :: uintptr(269) + SYS_process_vm_readv :: uintptr(270) + SYS_process_vm_writev :: uintptr(271) + SYS_kcmp :: uintptr(272) + SYS_finit_module :: uintptr(273) + SYS_sched_setattr :: uintptr(274) + SYS_sched_getattr :: uintptr(275) + SYS_renameat2 :: uintptr(276) + SYS_seccomp :: uintptr(277) + SYS_getrandom :: uintptr(278) + SYS_memfd_create :: uintptr(279) + SYS_bpf :: uintptr(280) + SYS_execveat :: uintptr(281) + SYS_userfaultfd :: uintptr(282) + SYS_membarrier :: uintptr(283) + SYS_mlock2 :: uintptr(284) + SYS_copy_file_range :: uintptr(285) + SYS_preadv2 :: uintptr(286) + SYS_pwritev2 :: uintptr(287) + SYS_pkey_mprotect :: uintptr(288) + SYS_pkey_alloc :: uintptr(289) + SYS_pkey_free :: uintptr(290) + SYS_statx :: uintptr(291) + SYS_io_pgetevents :: uintptr(292) + SYS_rseq :: uintptr(293) + SYS_kexec_file_load :: uintptr(294) + SYS_clock_gettime64 :: uintptr(403) + SYS_clock_settime64 :: uintptr(404) + SYS_clock_adjtime64 :: uintptr(405) + SYS_clock_getres_time64 :: uintptr(406) + SYS_clock_nanosleep_time64 :: uintptr(407) + SYS_timer_gettime64 :: uintptr(408) + SYS_timer_settime64 :: uintptr(409) + SYS_timerfd_gettime64 :: uintptr(410) + SYS_timerfd_settime64 :: uintptr(411) + SYS_utimensat_time64 :: uintptr(412) + SYS_pselect6_time64 :: uintptr(413) + SYS_ppoll_time64 :: uintptr(414) + SYS_io_pgetevents_time64 :: uintptr(416) + SYS_recvmmsg_time64 :: uintptr(417) + SYS_mq_timedsend_time64 :: uintptr(418) + SYS_mq_timedreceive_time64 :: uintptr(419) + SYS_semtimedop_time64 :: uintptr(420) + SYS_rt_sigtimedwait_time64 :: uintptr(421) + SYS_futex_time64 :: uintptr(422) + SYS_sched_rr_get_interval_time64 :: uintptr(423) + SYS_pidfd_send_signal :: uintptr(424) + SYS_io_uring_setup :: uintptr(425) + SYS_io_uring_enter :: uintptr(426) + SYS_io_uring_register :: uintptr(427) + SYS_open_tree :: uintptr(428) + SYS_move_mount :: uintptr(429) + SYS_fsopen :: uintptr(430) + SYS_fsconfig :: uintptr(431) + SYS_fsmount :: uintptr(432) + SYS_fspick :: uintptr(433) + SYS_pidfd_open :: uintptr(434) + SYS_clone3 :: uintptr(435) + SYS_close_range :: uintptr(436) + SYS_openat2 :: uintptr(437) + SYS_pidfd_getfd :: uintptr(438) + SYS_faccessat2 :: uintptr(439) + SYS_process_madvise :: uintptr(440) + SYS_epoll_pwait2 :: uintptr(441) + SYS_mount_setattr :: uintptr(442) + SYS_quotactl_fd :: uintptr(443) + SYS_landlock_create_ruleset :: uintptr(444) + SYS_landlock_add_rule :: uintptr(445) + SYS_landlock_restrict_self :: uintptr(446) + SYS_memfd_secret :: uintptr(447) + SYS_process_mrelease :: uintptr(448) + SYS_futex_waitv :: uintptr(449) + SYS_set_mempolicy_home_node :: uintptr(450) + SYS_cachestat :: uintptr(451) + SYS_fchmodat2 :: uintptr(452) + + SIGCHLD :: 17 } else { #panic("Unsupported architecture") } @@ -1742,7 +2074,7 @@ sys_getrandom :: proc "contextless" (buf: [^]byte, buflen: uint, flags: int) -> } sys_open :: proc "contextless" (path: cstring, flags: int, mode: uint = 0o000) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_open, uintptr(rawptr(path)), uintptr(flags), uintptr(mode))) } else { // NOTE: arm64 does not have open return int(intrinsics.syscall(SYS_openat, AT_FDCWD, uintptr(rawptr(path)), uintptr(flags), uintptr(mode))) @@ -1762,7 +2094,7 @@ sys_read :: proc "contextless" (fd: int, buf: rawptr, size: uint) -> int { } sys_pread :: proc "contextless" (fd: int, buf: rawptr, size: uint, offset: i64) -> int { - when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { return int(intrinsics.syscall(SYS_pread64, uintptr(fd), uintptr(buf), uintptr(size), uintptr(offset))) } else { low := uintptr(offset & 0xFFFFFFFF) @@ -1776,7 +2108,7 @@ sys_write :: proc "contextless" (fd: int, buf: rawptr, size: uint) -> int { } sys_pwrite :: proc "contextless" (fd: int, buf: rawptr, size: uint, offset: i64) -> int { - when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { return int(intrinsics.syscall(SYS_pwrite64, uintptr(fd), uintptr(buf), uintptr(size), uintptr(offset))) } else { low := uintptr(offset & 0xFFFFFFFF) @@ -1786,7 +2118,7 @@ sys_pwrite :: proc "contextless" (fd: int, buf: rawptr, size: uint, offset: i64) } sys_lseek :: proc "contextless" (fd: int, offset: i64, whence: int) -> i64 { - when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { return i64(intrinsics.syscall(SYS_lseek, uintptr(fd), uintptr(offset), uintptr(whence))) } else { low := uintptr(offset & 0xFFFFFFFF) @@ -1800,7 +2132,7 @@ sys_lseek :: proc "contextless" (fd: int, offset: i64, whence: int) -> i64 { sys_stat :: proc "contextless" (path: cstring, stat: rawptr) -> int { when ODIN_ARCH == .amd64 { return int(intrinsics.syscall(SYS_stat, uintptr(rawptr(path)), uintptr(stat))) - } else when ODIN_ARCH != .arm64 { + } else when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_stat64, uintptr(rawptr(path)), uintptr(stat))) } else { // NOTE: arm64 does not have stat return int(intrinsics.syscall(SYS_fstatat, AT_FDCWD, uintptr(rawptr(path)), uintptr(stat), 0)) @@ -1808,7 +2140,7 @@ sys_stat :: proc "contextless" (path: cstring, stat: rawptr) -> int { } sys_fstat :: proc "contextless" (fd: int, stat: rawptr) -> int { - when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { return int(intrinsics.syscall(SYS_fstat, uintptr(fd), uintptr(stat))) } else { return int(intrinsics.syscall(SYS_fstat64, uintptr(fd), uintptr(stat))) @@ -1818,7 +2150,7 @@ sys_fstat :: proc "contextless" (fd: int, stat: rawptr) -> int { sys_lstat :: proc "contextless" (path: cstring, stat: rawptr) -> int { when ODIN_ARCH == .amd64 { return int(intrinsics.syscall(SYS_lstat, uintptr(rawptr(path)), uintptr(stat))) - } else when ODIN_ARCH != .arm64 { + } else when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_lstat64, uintptr(rawptr(path)), uintptr(stat))) } else { // NOTE: arm64 does not have any lstat return int(intrinsics.syscall(SYS_fstatat, AT_FDCWD, uintptr(rawptr(path)), uintptr(stat), AT_SYMLINK_NOFOLLOW)) @@ -1826,7 +2158,7 @@ sys_lstat :: proc "contextless" (path: cstring, stat: rawptr) -> int { } sys_readlink :: proc "contextless" (path: cstring, buf: rawptr, bufsiz: uint) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_readlink, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz))) } else { // NOTE: arm64 does not have readlink return int(intrinsics.syscall(SYS_readlinkat, AT_FDCWD, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz))) @@ -1834,7 +2166,7 @@ sys_readlink :: proc "contextless" (path: cstring, buf: rawptr, bufsiz: uint) -> } sys_symlink :: proc "contextless" (old_name: cstring, new_name: cstring) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_symlink, uintptr(rawptr(old_name)), uintptr(rawptr(new_name)))) } else { // NOTE: arm64 does not have symlink return int(intrinsics.syscall(SYS_symlinkat, uintptr(rawptr(old_name)), AT_FDCWD, uintptr(rawptr(new_name)))) @@ -1842,7 +2174,7 @@ sys_symlink :: proc "contextless" (old_name: cstring, new_name: cstring) -> int } sys_access :: proc "contextless" (path: cstring, mask: int) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_access, uintptr(rawptr(path)), uintptr(mask))) } else { // NOTE: arm64 does not have access return int(intrinsics.syscall(SYS_faccessat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mask))) @@ -1862,7 +2194,7 @@ sys_fchdir :: proc "contextless" (fd: int) -> int { } sys_chmod :: proc "contextless" (path: cstring, mode: uint) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_chmod, uintptr(rawptr(path)), uintptr(mode))) } else { // NOTE: arm64 does not have chmod return int(intrinsics.syscall(SYS_fchmodat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mode))) @@ -1874,7 +2206,7 @@ sys_fchmod :: proc "contextless" (fd: int, mode: uint) -> int { } sys_chown :: proc "contextless" (path: cstring, user: int, group: int) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH !=. riscv64 { return int(intrinsics.syscall(SYS_chown, uintptr(rawptr(path)), uintptr(user), uintptr(group))) } else { // NOTE: arm64 does not have chown return int(intrinsics.syscall(SYS_fchownat, AT_FDCWD, uintptr(rawptr(path)), uintptr(user), uintptr(group), 0)) @@ -1886,7 +2218,7 @@ sys_fchown :: proc "contextless" (fd: int, user: int, group: int) -> int { } sys_lchown :: proc "contextless" (path: cstring, user: int, group: int) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_lchown, uintptr(rawptr(path)), uintptr(user), uintptr(group))) } else { // NOTE: arm64 does not have lchown return int(intrinsics.syscall(SYS_fchownat, AT_FDCWD, uintptr(rawptr(path)), uintptr(user), uintptr(group), AT_SYMLINK_NOFOLLOW)) @@ -1894,7 +2226,7 @@ sys_lchown :: proc "contextless" (path: cstring, user: int, group: int) -> int { } sys_rename :: proc "contextless" (old, new: cstring) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_rename, uintptr(rawptr(old)), uintptr(rawptr(new)))) } else { // NOTE: arm64 does not have rename return int(intrinsics.syscall(SYS_renameat, AT_FDCWD, uintptr(rawptr(old)), uintptr(rawptr(new)))) @@ -1902,7 +2234,7 @@ sys_rename :: proc "contextless" (old, new: cstring) -> int { } sys_link :: proc "contextless" (old_name: cstring, new_name: cstring) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_link, uintptr(rawptr(old_name)), uintptr(rawptr(new_name)))) } else { // NOTE: arm64 does not have link return int(intrinsics.syscall(SYS_linkat, AT_FDCWD, uintptr(rawptr(old_name)), AT_FDCWD, uintptr(rawptr(new_name)), AT_SYMLINK_FOLLOW)) @@ -1910,7 +2242,7 @@ sys_link :: proc "contextless" (old_name: cstring, new_name: cstring) -> int { } sys_unlink :: proc "contextless" (path: cstring) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_unlink, uintptr(rawptr(path)))) } else { // NOTE: arm64 does not have unlink return int(intrinsics.syscall(SYS_unlinkat, AT_FDCWD, uintptr(rawptr(path)), 0)) @@ -1922,7 +2254,7 @@ sys_unlinkat :: proc "contextless" (dfd: int, path: cstring, flag: int = 0) -> i } sys_rmdir :: proc "contextless" (path: cstring) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_rmdir, uintptr(rawptr(path)))) } else { // NOTE: arm64 does not have rmdir return int(intrinsics.syscall(SYS_unlinkat, AT_FDCWD, uintptr(rawptr(path)), AT_REMOVEDIR)) @@ -1930,7 +2262,7 @@ sys_rmdir :: proc "contextless" (path: cstring) -> int { } sys_mkdir :: proc "contextless" (path: cstring, mode: uint) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_mkdir, uintptr(rawptr(path)), uintptr(mode))) } else { // NOTE: arm64 does not have mkdir return int(intrinsics.syscall(SYS_mkdirat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mode))) @@ -1942,7 +2274,7 @@ sys_mkdirat :: proc "contextless" (dfd: int, path: cstring, mode: uint) -> int { } sys_mknod :: proc "contextless" (path: cstring, mode: uint, dev: int) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_mknod, uintptr(rawptr(path)), uintptr(mode), uintptr(dev))) } else { // NOTE: arm64 does not have mknod return int(intrinsics.syscall(SYS_mknodat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mode), uintptr(dev))) @@ -1954,7 +2286,7 @@ sys_mknodat :: proc "contextless" (dfd: int, path: cstring, mode: uint, dev: int } sys_truncate :: proc "contextless" (path: cstring, length: i64) -> int { - when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { return int(intrinsics.syscall(SYS_truncate, uintptr(rawptr(path)), uintptr(length))) } else { low := uintptr(length & 0xFFFFFFFF) @@ -1964,7 +2296,7 @@ sys_truncate :: proc "contextless" (path: cstring, length: i64) -> int { } sys_ftruncate :: proc "contextless" (fd: int, length: i64) -> int { - when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { return int(intrinsics.syscall(SYS_ftruncate, uintptr(fd), uintptr(length))) } else { low := uintptr(length & 0xFFFFFFFF) @@ -1982,7 +2314,7 @@ sys_getdents64 :: proc "contextless" (fd: int, dirent: rawptr, count: int) -> in } sys_fork :: proc "contextless" () -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_fork)) } else { return int(intrinsics.syscall(SYS_clone, SIGCHLD)) @@ -1992,7 +2324,7 @@ sys_pipe2 :: proc "contextless" (fds: rawptr, flags: int) -> int { return int(intrinsics.syscall(SYS_pipe2, uintptr(fds), uintptr(flags))) } sys_dup2 :: proc "contextless" (oldfd: int, newfd: int) -> int { - when ODIN_ARCH != .arm64 { + when ODIN_ARCH != .arm64 && ODIN_ARCH != .riscv64 { return int(intrinsics.syscall(SYS_dup2, uintptr(oldfd), uintptr(newfd))) } else { return int(intrinsics.syscall(SYS_dup3, uintptr(oldfd), uintptr(newfd), 0)) @@ -2076,7 +2408,7 @@ sys_fcntl :: proc "contextless" (fd: int, cmd: int, arg: int) -> int { sys_poll :: proc "contextless" (fds: rawptr, nfds: uint, timeout: int) -> int { // NOTE: specialcased here because `arm64` does not have `poll` - when ODIN_ARCH == .arm64 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { seconds := i64(timeout / 1_000) nanoseconds := i64((timeout % 1000) * 1_000_000) timeout_spec := timespec{seconds, nanoseconds} diff --git a/core/sys/unix/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 f9530b86f..32dd720b0 100644 --- a/core/sys/unix/sysctl_darwin.odin +++ b/core/sys/unix/sysctl_darwin.odin @@ -1,22 +1,62 @@ -//+build darwin +#+build darwin package unix +import "base:intrinsics" + import "core:sys/darwin" -import "core:intrinsics" _ :: 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 @@ -27,6 +67,8 @@ CTL_KERN :: 1 KERN_VERSION :: 4 // Darwin Kernel Version 21.5.0: Tue Apr 26 21:08:22 PDT 2022; root:darwin-8020.121.3~4/RELEASE_X86_64 KERN_OSRELDATE :: 26 // i32: OS release date KERN_OSVERSION :: 65 // Build number, e.g. 21F79 + KERN_PROCARGS :: 38 + KERN_PROCARGS2 :: 49 CTL_VM :: 2 CTL_VFS :: 3 CTL_NET :: 4 @@ -42,4 +84,4 @@ CTL_HW :: 6 HW_AVAILCPU :: 25 /* int: number of available CPUs */ CTL_MACHDEP :: 7 -CTL_USER :: 8 \ No newline at end of file +CTL_USER :: 8 diff --git a/core/sys/unix/sysctl_freebsd.odin b/core/sys/unix/sysctl_freebsd.odin index 5b0bcb88d..f5fee6c6c 100644 --- a/core/sys/unix/sysctl_freebsd.odin +++ b/core/sys/unix/sysctl_freebsd.odin @@ -1,18 +1,19 @@ -//+build freebsd +#+build freebsd package unix -import "core:intrinsics" +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 @@ -22,6 +23,8 @@ CTL_KERN :: 1 KERN_OSRELEASE :: 2 KERN_OSREV :: 3 KERN_VERSION :: 4 + KERN_PROC :: 14 + KERN_PROC_PATHNAME :: 12 CTL_VM :: 2 CTL_VFS :: 3 CTL_NET :: 4 diff --git a/core/sys/unix/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/sysctl_openbsd.odin b/core/sys/unix/sysctl_openbsd.odin index b93e8f9bd..49c9b6336 100644 --- a/core/sys/unix/sysctl_openbsd.odin +++ b/core/sys/unix/sysctl_openbsd.odin @@ -1,4 +1,4 @@ -//+build openbsd +#+build openbsd package unix import "core:c" diff --git a/core/sys/unix/time_unix.odin b/core/sys/unix/time_unix.odin deleted file mode 100644 index fa3a7a29d..000000000 --- a/core/sys/unix/time_unix.odin +++ /dev/null @@ -1,75 +0,0 @@ -//+build linux, darwin, freebsd, openbsd -package unix - -when ODIN_OS == .Darwin { - foreign import libc "System.framework" -} else { - foreign import libc "system:c" -} - -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 --- -} - -timespec :: struct { - tv_sec: i64, // seconds - tv_nsec: i64, // nanoseconds -} - -when ODIN_OS == .OpenBSD { - CLOCK_REALTIME :: 0 - CLOCK_PROCESS_CPUTIME_ID :: 2 - CLOCK_MONOTONIC :: 3 - CLOCK_THREAD_CPUTIME_ID :: 4 - CLOCK_UPTIME :: 5 - CLOCK_BOOTTIME :: 6 - - // CLOCK_MONOTONIC_RAW doesn't exist, use CLOCK_MONOTONIC - CLOCK_MONOTONIC_RAW :: CLOCK_MONOTONIC -} else { - CLOCK_REALTIME :: 0 // NOTE(tetra): May jump in time, when user changes the system time. - CLOCK_MONOTONIC :: 1 // NOTE(tetra): May stand still while system is asleep. - CLOCK_PROCESS_CPUTIME_ID :: 2 - CLOCK_THREAD_CPUTIME_ID :: 3 - CLOCK_MONOTONIC_RAW :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP. - CLOCK_REALTIME_COARSE :: 5 // NOTE(tetra): "COARSE" clocks are apparently much faster, but not "fine-grained." - CLOCK_MONOTONIC_COARSE :: 6 - CLOCK_BOOTTIME :: 7 // NOTE(tetra): Same as MONOTONIC, except also including time system was asleep. - CLOCK_REALTIME_ALARM :: 8 - CLOCK_BOOTTIME_ALARM :: 9 -} - -// TODO(tetra, 2019-11-05): The original implementation of this package for Darwin used this constants. -// I do not know if Darwin programmers are used to the existance of these constants or not, so -// I'm leaving aliases to them for now. -CLOCK_SYSTEM :: CLOCK_REALTIME -CLOCK_CALENDAR :: CLOCK_MONOTONIC - -boot_time_in_nanoseconds :: proc "c" () -> i64 { - ts_now, ts_boottime: timespec - clock_gettime(CLOCK_REALTIME, &ts_now) - clock_gettime(CLOCK_BOOTTIME, &ts_boottime) - - ns := (ts_now.tv_sec - ts_boottime.tv_sec) * 1e9 + ts_now.tv_nsec - ts_boottime.tv_nsec - return i64(ns) -} - -seconds_since_boot :: proc "c" () -> f64 { - ts_boottime: timespec - clock_gettime(CLOCK_BOOTTIME, &ts_boottime) - return f64(ts_boottime.tv_sec) + f64(ts_boottime.tv_nsec) / 1e9 -} - - -inline_nanosleep :: proc "c" (nanoseconds: i64) -> (remaining: timespec, res: i32) { - s, ns := nanoseconds / 1e9, nanoseconds % 1e9 - requested := timespec{tv_sec=s, tv_nsec=ns} - res = nanosleep(&requested, &remaining) - return -} - diff --git a/core/sys/unix/unix.odin b/core/sys/unix/unix.odin new file mode 100644 index 000000000..e9f58e554 --- /dev/null +++ b/core/sys/unix/unix.odin @@ -0,0 +1,8 @@ +package unix + +import "core:c" + +timespec :: struct { + secs: i64, + nsecs: c.long, +} diff --git a/core/sys/valgrind/callgrind.odin b/core/sys/valgrind/callgrind.odin index cf377e353..5cd58753a 100644 --- a/core/sys/valgrind/callgrind.odin +++ b/core/sys/valgrind/callgrind.odin @@ -1,7 +1,7 @@ -//+build amd64 +#+build amd64 package sys_valgrind -import "core:intrinsics" +import "base:intrinsics" Callgrind_Client_Request :: enum uintptr { Dump_Stats = 'C'<<24 | 'T'<<16, diff --git a/core/sys/valgrind/helgrind.odin b/core/sys/valgrind/helgrind.odin index 41c047d05..3f5e7a531 100644 --- a/core/sys/valgrind/helgrind.odin +++ b/core/sys/valgrind/helgrind.odin @@ -1,7 +1,7 @@ -//+build amd64 +#+build amd64 package sys_valgrind -import "core:intrinsics" +import "base:intrinsics" Helgrind_Client_Request :: enum uintptr { Clean_Memory = 'H'<<24 | 'G'<<16, diff --git a/core/sys/valgrind/memcheck.odin b/core/sys/valgrind/memcheck.odin index 6a9fab854..bc77444be 100644 --- a/core/sys/valgrind/memcheck.odin +++ b/core/sys/valgrind/memcheck.odin @@ -1,7 +1,7 @@ -//+build amd64 +#+build amd64 package sys_valgrind -import "core:intrinsics" +import "base:intrinsics" Mem_Check_Client_Request :: enum uintptr { Make_Mem_No_Access = 'M'<<24 | 'C'<<16, diff --git a/core/sys/valgrind/valgrind.odin b/core/sys/valgrind/valgrind.odin index b587ea3bf..b5c71664f 100644 --- a/core/sys/valgrind/valgrind.odin +++ b/core/sys/valgrind/valgrind.odin @@ -1,7 +1,7 @@ -//+build amd64 +#+build amd64 package sys_valgrind -import "core:intrinsics" +import "base:intrinsics" Client_Request :: enum uintptr { Running_On_Valgrind = 4097, diff --git a/core/sys/wasm/README.md b/core/sys/wasm/README.md new file mode 100644 index 000000000..1aaeaa429 --- /dev/null +++ b/core/sys/wasm/README.md @@ -0,0 +1,15 @@ +# WASM on the Web + +This directory is for use when targeting the `js_wasm32` target and the packages that rely on it. + +The `js_wasm32` target assumes that the WASM output will be ran within a web browser rather than a standalone VM. In the VM cases, either `wasi_wasm32` or `freestanding_wasm32` should be used accordingly. + +## Example for `js_wasm32` + +```html + + + +``` diff --git a/vendor/wasm/js/dom.odin b/core/sys/wasm/js/dom.odin similarity index 70% rename from vendor/wasm/js/dom.odin rename to core/sys/wasm/js/dom.odin index 2662c4201..902dfc941 100644 --- a/vendor/wasm/js/dom.odin +++ b/core/sys/wasm/js/dom.odin @@ -1,4 +1,4 @@ -//+build js wasm32, js wasm64p32 +#+build js wasm32, js wasm64p32 package wasm_js_interface foreign import dom_lib "odin_dom" @@ -8,12 +8,20 @@ foreign dom_lib { get_element_value_f64 :: proc(id: string) -> f64 --- set_element_value_f64 :: proc(id: string, value: f64) --- + get_element_key_f64 :: proc(id: string, key: string) -> f64 --- + set_element_key_f64 :: proc(id: string, key: string, value: f64) --- + set_element_value_string :: proc(id: string, value: string) --- get_element_value_string_length :: proc(id: string) -> int --- + set_element_key_string :: proc(id: string, key: string, value: string) --- + get_element_key_string_length :: proc(id: string, key: string, ) -> int --- + device_pixel_ratio :: proc() -> f64 --- window_set_scroll :: proc(x, y: f64) --- + + set_element_style :: proc(id: string, key: string, value: string) --- } get_element_value_string :: proc "contextless" (id: string, buf: []byte) -> string { @@ -24,10 +32,21 @@ get_element_value_string :: proc "contextless" (id: string, buf: []byte) -> stri } n := _get_element_value_string(id, buf) return string(buf[:n]) +} + +get_element_key_string :: proc "contextless" (id: string, key: string, buf: []byte) -> string { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="get_element_key_string") + _get_element_key_string :: proc(id: string, key: string, buf: []byte) -> int --- + } + n := _get_element_key_string(id, key, buf) + return string(buf[:n]) } + get_element_min_max :: proc "contextless" (id: string) -> (min, max: f64) { @(default_calling_convention="contextless") foreign dom_lib { @@ -70,7 +89,7 @@ window_get_scroll :: proc "contextless" () -> (x, y: f64) { @(link_name="window_get_scroll") _window_get_scroll :: proc(scroll: ^[2]f64) --- } - scroll := [2]f64{x, y} + scroll: [2]f64 _window_get_scroll(&scroll) - return -} + return scroll.x, scroll.y +} \ No newline at end of file diff --git a/vendor/wasm/js/dom_all_targets.odin b/core/sys/wasm/js/dom_all_targets.odin similarity index 96% rename from vendor/wasm/js/dom_all_targets.odin rename to core/sys/wasm/js/dom_all_targets.odin index 7b3ad1a64..171deed2f 100644 --- a/vendor/wasm/js/dom_all_targets.odin +++ b/core/sys/wasm/js/dom_all_targets.odin @@ -1,7 +1,7 @@ -//+build !js +#+build !js package wasm_js_interface -import "core:runtime" +import "base:runtime" get_element_value_string :: proc "contextless" (id: string, buf: []byte) -> string { diff --git a/vendor/wasm/js/events.odin b/core/sys/wasm/js/events.odin similarity index 81% rename from vendor/wasm/js/events.odin rename to core/sys/wasm/js/events.odin index 2d3f01ceb..ffa3a1202 100644 --- a/vendor/wasm/js/events.odin +++ b/core/sys/wasm/js/events.odin @@ -1,4 +1,4 @@ -//+build js wasm32, js wasm64p32 +#+build js wasm32, js wasm64p32 package wasm_js_interface foreign import dom_lib "odin_dom" @@ -30,9 +30,12 @@ Event_Kind :: enum u32 { Wheel, Focus, + Focus_In, + Focus_Out, Submit, Blur, Change, + Hash_Change, Select, Animation_Start, @@ -79,6 +82,9 @@ Event_Kind :: enum u32 { Context_Menu, + Gamepad_Connected, + Gamepad_Disconnected, + Custom, } @@ -109,9 +115,12 @@ event_kind_string := [Event_Kind]string{ .Wheel = "wheel", .Focus = "focus", + .Focus_In = "focusin", + .Focus_Out = "focusout", .Submit = "submit", .Blur = "blur", .Change = "change", + .Hash_Change = "hashchange", .Select = "select", .Animation_Start = "animationstart", @@ -158,6 +167,9 @@ event_kind_string := [Event_Kind]string{ .Context_Menu = "contextmenu", + .Gamepad_Connected = "gamepadconnected", + .Gamepad_Disconnected = "gamepaddisconnected", + .Custom = "?custom?", } @@ -174,8 +186,14 @@ Key_Location :: enum u8 { Numpad = 3, } -KEYBOARD_MAX_KEY_SIZE :: 16 -KEYBOARD_MAX_CODE_SIZE :: 16 +KEYBOARD_MAX_KEY_SIZE :: 32 +KEYBOARD_MAX_CODE_SIZE :: 32 + +GAMEPAD_MAX_ID_SIZE :: 64 +GAMEPAD_MAX_MAPPING_SIZE :: 64 + +GAMEPAD_MAX_BUTTONS :: 64 +GAMEPAD_MAX_AXES :: 16 Event_Target_Kind :: enum u32 { Element = 0, @@ -197,6 +215,30 @@ Event_Option :: enum u8 { } Event_Options :: distinct bit_set[Event_Option; u8] +Gamepad_Button :: struct { + value: f64, + pressed: bool, + touched: bool, +} + +Gamepad_State :: struct { + id: string, + mapping: string, + index: int, + connected: bool, + timestamp: f64, + + button_count: int, + axis_count: int, + buttons: [GAMEPAD_MAX_BUTTONS]Gamepad_Button `fmt:"v,button_count"`, + axes: [GAMEPAD_MAX_AXES]f64 `fmt:"v,axes_count"`, + + _id_len: int `fmt:"-"`, + _mapping_len: int `fmt:"-"`, + _id_buf: [GAMEPAD_MAX_ID_SIZE]byte `fmt:"-"`, + _mapping_buf: [GAMEPAD_MAX_MAPPING_SIZE]byte `fmt:"-"`, +} + Event :: struct { kind: Event_Kind, target_kind: Event_Target_Kind, @@ -233,10 +275,10 @@ Event :: struct { repeat: bool, - _key_len: int, - _code_len: int, - _key_buf: [KEYBOARD_MAX_KEY_SIZE]byte, - _code_buf: [KEYBOARD_MAX_KEY_SIZE]byte, + _key_len: int `fmt:"-"`, + _code_len: int `fmt:"-"`, + _key_buf: [KEYBOARD_MAX_KEY_SIZE]byte `fmt:"-"`, + _code_buf: [KEYBOARD_MAX_KEY_SIZE]byte `fmt:"-"`, }, mouse: struct { @@ -254,6 +296,8 @@ Event :: struct { button: i16, buttons: bit_set[0..<16; u16], }, + + gamepad: Gamepad_State, }, @@ -330,7 +374,18 @@ remove_custom_event_listener :: proc(id: string, name: string, user_data: rawptr return _remove_event_listener(id, name, user_data, callback) } +get_gamepad_state :: proc "contextless" (index: int, s: ^Gamepad_State) -> bool { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="get_gamepad_state") + _get_gamepad_state :: proc(index: int, s: ^Gamepad_State) -> bool --- + } + if s == nil { + return false + } + return _get_gamepad_state(index, s) +} @(export, link_name="odin_dom_do_event_callback") @@ -349,9 +404,13 @@ do_event_callback :: proc(user_data: rawptr, callback: proc(e: Event)) { init_event_raw(&event) - if event.kind == .Key_Up || event.kind == .Key_Down || event.kind == .Key_Press { + #partial switch event.kind { + case .Key_Up, .Key_Down, .Key_Press: event.key.key = string(event.key._key_buf[:event.key._key_len]) event.key.code = string(event.key._code_buf[:event.key._code_len]) + case .Gamepad_Connected, .Gamepad_Disconnected: + event.gamepad.id = string(event.gamepad._id_buf[:event.gamepad._id_len]) + event.gamepad.mapping = string(event.gamepad._mapping_buf[:event.gamepad._mapping_len]) } callback(event) diff --git a/vendor/wasm/js/events_all_targets.odin b/core/sys/wasm/js/events_all_targets.odin similarity index 99% rename from vendor/wasm/js/events_all_targets.odin rename to core/sys/wasm/js/events_all_targets.odin index 19a004250..b7e01ca10 100644 --- a/vendor/wasm/js/events_all_targets.odin +++ b/core/sys/wasm/js/events_all_targets.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package wasm_js_interface @@ -284,5 +284,4 @@ add_custom_event_listener :: proc(id: string, name: string, user_data: rawptr, c } remove_custom_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc(e: Event)) -> bool { panic("vendor:wasm/js not supported on non JS targets") -} - +} \ No newline at end of file diff --git a/vendor/wasm/js/general.odin b/core/sys/wasm/js/general.odin similarity index 88% rename from vendor/wasm/js/general.odin rename to core/sys/wasm/js/general.odin index 513c60a6f..4ed2ae298 100644 --- a/vendor/wasm/js/general.odin +++ b/core/sys/wasm/js/general.odin @@ -1,4 +1,4 @@ -//+build js wasm32, js wasm64p32 +#+build js wasm32, js wasm64p32 package wasm_js_interface foreign import "odin_env" diff --git a/vendor/wasm/js/memory_all_targets.odin b/core/sys/wasm/js/memory_all_targets.odin similarity index 96% rename from vendor/wasm/js/memory_all_targets.odin rename to core/sys/wasm/js/memory_all_targets.odin index e1de6a696..e80d13c0b 100644 --- a/vendor/wasm/js/memory_all_targets.odin +++ b/core/sys/wasm/js/memory_all_targets.odin @@ -1,4 +1,4 @@ -//+build !js +#+build !js package wasm_js_interface import "core:mem" diff --git a/vendor/wasm/js/memory_js.odin b/core/sys/wasm/js/memory_js.odin similarity index 89% rename from vendor/wasm/js/memory_js.odin rename to core/sys/wasm/js/memory_js.odin index cdeb58128..8232cd0c9 100644 --- a/vendor/wasm/js/memory_js.odin +++ b/core/sys/wasm/js/memory_js.odin @@ -1,8 +1,8 @@ -//+build js wasm32, js wasm64p32 +#+build js wasm32, js wasm64p32 package wasm_js_interface import "core:mem" -import "core:intrinsics" +import "base:intrinsics" PAGE_SIZE :: 64 * 1024 page_alloc :: proc(page_count: int) -> (data: []byte, err: mem.Allocator_Error) { @@ -24,7 +24,7 @@ page_allocator :: proc() -> mem.Allocator { case .Alloc, .Alloc_Non_Zeroed: assert(size % PAGE_SIZE == 0) return page_alloc(size/PAGE_SIZE) - case .Resize, .Free, .Free_All, .Query_Info: + case .Resize, .Free, .Free_All, .Query_Info, .Resize_Non_Zeroed: return nil, .Mode_Not_Implemented case .Query_Features: set := (^mem.Allocator_Mode_Set)(old_memory) diff --git a/vendor/wasm/js/runtime.js b/core/sys/wasm/js/odin.js similarity index 79% rename from vendor/wasm/js/runtime.js rename to core/sys/wasm/js/odin.js index 78fdcca18..07a77952c 100644 --- a/vendor/wasm/js/runtime.js +++ b/core/sys/wasm/js/odin.js @@ -18,6 +18,13 @@ class WasmMemoryInterface { 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) { @@ -67,22 +74,51 @@ 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); } storeU16(addr, value) { this.mem.setUint16 (addr, value, true); } @@ -90,21 +126,52 @@ 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); - } - 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); } + 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`'); + } + } + + // Returned length might not be the same as `value.length` if non-ascii strings are given. storeString(addr, value) { - const bytes = this.loadBytes(addr, value.length); - new TextEncoder().encodeInto(value, bytes); + const src = new TextEncoder().encode(value); + const dst = new Uint8Array(this.memory.buffer, addr, src.length); + dst.set(src); + return src.length; } }; @@ -204,11 +271,11 @@ class WebGLInterface { } } getSource(shader, strings_ptr, strings_length) { - const STRING_SIZE = 2*4; + 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; } @@ -395,7 +462,7 @@ class WebGLInterface { this.ctx.copyTexImage2D(target, level, internalformat, x, y, width, height, border); }, CopyTexSubImage2D: (target, level, xoffset, yoffset, x, y, width, height) => { - this.ctx.copyTexImage2D(target, level, xoffset, yoffset, x, y, width, height); + this.ctx.copyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height); }, @@ -538,8 +605,8 @@ class WebGLInterface { Flush: () => { this.ctx.flush(); }, - FramebufferRenderBuffer: (target, attachment, renderbuffertarget, renderbuffer) => { - this.ctx.framebufferRenderBuffer(target, attachment, renderbuffertarget, this.renderbuffers[renderbuffer]); + FramebufferRenderbuffer: (target, attachment, renderbuffertarget, renderbuffer) => { + this.ctx.framebufferRenderbuffer(target, attachment, renderbuffertarget, this.renderbuffers[renderbuffer]); }, FramebufferTexture2D: (target, attachment, textarget, texture, level) => { this.ctx.framebufferTexture2D(target, attachment, textarget, this.textures[texture], level); @@ -645,7 +712,7 @@ class WebGLInterface { IsBuffer: (buffer) => this.ctx.isBuffer(this.buffers[buffer]), - IsEnabled: (enabled) => this.ctx.isEnabled(this.enableds[enabled]), + IsEnabled: (cap) => this.ctx.isEnabled(cap), IsFramebuffer: (framebuffer) => this.ctx.isFramebuffer(this.framebuffers[framebuffer]), IsProgram: (program) => this.ctx.isProgram(this.programs[program]), IsRenderbuffer: (renderbuffer) => this.ctx.isRenderbuffer(this.renderbuffers[renderbuffer]), @@ -669,7 +736,7 @@ class WebGLInterface { ReadnPixels: (x, y, width, height, format, type, bufSize, data) => { - this.ctx.readPixels(x, y, width, format, type, this.mem.loadBytes(data, bufSize)); + this.ctx.readPixels(x, y, width, height, format, type, this.mem.loadBytes(data, bufSize)); }, RenderbufferStorage: (target, internalformat, width, height) => { this.ctx.renderbufferStorage(target, internalformat, width, height); @@ -735,11 +802,11 @@ class WebGLInterface { UniformMatrix2fv: (location, addr) => { let array = this.mem.loadF32Array(addr, 2*2); - this.ctx.uniformMatrix4fv(this.uniforms[location], false, array); + this.ctx.uniformMatrix2fv(this.uniforms[location], false, array); }, UniformMatrix3fv: (location, addr) => { let array = this.mem.loadF32Array(addr, 3*3); - this.ctx.uniformMatrix4fv(this.uniforms[location], false, array); + this.ctx.uniformMatrix3fv(this.uniforms[location], false, array); }, UniformMatrix4fv: (location, addr) => { let array = this.mem.loadF32Array(addr, 4*4); @@ -791,7 +858,7 @@ class WebGLInterface { /* Framebuffer objects */ BlitFramebuffer: (srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter) => { this.assertWebGL2(); - this.ctx.glitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); + this.ctx.blitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); }, FramebufferTextureLayer: (target, attachment, texture, level, layer) => { this.assertWebGL2(); @@ -822,7 +889,7 @@ class WebGLInterface { TexStorage3D: (target, levels, internalformat, width, height, depth) => { this.assertWebGL2(); - this.ctx.texStorage3D(target, level, internalformat, width, heigh, depth); + this.ctx.texStorage3D(target, levels, internalformat, width, height, depth); }, TexImage3D: (target, level, internalformat, width, height, depth, border, format, type, size, data) => { this.assertWebGL2(); @@ -855,7 +922,7 @@ class WebGLInterface { CopyTexSubImage3D: (target, level, xoffset, yoffset, zoffset, x, y, width, height) => { this.assertWebGL2(); - this.ctx.copyTexImage3D(target, level, xoffset, yoffset, zoffset, x, y, width, height); + this.ctx.copyTexSubImage3D(target, level, xoffset, yoffset, zoffset, x, y, width, height); }, /* Programs and shaders */ @@ -982,10 +1049,10 @@ class WebGLInterface { }, DeleteQuery: (id) => { this.assertWebGL2(); - let obj = this.querys[id]; + let obj = this.queries[id]; if (obj && id != 0) { this.ctx.deleteQuery(obj); - this.querys[id] = null; + this.queries[id] = null; } }, IsQuery: (query) => { @@ -1038,7 +1105,7 @@ class WebGLInterface { }, BindSampler: (unit, sampler) => { this.assertWebGL2(); - this.ctx.bindSampler(unit, this.samplers[Sampler]); + this.ctx.bindSampler(unit, this.samplers[sampler]); }, SamplerParameteri: (sampler, pname, param) => { this.assertWebGL2(); @@ -1083,7 +1150,7 @@ class WebGLInterface { /* Transform Feedback */ CreateTransformFeedback: () => { this.assertWebGL2(); - let transformFeedback = this.ctx.createtransformFeedback(); + let transformFeedback = this.ctx.createTransformFeedback(); let id = this.getNewId(this.transformFeedbacks); transformFeedback.name = id; this.transformFeedbacks[id] = transformFeedback; @@ -1115,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); @@ -1191,13 +1259,26 @@ class WebGLInterface { }; -function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { +function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { const MAX_INFO_CONSOLE_LINES = 512; let infoConsoleLines = new Array(); let currentLine = {}; currentLine[false] = ""; currentLine[true] = ""; let prevIsError = false; + + let event_temp = {}; + + const onEventReceived = (event_data, data, callback) => { + event_temp.data = event_data; + + const exports = wasmMemoryInterface.exports; + const odin_ctx = exports.default_context_ptr(); + + exports.odin_dom_do_event_callback(data, callback, odin_ctx); + + event_temp.data = null; + }; const writeToConsole = (line, isError) => { if (!line) { @@ -1309,11 +1390,16 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { info.scrollTop = info.scrollHeight; }; - 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); @@ -1334,7 +1420,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { // return a bigint to be converted to i64 time_now: () => BigInt(Date.now()), - tick_now: () => BigInt(performance.now()), + tick_now: () => performance.now(), time_sleep: (duration_ms) => { if (duration_ms > 0) { // TODO(bill): Does this even make any sense? @@ -1357,7 +1443,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) { @@ -1371,11 +1457,22 @@ 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; + if (!event_temp.data) { + return; + } - wmi.storeU32(off(4), event_temp_data.name_code); + let e = event_temp.data.event; + + wmi.storeU32(off(4), event_temp.data.name_code); if (e.target == document) { wmi.storeU32(off(4), 1); } else if (e.target == window) { @@ -1391,10 +1488,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); @@ -1406,8 +1505,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); @@ -1429,7 +1533,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { } else if (e instanceof KeyboardEvent) { // Note: those strings are constructed // on the native side from buffers that - // are filled later, so skip them + // are filled later, so skip them const keyPtr = off(W*2, W); const codePtr = off(W*2, W); @@ -1442,20 +1546,49 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { wmi.storeU8(off(1), !!e.repeat); - wmi.storeI32(off(W), e.key.length) - 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 instanceof Event) { - if ('scrollX' in e) { - wmi.storeF64(off(8), e.scrollX); - wmi.storeF64(off(8), e.scrollY); + wmi.storeInt(off(W, W), e.key.length) + wmi.storeInt(off(W, W), e.code.length) + wmi.storeString(off(32, 1), e.key); + wmi.storeString(off(32, 1), e.code); + } else if (e.type === 'scroll') { + wmi.storeF64(off(8, 8), window.scrollX); + wmi.storeF64(off(8, 8), window.scrollY); + } else if (e.type === 'visibilitychange') { + wmi.storeU8(off(1), !document.hidden); + } else if (e instanceof GamepadEvent) { + const idPtr = off(W*2, W); + const mappingPtr = off(W*2, W); + + wmi.storeI32(off(W, W), e.gamepad.index); + wmi.storeU8(off(1), !!e.gamepad.connected); + wmi.storeF64(off(8, 8), e.gamepad.timestamp); + + wmi.storeInt(off(W, W), e.gamepad.buttons.length); + wmi.storeInt(off(W, W), e.gamepad.axes.length); + + for (let i = 0; i < 64; i++) { + if (i < e.gamepad.buttons.length) { + let b = e.gamepad.buttons[i]; + wmi.storeF64(off(8, 8), b.value); + wmi.storeU8(off(1), !!b.pressed); + wmi.storeU8(off(1), !!b.touched); + } else { + off(16, 8); + } } + for (let i = 0; i < 16; i++) { + if (i < e.gamepad.axes.length) { + let a = e.gamepad.axes[i]; + wmi.storeF64(off(8, 8), a); + } else { + off(8, 8); + } + } + + wmi.storeInt(off(W, W), e.gamepad.id.length) + wmi.storeInt(off(W, W), e.gamepad.mapping.length) + wmi.storeString(off(64, 1), e.gamepad.id); + wmi.storeString(off(64, 1), e.gamepad.mapping); } }, @@ -1468,12 +1601,30 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { } let listener = (e) => { - const odin_ctx = wasmMemoryInterface.exports.default_context_ptr(); - event_temp_data.id_ptr = id_ptr; - event_temp_data.id_len = id_len; - event_temp_data.event = e; - event_temp_data.name_code = name_code; - wasmMemoryInterface.exports.odin_dom_do_event_callback(data, callback, odin_ctx); + let event_data = {}; + event_data.id_ptr = id_ptr; + event_data.id_len = id_len; + event_data.event = e; + event_data.name_code = name_code; + + onEventReceived(event_data, data, callback); + }; + wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener; + element.addEventListener(name, listener, !!use_capture); + return true; + }, + + add_window_event_listener: (name_ptr, name_len, name_code, data, callback, use_capture) => { + let name = wasmMemoryInterface.loadString(name_ptr, name_len); + let element = window; + let listener = (e) => { + let event_data = {}; + event_data.id_ptr = 0; + event_data.id_len = 0; + event_data.event = e; + event_data.name_code = name_code; + + onEventReceived(event_data, data, callback); }; wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener; element.addEventListener(name, listener, !!use_capture); @@ -1495,24 +1646,6 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { element.removeEventListener(name, listener); return true; }, - - - add_window_event_listener: (name_ptr, name_len, name_code, data, callback, use_capture) => { - let name = wasmMemoryInterface.loadString(name_ptr, name_len); - let element = window; - let listener = (e) => { - const odin_ctx = wasmMemoryInterface.exports.default_context_ptr(); - event_temp_data.id_ptr = 0; - event_temp_data.id_len = 0; - event_temp_data.event = e; - event_temp_data.name_code = name_code; - wasmMemoryInterface.exports.odin_dom_do_event_callback(data, callback, odin_ctx); - }; - wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener; - element.addEventListener(name, listener, !!use_capture); - return true; - }, - remove_window_event_listener: (name_ptr, name_len, data, callback) => { let name = wasmMemoryInterface.loadString(name_ptr, name_len); let element = window; @@ -1528,18 +1661,18 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { }, event_stop_propagation: () => { - if (event_temp_data && event_temp_data.event) { - event_temp_data.event.eventStopPropagation(); + if (event_temp.data && event_temp.data.event) { + event_temp.data.event.stopPropagation(); } }, event_stop_immediate_propagation: () => { - if (event_temp_data && event_temp_data.event) { - event_temp_data.event.eventStopImmediatePropagation(); + if (event_temp.data && event_temp.data.event) { + event_temp.data.event.stopImmediatePropagation(); } }, event_prevent_default: () => { - if (event_temp_data && event_temp_data.event) { - event_temp_data.event.preventDefault(); + if (event_temp.data && event_temp.data.event) { + event_temp.data.event.preventDefault(); } }, @@ -1547,9 +1680,9 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { let id = wasmMemoryInterface.loadString(id_ptr, id_len); let name = wasmMemoryInterface.loadString(name_ptr, name_len); let options = { - bubbles: (options_bits & (1<<0)) !== 0, - cancelabe: (options_bits & (1<<1)) !== 0, - composed: (options_bits & (1<<2)) !== 0, + bubbles: (options_bits & (1<<0)) !== 0, + cancelable: (options_bits & (1<<1)) !== 0, + composed: (options_bits & (1<<2)) !== 0, }; let element = getElement(id); @@ -1560,6 +1693,76 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { return false; }, + get_gamepad_state: (gamepad_id, ep) => { + let index = gamepad_id; + let gps = navigator.getGamepads(); + if (0 <= index && index < gps.length) { + let gamepad = gps[index]; + if (!gamepad) { + return false; + } + + const W = wasmMemoryInterface.intSize; + let offset = ep; + let off = (amount, alignment) => { + if (alignment === undefined) { + alignment = Math.min(amount, W); + } + if (offset % alignment != 0) { + offset += alignment - (offset%alignment); + } + let x = offset; + offset += amount; + return x; + }; + + let align = (alignment) => { + const modulo = offset & (alignment-1); + if (modulo != 0) { + offset += alignment - modulo + } + }; + + let wmi = wasmMemoryInterface; + + const idPtr = off(W*2, W); + const mappingPtr = off(W*2, W); + + wmi.storeI32(off(W), gamepad.index); + wmi.storeU8(off(1), !!gamepad.connected); + wmi.storeF64(off(8), gamepad.timestamp); + + wmi.storeInt(off(W), gamepad.buttons.length); + wmi.storeInt(off(W), gamepad.axes.length); + + for (let i = 0; i < 64; i++) { + if (i < gamepad.buttons.length) { + let b = gamepad.buttons[i]; + wmi.storeF64(off(8, 8), b.value); + wmi.storeU8(off(1), !!b.pressed); + wmi.storeU8(off(1), !!b.touched); + } else { + off(16, 8); + } + } + for (let i = 0; i < 16; i++) { + if (i < gamepad.axes.length) { + wmi.storeF64(off(8, 8), gamepad.axes[i]); + } else { + off(8, 8); + } + } + + wmi.storeInt(off(W, W), gamepad.id.length) + wmi.storeInt(off(W, W), gamepad.mapping.length) + wmi.storeString(off(64, 1), gamepad.id); + wmi.storeString(off(64, 1), gamepad.mapping); + + return true; + } + return false; + }, + get_element_value_f64: (id_ptr, id_len) => { let id = wasmMemoryInterface.loadString(id_ptr, id_len); let element = getElement(id); @@ -1603,7 +1806,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { element.value = value; } }, - set_element_value_string: (id_ptr, id_len, value_ptr, value_id) => { + set_element_value_string: (id_ptr, id_len, value_ptr, value_len) => { let id = wasmMemoryInterface.loadString(id_ptr, id_len); let value = wasmMemoryInterface.loadString(value_ptr, value_len); let element = getElement(id); @@ -1612,6 +1815,65 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { } }, + set_element_style: (id_ptr, id_len, key_ptr, key_len, value_ptr, value_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let key = wasmMemoryInterface.loadString(key_ptr, key_len); + let value = wasmMemoryInterface.loadString(value_ptr, value_len); + let element = getElement(id); + if (element) { + element.style[key] = value; + } + }, + + get_element_key_f64: (id_ptr, id_len, key_ptr, key_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let key = wasmMemoryInterface.loadString(key_ptr, key_len); + let element = getElement(id); + return element ? element[key] : 0; + }, + get_element_key_string: (id_ptr, id_len, key_ptr, key_len, buf_ptr, buf_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let key = wasmMemoryInterface.loadString(key_ptr, key_len); + let element = getElement(id); + if (element) { + let str = element[key]; + if (buf_len > 0 && buf_ptr) { + let n = Math.min(buf_len, str.length); + str = str.substring(0, n); + this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(str)) + return n; + } + } + return 0; + }, + get_element_key_string_length: (id_ptr, id_len, key_ptr, key_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let key = wasmMemoryInterface.loadString(key_ptr, key_len); + let element = getElement(id); + if (element && element[key]) { + return element[key].length; + } + return 0; + }, + + set_element_key_f64: (id_ptr, id_len, key_ptr, key_len, value) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let key = wasmMemoryInterface.loadString(key_ptr, key_len); + let element = getElement(id); + if (element) { + element[key] = value; + } + }, + set_element_key_string: (id_ptr, id_len, key_ptr, key_len, value_ptr, value_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let key = wasmMemoryInterface.loadString(key_ptr, key_len); + let value = wasmMemoryInterface.loadString(value_ptr, value_len); + let element = getElement(id); + if (element) { + element[key] = value; + } + }, + get_bounding_client_rect: (rect_ptr, id_ptr, id_len) => { let id = wasmMemoryInterface.loadString(id_ptr, id_len); @@ -1653,10 +1915,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) { @@ -1671,30 +1943,44 @@ 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(delta_time: f64) -> (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) { const odin_ctx = exports.default_context_ptr(); let prevTimeStamp = undefined; - const step = (currTimeStamp) => { + function step(currTimeStamp) { if (prevTimeStamp == undefined) { prevTimeStamp = currTimeStamp; } const dt = (currTimeStamp - prevTimeStamp)*0.001; prevTimeStamp = currTimeStamp; - exports.step(dt, odin_ctx); + + if (!exports.step(dt, odin_ctx)) { + exports._end(); + return; + } + window.requestAnimationFrame(step); - }; + } window.requestAnimationFrame(step); + } else { + exports._end(); } - exports._end(); - return; }; @@ -1707,4 +1993,4 @@ window.odin = { setupDefaultImports: odinSetupDefaultImports, runWasm: runWasm, }; -})(); \ No newline at end of file +})(); diff --git a/core/sys/wasm/wasi/wasi_api.odin b/core/sys/wasm/wasi/wasi_api.odin index e9ceb4667..8d50f1690 100644 --- a/core/sys/wasm/wasi/wasi_api.odin +++ b/core/sys/wasm/wasi/wasi_api.odin @@ -1,4 +1,4 @@ -//+build wasm32 +#+build wasm32 package sys_wasi foreign import wasi "wasi_snapshot_preview1" @@ -10,12 +10,13 @@ filesize_t :: distinct u64 timestamp_t :: distinct u64 clockid_t :: distinct u32 -CLOCK_MONOTONIC :: clockid_t(0) -CLOCK_PROCESS_CPUTIME_ID :: clockid_t(1) -CLOCK_REALTIME :: clockid_t(2) +CLOCK_REALTIME :: clockid_t(0) +CLOCK_MONOTONIC :: clockid_t(1) +CLOCK_PROCESS_CPUTIME_ID :: clockid_t(2) CLOCK_THREAD_CPUTIME_ID :: clockid_t(3) errno_t :: enum u16 { + NONE = 0, // No error occurred. System call completed successfully. SUCCESS = 0, // Argument list too long. @@ -714,7 +715,7 @@ subscription_t :: struct { * The type of the event to which to subscribe, and its contents */ using contents: struct { - tag: u8, + tag: eventtype_t, using u: struct #raw_union { clock: subscription_clock_t, fd_read: subscription_fd_readwrite_t, @@ -962,7 +963,7 @@ prestat_dir_t :: struct { } prestat_t :: struct { - tag: u8, + tag: preopentype_t, using u: struct { dir: prestat_dir_t, }, @@ -1158,7 +1159,7 @@ foreign wasi { /** * A buffer into which to write the preopened directory name. */ - path: string, + path: []byte, ) -> errno_t --- /** * Create a directory. diff --git a/core/sys/windows/advapi32.odin b/core/sys/windows/advapi32.odin index dc7ec1e08..f834511d4 100644 --- a/core/sys/windows/advapi32.odin +++ b/core/sys/windows/advapi32.odin @@ -1,11 +1,11 @@ -// +build windows +#+build windows package sys_windows foreign import advapi32 "system:Advapi32.lib" HCRYPTPROV :: distinct HANDLE -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign advapi32 { @(link_name = "SystemFunction036") RtlGenRandom :: proc(RandomBuffer: ^u8, RandomBufferLength: ULONG) -> BOOLEAN --- @@ -13,13 +13,26 @@ foreign advapi32 { DesiredAccess: DWORD, TokenHandle: ^HANDLE) -> BOOL --- + OpenThreadToken :: proc(ThreadHandle: HANDLE, + DesiredAccess: DWORD, + 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 --- } // Necessary to create a token to impersonate a user with for CreateProcessAsUser -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign advapi32 { LogonUserW :: proc( lpszUsername: LPCWSTR, @@ -39,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( @@ -129,6 +152,43 @@ foreign advapi32 { cbData: DWORD, ) -> LSTATUS --- + RegQueryInfoKeyW :: proc( + hKey: HKEY, + lpClass: LPWSTR, + lpcchClass: LPDWORD, + lpReserved: LPDWORD, + lpcSubKeys: LPDWORD, + lpcbMaxSubKeyLen: LPDWORD, + lpcbMaxClassLen: LPDWORD, + lpcValues: LPDWORD, + lpcbMaxValueNameLen: LPDWORD, + lpcbMaxValueLen: LPDWORD, + lpcbSecurityDescriptor: LPDWORD, + lpftLastWriteTime: ^FILETIME, + ) -> LSTATUS --- + + RegEnumKeyExW :: proc( + hKey: HKEY, + dwIndex: DWORD, + lpName: LPWSTR, + lpcchName: LPDWORD, + lpReserved: LPDWORD, + lpClass: LPWSTR, + lpcchClass: LPDWORD, + lpftLastWriteTime: ^FILETIME, + ) -> LSTATUS --- + + RegEnumValueW :: proc( + hKey: HKEY, + dwIndex: DWORD, + lpValueName: LPWSTR, + lpcchValueName: LPDWORD, + lpReserved: LPDWORD, + lpType: LPDWORD, + lpData: LPBYTE, + lpcbData: LPDWORD, + ) -> LSTATUS --- + GetFileSecurityW :: proc( lpFileName: LPCWSTR, RequestedInformation: SECURITY_INFORMATION, @@ -159,3 +219,156 @@ foreign advapi32 { AccessStatus: LPBOOL, ) -> BOOL --- } + +PTOKEN_INFORMATION_CLASS :: ^TOKEN_INFORMATION_CLASS +TOKEN_INFORMATION_CLASS :: enum i32 { + TokenUser = 1, + TokenGroups, + TokenPrivileges, + TokenOwner, + TokenPrimaryGroup, + TokenDefaultDacl, + TokenSource, + TokenType, + TokenImpersonationLevel, + TokenStatistics, + TokenRestrictedSids, + TokenSessionId, + TokenGroupsAndPrivileges, + TokenSessionReference, + TokenSandBoxInert, + TokenAuditPolicy, + TokenOrigin, + TokenElevationType, + TokenLinkedToken, + TokenElevation, + TokenHasRestrictions, + TokenAccessInformation, + TokenVirtualizationAllowed, + TokenVirtualizationEnabled, + TokenIntegrityLevel, + TokenUIAccess, + TokenMandatoryPolicy, + TokenLogonSid, + TokenIsAppContainer, + TokenCapabilities, + TokenAppContainerSid, + TokenAppContainerNumber, + TokenUserClaimAttributes, + TokenDeviceClaimAttributes, + TokenRestrictedUserClaimAttributes, + TokenRestrictedDeviceClaimAttributes, + TokenDeviceGroups, + TokenRestrictedDeviceGroups, + TokenSecurityAttributes, + TokenIsRestricted, + TokenProcessTrustLevel, + TokenPrivateNameSpace, + TokenSingletonAttributes, + TokenBnoIsolation, + TokenChildProcessFlags, + TokenIsLessPrivilegedAppContainer, + TokenIsSandboxed, + TokenIsAppSilo, + TokenLoggingInformation, + MaxTokenInfoClass, +} + +PSID_NAME_USE :: ^SID_NAME_USE +SID_NAME_USE :: enum i32 { + SidTypeUser = 1, + SidTypeGroup, + SidTypeDomain, + SidTypeAlias, + SidTypeWellKnownGroup, + SidTypeDeletedAccount, + SidTypeInvalid, + SidTypeUnknown, + SidTypeComputer, + SidTypeLabel, + SidTypeLogonSession, +} + +PTOKEN_USER :: ^TOKEN_USER +TOKEN_USER :: struct { + User: SID_AND_ATTRIBUTES, +} + +PSID_AND_ATTRIBUTES :: ^SID_AND_ATTRIBUTES +SID_AND_ATTRIBUTES :: struct { + Sid: rawptr, + Attributes: ULONG, +} + +PTOKEN_TYPE :: ^TOKEN_TYPE +TOKEN_TYPE :: enum { + TokenPrimary = 1, + TokenImpersonation = 2, +} + +PTOKEN_STATISTICS :: ^TOKEN_STATISTICS +TOKEN_STATISTICS :: struct { + TokenId: LUID, + AuthenticationId: LUID, + ExpirationTime: LARGE_INTEGER, + TokenType: TOKEN_TYPE, + ImpersonationLevel: SECURITY_IMPERSONATION_LEVEL, + DynamicCharged: DWORD, + DynamicAvailable: DWORD, + GroupCount: DWORD, + PrivilegeCount: DWORD, + ModifiedId: LUID, +} + + +TOKEN_SOURCE_LENGTH :: 8 +PTOKEN_SOURCE :: ^TOKEN_SOURCE +TOKEN_SOURCE :: struct { + SourceName: [TOKEN_SOURCE_LENGTH]CHAR, + SourceIdentifier: LUID, +} + + +PTOKEN_PRIVILEGES :: ^TOKEN_PRIVILEGES +TOKEN_PRIVILEGES :: struct { + PrivilegeCount: DWORD, + Privileges: [0]LUID_AND_ATTRIBUTES, +} + +PTOKEN_PRIMARY_GROUP :: ^TOKEN_PRIMARY_GROUP +TOKEN_PRIMARY_GROUP :: struct { + PrimaryGroup: PSID, +} + +PTOKEN_OWNER :: ^TOKEN_OWNER +TOKEN_OWNER :: struct { + Owner: PSID, +} + +PTOKEN_GROUPS_AND_PRIVILEGES :: ^TOKEN_GROUPS_AND_PRIVILEGES +TOKEN_GROUPS_AND_PRIVILEGES :: struct { + SidCount: DWORD, + SidLength: DWORD, + Sids: PSID_AND_ATTRIBUTES, + RestrictedSidCount: DWORD, + RestrictedSidLength: DWORD, + RestrictedSids: PSID_AND_ATTRIBUTES, + PrivilegeCount: DWORD, + PrivilegeLength: DWORD, + Privileges: PLUID_AND_ATTRIBUTES, + AuthenticationId: LUID, +} + +PTOKEN_DEFAULT_DACL :: ^TOKEN_DEFAULT_DACL +TOKEN_DEFAULT_DACL :: struct { + DefaultDacl: PACL, +} + +PACL :: ^ACL +ACL :: struct { + AclRevision: BYTE, + Sbz1: BYTE, + AclSize: WORD, + AceCount: WORD, + Sbz2: WORD, +} diff --git a/core/sys/windows/bcrypt.odin b/core/sys/windows/bcrypt.odin index 52eb4b1b6..f15f1e305 100644 --- a/core/sys/windows/bcrypt.odin +++ b/core/sys/windows/bcrypt.odin @@ -1,11 +1,11 @@ -// +build windows +#+build windows package sys_windows foreign import bcrypt "system:Bcrypt.lib" BCRYPT_USE_SYSTEM_PREFERRED_RNG: DWORD : 0x00000002 -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign bcrypt { BCryptGenRandom :: proc(hAlgorithm: LPVOID, pBuffer: [^]u8, cbBuffer: ULONG, dwFlags: ULONG) -> LONG --- } diff --git a/core/sys/windows/bluetooth.odin b/core/sys/windows/bluetooth.odin index c2534896b..86c66b9a1 100644 --- a/core/sys/windows/bluetooth.odin +++ b/core/sys/windows/bluetooth.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import "system:bthprops.lib" @@ -51,7 +51,7 @@ BLUETOOTH_DEVICE_INFO :: struct { name: [BLUETOOTH_MAX_NAME_SIZE]u16, // Name of the device } -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign bthprops { /* Version diff --git a/core/sys/windows/codepage.odin b/core/sys/windows/codepage.odin new file mode 100644 index 000000000..527289f03 --- /dev/null +++ b/core/sys/windows/codepage.odin @@ -0,0 +1,298 @@ +#+build windows +package sys_windows + +// https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers +CODEPAGE :: enum UINT { + // Default to ANSI code page + ACP = CP_ACP, + // Default to OEM code page + OEMCP = CP_OEMCP, + // Default to MAC code page + MACCP = CP_MACCP, + // Current thread's ANSI code page + THREAD_ACP = CP_THREAD_ACP, + // Symbol translations + SYMBOL = CP_SYMBOL, + + // IBM EBCDIC US-Canada + IBM037 = 037, + // OEM United States + IBM437 = 437, + // IBM EBCDIC International + IBM500 = 500, + // Arabic (ASMO 708) + ASMO_708 = 708, + // Arabic (Transparent ASMO); Arabic (DOS) + DOS_720 = 720, + // OEM Greek (formerly 437G); Greek (DOS) + IBM737 = 737, + // OEM Baltic; Baltic (DOS) + IBM775 = 775, + // OEM Multilingual Latin 1; Western European (DOS) + IBM850 = 850, + // OEM Latin 2; Central European (DOS) + IBM852 = 852, + // OEM Cyrillic (primarily Russian) + IBM855 = 855, + // OEM Turkish; Turkish (DOS) + IBM857 = 857, + // OEM Multilingual Latin 1 + Euro symbol + IBM00858 = 858, + // OEM Portuguese; Portuguese (DOS) + IBM860 = 860, + // OEM Icelandic; Icelandic (DOS) + IBM861 = 861, + // OEM Hebrew; Hebrew (DOS) + DOS_862 = 862, + // OEM French Canadian; French Canadian (DOS) + IBM863 = 863, + // OEM Arabic; Arabic (864) + IBM864 = 864, + // OEM Nordic; Nordic (DOS) + IBM865 = 865, + // OEM Russian; Cyrillic (DOS) + CP866 = 866, + // OEM Modern Greek; Greek, Modern (DOS) + IBM869 = 869, + // IBM EBCDIC Multilingual/ROECE (Latin 2); IBM EBCDIC Multilingual Latin 2 + IBM870 = 870, + // Thai (Windows) + WINDOWS_874 = 874, + // IBM EBCDIC Greek Modern + CP875 = 875, + // ANSI/OEM Japanese; Japanese (Shift-JIS) + SHIFT_JIS = 932, + // ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese Simplified (GB2312) + GB2312 = 936, + // ANSI/OEM Korean (Unified Hangul Code) + KS_C_5601_1987 = 949, + // ANSI/OEM Traditional Chinese (Taiwan; Hong Kong SAR, PRC); Chinese Traditional (Big5) + BIG5 = 950, + // IBM EBCDIC Turkish (Latin 5) + IBM1026 = 1026, + // IBM EBCDIC Latin 1/Open System + IBM01047 = 1047, + // IBM EBCDIC US-Canada (037 + Euro symbol); IBM EBCDIC (US-Canada-Euro) + IBM01140 = 1140, + // IBM EBCDIC Germany (20273 + Euro symbol); IBM EBCDIC (Germany-Euro) + IBM01141 = 1141, + // IBM EBCDIC Denmark-Norway (20277 + Euro symbol); IBM EBCDIC (Denmark-Norway-Euro) + IBM01142 = 1142, + // IBM EBCDIC Finland-Sweden (20278 + Euro symbol); IBM EBCDIC (Finland-Sweden-Euro) + IBM01143 = 1143, + // IBM EBCDIC Italy (20280 + Euro symbol); IBM EBCDIC (Italy-Euro) + IBM01144 = 1144, + // IBM EBCDIC Latin America-Spain (20284 + Euro symbol); IBM EBCDIC (Spain-Euro) + IBM01145 = 1145, + // IBM EBCDIC United Kingdom (20285 + Euro symbol); IBM EBCDIC (UK-Euro) + IBM01146 = 1146, + // IBM EBCDIC France (20297 + Euro symbol); IBM EBCDIC (France-Euro) + IBM01147 = 1147, + // IBM EBCDIC International (500 + Euro symbol); IBM EBCDIC (International-Euro) + IBM01148 = 1148, + // IBM EBCDIC Icelandic (20871 + Euro symbol); IBM EBCDIC (Icelandic-Euro) + IBM01149 = 1149, + // Unicode UTF-16, little endian byte order (BMP of ISO 10646); available only to managed applications + UTF16 = 1200, + // Unicode UTF-16, big endian byte order; available only to managed applications + UNICODEFFFE = 1201, + // ANSI Central European; Central European (Windows) + WINDOWS_1250 = 1250, + // ANSI Cyrillic; Cyrillic (Windows) + WINDOWS_1251 = 1251, + // ANSI Latin 1; Western European (Windows) + WINDOWS_1252 = 1252, + // ANSI Greek; Greek (Windows) + WINDOWS_1253 = 1253, + // ANSI Turkish; Turkish (Windows) + WINDOWS_1254 = 1254, + // ANSI Hebrew; Hebrew (Windows) + WINDOWS_1255 = 1255, + // ANSI Arabic; Arabic (Windows) + WINDOWS_1256 = 1256, + // ANSI Baltic; Baltic (Windows) + WINDOWS_1257 = 1257, + // ANSI/OEM Vietnamese; Vietnamese (Windows) + WINDOWS_1258 = 1258, + // Korean (Johab) + JOHAB = 1361, + // MAC Roman; Western European (Mac) + MACINTOSH = 10000, + // Japanese (Mac) + X_MAC_JAPANESE = 10001, + // MAC Traditional Chinese (Big5); Chinese Traditional (Mac) + X_MAC_CHINESETRAD = 10002, + // Korean (Mac) + X_MAC_KOREAN = 10003, + // Arabic (Mac) + X_MAC_ARABIC = 10004, + // Hebrew (Mac) + X_MAC_HEBREW = 10005, + // Greek (Mac) + X_MAC_GREEK = 10006, + // Cyrillic (Mac) + X_MAC_CYRILLIC = 10007, + // MAC Simplified Chinese (GB 2312); Chinese Simplified (Mac) + X_MAC_CHINESESIMP = 10008, + // Romanian (Mac) + X_MAC_ROMANIAN = 10010, + // Ukrainian (Mac) + X_MAC_UKRAINIAN = 10017, + // Thai (Mac) + X_MAC_THAI = 10021, + // MAC Latin 2; Central European (Mac) + X_MAC_CE = 10029, + // Icelandic (Mac) + X_MAC_ICELANDIC = 10079, + // Turkish (Mac) + X_MAC_TURKISH = 10081, + // Croatian (Mac) + X_MAC_CROATIAN = 10082, + // Unicode UTF-32, little endian byte order; available only to managed applications + UTF32 = 12000, + // Unicode UTF-32, big endian byte order; available only to managed applications + UTF32BE = 12001, + // CNS Taiwan; Chinese Traditional (CNS) + X_CHINESE_CNS = 20000, + // TCA Taiwan + X_CP20001 = 20001, + // Eten Taiwan; Chinese Traditional (Eten) + X_CHINESE_ETEN = 20002, + // IBM5550 Taiwan + X_CP20003 = 20003, + // TeleText Taiwan + X_CP20004 = 20004, + // Wang Taiwan + X_CP20005 = 20005, + // IA5 (IRV International Alphabet No. 5, 7-bit); Western European (IA5) + X_IA5 = 20105, + // IA5 German (7-bit) + X_IA5_GERMAN = 20106, + // IA5 Swedish (7-bit) + X_IA5_SWEDISH = 20107, + // IA5 Norwegian (7-bit) + X_IA5_NORWEGIAN = 20108, + // US-ASCII (7-bit) + US_ASCII = 20127, + // T.61 + X_CP20261 = 20261, + // ISO 6937 Non-Spacing Accent + X_CP20269 = 20269, + // IBM EBCDIC Germany + IBM273 = 20273, + // IBM EBCDIC Denmark-Norway + IBM277 = 20277, + // IBM EBCDIC Finland-Sweden + IBM278 = 20278, + // IBM EBCDIC Italy + IBM280 = 20280, + // IBM EBCDIC Latin America-Spain + IBM284 = 20284, + // IBM EBCDIC United Kingdom + IBM285 = 20285, + // IBM EBCDIC Japanese Katakana Extended + IBM290 = 20290, + // IBM EBCDIC France + IBM297 = 20297, + // IBM EBCDIC Arabic + IBM420 = 20420, + // IBM EBCDIC Greek + IBM423 = 20423, + // IBM EBCDIC Hebrew + IBM424 = 20424, + // IBM EBCDIC Korean Extended + X_EBCDIC_KOREANEXTENDED = 20833, + // IBM EBCDIC Thai + IBM_THAI = 20838, + // Russian (KOI8-R); Cyrillic (KOI8-R) + KOI8_R = 20866, + // IBM EBCDIC Icelandic + IBM871 = 20871, + // IBM EBCDIC Cyrillic Russian + IBM880 = 20880, + // IBM EBCDIC Turkish + IBM905 = 20905, + // IBM EBCDIC Latin 1/Open System (1047 + Euro symbol) + IBM00924 = 20924, + // Japanese (JIS 0208-1990 and 0212-1990) + EUC_JP = 20932, + // Simplified Chinese (GB2312); Chinese Simplified (GB2312-80) + X_CP20936 = 20936, + // Korean Wansung + X_CP20949 = 20949, + // IBM EBCDIC Cyrillic Serbian-Bulgarian + CP1025 = 21025, + // Ukrainian (KOI8-U); Cyrillic (KOI8-U) + KOI8_U = 21866, + // ISO 8859-1 Latin 1; Western European (ISO) + ISO_8859_1 = 28591, + // ISO 8859-2 Central European; Central European (ISO) + ISO_8859_2 = 28592, + // ISO 8859-3 Latin 3 + ISO_8859_3 = 28593, + // ISO 8859-4 Baltic + ISO_8859_4 = 28594, + // ISO 8859-5 Cyrillic + ISO_8859_5 = 28595, + // ISO 8859-6 Arabic + ISO_8859_6 = 28596, + // ISO 8859-7 Greek + ISO_8859_7 = 28597, + // ISO 8859-8 Hebrew; Hebrew (ISO-Visual) + ISO_8859_8 = 28598, + // ISO 8859-9 Turkish + ISO_8859_9 = 28599, + // ISO 8859-13 Estonian + ISO_8859_13 = 28603, + // ISO 8859-15 Latin 9 + ISO_8859_15 = 28605, + // Europa 3 + X_EUROPA = 29001, + // ISO 8859-8 Hebrew; Hebrew (ISO-Logical) + ISO_8859_8_I = 38598, + // ISO 2022 Japanese with no halfwidth Katakana; Japanese (JIS) + ISO_2022_JP = 50220, + // ISO 2022 Japanese with halfwidth Katakana; Japanese (JIS-Allow 1 byte Kana) + CSISO2022JP = 50221, + // ISO 2022 Japanese JIS X 0201-1989; Japanese (JIS-Allow 1 byte Kana - SO/SI) + ISO_2022_2_JP = 50222, + // ISO 2022 Korean + ISO_2022_KR = 50225, + // ISO 2022 Simplified Chinese; Chinese Simplified (ISO 2022) + X_CP50227 = 50227, + // EUC Japanese + EUC_JP_2 = 51932, + // EUC Simplified Chinese; Chinese Simplified (EUC) + EUC_CN = 51936, + // EUC Korean + EUC_KR = 51949, + // HZ-GB2312 Simplified Chinese; Chinese Simplified (HZ) + HZ_GB_2312 = 52936, + // **Windows XP and later:** GB18030 Simplified Chinese (4 byte); Chinese Simplified (GB18030) + GB18030 = 54936, + // ISCII Devanagari + X_ISCII_DE = 57002, + // ISCII Bangla + X_ISCII_BE = 57003, + // ISCII Tamil + X_ISCII_TA = 57004, + // ISCII Telugu + X_ISCII_TE = 57005, + // ISCII Assamese + X_ISCII_AS = 57006, + // ISCII Odia + X_ISCII_OR = 57007, + // ISCII Kannada + X_ISCII_KA = 57008, + // ISCII Malayalam + X_ISCII_MA = 57009, + // ISCII Gujarati + X_ISCII_GU = 57010, + // ISCII Punjabi + X_ISCII_PA = 57011, + + // Unicode (UTF-7) + UTF7 = CP_UTF7, /*65000*/ + // Unicode (UTF-8) + UTF8 = CP_UTF8, /*65001*/ +} diff --git a/core/sys/windows/comctl32.odin b/core/sys/windows/comctl32.odin index 983c45d36..477800413 100644 --- a/core/sys/windows/comctl32.odin +++ b/core/sys/windows/comctl32.odin @@ -1,9 +1,9 @@ -// +build windows +#+build windows package sys_windows foreign import "system:Comctl32.lib" -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign Comctl32 { LoadIconWithScaleDown :: proc(hinst: HINSTANCE, pszName: PCWSTR, cx: c_int, cy: c_int, phico: ^HICON) -> HRESULT --- } diff --git a/core/sys/windows/comdlg32.odin b/core/sys/windows/comdlg32.odin index 8284050f1..a9800b47a 100644 --- a/core/sys/windows/comdlg32.odin +++ b/core/sys/windows/comdlg32.odin @@ -1,9 +1,9 @@ -// +build windows +#+build windows package sys_windows foreign import "system:Comdlg32.lib" -LPOFNHOOKPROC :: #type proc "stdcall" (hdlg: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> UINT_PTR +LPOFNHOOKPROC :: #type proc "system" (hdlg: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> UINT_PTR OPENFILENAMEW :: struct { lStructSize: DWORD, @@ -31,7 +31,7 @@ OPENFILENAMEW :: struct { FlagsEx: DWORD, } -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign Comdlg32 { GetOpenFileNameW :: proc(arg1: ^OPENFILENAMEW) -> BOOL --- GetSaveFileNameW :: proc(arg1: ^OPENFILENAMEW) -> BOOL --- diff --git a/core/sys/windows/dbghelp.odin b/core/sys/windows/dbghelp.odin new file mode 100644 index 000000000..336992b4a --- /dev/null +++ b/core/sys/windows/dbghelp.odin @@ -0,0 +1,288 @@ +#+build windows +package sys_windows + +foreign import "system:Dbghelp.lib" + +RVA :: DWORD + +MINIDUMP_LOCATION_DESCRIPTOR :: struct { + DataSize: ULONG32, + Rva: RVA, +} + +MINIDUMP_DIRECTORY :: struct { + StreamType: ULONG32, + Location: MINIDUMP_LOCATION_DESCRIPTOR, +} + +MINIDUMP_EXCEPTION_INFORMATION :: struct { + ThreadId: DWORD, + ExceptionPointers: ^EXCEPTION_POINTERS, + ClientPointers: BOOL, +} + +MINIDUMP_MEMORY_INFO :: struct { + BaseAddress: ULONG64, + AllocationBase: ULONG64, + AllocationProtect: ULONG32, + __alignment1: ULONG32, + RegionSize: ULONG64, + State: ULONG32, + Protect: ULONG32, + Type: ULONG32, + __alignment2: ULONG32, +} + +MINIDUMP_USER_STREAM :: struct { + Type: ULONG32, + BufferSize: ULONG, + Buffer: PVOID, +} + +MINIDUMP_USER_STREAM_INFORMATION :: struct { + UserStreamCount: ULONG, + UserStreamArray: ^MINIDUMP_USER_STREAM, +} + +MINIDUMP_CALLBACK_ROUTINE :: #type proc "system" ( + CallbackParam: PVOID, + CallbackInput: ^MINIDUMP_CALLBACK_INPUT, + CallbackOutpu: ^MINIDUMP_CALLBACK_OUTPUT, +) -> BOOL + +MINIDUMP_CALLBACK_INFORMATION :: struct { + CallbackRoutine: MINIDUMP_CALLBACK_ROUTINE, + CallbackParam: PVOID, +} + +MINIDUMP_CALLBACK_INPUT :: struct { + ProcessId: ULONG, + ProcessHandle: HANDLE, + CallbackType: ULONG, + using _: struct #raw_union { + Status: HRESULT, + Thread: MINIDUMP_THREAD_CALLBACK, + ThreadEx: MINIDUMP_THREAD_EX_CALLBACK, + Module: MINIDUMP_MODULE_CALLBACK, + IncludeThread: MINIDUMP_INCLUDE_THREAD_CALLBACK, + IncludeModule: MINIDUMP_INCLUDE_MODULE_CALLBACK, + Io: MINIDUMP_IO_CALLBACK, + ReadMemoryFailure: MINIDUMP_READ_MEMORY_FAILURE_CALLBACK, + SecondaryFlags: ULONG, + VmQuery: MINIDUMP_VM_QUERY_CALLBACK, + VmPreRead: MINIDUMP_VM_PRE_READ_CALLBACK, + VmPostRead: MINIDUMP_VM_POST_READ_CALLBACK, + }, +} + +_MINIDUMP_ARM64_PAD :: ULONG when ODIN_ARCH == .arm64 else struct {} + +MINIDUMP_THREAD_CALLBACK :: struct { + ThreadId: ULONG, + ThreadHandle: HANDLE, + Pad: _MINIDUMP_ARM64_PAD, + Context: CONTEXT, + SizeOfContext: ULONG, + StackBase: ULONG64, + StackEnd: ULONG64, +} + +MINIDUMP_THREAD_EX_CALLBACK :: struct { + ThreadId: ULONG, + ThreadHandle: HANDLE, + Pad: _MINIDUMP_ARM64_PAD, + Context: CONTEXT, + SizeOfContext: ULONG, + StackBase: ULONG64, + StackEnd: ULONG64, + BackingStoreBase: ULONG64, + BackingStoreEnd: ULONG64, +} + +MINIDUMP_INCLUDE_THREAD_CALLBACK :: struct { + ThreadId: ULONG, +} + +// NOTE(jakubtomsu): From verrsrc.h +VS_FIXEDFILEINFO :: struct { + dwSignature: DWORD, /* e.g. 0xfeef04bd */ + dwStrucVersion: DWORD, /* e.g. 0x00000042 = "0.42" */ + dwFileVersionMS: DWORD, /* e.g. 0x00030075 = "3.75" */ + dwFileVersionLS: DWORD, /* e.g. 0x00000031 = "0.31" */ + dwProductVersionMS: DWORD, /* e.g. 0x00030010 = "3.10" */ + dwProductVersionLS: DWORD, /* e.g. 0x00000031 = "0.31" */ + dwFileFlagsMask: DWORD, /* = 0x3F for version "0.42" */ + dwFileFlags: DWORD, /* e.g. VFF_DEBUG | VFF_PRERELEASE */ + dwFileOS: DWORD, /* e.g. VOS_DOS_WINDOWS16 */ + dwFileType: DWORD, /* e.g. VFT_DRIVER */ + dwFileSubtype: DWORD, /* e.g. VFT2_DRV_KEYBOARD */ + dwFileDateMS: DWORD, /* e.g. 0 */ + dwFileDateLS: DWORD, /* e.g. 0 */ +} + +MINIDUMP_MODULE_CALLBACK :: struct { + FullPath: ^WCHAR, + BaseOfImage: ULONG64, + SizeOfImage: ULONG, + CheckSum: ULONG, + TimeDateStamp: ULONG, + VersionInfo: VS_FIXEDFILEINFO, + CvRecord: PVOID, + SizeOfCvRecord: ULONG, + MiscRecord: PVOID, + SizeOfMiscRecord: ULONG, +} + +MINIDUMP_INCLUDE_MODULE_CALLBACK :: struct { + BaseOfImage: u64, +} + +MINIDUMP_IO_CALLBACK :: struct { + Handle: HANDLE, + Offset: ULONG64, + Buffer: PVOID, + BufferBytes: ULONG, +} + +MINIDUMP_READ_MEMORY_FAILURE_CALLBACK :: struct { + Offset: ULONG64, + Bytes: ULONG, + FailureStatus: HRESULT, +} + +MINIDUMP_VM_QUERY_CALLBACK :: struct { + Offset: ULONG64, +} + +MINIDUMP_VM_PRE_READ_CALLBACK :: struct { + Offset: ULONG64, + Buffer: PVOID, + Size: ULONG, +} + +MINIDUMP_VM_POST_READ_CALLBACK :: struct { + Offset: ULONG64, + Buffer: PVOID, + Size: ULONG, + Completed: ULONG, + Status: HRESULT, +} + +MINIDUMP_CALLBACK_OUTPUT :: struct { + using _: struct #raw_union { + ModuleWriteFlags: ULONG, + ThreadWriteFlags: ULONG, + SecondaryFlags: ULONG, + using _: struct { + MemoryBase: ULONG64, + MemorySize: ULONG, + }, + using _: struct { + CheckCancel: BOOL, + Cancel: BOOL, + }, + Handle: HANDLE, + using _: struct { + VmRegion: MINIDUMP_MEMORY_INFO, + Continue: BOOL, + }, + using _: struct { + VmQueryStatus: HRESULT, + VmQueryResult: MINIDUMP_MEMORY_INFO, + }, + using _: struct { + VmReadStatus: HRESULT, + VmReadBytesCompleted: ULONG, + }, + Status: HRESULT, + }, +} + +MINIDUMP_TYPE :: enum u32 { + Normal = 0x00000000, + WithDataSegs = 0x00000001, + WithFullMemory = 0x00000002, + WithHandleData = 0x00000004, + FilterMemory = 0x00000008, + ScanMemory = 0x00000010, + WithUnloadedModules = 0x00000020, + WithIndirectlyReferencedMemory = 0x00000040, + FilterModulePaths = 0x00000080, + WithProcessThreadData = 0x00000100, + WithPrivateReadWriteMemory = 0x00000200, + WithoutOptionalData = 0x00000400, + WithFullMemoryInfo = 0x00000800, + WithThreadInfo = 0x00001000, + WithCodeSegs = 0x00002000, + WithoutAuxiliaryState = 0x00004000, + WithFullAuxiliaryState = 0x00008000, + WithPrivateWriteCopyMemory = 0x00010000, + IgnoreInaccessibleMemory = 0x00020000, + WithTokenInformation = 0x00040000, + WithModuleHeaders = 0x00080000, + FilterTriage = 0x00100000, + WithAvxXStateContext = 0x00200000, + WithIptTrace = 0x00400000, + ScanInaccessiblePartialPages = 0x00800000, + FilterWriteCombinedMemory = 0x01000000, + 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( + hProcess: HANDLE, + ProcessId: DWORD, + hFile: HANDLE, + DumpType: MINIDUMP_TYPE, + ExceptionParam: ^MINIDUMP_EXCEPTION_INFORMATION, + UserStreamParam: ^MINIDUMP_USER_STREAM_INFORMATION, + CallbackPara: ^MINIDUMP_CALLBACK_INFORMATION, + ) -> BOOL --- + + MiniDumpReadDumpStream :: proc( + BaseOfDump: PVOID, + StreamNumber: ULONG, + Dir: ^^MINIDUMP_DIRECTORY, + 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/dnsapi.odin b/core/sys/windows/dnsapi.odin index 623fa2889..4fd9f7a19 100644 --- a/core/sys/windows/dnsapi.odin +++ b/core/sys/windows/dnsapi.odin @@ -1,9 +1,9 @@ -// +build windows +#+build windows package sys_windows foreign import "system:Dnsapi.lib" -@(default_calling_convention="std") +@(default_calling_convention="system") foreign Dnsapi { DnsQuery_UTF8 :: proc(name: cstring, type: u16, options: DWORD, extra: PVOID, results: ^^DNS_RECORD, reserved: PVOID) -> DNS_STATUS --- DnsRecordListFree :: proc(list: ^DNS_RECORD, options: DWORD) --- diff --git a/core/sys/windows/dwmapi.odin b/core/sys/windows/dwmapi.odin index 9b5916ab1..11a46f53a 100644 --- a/core/sys/windows/dwmapi.odin +++ b/core/sys/windows/dwmapi.odin @@ -1,10 +1,10 @@ -// +build windows +#+build windows package sys_windows foreign import dwmapi "system:Dwmapi.lib" DWMWINDOWATTRIBUTE :: enum { - DWMWA_NCRENDERING_ENABLED, + DWMWA_NCRENDERING_ENABLED = 1, DWMWA_NCRENDERING_POLICY, DWMWA_TRANSITIONS_FORCEDISABLED, DWMWA_ALLOW_NCPAINT, @@ -28,7 +28,7 @@ DWMWINDOWATTRIBUTE :: enum { DWMWA_TEXT_COLOR, DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, DWMWA_SYSTEMBACKDROP_TYPE, - DWMWA_LAST, + DWMWA_LAST, } DWMNCRENDERINGPOLICY :: enum { @@ -38,10 +38,11 @@ DWMNCRENDERINGPOLICY :: enum { DWMNCRP_LAST, } -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign dwmapi { DwmFlush :: proc() -> HRESULT --- DwmIsCompositionEnabled :: proc(pfEnabled: ^BOOL) -> HRESULT --- DwmExtendFrameIntoClientArea :: proc(hWnd: HWND, pMarInset: PMARGINS) -> HRESULT --- + DwmGetWindowAttribute :: proc(hWnd: HWND, dwAttribute: DWORD, pvAttribute: PVOID, cbAttribute: DWORD) -> HRESULT --- DwmSetWindowAttribute :: proc(hWnd: HWND, dwAttribute: DWORD, pvAttribute: LPCVOID, cbAttribute: DWORD) -> HRESULT --- } diff --git a/core/sys/windows/gdi32.odin b/core/sys/windows/gdi32.odin index 9e2294c71..1d7a93d85 100644 --- a/core/sys/windows/gdi32.odin +++ b/core/sys/windows/gdi32.odin @@ -1,88 +1,353 @@ -// +build windows +#+build windows package sys_windows +import "core:math/fixed" + foreign import gdi32 "system:Gdi32.lib" -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign gdi32 { - GetStockObject :: proc(i: c_int) -> HGDIOBJ --- + GetDeviceCaps :: proc(hdc: HDC, index: INT) -> INT --- + GetStockObject :: proc(i: INT) -> HGDIOBJ --- SelectObject :: proc(hdc: HDC, h: HGDIOBJ) -> HGDIOBJ --- DeleteObject :: proc(ho: HGDIOBJ) -> BOOL --- SetBkColor :: proc(hdc: HDC, color: COLORREF) -> COLORREF --- + SetBkMode :: proc(hdc: HDC, mode: BKMODE) -> INT --- + + CreateCompatibleDC :: proc(hdc: HDC) -> HDC --- + DeleteDC :: proc(hdc: HDC) -> BOOL --- + CancelDC :: proc(hdc: HDC) -> BOOL --- + SaveDC :: proc(hdc: HDC) -> INT --- + RestoreDC :: proc(hdc: HDC, nSavedDC: INT) -> BOOL --- CreateDIBPatternBrush :: proc(h: HGLOBAL, iUsage: UINT) -> HBRUSH --- + CreateDIBitmap :: proc(hdc: HDC, pbmih: ^BITMAPINFOHEADER, flInit: DWORD, pjBits: VOID, pbmi: ^BITMAPINFO, iUsage: UINT) -> HBITMAP --- + CreateDIBSection :: proc(hdc: HDC, pbmi: ^BITMAPINFO, usage: UINT, ppvBits: VOID, hSection: HANDLE, offset: DWORD) -> HBITMAP --- + StretchDIBits :: proc(hdc: HDC, xDest, yDest, DestWidth, DestHeight, xSrc, ySrc, SrcWidth, SrcHeight: INT, lpBits: VOID, lpbmi: ^BITMAPINFO, iUsage: UINT, rop: DWORD) -> INT --- + StretchBlt :: proc(hdcDest: HDC, xDest, yDest, wDest, hDest: INT, hdcSrc: HDC, xSrc, ySrc, wSrc, hSrc: INT, rop: DWORD) -> BOOL --- - CreateDIBitmap :: proc( - hdc: HDC, - pbmih: ^BITMAPINFOHEADER, - flInit: DWORD, - pjBits: VOID, - pbmi: ^BITMAPINFO, - iUsage: UINT, - ) -> HBITMAP --- - - CreateDIBSection :: proc( - hdc: HDC, - pbmi: ^BITMAPINFO, - usage: UINT, - ppvBits: VOID, - hSection: HANDLE, - offset: DWORD, - ) -> HBITMAP --- - - StretchDIBits :: proc( - hdc: HDC, - xDest: c_int, - yDest: c_int, - DestWidth: c_int, - DestHeight: c_int, - xSrc: c_int, - ySrc: c_int, - SrcWidth: c_int, - SrcHeight: c_int, - lpBits: VOID, - lpbmi: ^BITMAPINFO, - iUsage: UINT, - rop: DWORD, - ) -> c_int --- - - StretchBlt :: proc( - hdcDest: HDC, - xDest: c_int, - yDest: c_int, - wDest: c_int, - hDest: c_int, - hdcSrc: HDC, - xSrc: c_int, - ySrc: c_int, - wSrc: c_int, - hSrc: c_int, - rop: DWORD, - ) -> BOOL --- - - SetPixelFormat :: proc(hdc: HDC, format: c_int, ppfd: ^PIXELFORMATDESCRIPTOR) -> BOOL --- - ChoosePixelFormat :: proc(hdc: HDC, ppfd: ^PIXELFORMATDESCRIPTOR) -> c_int --- - DescribePixelFormat :: proc(hdc: HDC, iPixelFormat: c_int, nBytes: UINT, ppfd: ^PIXELFORMATDESCRIPTOR) -> c_int --- - SwapBuffers :: proc(HDC) -> BOOL --- + SetPixelFormat :: proc(hdc: HDC, format: INT, ppfd: ^PIXELFORMATDESCRIPTOR) -> BOOL --- + ChoosePixelFormat :: proc(hdc: HDC, ppfd: ^PIXELFORMATDESCRIPTOR) -> INT --- + DescribePixelFormat :: proc(hdc: HDC, iPixelFormat: INT, nBytes: UINT, ppfd: ^PIXELFORMATDESCRIPTOR) -> INT --- + SwapBuffers :: proc(hdc: HDC) -> BOOL --- SetDCBrushColor :: proc(hdc: HDC, color: COLORREF) -> COLORREF --- GetDCBrushColor :: proc(hdc: HDC) -> COLORREF --- - PatBlt :: proc(hdc: HDC, x, y, w, h: c_int, rop: DWORD) -> BOOL --- - Rectangle :: proc(hdc: HDC, left, top, right, bottom: c_int) -> BOOL --- + PatBlt :: proc(hdc: HDC, x, y, w, h: INT, rop: DWORD) -> BOOL --- + Rectangle :: proc(hdc: HDC, left, top, right, bottom: INT) -> BOOL --- - CreateFontW :: proc( - cHeight, cWidth, cEscapement, cOrientation, cWeight: c_int, - bItalic, bUnderline, bStrikeOut, iCharSet, iOutPrecision: DWORD, - iClipPrecision, iQuality, iPitchAndFamily: DWORD, - pszFaceName: LPCWSTR, - ) -> HFONT --- - TextOutW :: proc(hdc: HDC, x, y: c_int, lpString: LPCWSTR, c: c_int) -> BOOL --- - GetTextExtentPoint32W :: proc(hdc: HDC, lpString: LPCWSTR, c: c_int, psizl: LPSIZE) -> BOOL --- + CreateFontW :: proc(cHeight, cWidth, cEscapement, cOrientation, cWeight: INT, bItalic, bUnderline, bStrikeOut, iCharSet, iOutPrecision: DWORD, iClipPrecision, iQuality, iPitchAndFamily: DWORD, pszFaceName: LPCWSTR) -> HFONT --- + CreateFontIndirectW :: proc(lplf: ^LOGFONTW) -> HFONT --- + CreateFontIndirectExW :: proc(unnamedParam1: ^ENUMLOGFONTEXDVW) -> HFONT --- + AddFontResourceW :: proc(unnamedParam1: LPCWSTR) -> INT --- + AddFontResourceExW :: proc(name: LPCWSTR, fl: DWORD, res: PVOID) -> INT --- + AddFontMemResourceEx :: proc(pFileView: PVOID, cjSize: DWORD, pvResrved: PVOID, pNumFonts: ^DWORD) -> HANDLE --- + EnumFontsW :: proc(hdc: HDC, lpLogfont: LPCWSTR, lpProc: FONTENUMPROCW, lParam: LPARAM) -> INT --- + EnumFontFamiliesW :: proc(hdc: HDC, lpLogfont: LPCWSTR, lpProc: FONTENUMPROCW, lParam: LPARAM) -> INT --- + EnumFontFamiliesExW :: proc(hdc: HDC, lpLogfont: LPLOGFONTW, lpProc: FONTENUMPROCW, lParam: LPARAM, dwFlags: DWORD) -> INT --- + + TextOutW :: proc(hdc: HDC, x, y: INT, lpString: LPCWSTR, c: INT) -> BOOL --- + GetTextExtentPoint32W :: proc(hdc: HDC, lpString: LPCWSTR, c: INT, psizl: LPSIZE) -> BOOL --- GetTextMetricsW :: proc(hdc: HDC, lptm: LPTEXTMETRICW) -> BOOL --- CreateSolidBrush :: proc(color: COLORREF) -> HBRUSH --- + + GetObjectW :: proc(h: HANDLE, c: INT, pv: LPVOID) -> int --- + CreateCompatibleBitmap :: proc(hdc: HDC, cx, cy: INT) -> HBITMAP --- + BitBlt :: proc(hdc: HDC, x, y, cx, cy: INT, hdcSrc: HDC, x1, y1: INT, rop: DWORD) -> BOOL --- + GetDIBits :: proc(hdc: HDC, hbm: HBITMAP, start, cLines: UINT, lpvBits: LPVOID, lpbmi: ^BITMAPINFO, usage: UINT) -> INT --- + SetDIBits :: proc(hdc: HDC, hbm: HBITMAP, start: UINT, cLines: UINT, lpBits: VOID, lpbmi: ^BITMAPINFO, ColorUse: UINT) -> INT --- + SetDIBColorTable :: proc(hdc: HDC, iStart: UINT, cEntries: UINT, prgbq: ^RGBQUAD) -> UINT --- + GetDIBColorTable :: proc(hdc: HDC, iStart: UINT, cEntries: UINT, prgbq: ^RGBQUAD) -> UINT --- + + CreatePen :: proc(iStyle, cWidth: INT, color: COLORREF) -> HPEN --- + ExtCreatePen :: proc(iPenStyle, cWidth: DWORD, plbrush: ^LOGBRUSH, cStyle: DWORD, pstyle: ^DWORD) -> HPEN --- + SetDCPenColor :: proc(hdc: HDC, color: COLORREF) -> COLORREF --- + GetDCPenColor :: proc(hdc: HDC) -> COLORREF --- + + CreatePalette :: proc(plpal: ^LOGPALETTE) -> HPALETTE --- + SelectPalette :: proc(hdc: HDC, hPal: HPALETTE, bForceBkgd: BOOL) -> HPALETTE --- + RealizePalette :: proc(hdc: HDC) -> UINT --- + + SetTextColor :: proc(hdc: HDC, color: COLORREF) -> COLORREF --- + RoundRect :: proc(hdc: HDC, left: INT, top: INT, right: INT, bottom: INT, width: INT, height: INT) -> BOOL --- + SetPixel :: proc(hdc: HDC, x: INT, y: INT, color: COLORREF) -> COLORREF --- + + GdiTransparentBlt :: proc(hdcDest: HDC, xoriginDest, yoriginDest, wDest, hDest: INT, hdcSrc: HDC, xoriginSrc, yoriginSrc, wSrc, hSrc: INT, crTransparent: UINT) -> BOOL --- + GdiGradientFill :: proc(hdc: HDC, pVertex: PTRIVERTEX, nVertex: ULONG, pMesh: PVOID, nCount: ULONG, ulMode: ULONG) -> BOOL --- + GdiAlphaBlend :: proc(hdcDest: HDC, xoriginDest, yoriginDest, wDest, hDest: INT, hdcSrc: HDC, xoriginSrc, yoriginSrc, wSrc, hSrc: INT, ftn: BLENDFUNCTION) -> BOOL --- } -RGB :: #force_inline proc "contextless" (r, g, b: u8) -> COLORREF { - return transmute(COLORREF)[4]u8{r, g, b, 0} +RGB :: #force_inline proc "contextless" (#any_int r, g, b: int) -> COLORREF { + return COLORREF(DWORD(BYTE(r)) | (DWORD(BYTE(g)) << 8) | (DWORD(BYTE(b)) << 16)) } + +PALETTERGB :: #force_inline proc "contextless" (#any_int r, g, b: int) -> COLORREF { + return 0x02000000 | RGB(r, g, b) +} + +PALETTEINDEX :: #force_inline proc "contextless" (#any_int i: int) -> COLORREF { + return COLORREF(DWORD(0x01000000) | DWORD(WORD(i))) +} + +FXPT2DOT30 :: distinct fixed.Fixed(i32, 30) + +CIEXYZ :: struct { + ciexyzX, ciexyzY, ciexyzZ: FXPT2DOT30, +} + +CIEXYZTRIPLE :: struct { + ciexyzRed, ciexyzGreen, ciexyzBlue: CIEXYZ, +} + +// https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapv5header +BITMAPV5HEADER :: struct { + bV5Size: DWORD, + bV5Width: LONG, + bV5Height: LONG, + bV5Planes: WORD, + bV5BitCount: WORD, + bV5Compression: DWORD, + bV5SizeImage: DWORD, + bV5XPelsPerMeter: LONG, + bV5YPelsPerMeter: LONG, + bV5ClrUsed: DWORD, + bV5ClrImportant: DWORD, + bV5RedMask: DWORD, + bV5GreenMask: DWORD, + bV5BlueMask: DWORD, + bV5AlphaMask: DWORD, + bV5CSType: DWORD, + bV5Endpoints: CIEXYZTRIPLE, + bV5GammaRed: DWORD, + bV5GammaGreen: DWORD, + bV5GammaBlue: DWORD, + bV5Intent: DWORD, + bV5ProfileData: DWORD, + bV5ProfileSize: DWORD, + bV5Reserved: DWORD, +} + +PALETTEENTRY :: struct { + peRed, peGreen, peBlue, peFlags: BYTE, +} + +LOGPALETTE :: struct { + palVersion: WORD, + palNumEntries: WORD, + palPalEntry: []PALETTEENTRY, +} + +BKMODE :: enum { + TRANSPARENT = 1, + OPAQUE = 2, +} + +ICONINFOEXW :: struct { + cbSize: DWORD, + fIcon: BOOL, + xHotspot, yHotspot: DWORD, + hbmMask, hbmColor: HBITMAP, + wResID: WORD, + szModName: [MAX_PATH]WCHAR, + szResName: [MAX_PATH]WCHAR, +} +PICONINFOEXW :: ^ICONINFOEXW + +AC_SRC_OVER :: 0x00 +AC_SRC_ALPHA :: 0x01 + +TransparentBlt :: GdiTransparentBlt +GradientFill :: GdiGradientFill +AlphaBlend :: GdiAlphaBlend + +COLOR16 :: USHORT +TRIVERTEX :: struct { + x, y: LONG, + Red, Green, Blue, Alpha: COLOR16, +} +PTRIVERTEX :: ^TRIVERTEX + +GRADIENT_TRIANGLE :: struct { + Vertex1, Vertex2, Vertex3: ULONG, +} +PGRADIENT_TRIANGLE :: ^GRADIENT_TRIANGLE + +GRADIENT_RECT :: struct { + UpperLeft, LowerRight: ULONG, +} +PGRADIENT_RECT :: ^GRADIENT_RECT + +BLENDFUNCTION :: struct { + BlendOp, BlendFlags, SourceConstantAlpha, AlphaFormat: BYTE, +} + +GRADIENT_FILL_RECT_H : ULONG : 0x00000000 +GRADIENT_FILL_RECT_V : ULONG : 0x00000001 +GRADIENT_FILL_TRIANGLE : ULONG : 0x00000002 +GRADIENT_FILL_OP_FLAG : ULONG : 0x000000ff + +/* Brush Styles */ +BS_SOLID :: 0 +BS_NULL :: 1 +BS_HOLLOW :: BS_NULL +BS_HATCHED :: 2 +BS_PATTERN :: 3 +BS_INDEXED :: 4 +BS_DIBPATTERN :: 5 +BS_DIBPATTERNPT :: 6 +BS_PATTERN8X8 :: 7 +BS_DIBPATTERN8X8 :: 8 +BS_MONOPATTERN :: 9 + +/* Hatch Styles */ +HS_HORIZONTAL :: 0 /* ----- */ +HS_VERTICAL :: 1 /* ||||| */ +HS_FDIAGONAL :: 2 /* \\\\\ */ +HS_BDIAGONAL :: 3 /* ///// */ +HS_CROSS :: 4 /* +++++ */ +HS_DIAGCROSS :: 5 /* xxxxx */ +HS_API_MAX :: 12 + +/* Pen Styles */ +PS_SOLID :: 0 +PS_DASH :: 1 /* ------- */ +PS_DOT :: 2 /* ....... */ +PS_DASHDOT :: 3 /* _._._._ */ +PS_DASHDOTDOT :: 4 /* _.._.._ */ +PS_NULL :: 5 +PS_INSIDEFRAME :: 6 +PS_USERSTYLE :: 7 +PS_ALTERNATE :: 8 +PS_STYLE_MASK :: 0x0000000F +PS_ENDCAP_ROUND :: 0x00000000 +PS_ENDCAP_SQUARE :: 0x00000100 +PS_ENDCAP_FLAT :: 0x00000200 +PS_ENDCAP_MASK :: 0x00000F00 +PS_JOIN_ROUND :: 0x00000000 +PS_JOIN_BEVEL :: 0x00001000 +PS_JOIN_MITER :: 0x00002000 +PS_JOIN_MASK :: 0x0000F000 +PS_COSMETIC :: 0x00000000 +PS_GEOMETRIC :: 0x00010000 +PS_TYPE_MASK :: 0x000F0000 + +LOGBRUSH :: struct { + lbStyle: UINT, + lbColor: COLORREF, + lbHatch: ULONG_PTR, +} +PLOGBRUSH :: ^LOGBRUSH + +/* CombineRgn() Styles */ +RGN_AND :: 1 +RGN_OR :: 2 +RGN_XOR :: 3 +RGN_DIFF :: 4 +RGN_COPY :: 5 + +/* StretchBlt() Modes */ +// BLACKONWHITE :: 1 +// WHITEONBLACK :: 2 +// COLORONCOLOR :: 3 +// HALFTONE :: 4 + +/* PolyFill() Modes */ +ALTERNATE :: 1 +WINDING :: 2 + +/* Layout Orientation Options */ +LAYOUT_RTL :: 0x00000001 // Right to left +LAYOUT_BTT :: 0x00000002 // Bottom to top +LAYOUT_VBH :: 0x00000004 // Vertical before horizontal +LAYOUT_ORIENTATIONMASK :: (LAYOUT_RTL | LAYOUT_BTT | LAYOUT_VBH) + +/* Text Alignment Options */ +TA_NOUPDATECP :: 0 +TA_UPDATECP :: 1 + +TA_LEFT :: 0 +TA_RIGHT :: 2 +TA_CENTER :: 6 + +TA_TOP :: 0 +TA_BOTTOM :: 8 +TA_BASELINE :: 24 +TA_RTLREADING :: 256 +TA_MASK :: (TA_BASELINE+TA_CENTER+TA_UPDATECP+TA_RTLREADING) + +MM_MAX_NUMAXES :: 16 +DESIGNVECTOR :: struct { + dvReserved: DWORD, + dvNumAxes: DWORD, + dvValues: [MM_MAX_NUMAXES]LONG, +} + +LF_FACESIZE :: 32 +LF_FULLFACESIZE :: 64 + +LOGFONTW :: struct { + lfHeight: LONG, + lfWidth: LONG, + lfEscapement: LONG, + lfOrientation: LONG, + lfWeight: LONG, + lfItalic: BYTE, + lfUnderline: BYTE, + lfStrikeOut: BYTE, + lfCharSet: BYTE, + lfOutPrecision: BYTE, + lfClipPrecision: BYTE, + lfQuality: BYTE, + lfPitchAndFamily: BYTE, + lfFaceName: [LF_FACESIZE]WCHAR, +} +LPLOGFONTW :: ^LOGFONTW + +ENUMLOGFONTW :: struct { + elfLogFont: LOGFONTW, + elfFullName: [LF_FULLFACESIZE]WCHAR, + elfStyle: [LF_FACESIZE]WCHAR, +} +LPENUMLOGFONTW :: ^ENUMLOGFONTW + +ENUMLOGFONTEXW :: struct { + elfLogFont: LOGFONTW, + elfFullName: [LF_FULLFACESIZE]WCHAR, + elfStyle: [LF_FACESIZE]WCHAR, + elfScript: [LF_FACESIZE]WCHAR, +} + +ENUMLOGFONTEXDVW :: struct { + elfEnumLogfontEx: ENUMLOGFONTEXW, + elfDesignVector: DESIGNVECTOR, +} + +NEWTEXTMETRICW :: struct { + tmHeight: LONG, + tmAscent: LONG, + tmDescent: LONG, + tmInternalLeading: LONG, + tmExternalLeading: LONG, + tmAveCharWidth: LONG, + tmMaxCharWidth: LONG, + tmWeight: LONG, + tmOverhang: LONG, + tmDigitizedAspectX: LONG, + tmDigitizedAspectY: LONG, + tmFirstChar: WCHAR, + tmLastChar: WCHAR, + tmDefaultChar: WCHAR, + tmBreakChar: WCHAR, + tmItalic: BYTE, + tmUnderlined: BYTE, + tmStruckOut: BYTE, + tmPitchAndFamily: BYTE, + tmCharSet: BYTE, + ntmFlags: DWORD, + ntmSizeEM: UINT, + ntmCellHeight: UINT, + ntmAvgWidth: UINT, +} + +FONTENUMPROCW :: #type proc(lpelf: ^ENUMLOGFONTW, lpntm: ^NEWTEXTMETRICW, FontType: DWORD, lParam: LPARAM) -> INT diff --git a/core/sys/windows/hidpi.odin b/core/sys/windows/hidpi.odin new file mode 100644 index 000000000..5e9787527 --- /dev/null +++ b/core/sys/windows/hidpi.odin @@ -0,0 +1,213 @@ +#+build windows +package sys_windows +import "core:c" + +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, + UsagePage: USAGE, + InputReportByteLength: USHORT, + OutputReportByteLength: USHORT, + FeatureReportByteLength: USHORT, + Reserved: [17]USHORT, + NumberLinkCollectionNodes: USHORT, + NumberInputButtonCaps: USHORT, + NumberInputValueCaps: USHORT, + NumberInputDataIndices: USHORT, + NumberOutputButtonCaps: USHORT, + NumberOutputValueCaps: USHORT, + NumberOutputDataIndices: USHORT, + NumberFeatureButtonCaps: USHORT, + NumberFeatureValueCaps: USHORT, + NumberFeatureDataIndices: USHORT, +} +PHIDP_CAPS :: ^HIDP_CAPS + +HIDP_BUTTON_CAPS :: struct { + UsagePage: USAGE, + ReportID: UCHAR, + IsAlias: BOOLEAN, + BitField: USHORT, + LinkCollection: USHORT, + LinkUsage: USAGE, + LinkUsagePage: USAGE, + IsRange: BOOLEAN, + IsStringRange: BOOLEAN, + IsDesignatorRange: BOOLEAN, + IsAbsolute: BOOLEAN, + ReportCount: USHORT, + Reserved2: USHORT, + Reserved: [9]ULONG, + using _: struct #raw_union { + Range: struct { + UsageMin: USAGE, + UsageMax: USAGE, + StringMin: USHORT, + StringMax: USHORT, + DesignatorMin: USHORT, + DesignatorMax: USHORT, + DataIndexMin: USHORT, + DataIndexMax: USHORT, + }, + NotRange: struct { + Usage: USAGE, + Reserved1: USAGE, + StringIndex: USHORT, + Reserved2: USHORT, + DesignatorIndex: USHORT, + Reserved3: USHORT, + DataIndex: USHORT, + Reserved4: USHORT, + }, + }, +} +PHIDP_BUTTON_CAPS :: ^HIDP_BUTTON_CAPS + +HIDP_VALUE_CAPS :: struct { + UsagePage: USAGE, + ReportID: UCHAR, + IsAlias: BOOLEAN, + BitField: USHORT, + LinkCollection: USHORT, + LinkUsage: USAGE, + LinkUsagePage: USAGE, + IsRange: BOOLEAN, + IsStringRange: BOOLEAN, + IsDesignatorRange: BOOLEAN, + IsAbsolute: BOOLEAN, + HasNull: BOOLEAN, + Reserved: UCHAR, + BitSize: USHORT, + ReportCount: USHORT, + Reserved2: [5]USHORT, + UnitsExp: ULONG, + Units: ULONG, + LogicalMin: LONG, + LogicalMax: LONG, + PhysicalMin: LONG, + PhysicalMax: LONG, + using _: struct #raw_union { + Range: struct { + UsageMin: USAGE, + UsageMax: USAGE, + StringMin: USHORT, + StringMax: USHORT, + DesignatorMin: USHORT, + DesignatorMax: USHORT, + DataIndexMin: USHORT, + DataIndexMax: USHORT, + }, + NotRange: struct { + Usage: USAGE, + Reserved1: USAGE, + StringIndex: USHORT, + Reserved2: USHORT, + DesignatorIndex: USHORT, + Reserved3: USHORT, + DataIndex: USHORT, + Reserved4: USHORT, + }, + }, +} +PHIDP_VALUE_CAPS :: ^HIDP_VALUE_CAPS + +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, + Output, + Feature, +} + +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") +foreign hid { + HidP_GetCaps :: proc(PreparsedData: PHIDP_PREPARSED_DATA, Capabilities: PHIDP_CAPS) -> NTSTATUS --- + HidP_GetButtonCaps :: proc(ReportType: HIDP_REPORT_TYPE, ButtonCaps: PHIDP_BUTTON_CAPS, ButtonCapsLength: PUSHORT, PreparsedData: PHIDP_PREPARSED_DATA) -> NTSTATUS --- + 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..eb2a85f2e --- /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/icu.odin b/core/sys/windows/icu.odin new file mode 100644 index 000000000..6ed8c9b40 --- /dev/null +++ b/core/sys/windows/icu.odin @@ -0,0 +1,14 @@ +#+build windows +package sys_windows + +foreign import "system:icu.lib" + +UError :: enum i32 { + U_ZERO_ERROR = 0, +} + +@(default_calling_convention="system") +foreign icu { + ucal_getWindowsTimeZoneID :: proc(id: wstring, len: i32, winid: wstring, winidCapacity: i32, status: ^UError) -> i32 --- + ucal_getDefaultTimeZone :: proc(result: wstring, cap: i32, status: ^UError) -> i32 --- +} diff --git a/core/sys/windows/ip_helper.odin b/core/sys/windows/ip_helper.odin index d8f93f533..7a6e545ac 100644 --- a/core/sys/windows/ip_helper.odin +++ b/core/sys/windows/ip_helper.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import "system:iphlpapi.lib" @@ -217,7 +217,7 @@ NL_DAD_STATE :: enum i32 { IpDadStatePreferred = 4, } -@(default_calling_convention = "std") +@(default_calling_convention = "system") foreign iphlpapi { /* The GetAdaptersAddresses function retrieves the addresses associated with the adapters on the local computer. diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index 0c612a974..f1d7202da 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows foreign import kernel32 "system:Kernel32.lib" @@ -20,8 +20,17 @@ COMMON_LVB_GRID_RVERTICAL :: WORD(0x1000) COMMON_LVB_REVERSE_VIDEO :: WORD(0x4000) COMMON_LVB_UNDERSCORE :: WORD(0x8000) COMMON_LVB_SBCSDBCS :: WORD(0x0300) +EV_BREAK :: DWORD(0x0040) +EV_CTS :: DWORD(0x0008) +EV_DSR :: DWORD(0x0010) +EV_ERR :: DWORD(0x0080) +EV_RING :: DWORD(0x0100) +EV_RLSD :: DWORD(0x0020) +EV_RXCHAR :: DWORD(0x0001) +EV_RXFLAG :: DWORD(0x0002) +EV_TXEMPTY :: DWORD(0x0004) -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign kernel32 { OutputDebugStringA :: proc(lpOutputString: LPCSTR) --- // The only A thing that is allowed OutputDebugStringW :: proc(lpOutputString: LPCWSTR) --- @@ -38,17 +47,32 @@ foreign kernel32 { lpNumberOfCharsWritten: LPDWORD, lpReserved: LPVOID) -> BOOL --- - GetConsoleMode :: proc(hConsoleHandle: HANDLE, - lpMode: LPDWORD) -> BOOL --- - SetConsoleMode :: proc(hConsoleHandle: HANDLE, - dwMode: DWORD) -> BOOL --- - SetConsoleCursorPosition :: proc(hConsoleHandle: HANDLE, - dwCursorPosition: COORD) -> BOOL --- - SetConsoleTextAttribute :: proc(hConsoleOutput: HANDLE, - wAttributes: WORD) -> BOOL --- - SetConsoleOutputCP :: proc(wCodePageID: UINT) -> 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 --- + SetConsoleTextAttribute :: proc(hConsoleOutput: HANDLE, wAttributes: WORD) -> BOOL --- + GetConsoleCP :: proc() -> CODEPAGE --- + SetConsoleCP :: proc(wCodePageID: CODEPAGE) -> BOOL --- + GetConsoleOutputCP :: proc() -> CODEPAGE --- + SetConsoleOutputCP :: proc(wCodePageID: CODEPAGE) -> BOOL --- + FlushConsoleInputBuffer :: proc(hConsoleInput: HANDLE) -> BOOL --- + GetFileInformationByHandle :: proc(hFile: HANDLE, lpFileInformation: LPBY_HANDLE_FILE_INFORMATION) -> BOOL --- + + SetHandleInformation :: proc(hObject: HANDLE, dwMask: DWORD, dwFlags: DWORD) -> BOOL --- @@ -64,11 +88,16 @@ 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, lpSecurityAttributes: LPSECURITY_ATTRIBUTES) -> BOOL --- + CreateSymbolicLinkW :: proc(lpSymlinkFileName: LPCWSTR, + lpTargetFileName: LPCWSTR, + dwFlags: DWORD) -> BOOLEAN --- + GetFileInformationByHandleEx :: proc(hFile: HANDLE, fileInfoClass: FILE_INFO_BY_HANDLE_CLASS, lpFileInformation: LPVOID, @@ -84,6 +113,14 @@ 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 --- + SetCommMask :: proc(handle: HANDLE, dwEvtMap: DWORD) -> BOOL --- + GetCommMask :: proc(handle: HANDLE, lpEvtMask: LPDWORD) -> BOOL --- + WaitCommEvent :: proc(handle: HANDLE, lpEvtMask: LPDWORD, lpOverlapped: LPOVERLAPPED) -> BOOL --- GetCommandLineW :: proc() -> LPCWSTR --- GetTempPathW :: proc(nBufferLength: DWORD, lpBuffer: LPCWSTR) -> DWORD --- GetCurrentProcess :: proc() -> HANDLE --- @@ -112,7 +149,7 @@ foreign kernel32 { CreateThread :: proc( lpThreadAttributes: LPSECURITY_ATTRIBUTES, dwStackSize: SIZE_T, - lpStartAddress: proc "stdcall" (rawptr) -> DWORD, + lpStartAddress: proc "system" (rawptr) -> DWORD, lpParameter: LPVOID, dwCreationFlags: DWORD, lpThreadId: LPDWORD, @@ -121,7 +158,7 @@ foreign kernel32 { hProcess: HANDLE, lpThreadAttributes: LPSECURITY_ATTRIBUTES, dwStackSize: SIZE_T, - lpStartAddress: proc "stdcall" (rawptr) -> DWORD, + lpStartAddress: proc "system" (rawptr) -> DWORD, lpParameter: LPVOID, dwCreationFlags: DWORD, lpThreadId: LPDWORD, @@ -130,6 +167,7 @@ foreign kernel32 { ResumeThread :: proc(thread: HANDLE) -> DWORD --- GetThreadPriority :: proc(thread: HANDLE) -> c_int --- SetThreadPriority :: proc(thread: HANDLE, priority: c_int) -> BOOL --- + SetThreadDescription :: proc(hThread: HANDLE, lpThreadDescription: PCWSTR) -> HRESULT --- GetExitCodeThread :: proc(thread: HANDLE, exit_code: ^DWORD) -> BOOL --- TerminateThread :: proc(thread: HANDLE, exit_code: DWORD) -> BOOL --- SuspendThread :: proc(hThread: HANDLE) -> DWORD --- @@ -172,6 +210,7 @@ foreign kernel32 { TolerableDelay: ULONG, ) -> BOOL --- WaitForSingleObject :: proc(hHandle: HANDLE, dwMilliseconds: DWORD) -> DWORD --- + WaitForSingleObjectEx :: proc(hHandle: HANDLE, dwMilliseconds: DWORD, bAlterable: BOOL) -> DWORD --- Sleep :: proc(dwMilliseconds: DWORD) --- GetProcessId :: proc(handle: HANDLE) -> DWORD --- CopyFileW :: proc( @@ -205,6 +244,16 @@ 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 --- + SetThreadContext :: proc( + hThread: HANDLE, + lpContext: LPCONTEXT, + ) -> BOOL --- CreateProcessW :: proc( lpApplicationName: LPCWSTR, lpCommandLine: LPWSTR, @@ -222,6 +271,7 @@ foreign kernel32 { SetEnvironmentVariableW :: proc(n: LPCWSTR, v: LPCWSTR) -> BOOL --- GetEnvironmentStringsW :: proc() -> LPWCH --- FreeEnvironmentStringsW :: proc(env_ptr: LPWCH) -> BOOL --- + ExpandEnvironmentStringsW :: proc(lpSrc: LPCWSTR, lpDst: LPWSTR, nSize: DWORD) -> DWORD --- GetModuleFileNameW :: proc(hModule: HMODULE, lpFilename: LPWSTR, nSize: DWORD) -> DWORD --- CreateDirectoryW :: proc( lpPathName: LPCWSTR, @@ -290,6 +340,14 @@ foreign kernel32 { hTemplateFile: HANDLE, ) -> HANDLE --- + GetFileTime :: proc( + hFile: HANDLE, + lpCreationTime: LPFILETIME, + lpLastAccessTime: LPFILETIME, + lpLastWriteTime: LPFILETIME, + ) -> BOOL --- + CompareFileTime :: proc(lpFileTime1: LPFILETIME, lpFileTime2: LPFILETIME) -> LONG --- + FindFirstFileW :: proc(fileName: LPCWSTR, findFileData: LPWIN32_FIND_DATAW) -> HANDLE --- FindNextFileW :: proc(findFile: HANDLE, findFileData: LPWIN32_FIND_DATAW) -> BOOL --- FindClose :: proc(findFile: HANDLE) -> BOOL --- @@ -314,12 +372,20 @@ foreign kernel32 { lpName: LPCWSTR, ) -> HANDLE --- ResetEvent :: proc(hEvent: HANDLE) -> BOOL --- + SetEvent :: proc(hEvent: HANDLE) -> BOOL --- WaitForMultipleObjects :: proc( nCount: DWORD, lpHandles: ^HANDLE, bWaitAll: BOOL, dwMilliseconds: DWORD, ) -> DWORD --- + WaitForMultipleObjectsEx :: proc( + nCount: DWORD, + lpHandles: ^HANDLE, + bWaitAll: BOOL, + dwMilliseconds: DWORD, + bAlterable: BOOL, + ) -> DWORD --- CreateNamedPipeW :: proc( lpName: LPCWSTR, dwOpenMode: DWORD, @@ -330,6 +396,14 @@ foreign kernel32 { nDefaultTimeOut: DWORD, lpSecurityAttributes: LPSECURITY_ATTRIBUTES, ) -> HANDLE --- + PeekNamedPipe :: proc( + hNamedPipe: HANDLE, + lpBuffer: rawptr, + nBufferSize: u32, + lpBytesRead: ^u32, + lpTotalBytesAvail: ^u32, + lpBytesLeftThisMessage: ^u32, + ) -> BOOL --- CancelIo :: proc(handle: HANDLE) -> BOOL --- GetOverlappedResult :: proc( hFile: HANDLE, @@ -346,6 +420,12 @@ foreign kernel32 { LocalReAlloc :: proc(mem: LPVOID, bytes: SIZE_T, flags: UINT) -> LPVOID --- LocalFree :: proc(mem: LPVOID) -> LPVOID --- + GlobalAlloc :: proc(flags: UINT, bytes: SIZE_T) -> LPVOID --- + GlobalReAlloc :: proc(mem: LPVOID, bytes: SIZE_T, flags: UINT) -> LPVOID --- + GlobalFree :: proc(mem: LPVOID) -> LPVOID --- + + GlobalLock :: proc(hMem: HGLOBAL) -> LPVOID --- + GlobalUnlock :: proc(hMem: HGLOBAL) -> BOOL --- ReadDirectoryChangesW :: proc( hDirectory: HANDLE, @@ -387,11 +467,34 @@ foreign kernel32 { GetFileAttributesExW :: proc(lpFileName: LPCWSTR, fInfoLevelId: GET_FILEEX_INFO_LEVELS, lpFileInformation: LPVOID) -> BOOL --- GetSystemInfo :: proc(system_info: ^SYSTEM_INFO) --- GetVersionExW :: proc(osvi: ^OSVERSIONINFOEXW) --- - + GetSystemDirectoryW :: proc(lpBuffer: LPWSTR, uSize: UINT) -> UINT --- + GetWindowsDirectoryW :: proc(lpBuffer: LPWSTR, uSize: UINT) -> UINT --- + GetSystemDefaultLangID :: proc() -> LANGID --- + GetSystemDefaultLCID :: proc() -> LCID --- + GetSystemDefaultLocaleName :: proc(lpLocaleName: LPWSTR, cchLocaleName: INT) -> INT --- + LCIDToLocaleName :: proc(Locale: LCID, lpName: LPWSTR, cchName: INT, dwFlags: DWORD) -> INT --- + LocaleNameToLCID :: proc(lpName: LPCWSTR, dwFlags: DWORD) -> LCID --- + SetDllDirectoryW :: proc(lpPathName: LPCWSTR) -> BOOL --- + AddDllDirectory :: proc(NewDirectory: PCWSTR) -> rawptr --- + RemoveDllDirectory :: proc(Cookie: rawptr) -> BOOL --- LoadLibraryW :: proc(c_str: LPCWSTR) -> HMODULE --- + LoadLibraryExW :: proc(c_str: LPCWSTR, hFile: HANDLE, dwFlags: LoadLibraryEx_Flags) -> HMODULE --- FreeLibrary :: proc(h: HMODULE) -> BOOL --- GetProcAddress :: proc(h: HMODULE, c_str: LPCSTR) -> rawptr --- + LoadResource :: proc(hModule: HMODULE, hResInfo: HRSRC) -> HGLOBAL --- + FreeResource :: proc(hResData: HGLOBAL) -> BOOL --- + LockResource :: proc(hResData: HGLOBAL) -> LPVOID --- + SizeofResource :: proc(hModule: HMODULE, hResInfo: HRSRC) -> DWORD --- + FindResourceW :: proc(hModule: HMODULE, lpName: LPCWSTR, lpType: LPCWSTR) -> HRSRC --- + FindResourceExW :: proc(hModule: HMODULE, lpType: LPCWSTR, lpName: LPCWSTR, wLanguage: LANGID) -> HRSRC --- + EnumResourceNamesW :: proc(hModule: HMODULE, lpType: LPCWSTR, lpEnumFunc: ENUMRESNAMEPROCW, lParam: LONG_PTR) -> BOOL --- + EnumResourceNamesExW :: proc(hModule: HMODULE, lpType: LPCWSTR, lpEnumFunc: ENUMRESNAMEPROCW, lParam: LONG_PTR, dwFlags: DWORD, LangId: LANGID) -> BOOL --- + EnumResourceTypesExW :: proc(hModule: HMODULE, lpEnumFunc: ENUMRESTYPEPROCW, lParam: LONG_PTR, dwFlags: DWORD, LangId: LANGID) -> BOOL --- + EnumResourceLanguagesExW :: proc(hModule: HMODULE, lpType: LPCWSTR, lpName: LPCWSTR, lpEnumFunc: ENUMRESLANGPROCW, lParam: LONG_PTR, dwFlags: DWORD, LangId: LANGID) -> BOOL --- + LookupIconIdFromDirectory :: proc(presbits: PBYTE, fIcon: BOOL) -> INT --- + LookupIconIdFromDirectoryEx :: proc(presbits: PBYTE, fIcon: BOOL, cxDesired: INT, cyDesired: INT, Flags: UINT) -> INT --- + CreateIconFromResourceEx :: proc(presbits: PBYTE, dwResSize: DWORD, fIcon: BOOL, dwVer: DWORD, cxDesired: INT, cyDesired: INT, Flags: UINT) -> HICON --- GetFullPathNameW :: proc(filename: LPCWSTR, buffer_length: DWORD, buffer: LPCWSTR, file_part: ^LPCWSTR) -> DWORD --- GetLongPathNameW :: proc(short, long: LPCWSTR, len: DWORD) -> DWORD --- @@ -408,13 +511,15 @@ 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 --- GetConsoleWindow :: proc() -> HWND --- GetConsoleScreenBufferInfo :: proc(hConsoleOutput: HANDLE, lpConsoleScreenBufferInfo: PCONSOLE_SCREEN_BUFFER_INFO) -> BOOL --- SetConsoleScreenBufferSize :: proc(hConsoleOutput: HANDLE, dwSize: COORD) -> BOOL --- - SetConsoleWindowInfo :: proc(hConsoleOutput: HANDLE, bAbsolute : BOOL, lpConsoleWindow: ^SMALL_RECT) -> BOOL --- + SetConsoleWindowInfo :: proc(hConsoleOutput: HANDLE, bAbsolute: BOOL, lpConsoleWindow: ^SMALL_RECT) -> BOOL --- GetConsoleCursorInfo :: proc(hConsoleOutput: HANDLE, lpConsoleCursorInfo: PCONSOLE_CURSOR_INFO) -> BOOL --- SetConsoleCursorInfo :: proc(hConsoleOutput: HANDLE, lpConsoleCursorInfo: PCONSOLE_CURSOR_INFO) -> BOOL --- @@ -430,15 +535,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 @@ -489,6 +598,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 @@ -525,6 +673,10 @@ FILE_MAP_RESERVE :: DWORD(0x80000000) FILE_MAP_TARGETS_INVALID :: DWORD(0x40000000) FILE_MAP_LARGE_PAGES :: DWORD(0x20000000) +// Flags for `SetFileCompletionNotificationModes`. +FILE_SKIP_COMPLETION_PORT_ON_SUCCESS :: 0x1 +FILE_SKIP_SET_EVENT_ON_HANDLE :: 0x2 + PAGE_NOACCESS :: 0x01 PAGE_READONLY :: 0x02 PAGE_READWRITE :: 0x04 @@ -562,7 +714,7 @@ MEM_TOP_DOWN :: 0x100000 MEM_LARGE_PAGES :: 0x20000000 MEM_4MB_PAGES :: 0x80000000 -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign kernel32 { VirtualAlloc :: proc( lpAddress: LPVOID, @@ -705,7 +857,7 @@ LowMemoryResourceNotification :: MEMORY_RESOURCE_NOTIFICATION_TYPE.LowMemoryRes HighMemoryResourceNotification :: MEMORY_RESOURCE_NOTIFICATION_TYPE.HighMemoryResourceNotification -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign kernel32 { CreateMemoryResourceNotification :: proc( NotificationType: MEMORY_RESOURCE_NOTIFICATION_TYPE, @@ -721,7 +873,7 @@ FILE_CACHE_MAX_HARD_DISABLE :: DWORD(0x00000002) FILE_CACHE_MIN_HARD_ENABLE :: DWORD(0x00000004) FILE_CACHE_MIN_HARD_DISABLE :: DWORD(0x00000008) -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign kernel32 { GetSystemFileCacheSize :: proc( lpMinimumFileCacheSize: PSIZE_T, @@ -751,7 +903,7 @@ WIN32_MEMORY_RANGE_ENTRY :: struct { PWIN32_MEMORY_RANGE_ENTRY :: ^WIN32_MEMORY_RANGE_ENTRY -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign kernel32 { PrefetchVirtualMemory :: proc( hProcess: HANDLE, @@ -809,23 +961,23 @@ foreign kernel32 { MEHC_PATROL_SCRUBBER_PRESENT :: ULONG(0x1) -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign kernel32 { GetMemoryErrorHandlingCapabilities :: proc( Capabilities: PULONG, ) -> BOOL --- } -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign kernel32 { GlobalMemoryStatusEx :: proc( lpBuffer: ^MEMORYSTATUSEX, ) -> BOOL --- } -PBAD_MEMORY_CALLBACK_ROUTINE :: #type proc "stdcall" () +PBAD_MEMORY_CALLBACK_ROUTINE :: #type proc "system" () -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign kernel32 { RegisterBadMemoryNotification :: proc( Callback: PBAD_MEMORY_CALLBACK_ROUTINE, @@ -846,7 +998,7 @@ VmOfferPriorityLow :: OFFER_PRIORITY.VmOfferPriorityLow VmOfferPriorityBelowNormal :: OFFER_PRIORITY.VmOfferPriorityBelowNormal VmOfferPriorityNormal :: OFFER_PRIORITY.VmOfferPriorityNormal -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign kernel32 { OfferVirtualMemory :: proc( VirtualAddress: PVOID, @@ -911,7 +1063,7 @@ WIN32_MEMORY_REGION_INFORMATION_u_s_Bitfield :: distinct ULONG Reserved : 32-6, }*/ -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign one_core { QueryVirtualMemoryInformation :: proc( Process: HANDLE, @@ -931,12 +1083,17 @@ foreign one_core { PageProtection: ULONG, PreferredNode: ULONG, ) -> PVOID --- + GetCommPorts :: proc( + lpPortNumbers: PULONG, + uPortNumbersCount: ULONG, + puPortNumbersFound: PULONG, + ) -> ULONG --- } NUMA_NO_PREFERRED_NODE :: 0xffffffff -MapViewOfFile2 :: #force_inline proc "stdcall" ( +MapViewOfFile2 :: #force_inline proc "system" ( FileMappingHandle: HANDLE, ProcessHandle: HANDLE, Offset: ULONG64, @@ -957,7 +1114,7 @@ MapViewOfFile2 :: #force_inline proc "stdcall" ( ) } -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign kernel32 { UnmapViewOfFile2 :: proc( ProcessHandle: HANDLE, @@ -966,7 +1123,7 @@ foreign kernel32 { ) -> BOOL --- } -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign kernel32 { GetProductInfo :: proc( OSMajorVersion: DWORD, @@ -977,157 +1134,15 @@ foreign kernel32 { ) -> BOOL --- } -HandlerRoutine :: proc "stdcall" (dwCtrlType: DWORD) -> BOOL +HandlerRoutine :: proc "system" (dwCtrlType: DWORD) -> BOOL PHANDLER_ROUTINE :: HandlerRoutine +// 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) - -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="stdcall") -foreign kernel32 { - GetCommState :: proc(handle: HANDLE, dcb: ^DCB) -> BOOL --- - SetCommState :: proc(handle: HANDLE, dcb: ^DCB) -> BOOL --- -} - - -LPFIBER_START_ROUTINE :: #type proc "stdcall" (lpFiberParameter: LPVOID) - -@(default_calling_convention = "stdcall") +@(default_calling_convention = "system") foreign kernel32 { CreateFiber :: proc(dwStackSize: SIZE_T, lpStartAddress: LPFIBER_START_ROUTINE, lpParameter: LPVOID) -> LPVOID --- DeleteFiber :: proc(lpFiber: LPVOID) --- @@ -1180,3 +1195,48 @@ SYSTEM_LOGICAL_PROCESSOR_INFORMATION :: struct { Relationship: LOGICAL_PROCESSOR_RELATIONSHIP, 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 +GMEM_NOCOMPACT :: 0x0010 +GMEM_NODISCARD :: 0x0020 +GMEM_ZEROINIT :: 0x0040 +GMEM_MODIFY :: 0x0080 +GMEM_DISCARDABLE :: 0x0100 +GMEM_NOT_BANKED :: 0x1000 +GMEM_SHARE :: 0x2000 +GMEM_DDESHARE :: 0x2000 +GMEM_NOTIFY :: 0x4000 +GMEM_LOWER :: GMEM_NOT_BANKED +GMEM_VALID_FLAGS :: 0x7F72 +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/key_codes.odin b/core/sys/windows/key_codes.odin index 284b0e437..0991ca4b3 100644 --- a/core/sys/windows/key_codes.odin +++ b/core/sys/windows/key_codes.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows // https://docs.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input diff --git a/core/sys/windows/known_folders.odin b/core/sys/windows/known_folders.odin new file mode 100644 index 000000000..cbaf5eeeb --- /dev/null +++ b/core/sys/windows/known_folders.odin @@ -0,0 +1,145 @@ +#+build windows +package sys_windows + +FOLDERID_NetworkFolder :: GUID {0xD20BEEC4, 0x5CA8, 0x4905, {0xAE, 0x3B, 0xBF, 0x25, 0x1E, 0xA0, 0x9B, 0x53}} +FOLDERID_ComputerFolder :: GUID {0x0AC0837C, 0xBBF8, 0x452A, {0x85, 0x0D, 0x79, 0xD0, 0x8E, 0x66, 0x7C, 0xA7}} +FOLDERID_InternetFolder :: GUID {0x4D9F7874, 0x4E0C, 0x4904, {0x96, 0x7B, 0x40, 0xB0, 0xD2, 0x0C, 0x3E, 0x4B}} +FOLDERID_ControlPanelFolder :: GUID {0x82A74AEB, 0xAEB4, 0x465C, {0xA0, 0x14, 0xD0, 0x97, 0xEE, 0x34, 0x6D, 0x63}} +FOLDERID_PrintersFolder :: GUID {0x76FC4E2D, 0xD6AD, 0x4519, {0xA6, 0x63, 0x37, 0xBD, 0x56, 0x06, 0x81, 0x85}} +FOLDERID_SyncManagerFolder :: GUID {0x43668BF8, 0xC14E, 0x49B2, {0x97, 0xC9, 0x74, 0x77, 0x84, 0xD7, 0x84, 0xB7}} +FOLDERID_SyncSetupFolder :: GUID {0xf214138 , 0xb1d3, 0x4a90, {0xbb, 0xa9, 0x27, 0xcb, 0xc0, 0xc5, 0x38, 0x9a}} +FOLDERID_ConflictFolder :: GUID {0x4bfefb45, 0x347d, 0x4006, {0xa5, 0xbe, 0xac, 0x0c, 0xb0, 0x56, 0x71, 0x92}} +FOLDERID_SyncResultsFolder :: GUID {0x289a9a43, 0xbe44, 0x4057, {0xa4, 0x1b, 0x58, 0x7a, 0x76, 0xd7, 0xe7, 0xf9}} +FOLDERID_RecycleBinFolder :: GUID {0xB7534046, 0x3ECB, 0x4C18, {0xBE, 0x4E, 0x64, 0xCD, 0x4C, 0xB7, 0xD6, 0xAC}} +FOLDERID_ConnectionsFolder :: GUID {0x6F0CD92B, 0x2E97, 0x45D1, {0x88, 0xFF, 0xB0, 0xD1, 0x86, 0xB8, 0xDE, 0xDD}} +FOLDERID_Fonts :: GUID {0xFD228CB7, 0xAE11, 0x4AE3, {0x86, 0x4C, 0x16, 0xF3, 0x91, 0x0A, 0xB8, 0xFE}} +FOLDERID_Desktop :: GUID {0xB4BFCC3A, 0xDB2C, 0x424C, {0xB0, 0x29, 0x7F, 0xE9, 0x9A, 0x87, 0xC6, 0x41}} +FOLDERID_Startup :: GUID {0xB97D20BB, 0xF46A, 0x4C97, {0xBA, 0x10, 0x5E, 0x36, 0x08, 0x43, 0x08, 0x54}} +FOLDERID_Programs :: GUID {0xA77F5D77, 0x2E2B, 0x44C3, {0xA6, 0xA2, 0xAB, 0xA6, 0x01, 0x05, 0x4A, 0x51}} +FOLDERID_StartMenu :: GUID {0x625B53C3, 0xAB48, 0x4EC1, {0xBA, 0x1F, 0xA1, 0xEF, 0x41, 0x46, 0xFC, 0x19}} +FOLDERID_Recent :: GUID {0xAE50C081, 0xEBD2, 0x438A, {0x86, 0x55, 0x8A, 0x09, 0x2E, 0x34, 0x98, 0x7A}} +FOLDERID_SendTo :: GUID {0x8983036C, 0x27C0, 0x404B, {0x8F, 0x08, 0x10, 0x2D, 0x10, 0xDC, 0xFD, 0x74}} +FOLDERID_Documents :: GUID {0xFDD39AD0, 0x238F, 0x46AF, {0xAD, 0xB4, 0x6C, 0x85, 0x48, 0x03, 0x69, 0xC7}} +FOLDERID_Favorites :: GUID {0x1777F761, 0x68AD, 0x4D8A, {0x87, 0xBD, 0x30, 0xB7, 0x59, 0xFA, 0x33, 0xDD}} +FOLDERID_NetHood :: GUID {0xC5ABBF53, 0xE17F, 0x4121, {0x89, 0x00, 0x86, 0x62, 0x6F, 0xC2, 0xC9, 0x73}} +FOLDERID_PrintHood :: GUID {0x9274BD8D, 0xCFD1, 0x41C3, {0xB3, 0x5E, 0xB1, 0x3F, 0x55, 0xA7, 0x58, 0xF4}} +FOLDERID_Templates :: GUID {0xA63293E8, 0x664E, 0x48DB, {0xA0, 0x79, 0xDF, 0x75, 0x9E, 0x05, 0x09, 0xF7}} +FOLDERID_CommonStartup :: GUID {0x82A5EA35, 0xD9CD, 0x47C5, {0x96, 0x29, 0xE1, 0x5D, 0x2F, 0x71, 0x4E, 0x6E}} +FOLDERID_CommonPrograms :: GUID {0x0139D44E, 0x6AFE, 0x49F2, {0x86, 0x90, 0x3D, 0xAF, 0xCA, 0xE6, 0xFF, 0xB8}} +FOLDERID_CommonStartMenu :: GUID {0xA4115719, 0xD62E, 0x491D, {0xAA, 0x7C, 0xE7, 0x4B, 0x8B, 0xE3, 0xB0, 0x67}} +FOLDERID_PublicDesktop :: GUID {0xC4AA340D, 0xF20F, 0x4863, {0xAF, 0xEF, 0xF8, 0x7E, 0xF2, 0xE6, 0xBA, 0x25}} +FOLDERID_ProgramData :: GUID {0x62AB5D82, 0xFDC1, 0x4DC3, {0xA9, 0xDD, 0x07, 0x0D, 0x1D, 0x49, 0x5D, 0x97}} +FOLDERID_CommonTemplates :: GUID {0xB94237E7, 0x57AC, 0x4347, {0x91, 0x51, 0xB0, 0x8C, 0x6C, 0x32, 0xD1, 0xF7}} +FOLDERID_PublicDocuments :: GUID {0xED4824AF, 0xDCE4, 0x45A8, {0x81, 0xE2, 0xFC, 0x79, 0x65, 0x08, 0x36, 0x34}} +FOLDERID_RoamingAppData :: GUID {0x3EB685DB, 0x65F9, 0x4CF6, {0xA0, 0x3A, 0xE3, 0xEF, 0x65, 0x72, 0x9F, 0x3D}} +FOLDERID_LocalAppData :: GUID {0xF1B32785, 0x6FBA, 0x4FCF, {0x9D, 0x55, 0x7B, 0x8E, 0x7F, 0x15, 0x70, 0x91}} +FOLDERID_LocalAppDataLow :: GUID {0xA520A1A4, 0x1780, 0x4FF6, {0xBD, 0x18, 0x16, 0x73, 0x43, 0xC5, 0xAF, 0x16}} +FOLDERID_InternetCache :: GUID {0x352481E8, 0x33BE, 0x4251, {0xBA, 0x85, 0x60, 0x07, 0xCA, 0xED, 0xCF, 0x9D}} +FOLDERID_Cookies :: GUID {0x2B0F765D, 0xC0E9, 0x4171, {0x90, 0x8E, 0x08, 0xA6, 0x11, 0xB8, 0x4F, 0xF6}} +FOLDERID_History :: GUID {0xD9DC8A3B, 0xB784, 0x432E, {0xA7, 0x81, 0x5A, 0x11, 0x30, 0xA7, 0x59, 0x63}} +FOLDERID_System :: GUID {0x1AC14E77, 0x02E7, 0x4E5D, {0xB7, 0x44, 0x2E, 0xB1, 0xAE, 0x51, 0x98, 0xB7}} +FOLDERID_SystemX86 :: GUID {0xD65231B0, 0xB2F1, 0x4857, {0xA4, 0xCE, 0xA8, 0xE7, 0xC6, 0xEA, 0x7D, 0x27}} +FOLDERID_Windows :: GUID {0xF38BF404, 0x1D43, 0x42F2, {0x93, 0x05, 0x67, 0xDE, 0x0B, 0x28, 0xFC, 0x23}} +FOLDERID_Profile :: GUID {0x5E6C858F, 0x0E22, 0x4760, {0x9A, 0xFE, 0xEA, 0x33, 0x17, 0xB6, 0x71, 0x73}} +FOLDERID_Pictures :: GUID {0x33E28130, 0x4E1E, 0x4676, {0x83, 0x5A, 0x98, 0x39, 0x5C, 0x3B, 0xC3, 0xBB}} +FOLDERID_ProgramFilesX86 :: GUID {0x7C5A40EF, 0xA0FB, 0x4BFC, {0x87, 0x4A, 0xC0, 0xF2, 0xE0, 0xB9, 0xFA, 0x8E}} +FOLDERID_ProgramFilesCommonX86 :: GUID {0xDE974D24, 0xD9C6, 0x4D3E, {0xBF, 0x91, 0xF4, 0x45, 0x51, 0x20, 0xB9, 0x17}} +FOLDERID_ProgramFilesX64 :: GUID {0x6d809377, 0x6af0, 0x444b, {0x89, 0x57, 0xa3, 0x77, 0x3f, 0x02, 0x20, 0x0e}} +FOLDERID_ProgramFilesCommonX64 :: GUID {0x6365d5a7, 0xf0d , 0x45e5, {0x87, 0xf6, 0xd, 0xa5, 0x6b, 0x6a, 0x4f, 0x7d }} +FOLDERID_ProgramFiles :: GUID {0x905e63b6, 0xc1bf, 0x494e, {0xb2, 0x9c, 0x65, 0xb7, 0x32, 0xd3, 0xd2, 0x1a}} +FOLDERID_ProgramFilesCommon :: GUID {0xF7F1ED05, 0x9F6D, 0x47A2, {0xAA, 0xAE, 0x29, 0xD3, 0x17, 0xC6, 0xF0, 0x66}} +FOLDERID_UserProgramFiles :: GUID {0x5cd7aee2, 0x2219, 0x4a67, {0xb8, 0x5d, 0x6c, 0x9c, 0xe1, 0x56, 0x60, 0xcb}} +FOLDERID_UserProgramFilesCommon :: GUID {0xbcbd3057, 0xca5c, 0x4622, {0xb4, 0x2d, 0xbc, 0x56, 0xdb, 0x0a, 0xe5, 0x16}} +FOLDERID_AdminTools :: GUID {0x724EF170, 0xA42D, 0x4FEF, {0x9F, 0x26, 0xB6, 0x0E, 0x84, 0x6F, 0xBA, 0x4F}} +FOLDERID_CommonAdminTools :: GUID {0xD0384E7D, 0xBAC3, 0x4797, {0x8F, 0x14, 0xCB, 0xA2, 0x29, 0xB3, 0x92, 0xB5}} +FOLDERID_Music :: GUID {0x4BD8D571, 0x6D19, 0x48D3, {0xBE, 0x97, 0x42, 0x22, 0x20, 0x08, 0x0E, 0x43}} +FOLDERID_Videos :: GUID {0x18989B1D, 0x99B5, 0x455B, {0x84, 0x1C, 0xAB, 0x7C, 0x74, 0xE4, 0xDD, 0xFC}} +FOLDERID_Ringtones :: GUID {0xC870044B, 0xF49E, 0x4126, {0xA9, 0xC3, 0xB5, 0x2A, 0x1F, 0xF4, 0x11, 0xE8}} +FOLDERID_PublicPictures :: GUID {0xB6EBFB86, 0x6907, 0x413C, {0x9A, 0xF7, 0x4F, 0xC2, 0xAB, 0xF0, 0x7C, 0xC5}} +FOLDERID_PublicMusic :: GUID {0x3214FAB5, 0x9757, 0x4298, {0xBB, 0x61, 0x92, 0xA9, 0xDE, 0xAA, 0x44, 0xFF}} +FOLDERID_PublicVideos :: GUID {0x2400183A, 0x6185, 0x49FB, {0xA2, 0xD8, 0x4A, 0x39, 0x2A, 0x60, 0x2B, 0xA3}} +FOLDERID_PublicRingtones :: GUID {0xE555AB60, 0x153B, 0x4D17, {0x9F, 0x04, 0xA5, 0xFE, 0x99, 0xFC, 0x15, 0xEC}} +FOLDERID_ResourceDir :: GUID {0x8AD10C31, 0x2ADB, 0x4296, {0xA8, 0xF7, 0xE4, 0x70, 0x12, 0x32, 0xC9, 0x72}} +FOLDERID_LocalizedResourcesDir :: GUID {0x2A00375E, 0x224C, 0x49DE, {0xB8, 0xD1, 0x44, 0x0D, 0xF7, 0xEF, 0x3D, 0xDC}} +FOLDERID_CommonOEMLinks :: GUID {0xC1BAE2D0, 0x10DF, 0x4334, {0xBE, 0xDD, 0x7A, 0xA2, 0x0B, 0x22, 0x7A, 0x9D}} +FOLDERID_CDBurning :: GUID {0x9E52AB10, 0xF80D, 0x49DF, {0xAC, 0xB8, 0x43, 0x30, 0xF5, 0x68, 0x78, 0x55}} +FOLDERID_UserProfiles :: GUID {0x0762D272, 0xC50A, 0x4BB0, {0xA3, 0x82, 0x69, 0x7D, 0xCD, 0x72, 0x9B, 0x80}} +FOLDERID_Playlists :: GUID {0xDE92C1C7, 0x837F, 0x4F69, {0xA3, 0xBB, 0x86, 0xE6, 0x31, 0x20, 0x4A, 0x23}} +FOLDERID_SamplePlaylists :: GUID {0x15CA69B3, 0x30EE, 0x49C1, {0xAC, 0xE1, 0x6B, 0x5E, 0xC3, 0x72, 0xAF, 0xB5}} +FOLDERID_SampleMusic :: GUID {0xB250C668, 0xF57D, 0x4EE1, {0xA6, 0x3C, 0x29, 0x0E, 0xE7, 0xD1, 0xAA, 0x1F}} +FOLDERID_SamplePictures :: GUID {0xC4900540, 0x2379, 0x4C75, {0x84, 0x4B, 0x64, 0xE6, 0xFA, 0xF8, 0x71, 0x6B}} +FOLDERID_SampleVideos :: GUID {0x859EAD94, 0x2E85, 0x48AD, {0xA7, 0x1A, 0x09, 0x69, 0xCB, 0x56, 0xA6, 0xCD}} +FOLDERID_PhotoAlbums :: GUID {0x69D2CF90, 0xFC33, 0x4FB7, {0x9A, 0x0C, 0xEB, 0xB0, 0xF0, 0xFC, 0xB4, 0x3C}} +FOLDERID_Public :: GUID {0xDFDF76A2, 0xC82A, 0x4D63, {0x90, 0x6A, 0x56, 0x44, 0xAC, 0x45, 0x73, 0x85}} +FOLDERID_ChangeRemovePrograms :: GUID {0xdf7266ac, 0x9274, 0x4867, {0x8d, 0x55, 0x3b, 0xd6, 0x61, 0xde, 0x87, 0x2d}} +FOLDERID_AppUpdates :: GUID {0xa305ce99, 0xf527, 0x492b, {0x8b, 0x1a, 0x7e, 0x76, 0xfa, 0x98, 0xd6, 0xe4}} +FOLDERID_AddNewPrograms :: GUID {0xde61d971, 0x5ebc, 0x4f02, {0xa3, 0xa9, 0x6c, 0x82, 0x89, 0x5e, 0x5c, 0x04}} +FOLDERID_Downloads :: GUID {0x374de290, 0x123f, 0x4565, {0x91, 0x64, 0x39, 0xc4, 0x92, 0x5e, 0x46, 0x7b}} +FOLDERID_PublicDownloads :: GUID {0x3d644c9b, 0x1fb8, 0x4f30, {0x9b, 0x45, 0xf6, 0x70, 0x23, 0x5f, 0x79, 0xc0}} +FOLDERID_SavedSearches :: GUID {0x7d1d3a04, 0xdebb, 0x4115, {0x95, 0xcf, 0x2f, 0x29, 0xda, 0x29, 0x20, 0xda}} +FOLDERID_QuickLaunch :: GUID {0x52a4f021, 0x7b75, 0x48a9, {0x9f, 0x6b, 0x4b, 0x87, 0xa2, 0x10, 0xbc, 0x8f}} +FOLDERID_Contacts :: GUID {0x56784854, 0xc6cb, 0x462b, {0x81, 0x69, 0x88, 0xe3, 0x50, 0xac, 0xb8, 0x82}} +FOLDERID_SidebarParts :: GUID {0xa75d362e, 0x50fc, 0x4fb7, {0xac, 0x2c, 0xa8, 0xbe, 0xaa, 0x31, 0x44, 0x93}} +FOLDERID_SidebarDefaultParts :: GUID {0x7b396e54, 0x9ec5, 0x4300, {0xbe, 0xa , 0x24, 0x82, 0xeb, 0xae, 0x1a, 0x26}} +FOLDERID_PublicGameTasks :: GUID {0xdebf2536, 0xe1a8, 0x4c59, {0xb6, 0xa2, 0x41, 0x45, 0x86, 0x47, 0x6a, 0xea}} +FOLDERID_GameTasks :: GUID {0x54fae61 , 0x4dd8, 0x4787, {0x80, 0xb6, 0x9 , 0x2 , 0x20, 0xc4, 0xb7, 0x0 }} +FOLDERID_SavedGames :: GUID {0x4c5c32ff, 0xbb9d, 0x43b0, {0xb5, 0xb4, 0x2d, 0x72, 0xe5, 0x4e, 0xaa, 0xa4}} +FOLDERID_Games :: GUID {0xcac52c1a, 0xb53d, 0x4edc, {0x92, 0xd7, 0x6b, 0x2e, 0x8a, 0xc1, 0x94, 0x34}} +FOLDERID_SEARCH_MAPI :: GUID {0x98ec0e18, 0x2098, 0x4d44, {0x86, 0x44, 0x66, 0x97, 0x93, 0x15, 0xa2, 0x81}} +FOLDERID_SEARCH_CSC :: GUID {0xee32e446, 0x31ca, 0x4aba, {0x81, 0x4f, 0xa5, 0xeb, 0xd2, 0xfd, 0x6d, 0x5e}} +FOLDERID_Links :: GUID {0xbfb9d5e0, 0xc6a9, 0x404c, {0xb2, 0xb2, 0xae, 0x6d, 0xb6, 0xaf, 0x49, 0x68}} +FOLDERID_UsersFiles :: GUID {0xf3ce0f7c, 0x4901, 0x4acc, {0x86, 0x48, 0xd5, 0xd4, 0x4b, 0x04, 0xef, 0x8f}} +FOLDERID_UsersLibraries :: GUID {0xa302545d, 0xdeff, 0x464b, {0xab, 0xe8, 0x61, 0xc8, 0x64, 0x8d, 0x93, 0x9b}} +FOLDERID_SearchHome :: GUID {0x190337d1, 0xb8ca, 0x4121, {0xa6, 0x39, 0x6d, 0x47, 0x2d, 0x16, 0x97, 0x2a}} +FOLDERID_OriginalImages :: GUID {0x2C36C0AA, 0x5812, 0x4b87, {0xbf, 0xd0, 0x4c, 0xd0, 0xdf, 0xb1, 0x9b, 0x39}} +FOLDERID_DocumentsLibrary :: GUID {0x7b0db17d, 0x9cd2, 0x4a93, {0x97, 0x33, 0x46, 0xcc, 0x89, 0x02, 0x2e, 0x7c}} +FOLDERID_MusicLibrary :: GUID {0x2112ab0a, 0xc86a, 0x4ffe, {0xa3, 0x68, 0xd , 0xe9, 0x6e, 0x47, 0x1 , 0x2e}} +FOLDERID_PicturesLibrary :: GUID {0xa990ae9f, 0xa03b, 0x4e80, {0x94, 0xbc, 0x99, 0x12, 0xd7, 0x50, 0x41, 0x4 }} +FOLDERID_VideosLibrary :: GUID {0x491e922f, 0x5643, 0x4af4, {0xa7, 0xeb, 0x4e, 0x7a, 0x13, 0x8d, 0x81, 0x74}} +FOLDERID_RecordedTVLibrary :: GUID {0x1a6fdba2, 0xf42d, 0x4358, {0xa7, 0x98, 0xb7, 0x4d, 0x74, 0x59, 0x26, 0xc5}} +FOLDERID_HomeGroup :: GUID {0x52528a6b, 0xb9e3, 0x4add, {0xb6, 0xd , 0x58, 0x8c, 0x2d, 0xba, 0x84, 0x2d}} +FOLDERID_HomeGroupCurrentUser :: GUID {0x9b74b6a3, 0xdfd , 0x4f11, {0x9e, 0x78, 0x5f, 0x78, 0x0 , 0xf2, 0xe7, 0x72}} +FOLDERID_DeviceMetadataStore :: GUID {0x5ce4a5e9, 0xe4eb, 0x479d, {0xb8, 0x9f, 0x13, 0x0c, 0x02, 0x88, 0x61, 0x55}} +FOLDERID_Libraries :: GUID {0x1b3ea5dc, 0xb587, 0x4786, {0xb4, 0xef, 0xbd, 0x1d, 0xc3, 0x32, 0xae, 0xae}} +FOLDERID_PublicLibraries :: GUID {0x48daf80b, 0xe6cf, 0x4f4e, {0xb8, 0x00, 0x0e, 0x69, 0xd8, 0x4e, 0xe3, 0x84}} +FOLDERID_UserPinned :: GUID {0x9e3995ab, 0x1f9c, 0x4f13, {0xb8, 0x27, 0x48, 0xb2, 0x4b, 0x6c, 0x71, 0x74}} +FOLDERID_ImplicitAppShortcuts :: GUID {0xbcb5256f, 0x79f6, 0x4cee, {0xb7, 0x25, 0xdc, 0x34, 0xe4, 0x2 , 0xfd, 0x46}} +FOLDERID_AccountPictures :: GUID {0x008ca0b1, 0x55b4, 0x4c56, {0xb8, 0xa8, 0x4d, 0xe4, 0xb2, 0x99, 0xd3, 0xbe}} +FOLDERID_PublicUserTiles :: GUID {0x0482af6c, 0x08f1, 0x4c34, {0x8c, 0x90, 0xe1, 0x7e, 0xc9, 0x8b, 0x1e, 0x17}} +FOLDERID_AppsFolder :: GUID {0x1e87508d, 0x89c2, 0x42f0, {0x8a, 0x7e, 0x64, 0x5a, 0x0f, 0x50, 0xca, 0x58}} +FOLDERID_StartMenuAllPrograms :: GUID {0xf26305ef, 0x6948, 0x40b9, {0xb2, 0x55, 0x81, 0x45, 0x3d, 0x9 , 0xc7, 0x85}} +FOLDERID_CommonStartMenuPlaces :: GUID {0xa440879f, 0x87a0, 0x4f7d, {0xb7, 0x0 , 0x2 , 0x7 , 0xb9, 0x66, 0x19, 0x4a}} +FOLDERID_ApplicationShortcuts :: GUID {0xa3918781, 0xe5f2, 0x4890, {0xb3, 0xd9, 0xa7, 0xe5, 0x43, 0x32, 0x32, 0x8c}} +FOLDERID_RoamingTiles :: GUID {0xbcfc5a , 0xed94, 0x4e48, {0x96, 0xa1, 0x3f, 0x62, 0x17, 0xf2, 0x19, 0x90}} +FOLDERID_RoamedTileImages :: GUID {0xaaa8d5a5, 0xf1d6, 0x4259, {0xba, 0xa8, 0x78, 0xe7, 0xef, 0x60, 0x83, 0x5e}} +FOLDERID_Screenshots :: GUID {0xb7bede81, 0xdf94, 0x4682, {0xa7, 0xd8, 0x57, 0xa5, 0x26, 0x20, 0xb8, 0x6f}} +FOLDERID_CameraRoll :: GUID {0xab5fb87b, 0x7ce2, 0x4f83, {0x91, 0x5d, 0x55, 0x8 , 0x46, 0xc9, 0x53, 0x7b}} +FOLDERID_SkyDrive :: GUID {0xa52bba46, 0xe9e1, 0x435f, {0xb3, 0xd9, 0x28, 0xda, 0xa6, 0x48, 0xc0, 0xf6}} +FOLDERID_OneDrive :: GUID {0xa52bba46, 0xe9e1, 0x435f, {0xb3, 0xd9, 0x28, 0xda, 0xa6, 0x48, 0xc0, 0xf6}} +FOLDERID_SkyDriveDocuments :: GUID {0x24d89e24, 0x2f19, 0x4534, {0x9d, 0xde, 0x6a, 0x66, 0x71, 0xfb, 0xb8, 0xfe}} +FOLDERID_SkyDrivePictures :: GUID {0x339719b5, 0x8c47, 0x4894, {0x94, 0xc2, 0xd8, 0xf7, 0x7a, 0xdd, 0x44, 0xa6}} +FOLDERID_SkyDriveMusic :: GUID {0xc3f2459e, 0x80d6, 0x45dc, {0xbf, 0xef, 0x1f, 0x76, 0x9f, 0x2b, 0xe7, 0x30}} +FOLDERID_SkyDriveCameraRoll :: GUID {0x767e6811, 0x49cb, 0x4273, {0x87, 0xc2, 0x20, 0xf3, 0x55, 0xe1, 0x08, 0x5b}} +FOLDERID_SearchHistory :: GUID {0x0d4c3db6, 0x03a3, 0x462f, {0xa0, 0xe6, 0x08, 0x92, 0x4c, 0x41, 0xb5, 0xd4}} +FOLDERID_SearchTemplates :: GUID {0x7e636bfe, 0xdfa9, 0x4d5e, {0xb4, 0x56, 0xd7, 0xb3, 0x98, 0x51, 0xd8, 0xa9}} +FOLDERID_CameraRollLibrary :: GUID {0x2b20df75, 0x1eda, 0x4039, {0x80, 0x97, 0x38, 0x79, 0x82, 0x27, 0xd5, 0xb7}} +FOLDERID_SavedPictures :: GUID {0x3b193882, 0xd3ad, 0x4eab, {0x96, 0x5a, 0x69, 0x82, 0x9d, 0x1f, 0xb5, 0x9f}} +FOLDERID_SavedPicturesLibrary :: GUID {0xe25b5812, 0xbe88, 0x4bd9, {0x94, 0xb0, 0x29, 0x23, 0x34, 0x77, 0xb6, 0xc3}} +FOLDERID_RetailDemo :: GUID {0x12d4c69e, 0x24ad, 0x4923, {0xbe, 0x19, 0x31, 0x32, 0x1c, 0x43, 0xa7, 0x67}} +FOLDERID_Device :: GUID {0x1C2AC1DC, 0x4358, 0x4B6C, {0x97, 0x33, 0xAF, 0x21, 0x15, 0x65, 0x76, 0xF0}} +FOLDERID_DevelopmentFiles :: GUID {0xdbe8e08e, 0x3053, 0x4bbc, {0xb1, 0x83, 0x2a, 0x7b, 0x2b, 0x19, 0x1e, 0x59}} +FOLDERID_Objects3D :: GUID {0x31c0dd25, 0x9439, 0x4f12, {0xbf, 0x41, 0x7f, 0xf4, 0xed, 0xa3, 0x87, 0x22}} +FOLDERID_AppCaptures :: GUID {0xedc0fe71, 0x98d8, 0x4f4a, {0xb9, 0x20, 0xc8, 0xdc, 0x13, 0x3c, 0xb1, 0x65}} +FOLDERID_LocalDocuments :: GUID {0xf42ee2d3, 0x909f, 0x4907, {0x88, 0x71, 0x4c, 0x22, 0xfc, 0x0b, 0xf7, 0x56}} +FOLDERID_LocalPictures :: GUID {0x0ddd015d, 0xb06c, 0x45d5, {0x8c, 0x4c, 0xf5, 0x97, 0x13, 0x85, 0x46, 0x39}} +FOLDERID_LocalVideos :: GUID {0x35286a68, 0x3c57, 0x41a1, {0xbb, 0xb1, 0x0e, 0xae, 0x73, 0xd7, 0x6c, 0x95}} +FOLDERID_LocalMusic :: GUID {0xa0c69a99, 0x21c8, 0x4671, {0x87, 0x03, 0x79, 0x34, 0x16, 0x2f, 0xcf, 0x1d}} +FOLDERID_LocalDownloads :: GUID {0x7d83ee9b, 0x2244, 0x4e70, {0xb1, 0xf5, 0x53, 0x93, 0x04, 0x2a, 0xf1, 0xe4}} +FOLDERID_RecordedCalls :: GUID {0x2f8b40c2, 0x83ed, 0x48ee, {0xb3, 0x83, 0xa1, 0xf1, 0x57, 0xec, 0x6f, 0x9a}} +FOLDERID_AllAppMods :: GUID {0x7ad67899, 0x66af, 0x43ba, {0x91, 0x56, 0x6a, 0xad, 0x42, 0xe6, 0xc5, 0x96}} +FOLDERID_CurrentAppMods :: GUID {0x3db40b20, 0x2a30, 0x4dbe, {0x91, 0x7e, 0x77, 0x1d, 0xd2, 0x1d, 0xd0, 0x99}} +FOLDERID_AppDataDesktop :: GUID {0xb2c5e279, 0x7add, 0x439f, {0xb2, 0x8c, 0xc4, 0x1f, 0xe1, 0xbb, 0xf6, 0x72}} +FOLDERID_AppDataDocuments :: GUID {0x7be16610, 0x1f7f, 0x44ac, {0xbf, 0xf0, 0x83, 0xe1, 0x5f, 0x2f, 0xfc, 0xa1}} +FOLDERID_AppDataFavorites :: GUID {0x7cfbefbc, 0xde1f, 0x45aa, {0xb8, 0x43, 0xa5, 0x42, 0xac, 0x53, 0x6c, 0xc9}} +FOLDERID_AppDataProgramData :: GUID {0x559d40a3, 0xa036, 0x40fa, {0xaf, 0x61, 0x84, 0xcb, 0x43, 0xa , 0x4d, 0x34}} +FOLDERID_LocalStorage :: GUID {0xB3EB08D3, 0xA1F3, 0x496B, {0x86, 0x5A, 0x42, 0xB5, 0x36, 0xCD, 0xA0, 0xEC}} diff --git a/core/sys/windows/netapi32.odin b/core/sys/windows/netapi32.odin index 0df277181..9442193ca 100644 --- a/core/sys/windows/netapi32.odin +++ b/core/sys/windows/netapi32.odin @@ -1,9 +1,9 @@ -// +build windows +#+build windows package sys_windows foreign import netapi32 "system:Netapi32.lib" -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign netapi32 { NetUserAdd :: proc( servername: wstring, diff --git a/core/sys/windows/ntdll.odin b/core/sys/windows/ntdll.odin index dda5b9711..747130749 100644 --- a/core/sys/windows/ntdll.odin +++ b/core/sys/windows/ntdll.odin @@ -1,9 +1,259 @@ -// +build windows +#+build windows package sys_windows foreign import ntdll_lib "system:ntdll.lib" -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign ntdll_lib { RtlGetVersion :: proc(lpVersionInformation: ^OSVERSIONINFOEXW) -> NTSTATUS --- + + + NtQueryInformationProcess :: proc( + ProcessHandle: HANDLE, + ProcessInformationClass: PROCESS_INFO_CLASS, + ProcessInformation: rawptr, + ProcessInformationLength: u32, + ReturnLength: ^u32, + ) -> u32 --- + + NtQueryInformationFile :: proc( + FileHandle: HANDLE, + IoStatusBlock: PIO_STATUS_BLOCK, + FileInformation: rawptr, + Length: ULONG, + FileInformationClass: FILE_INFORMATION_CLASS, + ) -> NTSTATUS --- + + NtQueryDirectoryFileEx :: proc( + FileHandle: HANDLE, + Event: HANDLE, + ApcRoutine: PIO_APC_ROUTINE, + ApcContext: PVOID, + IoStatusBlock: PIO_STATUS_BLOCK, + FileInformation: PVOID, + Length: ULONG, + FileInformationClass: FILE_INFORMATION_CLASS, + QueryFlags: ULONG, + FileName : PUNICODE_STRING, + ) -> NTSTATUS --- +} + + +PIO_APC_ROUTINE :: #type proc "system" (ApcContext: rawptr, IoStatusBlock: PIO_STATUS_BLOCK, Reserved: ULONG) + +PIO_STATUS_BLOCK :: ^IO_STATUS_BLOCK +IO_STATUS_BLOCK :: struct { + using _: struct #raw_union { + Status: NTSTATUS, + Pointer: rawptr, + }, + Information: ULONG_PTR, +} + + +PROCESS_INFO_CLASS :: enum c_int { + ProcessBasicInformation = 0, + ProcessDebugPort = 7, + ProcessWow64Information = 26, + ProcessImageFileName = 27, + ProcessBreakOnTermination = 29, + ProcessTelemetryIdInformation = 64, + ProcessSubsystemInformation = 75, +} + +SL_RESTART_SCAN :: 0x00000001 // The scan will start at the first entry in the directory. If this flag is not set, the scan will resume from where the last query ended. +SL_RETURN_SINGLE_ENTRY :: 0x00000002 // Normally the return buffer is packed with as many matching directory entries that fit. If this flag is set, the file system will return only one directory entry at a time. This does make the operation less efficient. +SL_INDEX_SPECIFIED :: 0x00000004 // The scan should start at a specified indexed position in the directory. This flag can only be set if you generate your own IRP_MJ_DIRECTORY_CONTROL IRP; the index is specified in the IRP. How the position is specified varies from file system to file system. +SL_RETURN_ON_DISK_ENTRIES_ONLY :: 0x00000008 // Any file system filters that perform directory virtualization or just-in-time expansion should simply pass the request through to the file system and return entries that are currently on disk. Not all file systems support this flag. +SL_NO_CURSOR_UPDATE_QUERY :: 0x00000010 // File systems maintain per-FileObject directory cursor information. When multiple threads do queries using the same FileObject, access to the per-FileObject structure is single threaded to prevent corruption of the cursor state. This flag tells the file system to not update per-FileObject cursor state information thus allowing multiple threads to query in parallel using the same handle. It behaves as if SL_RESTART_SCAN is specified on each call. If a wild card pattern is given on the next call, the operation will not pick up where the last query ended. This allows for true asynchronous directory query support. If this flag is used inside a TxF transaction the operation will be failed. Not all file systems support this flag. + + +PFILE_INFORMATION_CLASS :: ^FILE_INFORMATION_CLASS +FILE_INFORMATION_CLASS :: enum c_int { + FileDirectoryInformation = 1, + FileFullDirectoryInformation = 2, + FileBothDirectoryInformation = 3, + FileBasicInformation = 4, + FileStandardInformation = 5, + FileInternalInformation = 6, + FileEaInformation = 7, + FileAccessInformation = 8, + FileNameInformation = 9, + FileRenameInformation = 10, + FileLinkInformation = 11, + FileNamesInformation = 12, + FileDispositionInformation = 13, + FilePositionInformation = 14, + FileFullEaInformation = 15, + FileModeInformation = 16, + FileAlignmentInformation = 17, + FileAllInformation = 18, + FileAllocationInformation = 19, + FileEndOfFileInformation = 20, + FileAlternateNameInformation = 21, + FileStreamInformation = 22, + FilePipeInformation = 23, + FilePipeLocalInformation = 24, + FilePipeRemoteInformation = 25, + FileMailslotQueryInformation = 26, + FileMailslotSetInformation = 27, + FileCompressionInformation = 28, + FileObjectIdInformation = 29, + FileCompletionInformation = 30, + FileMoveClusterInformation = 31, + FileQuotaInformation = 32, + FileReparsePointInformation = 33, + FileNetworkOpenInformation = 34, + FileAttributeTagInformation = 35, + FileTrackingInformation = 36, + FileIdBothDirectoryInformation = 37, + FileIdFullDirectoryInformation = 38, + FileValidDataLengthInformation = 39, + FileShortNameInformation = 40, + FileIoCompletionNotificationInformation = 41, + FileIoStatusBlockRangeInformation = 42, + FileIoPriorityHintInformation = 43, + FileSfioReserveInformation = 44, + FileSfioVolumeInformation = 45, + FileHardLinkInformation = 46, + FileProcessIdsUsingFileInformation = 47, + FileNormalizedNameInformation = 48, + FileNetworkPhysicalNameInformation = 49, + FileIdGlobalTxDirectoryInformation = 50, + FileIsRemoteDeviceInformation = 51, + FileUnusedInformation = 52, + FileNumaNodeInformation = 53, + FileStandardLinkInformation = 54, + FileRemoteProtocolInformation = 55, + FileRenameInformationBypassAccessCheck = 56, + FileLinkInformationBypassAccessCheck = 57, + FileVolumeNameInformation = 58, + FileIdInformation = 59, + FileIdExtdDirectoryInformation = 60, + FileReplaceCompletionInformation = 61, + FileHardLinkFullIdInformation = 62, + FileIdExtdBothDirectoryInformation = 63, + FileDispositionInformationEx = 64, + FileRenameInformationEx = 65, + FileRenameInformationExBypassAccessCheck = 66, + FileDesiredStorageClassInformation = 67, + FileStatInformation = 68, + FileMemoryPartitionInformation = 69, + FileStatLxInformation = 70, + FileCaseSensitiveInformation = 71, + FileLinkInformationEx = 72, + FileLinkInformationExBypassAccessCheck = 73, + FileStorageReserveIdInformation = 74, + FileCaseSensitiveInformationForceAccessCheck = 75, + FileKnownFolderInformation = 76, + FileStatBasicInformation = 77, + FileId64ExtdDirectoryInformation = 78, + FileId64ExtdBothDirectoryInformation = 79, + FileIdAllExtdDirectoryInformation = 80, + FileIdAllExtdBothDirectoryInformation = 81, + FileStreamReservationInformation, + FileMupProviderInfo, + FileMaximumInformation, +} + +PFILE_ID_FULL_DIR_INFORMATION :: ^FILE_ID_FULL_DIR_INFORMATION +FILE_ID_FULL_DIR_INFORMATION :: struct { + NextEntryOffset: ULONG, + FileIndex: ULONG, + CreationTime: LARGE_INTEGER, + LastAccessTime: LARGE_INTEGER, + LastWriteTime: LARGE_INTEGER, + ChangeTime: LARGE_INTEGER, + EndOfFile: LARGE_INTEGER, + AllocationSize: LARGE_INTEGER, + FileAttributes: ULONG, + FileNameLength: ULONG, + EaSize: ULONG, + FileId: LARGE_INTEGER, + FileName: [1]WCHAR, +} + + +PROCESS_BASIC_INFORMATION :: struct { + ExitStatus: NTSTATUS, + PebBaseAddress: ^PEB, + AffinityMask: ULONG_PTR, + BasePriority: KPRIORITY, + UniqueProcessId: ULONG_PTR, + InheritedFromUniqueProcessId: ULONG_PTR, +} + +KPRIORITY :: rawptr + +PPS_POST_PROCESS_INIT_ROUTINE :: proc "system" () + + +PEB :: struct { + _: [2]u8, + BeingDebugged: u8, + _: [1]u8, + _: [2]rawptr, + Ldr: ^PEB_LDR_DATA, + ProcessParameters: ^RTL_USER_PROCESS_PARAMETERS, + _: [104]u8, + _: [52]rawptr, + PostProcessInitRoutine: PPS_POST_PROCESS_INIT_ROUTINE, + _: [128]u8, + _: [1]rawptr, + SessionId: u32, +} + + + + +PEB_LDR_DATA :: struct { + _: [8]u8, + _: [3]rawptr, + InMemoryOrderModuleList: LIST_ENTRY, +} + +RTL_USER_PROCESS_PARAMETERS :: struct { + MaximumLength: u32, + Length: u32, + Flags: u32, + DebugFlags: u32, + ConsoleHandle: rawptr, + ConsoleFlags: u32, + StdInputHandle: rawptr, + StdOutputHandle: rawptr, + StdErrorHandle: rawptr, + CurrentDirectoryPath: UNICODE_STRING, + CurrentDirectoryHandle: rawptr, + DllPath: UNICODE_STRING, + ImagePathName: UNICODE_STRING, + CommandLine: UNICODE_STRING, + Environment: rawptr, + StartingPositionLeft: u32, + StartingPositionTop: u32, + Width: u32, + Height: u32, + CharWidth: u32, + CharHeight: u32, + ConsoleTextAttributes: u32, + WindowFlags: u32, + ShowWindowFlags: u32, + WindowTitle: UNICODE_STRING, + DesktopName: UNICODE_STRING, + ShellInfo: UNICODE_STRING, + RuntimeData: UNICODE_STRING, + DLCurrentDirectory: [32]RTL_DRIVE_LETTER_CURDIR, + EnvironmentSize: u32, +} + +RTL_DRIVE_LETTER_CURDIR :: struct { + Flags: u16, + Length: u16, + TimeStamp: u32, + DosPath: UNICODE_STRING, +} + + +LIST_ENTRY :: struct { + Flink: ^LIST_ENTRY, + Blink: ^LIST_ENTRY, } \ No newline at end of file diff --git a/core/sys/windows/ole32.odin b/core/sys/windows/ole32.odin index 4a4b470ea..8535a6f87 100644 --- a/core/sys/windows/ole32.odin +++ b/core/sys/windows/ole32.odin @@ -1,30 +1,47 @@ -// +build windows package sys_windows foreign import "system:Ole32.lib" //objbase.h +// Note(Dragos): https://learn.microsoft.com/en-us/windows/win32/api/objbase/ne-objbase-coinit makes you believe that MULTITHREADED == 3. That is wrong. See definition of objbase.h +/* +typedef enum tagCOINIT +{ + 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. +#endif // DCOM +} COINIT; +*/ +// Where COINITBASE_MULTITHREADED == 0x00 COINIT :: enum DWORD { APARTMENTTHREADED = 0x2, - MULTITHREADED, + MULTITHREADED = 0, DISABLE_OLE1DDE = 0x4, SPEED_OVER_MEMORY = 0x8, } IUnknown :: struct { - using Vtbl: ^IUnknownVtbl, + using _iunknown_vtable: ^IUnknown_VTable, } -IUnknownVtbl :: struct { - QueryInterface: proc "stdcall" (This: ^IUnknown, riid: REFIID, ppvObject: ^rawptr) -> HRESULT, - AddRef: proc "stdcall" (This: ^IUnknown) -> ULONG, - Release: proc "stdcall" (This: ^IUnknown) -> ULONG, + +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, } LPUNKNOWN :: ^IUnknown -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign Ole32 { - CoInitializeEx :: proc(reserved: rawptr, co_init: COINIT) -> HRESULT --- + CoInitialize :: proc(reserved: rawptr = nil) -> HRESULT --- + CoInitializeEx :: proc(reserved: rawptr = nil, co_init: COINIT = .APARTMENTTHREADED) -> HRESULT --- CoUninitialize :: proc() --- CoCreateInstance :: proc( @@ -36,4 +53,17 @@ foreign Ole32 { ) -> HRESULT --- CoTaskMemFree :: proc(pv: rawptr) --- + + CLSIDFromProgID :: proc(lpszProgID: LPCOLESTR, lpclsid: LPCLSID) -> HRESULT --- + CLSIDFromProgIDEx :: proc(lpszProgID, LPCOLESTR, lpclsid: LPCLSID) -> HRESULT --- + CLSIDFromString :: proc(lpsz: LPOLESTR, pclsid: LPCLSID) -> HRESULT --- + IIDFromString :: proc(lpsz: LPOLESTR, lpiid: LPIID) -> HRESULT --- + ProgIDFromCLSID :: proc(clsid: REFCLSID, lplpszProgID: ^LPOLESTR) -> HRESULT --- + StringFromCLSID :: proc(rclsid: REFCLSID, lplpsz: ^LPOLESTR) -> HRESULT --- + StringFromGUID2 :: proc(rclsid: REFCLSID, lplpsz: LPOLESTR, cchMax: INT) -> INT --- + StringFromIID :: proc(rclsid: REFIID, lplpsz: ^LPOLESTR) -> HRESULT --- + + PropVariantClear :: proc(pvar: ^PROPVARIANT) -> HRESULT --- + PropVariantCopy :: proc(pvarDest: ^PROPVARIANT, pvarSrc: ^PROPVARIANT) -> HRESULT --- + FreePropVariantArray :: proc(cVariants: ULONG, rgvars: ^PROPVARIANT) -> HRESULT --- } diff --git a/core/sys/windows/shcore.odin b/core/sys/windows/shcore.odin new file mode 100644 index 000000000..08a76ebe6 --- /dev/null +++ b/core/sys/windows/shcore.odin @@ -0,0 +1,25 @@ +#+build windows + +package sys_windows + +foreign import shcore "system:Shcore.lib" + +@(default_calling_convention="system") +foreign shcore { + GetProcessDpiAwareness :: proc(hprocess: HANDLE, value: ^PROCESS_DPI_AWARENESS) -> HRESULT --- + SetProcessDpiAwareness :: proc(value: PROCESS_DPI_AWARENESS) -> HRESULT --- + GetDpiForMonitor :: proc(hmonitor: HMONITOR, dpiType: MONITOR_DPI_TYPE, dpiX: ^UINT, dpiY: ^UINT) -> HRESULT --- +} + +PROCESS_DPI_AWARENESS :: enum DWORD { + PROCESS_DPI_UNAWARE = 0, + PROCESS_SYSTEM_DPI_AWARE = 1, + PROCESS_PER_MONITOR_DPI_AWARE = 2, +} + +MONITOR_DPI_TYPE :: enum DWORD { + MDT_EFFECTIVE_DPI = 0, + MDT_ANGULAR_DPI = 1, + MDT_RAW_DPI = 2, + MDT_DEFAULT, +} diff --git a/core/sys/windows/shell32.odin b/core/sys/windows/shell32.odin index 0a6f90a44..54cee718c 100644 --- a/core/sys/windows/shell32.odin +++ b/core/sys/windows/shell32.odin @@ -1,11 +1,11 @@ -// +build windows +#+build windows package sys_windows foreign import shell32 "system:Shell32.lib" -@(default_calling_convention="stdcall") +@(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, @@ -22,9 +22,20 @@ foreign shell32 { ) -> c_int --- SHFileOperationW :: proc(lpFileOp: LPSHFILEOPSTRUCTW) -> c_int --- SHGetFolderPathW :: proc(hwnd: HWND, csidl: c_int, hToken: HANDLE, dwFlags: DWORD, pszPath: LPWSTR) -> HRESULT --- - SHAppBarMessage :: proc(dwMessage: DWORD, pData: PAPPBARDATA) -> UINT_PTR --- + SHAppBarMessage :: proc(dwMessage: DWORD, pData: PAPPBARDATA) -> UINT_PTR --- Shell_NotifyIconW :: proc(dwMessage: DWORD, lpData: ^NOTIFYICONDATAW) -> BOOL --- + SHChangeNotify :: proc(wEventId: LONG, uFlags: UINT, dwItem1: LPCVOID, dwItem2: LPCVOID) --- + + SHGetKnownFolderIDList :: proc(rfid: REFKNOWNFOLDERID, dwFlags: /* KNOWN_FOLDER_FLAG */ DWORD, hToken: HANDLE, ppidl: rawptr) -> HRESULT --- + SHSetKnownFolderPath :: proc(rfid: REFKNOWNFOLDERID, dwFlags: /* KNOWN_FOLDER_FLAG */ DWORD, hToken: HANDLE, pszPath: PCWSTR ) -> HRESULT --- + SHGetKnownFolderPath :: proc(rfid: REFKNOWNFOLDERID, dwFlags: /* KNOWN_FOLDER_FLAG */ DWORD, hToken: HANDLE, ppszPath: ^LPWSTR) -> HRESULT --- + + ExtractIconExW :: proc(pszFile: LPCWSTR, nIconIndex: INT, phiconLarge: ^HICON, phiconSmall: ^HICON, nIcons: UINT) -> UINT --- + DragAcceptFiles :: proc(hWnd: HWND, fAccept: BOOL) --- + DragQueryPoint :: proc(hDrop: HDROP, ppt: ^POINT) -> BOOL --- + DragQueryFileW :: proc(hDrop: HDROP, iFile: UINT, lpszFile: LPWSTR, cch: UINT) -> UINT --- + DragFinish :: proc(hDrop: HDROP) --- // @New } APPBARDATA :: struct { @@ -36,16 +47,16 @@ APPBARDATA :: struct { lParam: LPARAM, } PAPPBARDATA :: ^APPBARDATA - + ABM_NEW :: 0x00000000 ABM_REMOVE :: 0x00000001 ABM_QUERYPOS :: 0x00000002 ABM_SETPOS :: 0x00000003 ABM_GETSTATE :: 0x00000004 ABM_GETTASKBARPOS :: 0x00000005 -ABM_ACTIVATE :: 0x00000006 +ABM_ACTIVATE :: 0x00000006 ABM_GETAUTOHIDEBAR :: 0x00000007 -ABM_SETAUTOHIDEBAR :: 0x00000008 +ABM_SETAUTOHIDEBAR :: 0x00000008 ABM_WINDOWPOSCHANGED :: 0x0000009 ABM_SETSTATE :: 0x0000000a ABN_STATECHANGE :: 0x0000000 @@ -58,3 +69,84 @@ ABE_LEFT :: 0 ABE_TOP :: 1 ABE_RIGHT :: 2 ABE_BOTTOM :: 3 + +KNOWNFOLDERID :: GUID +REFKNOWNFOLDERID :: ^KNOWNFOLDERID + +HDROP :: HANDLE + +KNOWN_FOLDER_FLAG :: enum u32 { + DEFAULT = 0x00000000, + + // if NTDDI_VERSION >= NTDDI_WIN10_RS3 + FORCE_APP_DATA_REDIRECTION = 0x00080000, + + // if NTDDI_VERSION >= NTDDI_WIN10_RS2 + RETURN_FILTER_REDIRECTION_TARGET = 0x00040000, + FORCE_PACKAGE_REDIRECTION = 0x00020000, + NO_PACKAGE_REDIRECTION = 0x00010000, + FORCE_APPCONTAINER_REDIRECTION = 0x00020000, + + // if NTDDI_VERSION >= NTDDI_WIN7 + NO_APPCONTAINER_REDIRECTION = 0x00010000, + + CREATE = 0x00008000, + DONT_VERIFY = 0x00004000, + DONT_UNEXPAND = 0x00002000, + NO_ALIAS = 0x00001000, + INIT = 0x00000800, + DEFAULT_PATH = 0x00000400, + NOT_PARENT_RELATIVE = 0x00000200, + SIMPLE_IDLIST = 0x00000100, + ALIAS_ONLY = 0x80000000, +} + +SHCNRF_InterruptLevel :: 0x0001 +SHCNRF_ShellLevel :: 0x0002 +SHCNRF_RecursiveInterrupt :: 0x1000 +SHCNRF_NewDelivery :: 0x8000 + +SHCNE_RENAMEITEM :: 0x00000001 +SHCNE_CREATE :: 0x00000002 +SHCNE_DELETE :: 0x00000004 +SHCNE_MKDIR :: 0x00000008 +SHCNE_RMDIR :: 0x00000010 +SHCNE_MEDIAINSERTED :: 0x00000020 +SHCNE_MEDIAREMOVED :: 0x00000040 +SHCNE_DRIVEREMOVED :: 0x00000080 +SHCNE_DRIVEADD :: 0x00000100 +SHCNE_NETSHARE :: 0x00000200 +SHCNE_NETUNSHARE :: 0x00000400 +SHCNE_ATTRIBUTES :: 0x00000800 +SHCNE_UPDATEDIR :: 0x00001000 +SHCNE_UPDATEITEM :: 0x00002000 +SHCNE_SERVERDISCONNECT :: 0x00004000 +SHCNE_UPDATEIMAGE :: 0x00008000 +SHCNE_DRIVEADDGUI :: 0x00010000 +SHCNE_RENAMEFOLDER :: 0x00020000 +SHCNE_FREESPACE :: 0x00040000 + +SHCNE_EXTENDED_EVENT :: 0x04000000 + +SHCNE_ASSOCCHANGED :: 0x08000000 + +SHCNE_DISKEVENTS :: 0x0002381F +SHCNE_GLOBALEVENTS :: 0x0C0581E0 +SHCNE_ALLEVENTS :: 0x7FFFFFFF +SHCNE_INTERRUPT :: 0x80000000 + +SHCNEE_ORDERCHANGED :: 2 +SHCNEE_MSI_CHANGE :: 4 +SHCNEE_MSI_UNINSTALL :: 5 + +SHCNF_IDLIST :: 0x0000 +SHCNF_PATHA :: 0x0001 +SHCNF_PRINTERA :: 0x0002 +SHCNF_DWORD :: 0x0003 +SHCNF_PATHW :: 0x0005 +SHCNF_PRINTERW :: 0x0006 +SHCNF_TYPE :: 0x00FF +SHCNF_FLUSH :: 0x1000 +SHCNF_FLUSHNOWAIT :: 0x3000 + +SHCNF_NOTIFYRECURSIVE :: 0x10000 diff --git a/core/sys/windows/shlwapi.odin b/core/sys/windows/shlwapi.odin index 241ade8f6..095fff304 100644 --- a/core/sys/windows/shlwapi.odin +++ b/core/sys/windows/shlwapi.odin @@ -1,9 +1,9 @@ -// +build windows +#+build windows package sys_windows foreign import shlwapi "system:shlwapi.lib" -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign shlwapi { PathFileExistsW :: proc(pszPath: wstring) -> BOOL --- PathFindExtensionW :: proc(pszPath: wstring) -> wstring --- diff --git a/core/sys/windows/synchronization.odin b/core/sys/windows/synchronization.odin index c98730aa0..bcaeb3f5f 100644 --- a/core/sys/windows/synchronization.odin +++ b/core/sys/windows/synchronization.odin @@ -1,9 +1,9 @@ -// +build windows +#+build windows package sys_windows foreign import Synchronization "system:Synchronization.lib" -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign Synchronization { WaitOnAddress :: proc(Address: PVOID, CompareAddress: PVOID, AddressSize: SIZE_T, dwMilliseconds: DWORD) -> BOOL --- WakeByAddressSingle :: proc(Address: PVOID) --- diff --git a/core/sys/windows/system_params.odin b/core/sys/windows/system_params.odin index e94d777bf..e463feb7e 100644 --- a/core/sys/windows/system_params.odin +++ b/core/sys/windows/system_params.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows // Parameter for SystemParametersInfo. diff --git a/core/sys/windows/tlhelp.odin b/core/sys/windows/tlhelp.odin new file mode 100644 index 000000000..006c9c330 --- /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 d5377eb2f..aece4dc43 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -1,4 +1,3 @@ -// +build windows package sys_windows import "core:c" @@ -7,9 +6,9 @@ c_char :: c.char c_uchar :: c.uchar c_int :: c.int c_uint :: c.uint -c_long :: c.long +c_long :: i32 c_longlong :: c.longlong -c_ulong :: c.ulong +c_ulong :: u32 c_ulonglong :: c.ulonglong c_short :: c.short c_ushort :: c.ushort @@ -31,14 +30,20 @@ HICON :: distinct HANDLE HCURSOR :: distinct HANDLE HMENU :: distinct HANDLE HBRUSH :: distinct HANDLE +HPEN :: distinct HANDLE HGDIOBJ :: distinct HANDLE HBITMAP :: distinct HANDLE +HPALETTE :: distinct HANDLE HGLOBAL :: distinct HANDLE HHOOK :: distinct HANDLE +HWINEVENTHOOK :: distinct HANDLE HKEY :: distinct HANDLE HDESK :: distinct HANDLE HFONT :: distinct HANDLE HRGN :: distinct HANDLE +HRSRC :: distinct HANDLE +HWINSTA :: distinct HANDLE +HACCEL :: distinct HANDLE BOOL :: distinct b32 BYTE :: distinct u8 BOOLEAN :: distinct b8 @@ -64,6 +69,7 @@ LONG_PTR :: int UINT_PTR :: uintptr ULONG :: c_ulong ULONGLONG :: c_ulonglong +LONGLONG :: c_longlong UCHAR :: BYTE NTSTATUS :: c.long COLORREF :: DWORD @@ -75,6 +81,8 @@ LPRECT :: ^RECT LPPOINT :: ^POINT LSTATUS :: LONG PHKEY :: ^HKEY +PUSHORT :: ^USHORT +PCHAR :: ^CHAR UINT8 :: u8 UINT16 :: u16 @@ -86,14 +94,20 @@ INT16 :: i16 INT32 :: i32 INT64 :: i64 +ULONG32 :: u32 +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 @@ -129,12 +143,16 @@ LPWSAPROTOCOL_INFO :: ^WSAPROTOCOL_INFO LPSTR :: ^CHAR LPWSTR :: ^WCHAR OLECHAR :: WCHAR +BSTR :: ^OLECHAR LPOLESTR :: ^OLECHAR +LPCOLESTR :: LPCSTR LPFILETIME :: ^FILETIME LPWSABUF :: ^WSABUF LPWSAOVERLAPPED :: distinct rawptr LPWSAOVERLAPPED_COMPLETION_ROUTINE :: distinct rawptr LPCVOID :: rawptr +SCODE :: LONG +PSCODE :: ^SCODE PACCESS_TOKEN :: PVOID PSECURITY_DESCRIPTOR :: PVOID @@ -236,8 +254,6 @@ FILE_GENERIC_WRITE: DWORD : STANDARD_RIGHTS_WRITE | FILE_APPEND_DATA | SYNCHRONIZE -FILE_FLAG_OPEN_REPARSE_POINT: DWORD : 0x00200000 -FILE_FLAG_BACKUP_SEMANTICS: DWORD : 0x02000000 SECURITY_SQOS_PRESENT: DWORD : 0x00100000 FIONBIO: c_ulong : 0x8004667e @@ -688,13 +704,107 @@ FW_DEMIBOLD :: FW_SEMIBOLD FW_ULTRABOLD :: FW_EXTRABOLD FW_BLACK :: FW_HEAVY -PTIMERAPCROUTINE :: #type proc "stdcall" (lpArgToCompletionRoutine: LPVOID, dwTimerLowValue, dwTimerHighValue: DWORD) +PTIMERAPCROUTINE :: #type proc "system" (lpArgToCompletionRoutine: LPVOID, dwTimerLowValue, dwTimerHighValue: DWORD) -TIMERPROC :: #type proc "stdcall" (HWND, UINT, UINT_PTR, DWORD) +// Character Sets +ANSI_CHARSET :: 0 +DEFAULT_CHARSET :: 1 +SYMBOL_CHARSET :: 2 +SHIFTJIS_CHARSET :: 128 +HANGEUL_CHARSET :: 129 +HANGUL_CHARSET :: 129 +GB2312_CHARSET :: 134 +CHINESEBIG5_CHARSET :: 136 +OEM_CHARSET :: 255 +JOHAB_CHARSET :: 130 +HEBREW_CHARSET :: 177 +ARABIC_CHARSET :: 178 +GREEK_CHARSET :: 161 +TURKISH_CHARSET :: 162 +VIETNAMESE_CHARSET :: 163 +THAI_CHARSET :: 222 +EASTEUROPE_CHARSET :: 238 +RUSSIAN_CHARSET :: 204 +MAC_CHARSET :: 77 +BALTIC_CHARSET :: 186 -WNDPROC :: #type proc "stdcall" (HWND, UINT, WPARAM, LPARAM) -> LRESULT +// Font Signature Bitmaps +FS_LATIN1 :: 0x00000001 +FS_LATIN2 :: 0x00000002 +FS_CYRILLIC :: 0x00000004 +FS_GREEK :: 0x00000008 +FS_TURKISH :: 0x00000010 +FS_HEBREW :: 0x00000020 +FS_ARABIC :: 0x00000040 +FS_BALTIC :: 0x00000080 +FS_VIETNAMESE :: 0x00000100 +FS_THAI :: 0x00010000 +FS_JISJAPAN :: 0x00020000 +FS_CHINESESIMP :: 0x00040000 +FS_WANSUNG :: 0x00080000 +FS_CHINESETRAD :: 0x00100000 +FS_JOHAB :: 0x00200000 +FS_SYMBOL :: 0x80000000 -HOOKPROC :: #type proc "stdcall" (code: c_int, wParam: WPARAM, lParam: LPARAM) -> LRESULT +// Output Precisions +OUT_DEFAULT_PRECIS :: 0 +OUT_STRING_PRECIS :: 1 +OUT_CHARACTER_PRECIS :: 2 +OUT_STROKE_PRECIS :: 3 +OUT_TT_PRECIS :: 4 +OUT_DEVICE_PRECIS :: 5 +OUT_RASTER_PRECIS :: 6 +OUT_TT_ONLY_PRECIS :: 7 +OUT_OUTLINE_PRECIS :: 8 +OUT_SCREEN_OUTLINE_PRECIS :: 9 +OUT_PS_ONLY_PRECIS :: 10 + +// Clipping Precisions +CLIP_DEFAULT_PRECIS :: 0 +CLIP_CHARACTER_PRECIS :: 1 +CLIP_STROKE_PRECIS :: 2 +CLIP_MASK :: 0xf +CLIP_LH_ANGLES :: 1 << 4 +CLIP_TT_ALWAYS :: 2 << 4 +CLIP_DFA_DISABLE :: 4 << 4 +CLIP_EMBEDDED :: 8 << 4 + +// Output Qualities +DEFAULT_QUALITY :: 0 +DRAFT_QUALITY :: 1 +PROOF_QUALITY :: 2 +NONANTIALIASED_QUALITY :: 3 +ANTIALIASED_QUALITY :: 4 +CLEARTYPE_QUALITY :: 5 +CLEARTYPE_NATURAL_QUALITY :: 6 + +// Font Pitches +DEFAULT_PITCH :: 0 +FIXED_PITCH :: 1 +VARIABLE_PITCH :: 2 +MONO_FONT :: 8 + +// Font Families +FF_DONTCARE :: 0 << 4 +FF_ROMAN :: 1 << 4 +FF_SWISS :: 2 << 4 +FF_MODERN :: 3 << 4 +FF_SCRIPT :: 4 << 4 +FF_DECORATIVE :: 5 << 4 + +TIMERPROC :: #type proc "system" (HWND, UINT, UINT_PTR, DWORD) + +WNDPROC :: #type proc "system" (HWND, UINT, WPARAM, LPARAM) -> LRESULT + +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, @@ -915,6 +1025,16 @@ MF_RIGHTJUSTIFY :: 0x00004000 MF_MOUSESELECT :: 0x00008000 MF_END :: 0x00000080 // Obsolete -- only used by old RES files +// Menu flags for Add/Check/EnableMenuItem() +MFS_GRAYED :: 0x00000003 +MFS_DISABLED :: MFS_GRAYED +MFS_CHECKED :: MF_CHECKED +MFS_HILITE :: MF_HILITE +MFS_ENABLED :: MF_ENABLED +MFS_UNCHECKED :: MF_UNCHECKED +MFS_UNHILITE :: MF_UNHILITE +MFS_DEFAULT :: MF_DEFAULT + // Flags for TrackPopupMenu TPM_LEFTBUTTON :: 0x0000 TPM_RIGHTBUTTON :: 0x0002 @@ -1010,16 +1130,25 @@ TRACKMOUSEEVENT :: struct { } WIN32_FIND_DATAW :: struct { - dwFileAttributes: DWORD, - ftCreationTime: FILETIME, - ftLastAccessTime: FILETIME, - ftLastWriteTime: FILETIME, - nFileSizeHigh: DWORD, - nFileSizeLow: DWORD, - dwReserved0: DWORD, - dwReserved1: DWORD, - cFileName: [260]wchar_t, // #define MAX_PATH 260 - cAlternateFileName: [14]wchar_t, + dwFileAttributes: DWORD, + ftCreationTime: FILETIME, + ftLastAccessTime: FILETIME, + ftLastWriteTime: FILETIME, + nFileSizeHigh: DWORD, + nFileSizeLow: DWORD, + dwReserved0: DWORD, + dwReserved1: DWORD, + cFileName: [MAX_PATH]WCHAR, + cAlternateFileName: [14]WCHAR, +} + +FILE_ID_128 :: struct { + Identifier: [16]BYTE, +} + +FILE_ID_INFO :: struct { + VolumeSerialNumber: ULONGLONG, + FileId: FILE_ID_128, } CREATESTRUCTA :: struct { @@ -1075,6 +1204,11 @@ NMHDR :: struct { code: UINT, // NM_ code } +NCCALCSIZE_PARAMS :: struct { + rgrc: [3]RECT, + lppos: PWINDOWPOS, +} + // Generic WM_NOTIFY notification codes NM_OUTOFMEMORY :: ~uintptr(0) // -1 NM_CLICK :: NM_OUTOFMEMORY-1 // uses NMCLICK struct @@ -1969,6 +2103,16 @@ BITMAPINFO :: struct { bmiColors: [1]RGBQUAD, } +BITMAP :: struct { + bmType: LONG, + bmWidth: LONG, + bmHeight: LONG, + bmWidthBytes: LONG, + bmPlanes: WORD, + bmBitsPixel: WORD, + bmBits: LPVOID, +} + // pixel types PFD_TYPE_RGBA :: 0 PFD_TYPE_COLORINDEX :: 1 @@ -2061,7 +2205,13 @@ SRWLOCK_INIT :: SRWLOCK{} STARTF_USESTDHANDLES: DWORD : 0x00000100 VOLUME_NAME_DOS: DWORD : 0x0 -MOVEFILE_REPLACE_EXISTING: DWORD : 1 + +MOVEFILE_COPY_ALLOWED: DWORD: 0x2 +MOVEFILE_CREATE_HARDLINK: DWORD: 0x10 +MOVEFILE_DELAY_UNTIL_REBOOT: DWORD: 0x4 +MOVEFILE_FAIL_IF_NOT_TRACKABLE: DWORD: 0x20 +MOVEFILE_REPLACE_EXISTING: DWORD : 0x1 +MOVEFILE_WRITE_THROUGH: DWORD: 0x8 FILE_BEGIN: DWORD : 0 FILE_CURRENT: DWORD : 1 @@ -2071,11 +2221,22 @@ WAIT_OBJECT_0: DWORD : 0x00000000 WAIT_TIMEOUT: DWORD : 258 WAIT_FAILED: DWORD : 0xFFFFFFFF +FILE_FLAG_WRITE_THROUGH: DWORD : 0x80000000 +FILE_FLAG_OVERLAPPED: DWORD : 0x40000000 +FILE_FLAG_NO_BUFFERING: DWORD : 0x20000000 +FILE_FLAG_RANDOM_ACCESS: DWORD : 0x10000000 +FILE_FLAG_SEQUENTIAL_SCAN: DWORD : 0x08000000 +FILE_FLAG_DELETE_ON_CLOSE: DWORD : 0x04000000 +FILE_FLAG_BACKUP_SEMANTICS: DWORD : 0x02000000 +FILE_FLAG_POSIX_SEMANTICS: DWORD : 0x01000000 +FILE_FLAG_SESSION_AWARE: DWORD : 0x00800000 +FILE_FLAG_OPEN_REPARSE_POINT: DWORD : 0x00200000 +FILE_FLAG_OPEN_NO_RECALL: DWORD : 0x00100000 +FILE_FLAG_FIRST_PIPE_INSTANCE: DWORD : 0x00080000 + PIPE_ACCESS_INBOUND: DWORD : 0x00000001 PIPE_ACCESS_OUTBOUND: DWORD : 0x00000002 PIPE_ACCESS_DUPLEX: DWORD : 0x00000003 -FILE_FLAG_FIRST_PIPE_INSTANCE: DWORD : 0x00080000 -FILE_FLAG_OVERLAPPED: DWORD : 0x40000000 PIPE_WAIT: DWORD : 0x00000000 PIPE_TYPE_BYTE: DWORD : 0x00000000 PIPE_TYPE_MESSAGE: DWORD : 0x00000004 @@ -2113,6 +2274,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, @@ -2156,6 +2318,14 @@ CP_SYMBOL :: 42 // SYMBOL translations CP_UTF7 :: 65000 // UTF-7 translation CP_UTF8 :: 65001 // UTF-8 translation +LCID :: DWORD +LANGID :: WORD + +LANG_NEUTRAL :: 0x00 +LANG_INVARIANT :: 0x7f +SUBLANG_NEUTRAL :: 0x00 // language neutral +SUBLANG_DEFAULT :: 0x01 // user default + MB_ERR_INVALID_CHARS :: 8 WC_ERR_INVALID_CHARS :: 128 @@ -2163,7 +2333,7 @@ WC_ERR_INVALID_CHARS :: 128 MAX_PATH :: 0x00000104 MAX_PATH_WIDE :: 0x8000 -INVALID_FILE_ATTRIBUTES :: -1 +INVALID_FILE_ATTRIBUTES :: DWORD(0xffff_ffff) FILE_TYPE_DISK :: 0x0001 FILE_TYPE_CHAR :: 0x0002 @@ -2172,6 +2342,7 @@ FILE_TYPE_PIPE :: 0x0003 RECT :: struct {left, top, right, bottom: LONG} POINT :: struct {x, y: LONG} +PWINDOWPOS :: ^WINDOWPOS WINDOWPOS :: struct { hwnd: HWND, hwndInsertAfter: HWND, @@ -2311,7 +2482,7 @@ MOUNT_POINT_REPARSE_BUFFER :: struct { PathBuffer: WCHAR, } -LPPROGRESS_ROUTINE :: #type proc "stdcall" ( +LPPROGRESS_ROUTINE :: #type proc "system" ( TotalFileSize: LARGE_INTEGER, TotalBytesTransferred: LARGE_INTEGER, StreamSize: LARGE_INTEGER, @@ -2370,8 +2541,10 @@ REFIID :: ^GUID REFGUID :: GUID IID :: GUID +LPIID :: ^IID CLSID :: GUID REFCLSID :: ^CLSID +LPCLSID :: ^CLSID CLSCTX_INPROC_SERVER :: 0x1 CLSCTX_INPROC_HANDLER :: 0x2 @@ -2401,6 +2574,7 @@ CLSCTX_RESERVED6 :: 0x1000000 CLSCTX_ACTIVATE_ARM32_SERVER :: 0x2000000 CLSCTX_ALLOW_LOWER_TRUST_REGISTRATION :: 0x4000000 CLSCTX_PS_DLL :: 0x80000000 +CLSCTX_ALL :: CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER WSAPROTOCOLCHAIN :: struct { ChainLen: c_int, @@ -2460,10 +2634,11 @@ OBJECT_ATTRIBUTES :: struct { SecurityQualityOfService: rawptr, } +PUNICODE_STRING :: ^UNICODE_STRING UNICODE_STRING :: struct { - Length: u16, - MaximumLength: u16, - Buffer: ^u16, + Length: u16 `fmt:"-"`, + MaximumLength: u16 `fmt:"-"`, + Buffer: [^]u16 `fmt:"s,Length"`, } OVERLAPPED :: struct { @@ -2481,7 +2656,7 @@ OVERLAPPED_ENTRY :: struct { dwNumberOfBytesTransferred: DWORD, } -LPOVERLAPPED_COMPLETION_ROUTINE :: #type proc "stdcall" ( +LPOVERLAPPED_COMPLETION_ROUTINE :: #type proc "system" ( dwErrorCode: DWORD, dwNumberOfBytesTransfered: DWORD, lpOverlapped: LPOVERLAPPED, @@ -2520,11 +2695,23 @@ EXCEPTION_MAXIMUM_PARAMETERS :: 15 EXCEPTION_DATATYPE_MISALIGNMENT :: 0x80000002 EXCEPTION_BREAKPOINT :: 0x80000003 +EXCEPTION_SINGLE_STEP :: 0x80000004 EXCEPTION_ACCESS_VIOLATION :: 0xC0000005 +EXCEPTION_IN_PAGE_ERROR :: 0xC0000006 EXCEPTION_ILLEGAL_INSTRUCTION :: 0xC000001D +EXCEPTION_NONCONTINUABLE_EXCEPTION :: 0xC0000025 +EXCEPTION_INVALID_DISPOSITION :: 0xC0000026 EXCEPTION_ARRAY_BOUNDS_EXCEEDED :: 0xC000008C +EXCEPTION_FLT_DENORMAL_OPERAND :: 0xC000008D +EXCEPTION_FLT_DIVIDE_BY_ZERO :: 0xC000008E +EXCEPTION_FLT_INEXACT_RESULT :: 0xC000008F +EXCEPTION_FLT_INVALID_OPERATION :: 0xC0000090 +EXCEPTION_FLT_OVERFLOW :: 0xC0000091 +EXCEPTION_FLT_STACK_CHECK :: 0xC0000092 +EXCEPTION_FLT_UNDERFLOW :: 0xC0000093 EXCEPTION_INT_DIVIDE_BY_ZERO :: 0xC0000094 EXCEPTION_INT_OVERFLOW :: 0xC0000095 +EXCEPTION_PRIV_INSTRUCTION :: 0xC0000096 EXCEPTION_STACK_OVERFLOW :: 0xC00000FD STATUS_PRIVILEGED_INSTRUCTION :: 0xC0000096 @@ -2538,45 +2725,177 @@ EXCEPTION_RECORD :: struct { ExceptionInformation: [EXCEPTION_MAXIMUM_PARAMETERS]LPVOID, } -CONTEXT :: struct{} // TODO(bill) + +CONTEXT :: struct { + P1Home: DWORD64, + P2Home: DWORD64, + P3Home: DWORD64, + P4Home: DWORD64, + P5Home: DWORD64, + P6Home: DWORD64, + ContextFlags: DWORD, + MxCsr: DWORD, + SegCs: WORD, + SegDs: WORD, + SegEs: WORD, + SegFs: WORD, + SegGs: WORD, + SegSs: WORD, + EFlags: DWORD, + Dr0: DWORD64, + Dr1: DWORD64, + Dr2: DWORD64, + Dr3: DWORD64, + Dr6: DWORD64, + Dr7: DWORD64, + Rax: DWORD64, + Rcx: DWORD64, + Rdx: DWORD64, + Rbx: DWORD64, + Rsp: DWORD64, + Rbp: DWORD64, + Rsi: DWORD64, + Rdi: DWORD64, + R8: DWORD64, + R9: DWORD64, + R10: DWORD64, + R11: DWORD64, + R12: DWORD64, + R13: DWORD64, + R14: DWORD64, + R15: DWORD64, + Rip: DWORD64, + _: struct #raw_union { + FltSave: XMM_SAVE_AREA32, + Q: [16]NEON128, + D: [32]ULONGLONG, + _: struct { + Header: [2]M128A, + Legacy: [8]M128A, + Xmm0: M128A, + Xmm1: M128A, + Xmm2: M128A, + Xmm3: M128A, + Xmm4: M128A, + Xmm5: M128A, + Xmm6: M128A, + Xmm7: M128A, + Xmm8: M128A, + Xmm9: M128A, + Xmm10: M128A, + Xmm11: M128A, + Xmm12: M128A, + Xmm13: M128A, + Xmm14: M128A, + Xmm15: M128A, + }, + S: [32]DWORD, + }, + VectorRegister: [26]M128A, + VectorControl: DWORD64, + DebugControl: DWORD64, + LastBranchToRip: DWORD64, + LastBranchFromRip: DWORD64, + LastExceptionToRip: DWORD64, + LastExceptionFromRip: DWORD64, +} + +PCONTEXT :: ^CONTEXT +LPCONTEXT :: ^CONTEXT + +when size_of(uintptr) == 32 { + XSAVE_FORMAT :: struct #align(16) { + ControlWord: WORD, + StatusWord: WORD, + TagWord: BYTE, + Reserved1: BYTE, + ErrorOpcode: WORD, + ErrorOffset: DWORD, + ErrorSelector: WORD, + Reserved2: WORD, + DataOffset: DWORD, + DataSelector: WORD, + Reserved3: WORD, + MxCsr: DWORD, + MxCsr_Mask: DWORD, + FloatRegisters: [8]M128A, + // 32-bit specific + XmmRegisters: [8]M128A, + Reserved4: [192]BYTE, + StackControl: [7]DWORD, + Cr0NpxState: DWORD, + } +} else { + XSAVE_FORMAT :: struct #align(16) { + ControlWord: WORD, + StatusWord: WORD, + TagWord: BYTE, + Reserved1: BYTE, + ErrorOpcode: WORD, + ErrorOffset: DWORD, + ErrorSelector: WORD, + Reserved2: WORD, + DataOffset: DWORD, + DataSelector: WORD, + Reserved3: WORD, + MxCsr: DWORD, + MxCsr_Mask: DWORD, + FloatRegisters: [8]M128A, + // 64-bit specific + XmmRegisters: [16]M128A, + Reserved4: [96]BYTE, + } +} + +XMM_SAVE_AREA32 :: XSAVE_FORMAT + +M128A :: struct { + Low: ULONGLONG, + High: LONGLONG, +} + +NEON128 :: struct { + Low: ULONGLONG, + High: LONGLONG, +} EXCEPTION_POINTERS :: struct { ExceptionRecord: ^EXCEPTION_RECORD, - ContextRecord: ^CONTEXT, + ContextRecord: ^CONTEXT, } -PVECTORED_EXCEPTION_HANDLER :: #type proc "stdcall" (ExceptionInfo: ^EXCEPTION_POINTERS) -> LONG +PVECTORED_EXCEPTION_HANDLER :: #type proc "system" (ExceptionInfo: ^EXCEPTION_POINTERS) -> LONG CONSOLE_READCONSOLE_CONTROL :: struct { - nLength: ULONG, - nInitialChars: ULONG, - dwCtrlWakeupMask: ULONG, + nLength: ULONG, + nInitialChars: ULONG, + dwCtrlWakeupMask: ULONG, dwControlKeyState: ULONG, } PCONSOLE_READCONSOLE_CONTROL :: ^CONSOLE_READCONSOLE_CONTROL BY_HANDLE_FILE_INFORMATION :: struct { - dwFileAttributes: DWORD, - ftCreationTime: FILETIME, - ftLastAccessTime: FILETIME, - ftLastWriteTime: FILETIME, + dwFileAttributes: DWORD, + ftCreationTime: FILETIME, + ftLastAccessTime: FILETIME, + ftLastWriteTime: FILETIME, dwVolumeSerialNumber: DWORD, - nFileSizeHigh: DWORD, - nFileSizeLow: DWORD, - nNumberOfLinks: DWORD, - nFileIndexHigh: DWORD, - nFileIndexLow: DWORD, + nFileSizeHigh: DWORD, + nFileSizeLow: DWORD, + nNumberOfLinks: DWORD, + nFileIndexHigh: DWORD, + nFileIndexLow: DWORD, } LPBY_HANDLE_FILE_INFORMATION :: ^BY_HANDLE_FILE_INFORMATION FILE_STANDARD_INFO :: struct { AllocationSize: LARGE_INTEGER, - EndOfFile: LARGE_INTEGER, - NumberOfLinks: DWORD, - DeletePending: BOOLEAN, - Directory: BOOLEAN, + EndOfFile: LARGE_INTEGER, + NumberOfLinks: DWORD, + DeletePending: BOOLEAN, + Directory: BOOLEAN, } FILE_ATTRIBUTE_TAG_INFO :: struct { @@ -2600,7 +2919,7 @@ ADDRINFOEXW :: struct { ai_next: ^ADDRINFOEXW, } -LPLOOKUPSERVICE_COMPLETION_ROUTINE :: #type proc "stdcall" ( +LPLOOKUPSERVICE_COMPLETION_ROUTINE :: #type proc "system" ( dwErrorCode: DWORD, dwNumberOfBytesTransfered: DWORD, lpOverlapped: LPOVERLAPPED, @@ -2642,6 +2961,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 { @@ -2685,37 +3020,21 @@ 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 SID :: struct #packed { - Revision: byte, - SubAuthorityCount: byte, + Revision: byte, + SubAuthorityCount: byte, IdentifierAuthority: SID_IDENTIFIER_AUTHORITY, - SubAuthority: [15]DWORD, // Array of DWORDs + SubAuthority: [15]DWORD, // Array of DWORDs } #assert(size_of(SID) == SECURITY_MAX_SID_SIZE) SID_IDENTIFIER_AUTHORITY :: struct #packed { Value: [6]u8, } +#assert(size_of(SID_IDENTIFIER_AUTHORITY) == 6) // For NetAPI32 // https://github.com/tpn/winsdk-10/blob/master/Include/10.0.14393.0/shared/lmerr.h @@ -3109,8 +3428,6 @@ TIME_ZONE_INFORMATION :: struct { DaylightBias: LONG, } - -@(private="file") IMAGE_DOS_HEADER :: struct { e_magic: WORD, e_cblp: WORD, @@ -3228,6 +3545,19 @@ IMAGE_EXPORT_DIRECTORY :: struct { AddressOfNameOrdinals: DWORD, // RVA from base of image } +IMAGE_DEBUG_DIRECTORY :: struct { + Characteristics: DWORD, + TimeDateStamp: DWORD, + MajorVersion: WORD, + MinorVersion: WORD, + Type: DWORD, + SizeOfData: DWORD, + AddressOfRawData: DWORD, + PointerToRawData: DWORD, +} + +IMAGE_DEBUG_TYPE_CODEVIEW :: 2 + SICHINTF :: DWORD SHCONTF :: DWORD SFGAOF :: ULONG @@ -3249,11 +3579,11 @@ SIGDN :: enum c_int { } SIATTRIBFLAGS :: enum c_int { - AND = 0x1, - OR = 0x2, - APPCOMPAT = 0x3, - MASK = 0x3, - ALLITEMS = 0x4000, + AND = 0x1, + OR = 0x2, + APPCOMPAT = 0x3, + MASK = 0x3, + ALLITEMS = 0x4000, } FDAP :: enum c_int { @@ -3413,7 +3743,7 @@ IModalWindow :: struct #raw_union { } IModalWindowVtbl :: struct { using IUnknownVtbl: IUnknownVtbl, - Show: proc "stdcall" (this: ^IModalWindow, hwndOwner: HWND) -> HRESULT, + Show: proc "system" (this: ^IModalWindow, hwndOwner: HWND) -> HRESULT, } ISequentialStream :: struct #raw_union { @@ -3422,8 +3752,8 @@ ISequentialStream :: struct #raw_union { } ISequentialStreamVtbl :: struct { using IUnknownVtbl: IUnknownVtbl, - Read: proc "stdcall" (this: ^ISequentialStream, pv: rawptr, cb: ULONG, pcbRead: ^ULONG) -> HRESULT, - Write: proc "stdcall" (this: ^ISequentialStream, pv: rawptr, cb: ULONG, pcbWritten: ^ULONG) -> HRESULT, + Read: proc "system" (this: ^ISequentialStream, pv: rawptr, cb: ULONG, pcbRead: ^ULONG) -> HRESULT, + Write: proc "system" (this: ^ISequentialStream, pv: rawptr, cb: ULONG, pcbWritten: ^ULONG) -> HRESULT, } IStream :: struct #raw_union { @@ -3432,15 +3762,15 @@ IStream :: struct #raw_union { } IStreamVtbl :: struct { using ISequentialStreamVtbl: ISequentialStreamVtbl, - Seek: proc "stdcall" (this: ^IStream, dlibMove: LARGE_INTEGER, dwOrigin: DWORD, plibNewPosition: ^ULARGE_INTEGER) -> HRESULT, - SetSize: proc "stdcall" (this: ^IStream, libNewSize: ULARGE_INTEGER) -> HRESULT, - CopyTo: proc "stdcall" (this: ^IStream, pstm: ^IStream, cb: ULARGE_INTEGER, pcbRead: ^ULARGE_INTEGER, pcbWritten: ^ULARGE_INTEGER) -> HRESULT, - Commit: proc "stdcall" (this: ^IStream, grfCommitFlags: DWORD) -> HRESULT, - Revert: proc "stdcall" (this: ^IStream) -> HRESULT, - LockRegion: proc "stdcall" (this: ^IStream, libOffset: ULARGE_INTEGER, cb: ULARGE_INTEGER, dwLockType: DWORD) -> HRESULT, - UnlockRegion: proc "stdcall" (this: ^IStream, libOffset: ULARGE_INTEGER, cb: ULARGE_INTEGER, dwLockType: DWORD) -> HRESULT, - Stat: proc "stdcall" (this: ^IStream, pstatstg: ^STATSTG, grfStatFlag: DWORD) -> HRESULT, - Clone: proc "stdcall" (this: ^IStream, ppstm: ^^IStream) -> HRESULT, + Seek: proc "system" (this: ^IStream, dlibMove: LARGE_INTEGER, dwOrigin: DWORD, plibNewPosition: ^ULARGE_INTEGER) -> HRESULT, + SetSize: proc "system" (this: ^IStream, libNewSize: ULARGE_INTEGER) -> HRESULT, + CopyTo: proc "system" (this: ^IStream, pstm: ^IStream, cb: ULARGE_INTEGER, pcbRead: ^ULARGE_INTEGER, pcbWritten: ^ULARGE_INTEGER) -> HRESULT, + Commit: proc "system" (this: ^IStream, grfCommitFlags: DWORD) -> HRESULT, + Revert: proc "system" (this: ^IStream) -> HRESULT, + LockRegion: proc "system" (this: ^IStream, libOffset: ULARGE_INTEGER, cb: ULARGE_INTEGER, dwLockType: DWORD) -> HRESULT, + UnlockRegion: proc "system" (this: ^IStream, libOffset: ULARGE_INTEGER, cb: ULARGE_INTEGER, dwLockType: DWORD) -> HRESULT, + Stat: proc "system" (this: ^IStream, pstatstg: ^STATSTG, grfStatFlag: DWORD) -> HRESULT, + Clone: proc "system" (this: ^IStream, ppstm: ^^IStream) -> HRESULT, } IPersist :: struct #raw_union { @@ -3449,7 +3779,7 @@ IPersist :: struct #raw_union { } IPersistVtbl :: struct { using IUnknownVtbl: IUnknownVtbl, - GetClassID: proc "stdcall" (this: ^IPersist, pClassID: ^CLSID) -> HRESULT, + GetClassID: proc "system" (this: ^IPersist, pClassID: ^CLSID) -> HRESULT, } IPersistStream :: struct #raw_union { @@ -3458,10 +3788,10 @@ IPersistStream :: struct #raw_union { } IPersistStreamVtbl :: struct { using IPersistVtbl: IPersistVtbl, - IsDirty: proc "stdcall" (this: ^IPersistStream) -> HRESULT, - Load: proc "stdcall" (this: ^IPersistStream, pStm: ^IStream) -> HRESULT, - Save: proc "stdcall" (this: ^IPersistStream, pStm: ^IStream, fClearDirty: BOOL) -> HRESULT, - GetSizeMax: proc "stdcall" (this: ^IPersistStream, pcbSize: ^ULARGE_INTEGER) -> HRESULT, + IsDirty: proc "system" (this: ^IPersistStream) -> HRESULT, + Load: proc "system" (this: ^IPersistStream, pStm: ^IStream) -> HRESULT, + Save: proc "system" (this: ^IPersistStream, pStm: ^IStream, fClearDirty: BOOL) -> HRESULT, + GetSizeMax: proc "system" (this: ^IPersistStream, pcbSize: ^ULARGE_INTEGER) -> HRESULT, } IMoniker :: struct #raw_union { @@ -3470,21 +3800,21 @@ IMoniker :: struct #raw_union { } IMonikerVtbl :: struct { using IPersistStreamVtbl: IPersistStreamVtbl, - BindToObject: proc "stdcall" (this: ^IMoniker, pbc: ^IBindCtx, pmkToLeft: ^IMoniker, riidResult: REFIID, ppvResult: ^rawptr) -> HRESULT, - BindToStorage: proc "stdcall" (this: ^IMoniker, pbc: ^IBindCtx, pmkToLeft: ^IMoniker, riid: REFIID, ppvObj: ^rawptr) -> HRESULT, - Reduce: proc "stdcall" (this: ^IMoniker, pbc: ^IBindCtx, dwReduceHowFar: DWORD, ppmkToLeft: ^^IMoniker, ppmkReduced: ^^IMoniker) -> HRESULT, - ComposeWith: proc "stdcall" (this: ^IMoniker, pmkRight: ^IMoniker, fOnlyIfNotGeneric: BOOL, ppmkComposite: ^^IMoniker) -> HRESULT, - Enum: proc "stdcall" (this: ^IMoniker, fForward: BOOL, ppenumMoniker: ^^IEnumMoniker) -> HRESULT, - IsEqual: proc "stdcall" (this: ^IMoniker, pmkOtherMoniker: ^IMoniker) -> HRESULT, - Hash: proc "stdcall" (this: ^IMoniker, pdwHash: ^DWORD) -> HRESULT, - IsRunning: proc "stdcall" (this: ^IMoniker, pbc: ^IBindCtx, pmkToLeft: ^IMoniker, pmkNewlyRunning: ^IMoniker) -> HRESULT, - GetTimeOfLastChange: proc "stdcall" (this: ^IMoniker, pbc: ^IBindCtx, pmkToLeft: ^IMoniker, pFileTime: ^FILETIME) -> HRESULT, - Inverse: proc "stdcall" (this: ^IMoniker, ppmk: ^^IMoniker) -> HRESULT, - CommonPrefixWith: proc "stdcall" (this: ^IMoniker, pmkOther: ^IMoniker, ppmkPrefix: ^^IMoniker) -> HRESULT, - RelativePathTo: proc "stdcall" (this: ^IMoniker, pmkOther: ^IMoniker, ppmkRelPath: ^^IMoniker) -> HRESULT, - GetDisplayName: proc "stdcall" (this: ^IMoniker, pbc: ^IBindCtx, pmkToLeft: ^IMoniker, ppszDisplayName: ^LPOLESTR) -> HRESULT, - ParseDisplayName: proc "stdcall" (this: ^IMoniker, pbc: ^IBindCtx, pmkToLeft: ^IMoniker, pszDisplayName: LPOLESTR, pchEaten: ^ULONG, ppmkOut: ^^IMoniker) -> HRESULT, - IsSystemMoniker: proc "stdcall" (this: ^IMoniker, pdwMksys: ^DWORD) -> HRESULT, + BindToObject: proc "system" (this: ^IMoniker, pbc: ^IBindCtx, pmkToLeft: ^IMoniker, riidResult: REFIID, ppvResult: ^rawptr) -> HRESULT, + BindToStorage: proc "system" (this: ^IMoniker, pbc: ^IBindCtx, pmkToLeft: ^IMoniker, riid: REFIID, ppvObj: ^rawptr) -> HRESULT, + Reduce: proc "system" (this: ^IMoniker, pbc: ^IBindCtx, dwReduceHowFar: DWORD, ppmkToLeft: ^^IMoniker, ppmkReduced: ^^IMoniker) -> HRESULT, + ComposeWith: proc "system" (this: ^IMoniker, pmkRight: ^IMoniker, fOnlyIfNotGeneric: BOOL, ppmkComposite: ^^IMoniker) -> HRESULT, + Enum: proc "system" (this: ^IMoniker, fForward: BOOL, ppenumMoniker: ^^IEnumMoniker) -> HRESULT, + IsEqual: proc "system" (this: ^IMoniker, pmkOtherMoniker: ^IMoniker) -> HRESULT, + Hash: proc "system" (this: ^IMoniker, pdwHash: ^DWORD) -> HRESULT, + IsRunning: proc "system" (this: ^IMoniker, pbc: ^IBindCtx, pmkToLeft: ^IMoniker, pmkNewlyRunning: ^IMoniker) -> HRESULT, + GetTimeOfLastChange: proc "system" (this: ^IMoniker, pbc: ^IBindCtx, pmkToLeft: ^IMoniker, pFileTime: ^FILETIME) -> HRESULT, + Inverse: proc "system" (this: ^IMoniker, ppmk: ^^IMoniker) -> HRESULT, + CommonPrefixWith: proc "system" (this: ^IMoniker, pmkOther: ^IMoniker, ppmkPrefix: ^^IMoniker) -> HRESULT, + RelativePathTo: proc "system" (this: ^IMoniker, pmkOther: ^IMoniker, ppmkRelPath: ^^IMoniker) -> HRESULT, + GetDisplayName: proc "system" (this: ^IMoniker, pbc: ^IBindCtx, pmkToLeft: ^IMoniker, ppszDisplayName: ^LPOLESTR) -> HRESULT, + ParseDisplayName: proc "system" (this: ^IMoniker, pbc: ^IBindCtx, pmkToLeft: ^IMoniker, pszDisplayName: LPOLESTR, pchEaten: ^ULONG, ppmkOut: ^^IMoniker) -> HRESULT, + IsSystemMoniker: proc "system" (this: ^IMoniker, pdwMksys: ^DWORD) -> HRESULT, } IEnumMoniker :: struct #raw_union { @@ -3493,10 +3823,10 @@ IEnumMoniker :: struct #raw_union { } IEnumMonikerVtbl :: struct { using IUnknownVtbl: IUnknownVtbl, - Next: proc "stdcall" (this: ^IEnumMoniker, celt: ULONG, rgelt: ^^IMoniker, pceltFetched: ^ULONG) -> HRESULT, - Skip: proc "stdcall" (this: ^IEnumMoniker, celt: ULONG) -> HRESULT, - Reset: proc "stdcall" (this: ^IEnumMoniker) -> HRESULT, - Clone: proc "stdcall" (this: ^IEnumMoniker, ppenum: ^^IEnumMoniker) -> HRESULT, + Next: proc "system" (this: ^IEnumMoniker, celt: ULONG, rgelt: ^^IMoniker, pceltFetched: ^ULONG) -> HRESULT, + Skip: proc "system" (this: ^IEnumMoniker, celt: ULONG) -> HRESULT, + Reset: proc "system" (this: ^IEnumMoniker) -> HRESULT, + Clone: proc "system" (this: ^IEnumMoniker, ppenum: ^^IEnumMoniker) -> HRESULT, } IRunningObjectTable :: struct #raw_union { @@ -3505,13 +3835,13 @@ IRunningObjectTable :: struct #raw_union { } IRunningObjectTableVtbl :: struct { using IUnknownVtbl: IUnknownVtbl, - Register: proc "stdcall" (this: ^IRunningObjectTable, grfFlags: DWORD, punkObject: ^IUnknown, pmkObjectName: ^IMoniker, pdwRegister: ^DWORD) -> HRESULT, - Revoke: proc "stdcall" (this: ^IRunningObjectTable, dwRegister: DWORD) -> HRESULT, - IsRunning: proc "stdcall" (this: ^IRunningObjectTable, pmkObjectName: ^IMoniker) -> HRESULT, - GetObject: proc "stdcall" (this: ^IRunningObjectTable, pmkObjectName: ^IMoniker, ppunkObject: ^^IUnknown) -> HRESULT, - NoteChangeTime: proc "stdcall" (this: ^IRunningObjectTable, dwRegister: DWORD, pfiletime: ^FILETIME) -> HRESULT, - GetTimeOfLastChange: proc "stdcall" (this: ^IRunningObjectTable, pmkObjectName: ^IMoniker, pfiletime: ^FILETIME) -> HRESULT, - EnumRunning: proc "stdcall" (this: ^IRunningObjectTable, ppenumMoniker: ^^IEnumMoniker) -> HRESULT, + Register: proc "system" (this: ^IRunningObjectTable, grfFlags: DWORD, punkObject: ^IUnknown, pmkObjectName: ^IMoniker, pdwRegister: ^DWORD) -> HRESULT, + Revoke: proc "system" (this: ^IRunningObjectTable, dwRegister: DWORD) -> HRESULT, + IsRunning: proc "system" (this: ^IRunningObjectTable, pmkObjectName: ^IMoniker) -> HRESULT, + GetObject: proc "system" (this: ^IRunningObjectTable, pmkObjectName: ^IMoniker, ppunkObject: ^^IUnknown) -> HRESULT, + NoteChangeTime: proc "system" (this: ^IRunningObjectTable, dwRegister: DWORD, pfiletime: ^FILETIME) -> HRESULT, + GetTimeOfLastChange: proc "system" (this: ^IRunningObjectTable, pmkObjectName: ^IMoniker, pfiletime: ^FILETIME) -> HRESULT, + EnumRunning: proc "system" (this: ^IRunningObjectTable, ppenumMoniker: ^^IEnumMoniker) -> HRESULT, } IEnumString :: struct #raw_union { @@ -3520,10 +3850,10 @@ IEnumString :: struct #raw_union { } IEnumStringVtbl :: struct { using IUnknownVtbl: IUnknownVtbl, - Next: proc "stdcall" (this: ^IEnumString, celt: ULONG, rgelt: ^LPOLESTR, pceltFetched: ^ULONG) -> HRESULT, - Skip: proc "stdcall" (this: ^IEnumString, celt: ULONG) -> HRESULT, - Reset: proc "stdcall" (this: ^IEnumString) -> HRESULT, - Clone: proc "stdcall" (this: ^IEnumString, ppenum: ^^IEnumString) -> HRESULT, + Next: proc "system" (this: ^IEnumString, celt: ULONG, rgelt: ^LPOLESTR, pceltFetched: ^ULONG) -> HRESULT, + Skip: proc "system" (this: ^IEnumString, celt: ULONG) -> HRESULT, + Reset: proc "system" (this: ^IEnumString) -> HRESULT, + Clone: proc "system" (this: ^IEnumString, ppenum: ^^IEnumString) -> HRESULT, } IBindCtx :: struct #raw_union { @@ -3532,16 +3862,16 @@ IBindCtx :: struct #raw_union { } IBindCtxVtbl :: struct { using IUnknownVtbl: IUnknownVtbl, - RegisterObjectBound: proc "stdcall" (this: ^IBindCtx, punk: ^IUnknown) -> HRESULT, - RevokeObjectBound: proc "stdcall" (this: ^IBindCtx, punk: ^IUnknown) -> HRESULT, - ReleaseBoundObjects: proc "stdcall" (this: ^IBindCtx) -> HRESULT, - SetBindOptions: proc "stdcall" (this: ^IBindCtx, pbindopts: ^BIND_OPTS) -> HRESULT, - GetBindOptions: proc "stdcall" (this: ^IBindCtx, pbindopts: ^BIND_OPTS) -> HRESULT, - GetRunningObjectTable: proc "stdcall" (this: ^IBindCtx, pprot: ^^IRunningObjectTable) -> HRESULT, - RegisterObjectParam: proc "stdcall" (this: ^IBindCtx, pszKey: LPOLESTR, punk: ^IUnknown) -> HRESULT, - GetObjectParam: proc "stdcall" (this: ^IBindCtx, pszKey: LPOLESTR, ppunk: ^^IUnknown) -> HRESULT, - EnumObjectParam: proc "stdcall" (this: ^IBindCtx, ppenum: ^^IEnumString) -> HRESULT, - RevokeObjectParam: proc "stdcall" (this: ^IBindCtx, pszKey: LPOLESTR) -> HRESULT, + RegisterObjectBound: proc "system" (this: ^IBindCtx, punk: ^IUnknown) -> HRESULT, + RevokeObjectBound: proc "system" (this: ^IBindCtx, punk: ^IUnknown) -> HRESULT, + ReleaseBoundObjects: proc "system" (this: ^IBindCtx) -> HRESULT, + SetBindOptions: proc "system" (this: ^IBindCtx, pbindopts: ^BIND_OPTS) -> HRESULT, + GetBindOptions: proc "system" (this: ^IBindCtx, pbindopts: ^BIND_OPTS) -> HRESULT, + GetRunningObjectTable: proc "system" (this: ^IBindCtx, pprot: ^^IRunningObjectTable) -> HRESULT, + RegisterObjectParam: proc "system" (this: ^IBindCtx, pszKey: LPOLESTR, punk: ^IUnknown) -> HRESULT, + GetObjectParam: proc "system" (this: ^IBindCtx, pszKey: LPOLESTR, ppunk: ^^IUnknown) -> HRESULT, + EnumObjectParam: proc "system" (this: ^IBindCtx, ppenum: ^^IEnumString) -> HRESULT, + RevokeObjectParam: proc "system" (this: ^IBindCtx, pszKey: LPOLESTR) -> HRESULT, } IEnumShellItems :: struct #raw_union { @@ -3550,10 +3880,10 @@ IEnumShellItems :: struct #raw_union { } IEnumShellItemsVtbl :: struct { using IUnknownVtbl: IUnknownVtbl, - Next: proc "stdcall" (this: ^IEnumShellItems, celt: ULONG, rgelt: ^^IShellItem, pceltFetched: ^ULONG) -> HRESULT, - Skip: proc "stdcall" (this: ^IEnumShellItems, celt: ULONG) -> HRESULT, - Reset: proc "stdcall" (this: ^IEnumShellItems) -> HRESULT, - Clone: proc "stdcall" (this: ^IEnumShellItems, ppenum: ^^IEnumShellItems) -> HRESULT, + Next: proc "system" (this: ^IEnumShellItems, celt: ULONG, rgelt: ^^IShellItem, pceltFetched: ^ULONG) -> HRESULT, + Skip: proc "system" (this: ^IEnumShellItems, celt: ULONG) -> HRESULT, + Reset: proc "system" (this: ^IEnumShellItems) -> HRESULT, + Clone: proc "system" (this: ^IEnumShellItems, ppenum: ^^IEnumShellItems) -> HRESULT, } IShellItem :: struct #raw_union { @@ -3562,11 +3892,11 @@ IShellItem :: struct #raw_union { } IShellItemVtbl :: struct { using IUnknownVtbl: IUnknownVtbl, - BindToHandler: proc "stdcall" (this: ^IShellItem, pbc: ^IBindCtx, bhid: REFGUID, riid: REFIID, ppv: ^rawptr) -> HRESULT, - GetParent: proc "stdcall" (this: ^IShellItem, ppsiFolder: ^^IShellItem) -> HRESULT, - GetDisplayName: proc "stdcall" (this: ^IShellItem, sigdnName: SIGDN, ppszName: ^LPWSTR) -> HRESULT, - GetAttributes: proc "stdcall" (this: ^IShellItem, sfgaoMask: SFGAOF, psfgaoAttribs: ^SFGAOF) -> HRESULT, - Compare: proc "stdcall" (this: ^IShellItem, psi: ^IShellItem, hint: SICHINTF, piOrder: ^c_int) -> HRESULT, + BindToHandler: proc "system" (this: ^IShellItem, pbc: ^IBindCtx, bhid: REFGUID, riid: REFIID, ppv: ^rawptr) -> HRESULT, + GetParent: proc "system" (this: ^IShellItem, ppsiFolder: ^^IShellItem) -> HRESULT, + GetDisplayName: proc "system" (this: ^IShellItem, sigdnName: SIGDN, ppszName: ^LPWSTR) -> HRESULT, + GetAttributes: proc "system" (this: ^IShellItem, sfgaoMask: SFGAOF, psfgaoAttribs: ^SFGAOF) -> HRESULT, + Compare: proc "system" (this: ^IShellItem, psi: ^IShellItem, hint: SICHINTF, piOrder: ^c_int) -> HRESULT, } IShellItemArray :: struct #raw_union { @@ -3575,13 +3905,13 @@ IShellItemArray :: struct #raw_union { } IShellItemArrayVtbl :: struct { using IUnknownVtbl: IUnknownVtbl, - BindToHandler: proc "stdcall" (this: ^IShellItemArray, pbc: ^IBindCtx, bhid: REFGUID, riid: REFIID, ppvOut: ^rawptr) -> HRESULT, - GetPropertyStore: proc "stdcall" (this: ^IShellItemArray, flags: GETPROPERTYSTOREFLAGS, riid: REFIID, ppv: ^rawptr) -> HRESULT, - GetPropertyDescriptionList: proc "stdcall" (this: ^IShellItemArray, keyType: REFPROPERTYKEY, riid: REFIID, ppv: ^rawptr) -> HRESULT, - GetAttributes: proc "stdcall" (this: ^IShellItemArray, AttribFlags: SIATTRIBFLAGS, sfgaoMask: SFGAOF, psfgaoAttribs: ^SFGAOF) -> HRESULT, - GetCount: proc "stdcall" (this: ^IShellItemArray, pdwNumItems: ^DWORD) -> HRESULT, - GetItemAt: proc "stdcall" (this: ^IShellItemArray, dwIndex: DWORD, ppsi: ^^IShellItem) -> HRESULT, - EnumItems: proc "stdcall" (this: ^IShellItemArray, ppenumShellItems: ^^IEnumShellItems) -> HRESULT, + BindToHandler: proc "system" (this: ^IShellItemArray, pbc: ^IBindCtx, bhid: REFGUID, riid: REFIID, ppvOut: ^rawptr) -> HRESULT, + GetPropertyStore: proc "system" (this: ^IShellItemArray, flags: GETPROPERTYSTOREFLAGS, riid: REFIID, ppv: ^rawptr) -> HRESULT, + GetPropertyDescriptionList: proc "system" (this: ^IShellItemArray, keyType: REFPROPERTYKEY, riid: REFIID, ppv: ^rawptr) -> HRESULT, + GetAttributes: proc "system" (this: ^IShellItemArray, AttribFlags: SIATTRIBFLAGS, sfgaoMask: SFGAOF, psfgaoAttribs: ^SFGAOF) -> HRESULT, + GetCount: proc "system" (this: ^IShellItemArray, pdwNumItems: ^DWORD) -> HRESULT, + GetItemAt: proc "system" (this: ^IShellItemArray, dwIndex: DWORD, ppsi: ^^IShellItem) -> HRESULT, + EnumItems: proc "system" (this: ^IShellItemArray, ppenumShellItems: ^^IEnumShellItems) -> HRESULT, } IFileDialogEvents :: struct #raw_union { @@ -3590,13 +3920,13 @@ IFileDialogEvents :: struct #raw_union { } IFileDialogEventsVtbl :: struct { using IUnknownVtbl: IUnknownVtbl, - OnFileOk: proc "stdcall" (this: ^IFileDialogEvents, pfd: ^IFileDialog) -> HRESULT, - OnFolderChanging: proc "stdcall" (this: ^IFileDialogEvents, pfd: ^IFileDialog, psiFolder: ^IShellItem) -> HRESULT, - OnFolderChange: proc "stdcall" (this: ^IFileDialogEvents, pfd: ^IFileDialog) -> HRESULT, - OnSelectionChange: proc "stdcall" (this: ^IFileDialogEvents, pfd: ^IFileDialog) -> HRESULT, - OnShareViolation: proc "stdcall" (this: ^IFileDialogEvents, pfd: ^IFileDialog, psi: ^IShellItem, pResponse: ^FDE_SHAREVIOLATION_RESPONSE) -> HRESULT, - OnTypeChange: proc "stdcall" (this: ^IFileDialogEvents, pfd: ^IFileDialog) -> HRESULT, - OnOverwrite: proc "stdcall" (this: ^IFileDialogEvents, pfd: ^IFileDialog, psi: ^IShellItem, pResponse: ^FDE_SHAREVIOLATION_RESPONSE) -> HRESULT, + OnFileOk: proc "system" (this: ^IFileDialogEvents, pfd: ^IFileDialog) -> HRESULT, + OnFolderChanging: proc "system" (this: ^IFileDialogEvents, pfd: ^IFileDialog, psiFolder: ^IShellItem) -> HRESULT, + OnFolderChange: proc "system" (this: ^IFileDialogEvents, pfd: ^IFileDialog) -> HRESULT, + OnSelectionChange: proc "system" (this: ^IFileDialogEvents, pfd: ^IFileDialog) -> HRESULT, + OnShareViolation: proc "system" (this: ^IFileDialogEvents, pfd: ^IFileDialog, psi: ^IShellItem, pResponse: ^FDE_SHAREVIOLATION_RESPONSE) -> HRESULT, + OnTypeChange: proc "system" (this: ^IFileDialogEvents, pfd: ^IFileDialog) -> HRESULT, + OnOverwrite: proc "system" (this: ^IFileDialogEvents, pfd: ^IFileDialog, psi: ^IShellItem, pResponse: ^FDE_SHAREVIOLATION_RESPONSE) -> HRESULT, } IShellItemFilter :: struct #raw_union { @@ -3605,8 +3935,8 @@ IShellItemFilter :: struct #raw_union { } IShellItemFilterVtbl :: struct { using IUnknownVtbl: IUnknownVtbl, - IncludeItem: proc "stdcall" (this: ^IShellItemFilter, psi: ^IShellItem) -> HRESULT, - GetEnumFlagsForItem: proc "stdcall" (this: ^IShellItemFilter, psi: ^IShellItem, pgrfFlags: ^SHCONTF) -> HRESULT, + IncludeItem: proc "system" (this: ^IShellItemFilter, psi: ^IShellItem) -> HRESULT, + GetEnumFlagsForItem: proc "system" (this: ^IShellItemFilter, psi: ^IShellItem, pgrfFlags: ^SHCONTF) -> HRESULT, } IFileDialog :: struct #raw_union { @@ -3615,29 +3945,29 @@ IFileDialog :: struct #raw_union { } IFileDialogVtbl :: struct { using IModalWindowVtbl: IModalWindowVtbl, - SetFileTypes: proc "stdcall" (this: ^IFileDialog, cFileTypes: UINT, rgFilterSpec: ^COMDLG_FILTERSPEC) -> HRESULT, - SetFileTypeIndex: proc "stdcall" (this: ^IFileDialog, iFileType: UINT) -> HRESULT, - GetFileTypeIndex: proc "stdcall" (this: ^IFileDialog, piFileType: ^UINT) -> HRESULT, - Advise: proc "stdcall" (this: ^IFileDialog, pfde: ^IFileDialogEvents, pdwCookie: ^DWORD) -> HRESULT, - Unadvise: proc "stdcall" (this: ^IFileDialog, dwCookie: DWORD) -> HRESULT, - SetOptions: proc "stdcall" (this: ^IFileDialog, fos: FILEOPENDIALOGOPTIONS) -> HRESULT, - GetOptions: proc "stdcall" (this: ^IFileDialog, pfos: ^FILEOPENDIALOGOPTIONS) -> HRESULT, - SetDefaultFolder: proc "stdcall" (this: ^IFileDialog, psi: ^IShellItem) -> HRESULT, - SetFolder: proc "stdcall" (this: ^IFileDialog, psi: ^IShellItem) -> HRESULT, - GetFolder: proc "stdcall" (this: ^IFileDialog, ppsi: ^^IShellItem) -> HRESULT, - GetCurrentSelection: proc "stdcall" (this: ^IFileDialog, ppsi: ^^IShellItem) -> HRESULT, - SetFileName: proc "stdcall" (this: ^IFileDialog, pszName: LPCWSTR) -> HRESULT, - GetFileName: proc "stdcall" (this: ^IFileDialog, pszName: ^LPCWSTR) -> HRESULT, - SetTitle: proc "stdcall" (this: ^IFileDialog, pszTitle: LPCWSTR) -> HRESULT, - SetOkButtonLabel: proc "stdcall" (this: ^IFileDialog, pszText: LPCWSTR) -> HRESULT, - SetFileNameLabel: proc "stdcall" (this: ^IFileDialog, pszLabel: LPCWSTR) -> HRESULT, - GetResult: proc "stdcall" (this: ^IFileDialog, ppsi: ^^IShellItem) -> HRESULT, - AddPlace: proc "stdcall" (this: ^IFileDialog, psi: ^IShellItem, fdap: FDAP) -> HRESULT, - SetDefaultExtension: proc "stdcall" (this: ^IFileDialog, pszDefaultExtension: LPCWSTR) -> HRESULT, - Close: proc "stdcall" (this: ^IFileDialog, hr: HRESULT) -> HRESULT, - SetClientGuid: proc "stdcall" (this: ^IFileDialog, guid: REFGUID) -> HRESULT, - ClearClientData: proc "stdcall" (this: ^IFileDialog) -> HRESULT, - SetFilter: proc "stdcall" (this: ^IFileDialog, pFilter: ^IShellItemFilter) -> HRESULT, + SetFileTypes: proc "system" (this: ^IFileDialog, cFileTypes: UINT, rgFilterSpec: ^COMDLG_FILTERSPEC) -> HRESULT, + SetFileTypeIndex: proc "system" (this: ^IFileDialog, iFileType: UINT) -> HRESULT, + GetFileTypeIndex: proc "system" (this: ^IFileDialog, piFileType: ^UINT) -> HRESULT, + Advise: proc "system" (this: ^IFileDialog, pfde: ^IFileDialogEvents, pdwCookie: ^DWORD) -> HRESULT, + Unadvise: proc "system" (this: ^IFileDialog, dwCookie: DWORD) -> HRESULT, + SetOptions: proc "system" (this: ^IFileDialog, fos: FILEOPENDIALOGOPTIONS) -> HRESULT, + GetOptions: proc "system" (this: ^IFileDialog, pfos: ^FILEOPENDIALOGOPTIONS) -> HRESULT, + SetDefaultFolder: proc "system" (this: ^IFileDialog, psi: ^IShellItem) -> HRESULT, + SetFolder: proc "system" (this: ^IFileDialog, psi: ^IShellItem) -> HRESULT, + GetFolder: proc "system" (this: ^IFileDialog, ppsi: ^^IShellItem) -> HRESULT, + GetCurrentSelection: proc "system" (this: ^IFileDialog, ppsi: ^^IShellItem) -> HRESULT, + SetFileName: proc "system" (this: ^IFileDialog, pszName: LPCWSTR) -> HRESULT, + GetFileName: proc "system" (this: ^IFileDialog, pszName: ^LPCWSTR) -> HRESULT, + SetTitle: proc "system" (this: ^IFileDialog, pszTitle: LPCWSTR) -> HRESULT, + SetOkButtonLabel: proc "system" (this: ^IFileDialog, pszText: LPCWSTR) -> HRESULT, + SetFileNameLabel: proc "system" (this: ^IFileDialog, pszLabel: LPCWSTR) -> HRESULT, + GetResult: proc "system" (this: ^IFileDialog, ppsi: ^^IShellItem) -> HRESULT, + AddPlace: proc "system" (this: ^IFileDialog, psi: ^IShellItem, fdap: FDAP) -> HRESULT, + SetDefaultExtension: proc "system" (this: ^IFileDialog, pszDefaultExtension: LPCWSTR) -> HRESULT, + Close: proc "system" (this: ^IFileDialog, hr: HRESULT) -> HRESULT, + SetClientGuid: proc "system" (this: ^IFileDialog, guid: REFGUID) -> HRESULT, + ClearClientData: proc "system" (this: ^IFileDialog) -> HRESULT, + SetFilter: proc "system" (this: ^IFileDialog, pFilter: ^IShellItemFilter) -> HRESULT, } IFileOpenDialog :: struct #raw_union { @@ -3646,8 +3976,8 @@ IFileOpenDialog :: struct #raw_union { } IFileOpenDialogVtbl :: struct { using IFileDialogVtbl: IFileDialogVtbl, - GetResults: proc "stdcall" (this: ^IFileOpenDialog, ppenum: ^^IShellItemArray) -> HRESULT, - GetSelectedItems: proc "stdcall" (this: ^IFileOpenDialog, ppsai: ^^IShellItemArray) -> HRESULT, + GetResults: proc "system" (this: ^IFileOpenDialog, ppenum: ^^IShellItemArray) -> HRESULT, + GetSelectedItems: proc "system" (this: ^IFileOpenDialog, ppsai: ^^IShellItemArray) -> HRESULT, } IPropertyStore :: struct #raw_union { @@ -3656,11 +3986,11 @@ IPropertyStore :: struct #raw_union { } IPropertyStoreVtbl :: struct { using IUnknownVtbl: IUnknownVtbl, - GetCount: proc "stdcall" (this: ^IPropertyStore, cProps: ^DWORD) -> HRESULT, - GetAt: proc "stdcall" (this: ^IPropertyStore, iProp: DWORD, pkey: ^PROPERTYKEY) -> HRESULT, - GetValue: proc "stdcall" (this: ^IPropertyStore, key: REFPROPERTYKEY, pv: ^PROPVARIANT) -> HRESULT, - SetValue: proc "stdcall" (this: ^IPropertyStore, key: REFPROPERTYKEY, propvar: REFPROPVARIANT) -> HRESULT, - Commit: proc "stdcall" (this: ^IPropertyStore) -> HRESULT, + GetCount: proc "system" (this: ^IPropertyStore, cProps: ^DWORD) -> HRESULT, + GetAt: proc "system" (this: ^IPropertyStore, iProp: DWORD, pkey: ^PROPERTYKEY) -> HRESULT, + GetValue: proc "system" (this: ^IPropertyStore, key: REFPROPERTYKEY, pv: ^PROPVARIANT) -> HRESULT, + SetValue: proc "system" (this: ^IPropertyStore, key: REFPROPERTYKEY, propvar: REFPROPVARIANT) -> HRESULT, + Commit: proc "system" (this: ^IPropertyStore) -> HRESULT, } IPropertyDescriptionList :: struct #raw_union { @@ -3669,8 +3999,8 @@ IPropertyDescriptionList :: struct #raw_union { } IPropertyDescriptionListVtbl :: struct { using IUnknownVtbl: IUnknownVtbl, - GetCount: proc "stdcall" (this: ^IPropertyDescriptionList, pcElem: ^UINT) -> HRESULT, - GetAt: proc "stdcall" (this: ^IPropertyDescriptionList, iElem: UINT, riid: REFIID, ppv: ^rawptr) -> HRESULT, + GetCount: proc "system" (this: ^IPropertyDescriptionList, pcElem: ^UINT) -> HRESULT, + GetAt: proc "system" (this: ^IPropertyDescriptionList, iElem: UINT, riid: REFIID, ppv: ^rawptr) -> HRESULT, } IFileOperationProgressSink :: struct #raw_union { @@ -3679,22 +4009,22 @@ IFileOperationProgressSink :: struct #raw_union { } IFileOperationProgressSinkVtbl :: struct { using IUnknownVtbl: IUnknownVtbl, - StartOperations: proc "stdcall" (this: ^IFileOperationProgressSink) -> HRESULT, - FinishOperations: proc "stdcall" (this: ^IFileOperationProgressSink, hrResult: HRESULT) -> HRESULT, - PreRenameItem: proc "stdcall" (this: ^IFileOperationProgressSink, dwFlags: DWORD, psiItem: ^IShellItem, pszNewName: LPCWSTR) -> HRESULT, - PostRenameItem: proc "stdcall" (this: ^IFileOperationProgressSink, dwFlags: DWORD, psiItem: ^IShellItem, pszNewName: LPCWSTR, hrRename: HRESULT, psiNewlyCreated: ^IShellItem) -> HRESULT, - PreMoveItem: proc "stdcall" (this: ^IFileOperationProgressSink, dwFlags: DWORD, psiItem: ^IShellItem, psiDestinationFolder: ^IShellItem, pszNewName: LPCWSTR) -> HRESULT, - PostMoveItem: proc "stdcall" (this: ^IFileOperationProgressSink, dwFlags: DWORD, psiItem: ^IShellItem, psiDestinationFolder: ^IShellItem, pszNewName: LPCWSTR, hrMove: HRESULT, psiNewlyCreated: ^IShellItem) -> HRESULT, - PreCopyItem: proc "stdcall" (this: ^IFileOperationProgressSink, dwFlags: DWORD, psiItem: ^IShellItem, psiDestinationFolder: ^IShellItem, pszNewName: LPCWSTR) -> HRESULT, - PostCopyItem: proc "stdcall" (this: ^IFileOperationProgressSink, dwFlags: DWORD, psiItem: ^IShellItem, psiDestinationFolder: ^IShellItem, pszNewName: LPCWSTR, hrMove: HRESULT, psiNewlyCreated: ^IShellItem) -> HRESULT, - PreDeleteItem: proc "stdcall" (this: ^IFileOperationProgressSink, dwFlags: DWORD, psiItem: ^IShellItem) -> HRESULT, - PostDeleteItem: proc "stdcall" (this: ^IFileOperationProgressSink, dwFlags: DWORD, psiItem: ^IShellItem, hrDelete: HRESULT, psiNewlyCreated: ^IShellItem) -> HRESULT, - PreNewItem: proc "stdcall" (this: ^IFileOperationProgressSink, dwFlags: DWORD, psiDestinationFolder: ^IShellItem, pszNewName: LPCWSTR) -> HRESULT, - PostNewItem: proc "stdcall" (this: ^IFileOperationProgressSink, dwFlags: DWORD, psiDestinationFolder: ^IShellItem, pszNewName: LPCWSTR, pszTemplateName: LPCWSTR, dwFileAttributes: DWORD, hrNew: HRESULT, psiNewItem: ^IShellItem) -> HRESULT, - UpdateProgress: proc "stdcall" (this: ^IFileOperationProgressSink, iWorkTotal: UINT, iWorkSoFar: UINT) -> HRESULT, - ResetTimer: proc "stdcall" (this: ^IFileOperationProgressSink) -> HRESULT, - PauseTimer: proc "stdcall" (this: ^IFileOperationProgressSink) -> HRESULT, - ResumeTimer: proc "stdcall" (this: ^IFileOperationProgressSink) -> HRESULT, + StartOperations: proc "system" (this: ^IFileOperationProgressSink) -> HRESULT, + FinishOperations: proc "system" (this: ^IFileOperationProgressSink, hrResult: HRESULT) -> HRESULT, + PreRenameItem: proc "system" (this: ^IFileOperationProgressSink, dwFlags: DWORD, psiItem: ^IShellItem, pszNewName: LPCWSTR) -> HRESULT, + PostRenameItem: proc "system" (this: ^IFileOperationProgressSink, dwFlags: DWORD, psiItem: ^IShellItem, pszNewName: LPCWSTR, hrRename: HRESULT, psiNewlyCreated: ^IShellItem) -> HRESULT, + PreMoveItem: proc "system" (this: ^IFileOperationProgressSink, dwFlags: DWORD, psiItem: ^IShellItem, psiDestinationFolder: ^IShellItem, pszNewName: LPCWSTR) -> HRESULT, + PostMoveItem: proc "system" (this: ^IFileOperationProgressSink, dwFlags: DWORD, psiItem: ^IShellItem, psiDestinationFolder: ^IShellItem, pszNewName: LPCWSTR, hrMove: HRESULT, psiNewlyCreated: ^IShellItem) -> HRESULT, + PreCopyItem: proc "system" (this: ^IFileOperationProgressSink, dwFlags: DWORD, psiItem: ^IShellItem, psiDestinationFolder: ^IShellItem, pszNewName: LPCWSTR) -> HRESULT, + PostCopyItem: proc "system" (this: ^IFileOperationProgressSink, dwFlags: DWORD, psiItem: ^IShellItem, psiDestinationFolder: ^IShellItem, pszNewName: LPCWSTR, hrMove: HRESULT, psiNewlyCreated: ^IShellItem) -> HRESULT, + PreDeleteItem: proc "system" (this: ^IFileOperationProgressSink, dwFlags: DWORD, psiItem: ^IShellItem) -> HRESULT, + PostDeleteItem: proc "system" (this: ^IFileOperationProgressSink, dwFlags: DWORD, psiItem: ^IShellItem, hrDelete: HRESULT, psiNewlyCreated: ^IShellItem) -> HRESULT, + PreNewItem: proc "system" (this: ^IFileOperationProgressSink, dwFlags: DWORD, psiDestinationFolder: ^IShellItem, pszNewName: LPCWSTR) -> HRESULT, + PostNewItem: proc "system" (this: ^IFileOperationProgressSink, dwFlags: DWORD, psiDestinationFolder: ^IShellItem, pszNewName: LPCWSTR, pszTemplateName: LPCWSTR, dwFileAttributes: DWORD, hrNew: HRESULT, psiNewItem: ^IShellItem) -> HRESULT, + UpdateProgress: proc "system" (this: ^IFileOperationProgressSink, iWorkTotal: UINT, iWorkSoFar: UINT) -> HRESULT, + ResetTimer: proc "system" (this: ^IFileOperationProgressSink) -> HRESULT, + PauseTimer: proc "system" (this: ^IFileOperationProgressSink) -> HRESULT, + ResumeTimer: proc "system" (this: ^IFileOperationProgressSink) -> HRESULT, } IFileSaveDialog :: struct #raw_union { @@ -3703,11 +4033,11 @@ IFileSaveDialog :: struct #raw_union { } IFileSaveDialogVtbl :: struct { using IFileDialogVtbl: IFileDialogVtbl, - SetSaveAsItem: proc "stdcall" (this: ^IFileSaveDialog, psi: ^IShellItem) -> HRESULT, - SetProperties: proc "stdcall" (this: ^IFileSaveDialog, pStore: ^IPropertyStore) -> HRESULT, - SetCollectedProperties: proc "stdcall" (this: ^IFileSaveDialog, pList: ^IPropertyDescriptionList, fAppendDefault: BOOL) -> HRESULT, - GetProperties: proc "stdcall" (this: ^IFileSaveDialog, ppStore: ^^IPropertyStore) -> HRESULT, - ApplyProperties: proc "stdcall" (this: ^IFileSaveDialog, psi: ^IShellItem, pStore: ^IPropertyStore, hwnd: HWND, pSink: ^IFileOperationProgressSink) -> HRESULT, + SetSaveAsItem: proc "system" (this: ^IFileSaveDialog, psi: ^IShellItem) -> HRESULT, + SetProperties: proc "system" (this: ^IFileSaveDialog, pStore: ^IPropertyStore) -> HRESULT, + SetCollectedProperties: proc "system" (this: ^IFileSaveDialog, pList: ^IPropertyDescriptionList, fAppendDefault: BOOL) -> HRESULT, + GetProperties: proc "system" (this: ^IFileSaveDialog, ppStore: ^^IPropertyStore) -> HRESULT, + ApplyProperties: proc "system" (this: ^IFileSaveDialog, psi: ^IShellItem, pStore: ^IPropertyStore, hwnd: HWND, pSink: ^IFileOperationProgressSink) -> HRESULT, } ITaskbarList :: struct #raw_union { @@ -3716,11 +4046,11 @@ ITaskbarList :: struct #raw_union { } ITaskbarListVtbl :: struct { using IUnknownVtbl: IUnknownVtbl, - HrInit: proc "stdcall" (this: ^ITaskbarList) -> HRESULT, - AddTab: proc "stdcall" (this: ^ITaskbarList, hwnd: HWND) -> HRESULT, - DeleteTab: proc "stdcall" (this: ^ITaskbarList, hwnd: HWND) -> HRESULT, - ActivateTab: proc "stdcall" (this: ^ITaskbarList, hwnd: HWND) -> HRESULT, - SetActiveAlt: proc "stdcall" (this: ^ITaskbarList, hwnd: HWND) -> HRESULT, + HrInit: proc "system" (this: ^ITaskbarList) -> HRESULT, + AddTab: proc "system" (this: ^ITaskbarList, hwnd: HWND) -> HRESULT, + DeleteTab: proc "system" (this: ^ITaskbarList, hwnd: HWND) -> HRESULT, + ActivateTab: proc "system" (this: ^ITaskbarList, hwnd: HWND) -> HRESULT, + SetActiveAlt: proc "system" (this: ^ITaskbarList, hwnd: HWND) -> HRESULT, } ITaskbarList2 :: struct #raw_union { @@ -3729,7 +4059,7 @@ ITaskbarList2 :: struct #raw_union { } ITaskbarList2Vtbl :: struct { using ITaskbarListVtbl: ITaskbarListVtbl, - MarkFullscreenWindow: proc "stdcall" (this: ^ITaskbarList2, hwnd: HWND, fFullscreen: BOOL) -> HRESULT, + MarkFullscreenWindow: proc "system" (this: ^ITaskbarList2, hwnd: HWND, fFullscreen: BOOL) -> HRESULT, } TBPFLAG :: enum c_int { @@ -3774,18 +4104,18 @@ ITaskbarList3 :: struct #raw_union { } ITaskbarList3Vtbl :: struct { using ITaskbarList2Vtbl: ITaskbarList2Vtbl, - SetProgressValue: proc "stdcall" (this: ^ITaskbarList3, hwnd: HWND, ullCompleted: ULONGLONG, ullTotal: ULONGLONG) -> HRESULT, - SetProgressState: proc "stdcall" (this: ^ITaskbarList3, hwnd: HWND, tbpFlags: TBPFLAG) -> HRESULT, - RegisterTab: proc "stdcall" (this: ^ITaskbarList3, hwndTab: HWND, hwndMDI: HWND) -> HRESULT, - UnregisterTab: proc "stdcall" (this: ^ITaskbarList3, hwndTab: HWND) -> HRESULT, - SetTabOrder: proc "stdcall" (this: ^ITaskbarList3, hwndTab: HWND, hwndInsertBefore: HWND) -> HRESULT, - SetTabActive: proc "stdcall" (this: ^ITaskbarList3, hwndTab: HWND, hwndMDI: HWND, dwReserved: DWORD) -> HRESULT, - ThumbBarAddButtons: proc "stdcall" (this: ^ITaskbarList3, hwnd: HWND, cButtons: UINT, pButton: LPTHUMBBUTTON) -> HRESULT, - ThumbBarUpdateButtons: proc "stdcall" (this: ^ITaskbarList3, hwnd: HWND, cButtons: UINT, pButton: LPTHUMBBUTTON) -> HRESULT, - ThumbBarSetImageList: proc "stdcall" (this: ^ITaskbarList3, hwnd: HWND, himl: HIMAGELIST) -> HRESULT, - SetOverlayIcon: proc "stdcall" (this: ^ITaskbarList3, hwnd: HWND, hIcon: HICON, pszDescription: LPCWSTR) -> HRESULT, - SetThumbnailTooltip: proc "stdcall" (this: ^ITaskbarList3, hwnd: HWND, pszTip: LPCWSTR) -> HRESULT, - SetThumbnailClip: proc "stdcall" (this: ^ITaskbarList3, hwnd: HWND, prcClip: ^RECT) -> HRESULT, + SetProgressValue: proc "system" (this: ^ITaskbarList3, hwnd: HWND, ullCompleted: ULONGLONG, ullTotal: ULONGLONG) -> HRESULT, + SetProgressState: proc "system" (this: ^ITaskbarList3, hwnd: HWND, tbpFlags: TBPFLAG) -> HRESULT, + RegisterTab: proc "system" (this: ^ITaskbarList3, hwndTab: HWND, hwndMDI: HWND) -> HRESULT, + UnregisterTab: proc "system" (this: ^ITaskbarList3, hwndTab: HWND) -> HRESULT, + SetTabOrder: proc "system" (this: ^ITaskbarList3, hwndTab: HWND, hwndInsertBefore: HWND) -> HRESULT, + SetTabActive: proc "system" (this: ^ITaskbarList3, hwndTab: HWND, hwndMDI: HWND, dwReserved: DWORD) -> HRESULT, + ThumbBarAddButtons: proc "system" (this: ^ITaskbarList3, hwnd: HWND, cButtons: UINT, pButton: LPTHUMBBUTTON) -> HRESULT, + ThumbBarUpdateButtons: proc "system" (this: ^ITaskbarList3, hwnd: HWND, cButtons: UINT, pButton: LPTHUMBBUTTON) -> HRESULT, + ThumbBarSetImageList: proc "system" (this: ^ITaskbarList3, hwnd: HWND, himl: HIMAGELIST) -> HRESULT, + SetOverlayIcon: proc "system" (this: ^ITaskbarList3, hwnd: HWND, hIcon: HICON, pszDescription: LPCWSTR) -> HRESULT, + SetThumbnailTooltip: proc "system" (this: ^ITaskbarList3, hwnd: HWND, pszTip: LPCWSTR) -> HRESULT, + SetThumbnailClip: proc "system" (this: ^ITaskbarList3, hwnd: HWND, prcClip: ^RECT) -> HRESULT, } MEMORYSTATUSEX :: struct { @@ -3950,6 +4280,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 // @@ -4141,36 +4535,36 @@ DNS_STATUS :: distinct DWORD // zero is success DNS_INFO_NO_RECORDS :: 9501 DNS_QUERY_NO_RECURSION :: 0x00000004 -DNS_RECORD :: struct { - pNext: ^DNS_RECORD, - pName: cstring, - wType: WORD, - wDataLength: USHORT, - Flags: DWORD, - dwTtl: DWORD, - _: DWORD, - Data: struct #raw_union { - CNAME: DNS_PTR_DATAA, - A: u32be, // Ipv4 Address - AAAA: u128be, // Ipv6 Address - TXT: DNS_TXT_DATAA, - NS: DNS_PTR_DATAA, - MX: DNS_MX_DATAA, - SRV: DNS_SRV_DATAA, - }, +DNS_RECORD :: struct { // aka DNS_RECORDA + pNext: ^DNS_RECORD, + pName: cstring, + wType: WORD, + wDataLength: USHORT, + Flags: DWORD, + dwTtl: DWORD, + _: DWORD, + Data: struct #raw_union #align(4) { + CNAME: DNS_PTR_DATAA, + A: u32be, // Ipv4 Address + AAAA: u128be, // Ipv6 Address + TXT: DNS_TXT_DATAA, + NS: DNS_PTR_DATAA, + MX: DNS_MX_DATAA, + SRV: DNS_SRV_DATAA, + }, } DNS_TXT_DATAA :: struct { - dwStringCount: DWORD, - pStringArray: cstring, + dwStringCount: DWORD, + pStringArray: cstring, } DNS_PTR_DATAA :: cstring DNS_MX_DATAA :: struct { - pNameExchange: cstring, // the hostname - wPreference: WORD, // lower values preferred - _: WORD, // padding. + pNameExchange: cstring, // the hostname + wPreference: WORD, // lower values preferred + _: WORD, // padding. } DNS_SRV_DATAA :: struct { pNameTarget: cstring, @@ -4184,3 +4578,96 @@ SOCKADDR :: struct { sa_family: ADDRESS_FAMILY, sa_data: [14]CHAR, } + +ENUMRESNAMEPROCW :: #type proc (hModule: HMODULE, lpType: LPCWSTR, lpName: LPWSTR, lParam: LONG_PTR)-> BOOL +ENUMRESTYPEPROCW :: #type proc (hModule: HMODULE, lpType: LPCWSTR, lParam: LONG_PTR)-> BOOL +ENUMRESLANGPROCW :: #type proc (hModule: HMODULE, lpType: LPCWSTR, lpName: LPWSTR, wIDLanguage: LANGID, lParam: LONG_PTR)-> BOOL + +DTR_Control :: enum byte { + Disable = 0, + Enable = 1, + 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 ee536e0a8..da979a3e3 100644 --- a/core/sys/windows/user32.odin +++ b/core/sys/windows/user32.odin @@ -1,20 +1,32 @@ -// +build windows +#+build windows package sys_windows +import "base:intrinsics" foreign import user32 "system:User32.lib" -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign user32 { - GetClassInfoW :: proc(hInstance: HINSTANCE, lpClassNAme: LPCWSTR, lpWndClass: ^WNDCLASSW) -> BOOL --- - GetClassInfoExW :: proc(hInsatnce: HINSTANCE, lpszClass: LPCWSTR, lpwcx: ^WNDCLASSEXW) -> BOOL --- + GetClassInfoW :: proc(hInstance: HINSTANCE, lpClassName: LPCWSTR, lpWndClass: ^WNDCLASSW) -> BOOL --- + GetClassInfoExW :: proc(hInstance: HINSTANCE, lpszClass: LPCWSTR, lpwcx: ^WNDCLASSEXW) -> BOOL --- - GetClassLongW :: proc(hWnd: HWND, nIndex: c_int) -> DWORD --- - SetClassLongW :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG) -> DWORD --- + GetClassLongW :: proc(hWnd: HWND, nIndex: INT) -> DWORD --- + SetClassLongW :: proc(hWnd: HWND, nIndex: INT, dwNewLong: LONG) -> DWORD --- - GetWindowLongW :: proc(hWnd: HWND, nIndex: c_int) -> LONG --- - SetWindowLongW :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG) -> LONG --- + GetWindowLongW :: proc(hWnd: HWND, nIndex: INT) -> LONG --- + SetWindowLongW :: proc(hWnd: HWND, nIndex: INT, dwNewLong: LONG) -> LONG --- - GetClassNameW :: proc(hWnd: HWND, lpClassName: LPWSTR, nMaxCount: c_int) -> c_int --- + GetClassNameW :: proc(hWnd: HWND, lpClassName: LPWSTR, nMaxCount: INT) -> INT --- + + GetParent :: proc(hWnd: HWND) -> HWND --- + 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 --- @@ -25,10 +37,7 @@ foreign user32 { lpClassName: LPCWSTR, lpWindowName: LPCWSTR, dwStyle: DWORD, - X: c_int, - Y: c_int, - nWidth: c_int, - nHeight: c_int, + X, Y, nWidth, nHeight: INT, hWndParent: HWND, hMenu: HMENU, hInstance: HINSTANCE, @@ -37,22 +46,34 @@ foreign user32 { DestroyWindow :: proc(hWnd: HWND) -> BOOL --- - ShowWindow :: proc(hWnd: HWND, nCmdShow: c_int) -> BOOL --- + ShowWindow :: proc(hWnd: HWND, nCmdShow: INT) -> BOOL --- IsWindow :: proc(hWnd: HWND) -> BOOL --- + IsWindowVisible :: proc(hwnd: HWND) -> BOOL --- + IsWindowEnabled :: proc(hwnd: HWND) -> BOOL --- + IsIconic :: proc(hwnd: HWND) -> BOOL --- + IsZoomed :: proc(hwnd: HWND) -> BOOL --- BringWindowToTop :: proc(hWnd: HWND) -> BOOL --- GetTopWindow :: proc(hWnd: HWND) -> HWND --- SetForegroundWindow :: proc(hWnd: HWND) -> BOOL --- GetForegroundWindow :: proc() -> HWND --- + GetDesktopWindow :: proc() -> HWND --- UpdateWindow :: proc(hWnd: HWND) -> BOOL --- SetActiveWindow :: proc(hWnd: HWND) -> HWND --- GetActiveWindow :: proc() -> HWND --- - - GetMessageW :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT) -> BOOL --- + RedrawWindow :: proc(hwnd: HWND, lprcUpdate: LPRECT, hrgnUpdate: HRGN, flags: RedrawWindowFlags) -> BOOL --- + SetParent :: proc(hWndChild: HWND, hWndNewParent: HWND) -> HWND --- + SetPropW :: proc(hWnd: HWND, lpString: LPCWSTR, hData: HANDLE) -> BOOL --- + GetPropW :: proc(hWnd: HWND, lpString: LPCWSTR) -> HANDLE --- + RemovePropW :: proc(hWnd: HWND, lpString: LPCWSTR) -> HANDLE --- + EnumPropsW :: proc(hWnd: HWND, lpEnumFunc: PROPENUMPROCW) -> INT --- + EnumPropsExW :: proc(hWnd: HWND, lpEnumFunc: PROPENUMPROCW, lParam: LPARAM) -> INT --- + GetMessageW :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT) -> INT --- TranslateMessage :: proc(lpMsg: ^MSG) -> BOOL --- DispatchMessageW :: proc(lpMsg: ^MSG) -> LRESULT --- WaitMessage :: proc() -> BOOL --- + MsgWaitForMultipleObjects :: proc(nCount: DWORD, pHandles: ^HANDLE, fWaitAll: BOOL, dwMilliseconds: DWORD, dwWakeMask: DWORD) -> DWORD --- PeekMessageA :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT, wRemoveMsg: UINT) -> BOOL --- PeekMessageW :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT, wRemoveMsg: UINT) -> BOOL --- @@ -65,7 +86,7 @@ foreign user32 { PostThreadMessageA :: proc(idThread: DWORD, Msg: UINT, wParam: WPARAM, lParam: LPARAM) -> BOOL --- PostThreadMessageW :: proc(idThread: DWORD, Msg: UINT, wParam: WPARAM, lParam: LPARAM) -> BOOL --- - PostQuitMessage :: proc(nExitCode: c_int) --- + PostQuitMessage :: proc(nExitCode: INT) --- GetQueueStatus :: proc(flags: UINT) -> DWORD --- @@ -79,25 +100,26 @@ foreign user32 { LoadIconA :: proc(hInstance: HINSTANCE, lpIconName: LPCSTR) -> HICON --- LoadIconW :: proc(hInstance: HINSTANCE, lpIconName: LPCWSTR) -> HICON --- + GetIconInfoExW :: proc(hIcon: HICON, piconinfo: PICONINFOEXW) -> BOOL --- LoadCursorA :: proc(hInstance: HINSTANCE, lpCursorName: LPCSTR) -> HCURSOR --- LoadCursorW :: proc(hInstance: HINSTANCE, lpCursorName: LPCWSTR) -> HCURSOR --- - LoadImageW :: proc(hInst: HINSTANCE, name: LPCWSTR, type: UINT, cx: c_int, cy: c_int, fuLoad: UINT) -> HANDLE --- + LoadImageW :: proc(hInst: HINSTANCE, name: LPCWSTR, type: UINT, cx, cy: INT, fuLoad: UINT) -> HANDLE --- + + CreateIcon :: proc(hInstance: HINSTANCE, nWidth, nHeight: INT, cPlanes: BYTE, cBitsPixel: BYTE, lpbANDbits: PBYTE, lpbXORbits: PBYTE) -> HICON --- + CreateIconFromResource :: proc(presbits: PBYTE, dwResSize: DWORD, fIcon: BOOL, dwVer: DWORD) -> HICON --- + DestroyIcon :: proc(hIcon: HICON) -> BOOL --- + DrawIcon :: proc(hDC: HDC, X, Y: INT, hIcon: HICON) -> BOOL --- + + CreateCursor :: proc(hInst: HINSTANCE, xHotSpot, yHotSpot, nWidth, nHeight: INT, pvANDPlane: PVOID, pvXORPlane: PVOID) -> HCURSOR --- + DestroyCursor :: proc(hCursor: HCURSOR) -> BOOL --- GetWindowRect :: proc(hWnd: HWND, lpRect: LPRECT) -> BOOL --- GetClientRect :: proc(hWnd: HWND, lpRect: LPRECT) -> BOOL --- ClientToScreen :: proc(hWnd: HWND, lpPoint: LPPOINT) -> BOOL --- ScreenToClient :: proc(hWnd: HWND, lpPoint: LPPOINT) -> BOOL --- - SetWindowPos :: proc( - hWnd: HWND, - hWndInsertAfter: HWND, - X: c_int, - Y: c_int, - cx: c_int, - cy: c_int, - uFlags: UINT, - ) -> BOOL --- - MoveWindow :: proc(hWnd: HWND, X, Y, hWidth, hHeight: c_int, bRepaint: BOOL) -> BOOL --- - GetSystemMetrics :: proc(nIndex: c_int) -> c_int --- + SetWindowPos :: proc(hWnd: HWND, hWndInsertAfter: HWND, X, Y, cx, cy: INT, uFlags: UINT) -> BOOL --- + MoveWindow :: proc(hWnd: HWND, X, Y, hWidth, hHeight: INT, bRepaint: BOOL) -> BOOL --- + GetSystemMetrics :: proc(nIndex: INT) -> INT --- AdjustWindowRect :: proc(lpRect: LPRECT, dwStyle: DWORD, bMenu: BOOL) -> BOOL --- AdjustWindowRectEx :: proc(lpRect: LPRECT, dwStyle: DWORD, bMenu: BOOL, dwExStyle: DWORD) -> BOOL --- AdjustWindowRectExForDpi :: proc(lpRect: LPRECT, dwStyle: DWORD, bMenu: BOOL, dwExStyle: DWORD, dpi: UINT) -> BOOL --- @@ -107,17 +129,36 @@ foreign user32 { GetWindowDC :: proc(hWnd: HWND) -> HDC --- GetDC :: proc(hWnd: HWND) -> HDC --- - ReleaseDC :: proc(hWnd: HWND, hDC: HDC) -> c_int --- + GetDCEx :: proc(hWnd: HWND, hrgnClip: HRGN, flags: DWORD) -> HDC --- + ReleaseDC :: proc(hWnd: HWND, hDC: HDC) -> INT --- - GetDlgCtrlID :: proc(hWnd: HWND) -> c_int --- - GetDlgItem :: proc(hDlg: HWND, nIDDlgItem: c_int) -> HWND --- + GetDlgCtrlID :: proc(hWnd: HWND) -> INT --- + GetDlgItem :: proc(hDlg: HWND, nIDDlgItem: INT) -> HWND --- + CreateMenu :: proc() -> HMENU --- CreatePopupMenu :: proc() -> HMENU --- + DeleteMenu :: proc(hMenu: HMENU, uPosition: UINT, uFlags: UINT) -> BOOL --- DestroyMenu :: proc(hMenu: HMENU) -> BOOL --- + InsertMenuW :: proc(hMenu: HMENU, uPosition: UINT, uFlags: UINT, uIDNewItem: UINT_PTR, lpNewItem: LPCWSTR) -> BOOL --- AppendMenuW :: proc(hMenu: HMENU, uFlags: UINT, uIDNewItem: UINT_PTR, lpNewItem: LPCWSTR) -> BOOL --- - TrackPopupMenu :: proc(hMenu: HMENU, uFlags: UINT, x: int, y: int, nReserved: int, hWnd: HWND, prcRect: ^RECT) -> i32 --- + GetMenu :: proc(hWnd: HWND) -> HMENU --- + SetMenu :: proc(hWnd: HWND, hMenu: HMENU) -> BOOL --- + TrackPopupMenu :: proc(hMenu: HMENU, uFlags: UINT, x, y: INT, nReserved: INT, hWnd: HWND, prcRect: ^RECT) -> INT --- RegisterWindowMessageW :: proc(lpString: LPCWSTR) -> UINT --- + CreateAcceleratorTableW :: proc(paccel: LPACCEL, cAccel: INT) -> HACCEL --- + DestroyAcceleratorTable :: proc(hAccel: HACCEL) -> BOOL --- + LoadAcceleratorsW :: proc(hInstance: HINSTANCE, lpTableName: LPCWSTR) -> HACCEL --- + TranslateAcceleratorW :: proc(hWnd: HWND, hAccTable: HACCEL, lpMsg: LPMSG) -> INT --- + CopyAcceleratorTableW :: proc(hAccelSrc: HACCEL, lpAccelDst: LPACCEL, cAccelEntries: INT) -> INT --- + + InsertMenuItemW :: proc(hmenu: HMENU, item: UINT, fByPosition: BOOL, lpmi: LPMENUITEMINFOW) -> BOOL --- + GetMenuItemInfoW :: proc(hmenu: HMENU, item: UINT, fByPosition: BOOL, lpmii: LPMENUITEMINFOW) -> BOOL --- + SetMenuItemInfoW :: proc(hmenu: HMENU, item: UINT, fByPositon: BOOL, lpmii: LPMENUITEMINFOW) -> BOOL --- + GetMenuDefaultItem :: proc(hMenu: HMENU, fByPos: UINT, gmdiFlags: UINT) -> UINT --- + SetMenuDefaultItem :: proc(hMenu: HMENU, uItem: UINT, fByPos: UINT) -> BOOL --- + GetMenuItemRect :: proc(hWnd: HWND, hMenu: HMENU, uItem: UINT, lprcItem: LPRECT) -> c_int --- + GetUpdateRect :: proc(hWnd: HWND, lpRect: LPRECT, bErase: BOOL) -> BOOL --- ValidateRect :: proc(hWnd: HWND, lpRect: ^RECT) -> BOOL --- InvalidateRect :: proc(hWnd: HWND, lpRect: ^RECT, bErase: BOOL) -> BOOL --- @@ -130,30 +171,35 @@ foreign user32 { ReleaseCapture :: proc() -> BOOL --- TrackMouseEvent :: proc(lpEventTrack: LPTRACKMOUSEEVENT) -> BOOL --- - GetKeyState :: proc(nVirtKey: c_int) -> SHORT --- - GetAsyncKeyState :: proc(vKey: c_int) -> SHORT --- - + GetKeyState :: proc(nVirtKey: INT) -> SHORT --- + GetAsyncKeyState :: proc(vKey: INT) -> SHORT --- + GetKeyboardState :: proc(lpKeyState: PBYTE) -> BOOL --- MapVirtualKeyW :: proc(uCode: UINT, uMapType: UINT) -> UINT --- + ToUnicode :: proc(nVirtKey: UINT, wScanCode: UINT, lpKeyState: ^BYTE, pwszBuff: LPWSTR, cchBuff: INT, wFlags: UINT) -> INT --- - SetWindowsHookExW :: proc(idHook: c_int, lpfn: HOOKPROC, hmod: HINSTANCE, dwThreadId: DWORD) -> HHOOK --- + SetWindowsHookExW :: proc(idHook: INT, lpfn: HOOKPROC, hmod: HINSTANCE, dwThreadId: DWORD) -> HHOOK --- UnhookWindowsHookEx :: proc(hhk: HHOOK) -> BOOL --- - CallNextHookEx :: proc(hhk: HHOOK, nCode: c_int, wParam: WPARAM, lParam: LPARAM) -> LRESULT --- + CallNextHookEx :: proc(hhk: HHOOK, nCode: INT, wParam: WPARAM, lParam: LPARAM) -> LRESULT --- SetTimer :: proc(hWnd: HWND, nIDEvent: UINT_PTR, uElapse: UINT, lpTimerFunc: TIMERPROC) -> UINT_PTR --- KillTimer :: proc(hWnd: HWND, uIDEvent: UINT_PTR) -> BOOL --- - // MessageBoxA :: proc(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT) -> c_int --- - MessageBoxW :: proc(hWnd: HWND, lpText: LPCWSTR, lpCaption: LPCWSTR, uType: UINT) -> c_int --- - // MessageBoxExA :: proc(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT, wLanguageId: WORD) -> c_int --- - MessageBoxExW :: proc(hWnd: HWND, lpText: LPCWSTR, lpCaption: LPCWSTR, uType: UINT, wLanguageId: WORD) -> c_int --- + // MessageBoxA :: proc(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT) -> INT --- + MessageBoxW :: proc(hWnd: HWND, lpText: LPCWSTR, lpCaption: LPCWSTR, uType: UINT) -> INT --- + // MessageBoxExA :: proc(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT, wLanguageId: WORD) -> INT --- + MessageBoxExW :: proc(hWnd: HWND, lpText: LPCWSTR, lpCaption: LPCWSTR, uType: UINT, wLanguageId: WORD) -> INT --- ClipCursor :: proc(lpRect: LPRECT) -> BOOL --- GetCursorPos :: proc(lpPoint: LPPOINT) -> BOOL --- - SetCursorPos :: proc(X: c_int, Y: c_int) -> BOOL --- + SetCursorPos :: proc(X, Y: INT) -> BOOL --- SetCursor :: proc(hCursor: HCURSOR) -> HCURSOR --- + when !intrinsics.is_package_imported("raylib") { + ShowCursor :: proc(bShow: BOOL) -> INT --- + } + EnumDisplayDevicesW :: proc (lpDevice: LPCWSTR, iDevNum: DWORD, lpDisplayDevice: PDISPLAY_DEVICEW, dwFlags: DWORD) -> BOOL --- EnumDisplaySettingsW :: proc(lpszDeviceName: LPCWSTR, iModeNum: DWORD, lpDevMode: ^DEVMODEW) -> BOOL --- MonitorFromPoint :: proc(pt: POINT, dwFlags: Monitor_From_Flags) -> HMONITOR --- @@ -161,6 +207,11 @@ foreign user32 { MonitorFromWindow :: proc(hwnd: HWND, dwFlags: Monitor_From_Flags) -> HMONITOR --- EnumDisplayMonitors :: proc(hdc: HDC, lprcClip: LPRECT, lpfnEnum: Monitor_Enum_Proc, dwData: LPARAM) -> BOOL --- + EnumWindows :: proc(lpEnumFunc: Window_Enum_Proc, lParam: LPARAM) -> BOOL --- + + IsProcessDPIAware :: proc() -> BOOL --- + SetProcessDPIAware :: proc() -> BOOL --- + SetThreadDpiAwarenessContext :: proc(dpiContext: DPI_AWARENESS_CONTEXT) -> DPI_AWARENESS_CONTEXT --- GetThreadDpiAwarenessContext :: proc() -> DPI_AWARENESS_CONTEXT --- GetWindowDpiAwarenessContext :: proc(hwnd: HWND) -> DPI_AWARENESS_CONTEXT --- @@ -195,14 +246,14 @@ foreign user32 { lpdwResult: PDWORD_PTR, ) -> LRESULT --- - GetSysColor :: proc(nIndex: c_int) -> DWORD --- - GetSysColorBrush :: proc(nIndex: c_int) -> HBRUSH --- - SetSysColors :: proc(cElements: c_int, lpaElements: ^INT, lpaRgbValues: ^COLORREF) -> BOOL --- + GetSysColor :: proc(nIndex: INT) -> DWORD --- + GetSysColorBrush :: proc(nIndex: INT) -> HBRUSH --- + SetSysColors :: proc(cElements: INT, lpaElements: ^INT, lpaRgbValues: ^COLORREF) -> BOOL --- MessageBeep :: proc(uType: UINT) -> BOOL --- IsDialogMessageW :: proc(hDlg: HWND, lpMsg: LPMSG) -> BOOL --- - GetWindowTextLengthW :: proc(hWnd: HWND) -> c_int --- - GetWindowTextW :: proc(hWnd: HWND, lpString: LPWSTR, nMaxCount: c_int) -> c_int --- + GetWindowTextLengthW :: proc(hWnd: HWND) -> INT --- + GetWindowTextW :: proc(hWnd: HWND, lpString: LPWSTR, nMaxCount: INT) -> INT --- SetWindowTextW :: proc(hWnd: HWND, lpString: LPCWSTR) -> BOOL --- CallWindowProcW :: proc(lpPrevWndFunc: WNDPROC, hWnd: HWND, Msg: UINT, wParam: WPARAM, lParam: LPARAM) -> LRESULT --- EnableWindow :: proc(hWnd: HWND, bEnable: BOOL) -> BOOL --- @@ -215,12 +266,20 @@ foreign user32 { GetRegisteredRawInputDevices :: proc(pRawInputDevices: PRAWINPUTDEVICE, puiNumDevices: PUINT, cbSize: UINT) -> UINT --- RegisterRawInputDevices :: proc(pRawInputDevices: PCRAWINPUTDEVICE, uiNumDevices: UINT, cbSize: UINT) -> BOOL --- - SendInput :: proc(cInputs: UINT, pInputs: [^]INPUT, cbSize: c_int) -> UINT --- + SendInput :: proc(cInputs: UINT, pInputs: [^]INPUT, cbSize: INT) -> UINT --- SetLayeredWindowAttributes :: proc(hWnd: HWND, crKey: COLORREF, bAlpha: BYTE, dwFlags: DWORD) -> BOOL --- FillRect :: proc(hDC: HDC, lprc: ^RECT, hbr: HBRUSH) -> int --- - EqualRect :: proc(lprc1: ^RECT, lprc2: ^RECT) -> BOOL --- + EqualRect :: proc(lprc1, lprc2: ^RECT) -> BOOL --- + OffsetRect :: proc(lprc1: ^RECT, dx, dy: INT) -> BOOL --- + InflateRect :: proc(lprc1: ^RECT, dx, dy: INT) -> BOOL --- + IntersectRect :: proc(lprcDst, lprcSrc1, lprcSrc2: ^RECT) -> BOOL --- + SubtractRect :: proc(lprcDst, lprcSrc1, lprcSrc2: ^RECT) -> BOOL --- + UnionRect :: proc(lprcDst, lprcSrc1, lprcSrc2: ^RECT) -> BOOL --- + IsRectEmpty :: proc(lprc: ^RECT) -> BOOL --- + SetRectEmpty :: proc(lprc: ^RECT) -> BOOL --- + CopyRect :: proc(lprcDst, lprcSrc: ^RECT) -> BOOL --- GetWindowInfo :: proc(hwnd: HWND, pwi: PWINDOWINFO) -> BOOL --- GetWindowPlacement :: proc(hWnd: HWND, lpwndpl: ^WINDOWPLACEMENT) -> BOOL --- @@ -229,18 +288,41 @@ foreign user32 { CreateRectRgnIndirect :: proc(lprect: ^RECT) -> HRGN --- GetSystemMetricsForDpi :: proc(nIndex: int, dpi: UINT) -> int --- + GetCursorInfo :: proc(pci: PCURSORINFO) -> BOOL --- + GetSystemMenu :: proc(hWnd: HWND, bRevert: BOOL) -> HMENU --- EnableMenuItem :: proc(hMenu: HMENU, uIDEnableItem: UINT, uEnable: UINT) -> BOOL --- + MenuItemFromPoint :: proc(hWnd: HWND, hMenu: HMENU, ptScreen: POINT) -> INT --- + + DrawTextW :: proc(hdc: HDC, lpchText: LPCWSTR, cchText: INT, lprc: LPRECT, format: DrawTextFormat) -> INT --- + DrawTextExW :: proc(hdc: HDC, lpchText: LPCWSTR, cchText: INT, lprc: LPRECT, format: DrawTextFormat, lpdtp: PDRAWTEXTPARAMS) -> INT --- + + GetLocaleInfoEx :: proc(lpLocaleName: LPCWSTR, LCType: LCTYPE, lpLCData: LPWSTR, cchData: INT) -> INT --- + IsValidLocaleName :: proc(lpLocaleName: LPCWSTR) -> BOOL --- + ResolveLocaleName :: proc(lpNameToResolve: LPCWSTR, lpLocaleName: LPWSTR, cchLocaleName: INT) -> INT --- + IsValidCodePage :: proc(CodePage: UINT) -> BOOL --- + GetACP :: proc() -> CODEPAGE --- + GetCPInfoExW :: proc(CodePage: CODEPAGE, dwFlags: DWORD, lpCPInfoEx: LPCPINFOEXW) -> BOOL --- + + GetProcessWindowStation :: proc() -> HWINSTA --- + GetUserObjectInformationW :: proc(hObj: HANDLE, nIndex: GetUserObjectInformationFlags, pvInfo: PVOID, nLength: DWORD, lpnLengthNeeded: LPDWORD) -> BOOL --- + + OpenClipboard :: proc(hWndNewOwner: HWND) -> BOOL --- + CloseClipboard :: proc() -> BOOL --- + GetClipboardData :: proc(uFormat: UINT) -> HANDLE --- + SetClipboardData :: proc(uFormat: UINT, hMem: HANDLE) -> HANDLE --- + IsClipboardFormatAvailable :: proc(format: UINT) -> BOOL --- + EmptyClipboard :: proc() -> BOOL --- } -CreateWindowW :: #force_inline proc "stdcall" ( +CreateWindowW :: #force_inline proc "system" ( lpClassName: LPCTSTR, lpWindowName: LPCTSTR, dwStyle: DWORD, - X: c_int, - Y: c_int, - nWidth: c_int, - nHeight: c_int, + X: INT, + Y: INT, + nWidth: INT, + nHeight: INT, hWndParent: HWND, hMenu: HMENU, hInstance: HINSTANCE, @@ -263,13 +345,13 @@ CreateWindowW :: #force_inline proc "stdcall" ( } when ODIN_ARCH == .amd64 { - @(default_calling_convention="stdcall") + @(default_calling_convention="system") foreign user32 { - GetClassLongPtrW :: proc(hWnd: HWND, nIndex: c_int) -> ULONG_PTR --- - SetClassLongPtrW :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG_PTR) -> ULONG_PTR --- + GetClassLongPtrW :: proc(hWnd: HWND, nIndex: INT) -> ULONG_PTR --- + SetClassLongPtrW :: proc(hWnd: HWND, nIndex: INT, dwNewLong: LONG_PTR) -> ULONG_PTR --- - GetWindowLongPtrW :: proc(hWnd: HWND, nIndex: c_int) -> LONG_PTR --- - SetWindowLongPtrW :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG_PTR) -> LONG_PTR --- + GetWindowLongPtrW :: proc(hWnd: HWND, nIndex: INT) -> LONG_PTR --- + SetWindowLongPtrW :: proc(hWnd: HWND, nIndex: INT, dwNewLong: LONG_PTR) -> LONG_PTR --- } } else when ODIN_ARCH == .i386 { GetClassLongPtrW :: GetClassLongW @@ -279,8 +361,8 @@ when ODIN_ARCH == .amd64 { SetWindowLongPtrW :: SetWindowLongW } -GET_SC_WPARAM :: #force_inline proc "contextless" (wParam: WPARAM) -> c_int { - return c_int(wParam) & 0xFFF0 +GET_SC_WPARAM :: #force_inline proc "contextless" (wParam: WPARAM) -> INT { + return INT(wParam) & 0xFFF0 } GET_WHEEL_DELTA_WPARAM :: #force_inline proc "contextless" (wParam: WPARAM) -> c_short { @@ -299,6 +381,11 @@ GET_XBUTTON_WPARAM :: #force_inline proc "contextless" (wParam: WPARAM) -> WORD return HIWORD(cast(DWORD)wParam) } +// Retrieves the input code from wParam in WM_INPUT message. +GET_RAWINPUT_CODE_WPARAM :: #force_inline proc "contextless" (wParam: WPARAM) -> RAWINPUT_CODE { + return RAWINPUT_CODE(wParam & 0xFF) +} + MAKEINTRESOURCEW :: #force_inline proc "contextless" (#any_int i: int) -> LPWSTR { return cast(LPWSTR)uintptr(WORD(i)) } @@ -309,7 +396,8 @@ Monitor_From_Flags :: enum DWORD { MONITOR_DEFAULTTONEAREST = 0x00000002, // Returns a handle to the display monitor that is nearest to the window } -Monitor_Enum_Proc :: #type proc "stdcall" (HMONITOR, HDC, LPRECT, LPARAM) -> BOOL +Monitor_Enum_Proc :: #type proc "system" (HMONITOR, HDC, LPRECT, LPARAM) -> BOOL +Window_Enum_Proc :: #type proc "system" (HWND, LPARAM) -> BOOL USER_DEFAULT_SCREEN_DPI :: 96 DPI_AWARENESS_CONTEXT :: distinct HANDLE @@ -319,6 +407,16 @@ DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE :: DPI_AWARENESS_CONTEXT(~uintptr(2)) DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 :: DPI_AWARENESS_CONTEXT(~uintptr(3)) // -4 DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED :: DPI_AWARENESS_CONTEXT(~uintptr(4)) // -5 +RAWINPUT_CODE :: enum { + // The input is in the regular message flow, + // the app is required to call DefWindowProc + // so that the system can perform clean ups. + RIM_INPUT = 0, + // The input is sink only. The app is expected + // to behave nicely. + RIM_INPUTSINK = 1, +} + RAWINPUTHEADER :: struct { dwType: DWORD, dwSize: DWORD, @@ -334,9 +432,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, }, @@ -412,7 +510,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, @@ -433,14 +531,25 @@ 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 -MOUSE_ATTRIUBTTES_CHANGED :: 0x04 +MOUSE_ATTRIBUTES_CHANGED :: 0x04 MOUSE_MOVE_NOCOALESCE :: 0x08 RI_MOUSE_BUTTON_1_DOWN :: 0x0001 @@ -467,8 +576,8 @@ WINDOWPLACEMENT :: struct { flags: UINT, showCmd: UINT, ptMinPosition: POINT, - ptMaxPosition: POINT, - rcNormalPosition: RECT, + ptMaxPosition: POINT, + rcNormalPosition: RECT, } WINDOWINFO :: struct { @@ -484,3 +593,253 @@ WINDOWINFO :: struct { wCreatorVersion: WORD, } PWINDOWINFO :: ^WINDOWINFO + +CURSORINFO :: struct { + cbSize: DWORD, + flags: DWORD, + hCursor: HCURSOR, + ptScreenPos: POINT, +} +PCURSORINFO :: ^CURSORINFO + + +DRAWTEXTPARAMS :: struct { + cbSize: UINT, + iTabLength: INT, + iLeftMargin: INT, + iRightMargin: INT, + uiLengthDrawn: UINT, +} +PDRAWTEXTPARAMS :: ^DRAWTEXTPARAMS + +DrawTextFormat :: enum UINT { + DT_TOP = 0x00000000, + DT_LEFT = 0x00000000, + DT_CENTER = 0x00000001, + DT_RIGHT = 0x00000002, + DT_VCENTER = 0x00000004, + DT_BOTTOM = 0x00000008, + DT_WORDBREAK = 0x00000010, + DT_SINGLELINE = 0x00000020, + DT_EXPANDTABS = 0x00000040, + DT_TABSTOP = 0x00000080, + DT_NOCLIP = 0x00000100, + DT_EXTERNALLEADING = 0x00000200, + DT_CALCRECT = 0x00000400, + DT_NOPREFIX = 0x00000800, + DT_INTERNAL = 0x00001000, + DT_EDITCONTROL = 0x00002000, + DT_PATH_ELLIPSIS = 0x00004000, + DT_END_ELLIPSIS = 0x00008000, + DT_MODIFYSTRING = 0x00010000, + DT_RTLREADING = 0x00020000, + DT_WORD_ELLIPSIS = 0x00040000, + DT_NOFULLWIDTHCHARBREAK = 0x00080000, + DT_HIDEPREFIX = 0x00100000, + DT_PREFIXONLY = 0x00200000, +} + +RedrawWindowFlags :: enum UINT { + RDW_INVALIDATE = 0x0001, + RDW_INTERNALPAINT = 0x0002, + RDW_ERASE = 0x0004, + RDW_VALIDATE = 0x0008, + RDW_NOINTERNALPAINT = 0x0010, + RDW_NOERASE = 0x0020, + RDW_NOCHILDREN = 0x0040, + RDW_ALLCHILDREN = 0x0080, + RDW_UPDATENOW = 0x0100, + RDW_ERASENOW = 0x0200, + RDW_FRAME = 0x0400, + RDW_NOFRAME = 0x0800, +} + +GetUserObjectInformationFlags :: enum INT { + UOI_FLAGS = 1, + UOI_NAME = 2, + UOI_TYPE = 3, + UOI_USER_SID = 4, + UOI_HEAPSIZE = 5, + UOI_IO = 6, + UOI_TIMERPROC_EXCEPTION_SUPPRESSION = 7, +} + +USEROBJECTFLAGS :: struct { + fInherit: BOOL, + fReserved: BOOL, + dwFlags: DWORD, +} + +PROPENUMPROCW :: #type proc(unnamedParam1: HWND, unnamedParam2: LPCWSTR, unnamedParam3: HANDLE) -> BOOL +PROPENUMPROCEXW :: #type proc(unnamedParam1: HWND, unnamedParam2: LPCWSTR, unnamedParam3: HANDLE, unnamedParam4: ULONG_PTR) -> BOOL + +RT_CURSOR :: LPWSTR(uintptr(0x00000001)) +RT_BITMAP :: LPWSTR(uintptr(0x00000002)) +RT_ICON :: LPWSTR(uintptr(0x00000003)) +RT_MENU :: LPWSTR(uintptr(0x00000004)) +RT_DIALOG :: LPWSTR(uintptr(0x00000005)) +RT_STRING :: LPWSTR(uintptr(0x00000006)) +RT_FONTDIR :: LPWSTR(uintptr(0x00000007)) +RT_FONT :: LPWSTR(uintptr(0x00000008)) +RT_ACCELERATOR :: LPWSTR(uintptr(0x00000009)) +RT_RCDATA :: LPWSTR(uintptr(0x0000000A)) +RT_MESSAGETABLE :: LPWSTR(uintptr(0x0000000B)) +RT_GROUP_CURSOR :: LPWSTR(uintptr(0x0000000C)) +RT_GROUP_ICON :: LPWSTR(uintptr(0x0000000E)) +RT_VERSION :: LPWSTR(uintptr(0x00000010)) +RT_DLGINCLUDE :: LPWSTR(uintptr(0x00000011)) +RT_PLUGPLAY :: LPWSTR(uintptr(0x00000013)) +RT_VXD :: LPWSTR(uintptr(0x00000014)) +RT_ANICURSOR :: LPWSTR(uintptr(0x00000015)) +RT_ANIICON :: LPWSTR(uintptr(0x00000016)) +RT_MANIFEST :: LPWSTR(uintptr(0x00000018)) + +CREATEPROCESS_MANIFEST_RESOURCE_ID :: LPWSTR(uintptr(0x00000001)) +ISOLATIONAWARE_MANIFEST_RESOURCE_ID :: LPWSTR(uintptr(0x00000002)) +ISOLATIONAWARE_NOSTATICIMPORT_MANIFEST_RESOURCE_ID :: LPWSTR(uintptr(0x00000003)) +ISOLATIONPOLICY_MANIFEST_RESOURCE_ID :: LPWSTR(uintptr(0x00000004)) +ISOLATIONPOLICY_BROWSER_MANIFEST_RESOURCE_ID :: LPWSTR(uintptr(0x00000005)) +MINIMUM_RESERVED_MANIFEST_RESOURCE_ID :: LPWSTR(uintptr(0x00000001)) +MAXIMUM_RESERVED_MANIFEST_RESOURCE_ID :: LPWSTR(uintptr(0x00000010)) + +ACCEL :: struct { + /* Also called the flags field */ + fVirt: BYTE, + key: WORD, + cmd: WORD, +} +LPACCEL :: ^ACCEL + +MIIM_STATE :: 0x00000001 +MIIM_ID :: 0x00000002 +MIIM_SUBMENU :: 0x00000004 +MIIM_CHECKMARKS :: 0x00000008 +MIIM_TYPE :: 0x00000010 +MIIM_DATA :: 0x00000020 + +MIIM_STRING :: 0x00000040 +MIIM_BITMAP :: 0x00000080 +MIIM_FTYPE :: 0x00000100 + +MENUITEMINFOW :: struct { + cbSize: UINT, + fMask: UINT, + fType: UINT, // used if MIIM_TYPE (4.0) or MIIM_FTYPE (>4.0) + fState: UINT, // used if MIIM_STATE + wID: UINT, // used if MIIM_ID + hSubMenu: HMENU, // used if MIIM_SUBMENU + hbmpChecked: HBITMAP, // used if MIIM_CHECKMARKS + hbmpUnchecked: HBITMAP, // used if MIIM_CHECKMARKS + dwItemData: ULONG_PTR, // used if MIIM_DATA + dwTypeData: LPWSTR, // used if MIIM_TYPE (4.0) or MIIM_STRING (>4.0) + cch: UINT, // used if MIIM_TYPE (4.0) or MIIM_STRING (>4.0) + hbmpItem: HBITMAP, // used if MIIM_BITMAP +} +LPMENUITEMINFOW :: ^MENUITEMINFOW +DISPLAY_DEVICEW :: struct { + cb: DWORD, + DeviceName: [32]WCHAR, + DeviceString: [128]WCHAR, + StateFlags: DWORD, + DeviceID: [128]WCHAR, + DeviceKey: [128]WCHAR, +} +PDISPLAY_DEVICEW :: ^DISPLAY_DEVICEW + +// OUTOFCONTEXT is the zero value, use {} +WinEventFlags :: bit_set[WinEventFlag; DWORD] + +WinEventFlag :: enum DWORD { + SKIPOWNTHREAD = 0, + SKIPOWNPROCESS = 1, + INCONTEXT = 2, +} + +// Standard Clipboard Formats +CF_TEXT :: 1 +CF_BITMAP :: 2 +CF_METAFILEPICT :: 3 +CF_SYLK :: 4 +CF_DIF :: 5 +CF_TIFF :: 6 +CF_OEMTEXT :: 7 +CF_DIB :: 8 +CF_PALETTE :: 9 +CF_PENDATA :: 10 +CF_RIFF :: 11 +CF_WAVE :: 12 +CF_UNICODETEXT :: 13 +CF_ENHMETAFILE :: 14 +CF_HDROP :: 15 +CF_LOCALE :: 16 +CF_DIBV5 :: 17 +CF_DSPBITMAP :: 0x0082 +CF_DSPENHMETAFILE :: 0x008E +CF_DSPMETAFILEPICT :: 0x0083 +CF_DSPTEXT :: 0x0081 +CF_GDIOBJFIRST :: 0x0300 +CF_GDIOBJLAST :: 0x03FF +CF_OWNERDISPLAY :: 0x0080 +CF_PRIVATEFIRST :: 0x0200 +CF_PRIVATELAST :: 0x02FF + +STICKYKEYS :: struct { + cbSize: UINT, + dwFlags: DWORD, +} +LPSTICKYKEYS :: ^STICKYKEYS + +SKF_STICKYKEYSON :: 0x1 +SKF_AVAILABLE :: 0x2 +SKF_HOTKEYACTIVE :: 0x4 +SKF_CONFIRMHOTKEY :: 0x8 +SKF_HOTKEYSOUND :: 0x10 +SKF_INDICATOR :: 0x20 +SKF_AUDIBLEFEEDBACK :: 0x40 +SKF_TRISTATE :: 0x80 +SKF_TWOKEYSOFF :: 0x100 +SKF_LSHIFTLOCKED :: 0x10000 +SKF_RSHIFTLOCKED :: 0x20000 +SKF_LCTLLOCKED :: 0x40000 +SKF_RCTLLOCKED :: 0x80000 +SKF_LALTLOCKED :: 0x100000 +SKF_RALTLOCKED :: 0x200000 +SKF_LWINLOCKED :: 0x400000 +SKF_RWINLOCKED :: 0x800000 +SKF_LSHIFTLATCHED :: 0x1000000 +SKF_RSHIFTLATCHED :: 0x2000000 +SKF_LCTLLATCHED :: 0x4000000 +SKF_RCTLLATCHED :: 0x8000000 +SKF_LALTLATCHED :: 0x10000000 +SKF_RALTLATCHED :: 0x20000000 + +TOGGLEKEYS :: struct { + cbSize: UINT, + dwFlags: DWORD, +} +LPTOGGLEKEYS :: ^TOGGLEKEYS + +TKF_TOGGLEKEYSON :: 0x1 +TKF_AVAILABLE :: 0x2 +TKF_HOTKEYACTIVE :: 0x4 +TKF_CONFIRMHOTKEY :: 0x8 +TKF_HOTKEYSOUND :: 0x10 +TKF_INDICATOR :: 0x20 + +FILTERKEYS :: struct { + cbSize: UINT, + dwFlags: DWORD, + iWaitMSec: DWORD, + iDelayMSec: DWORD, + iRepeatMSec: DWORD, + iBounceMSec: DWORD, +} +LPFILTERKEYS :: ^FILTERKEYS + +FKF_FILTERKEYSON :: 0x1 +FKF_AVAILABLE :: 0x2 +FKF_HOTKEYACTIVE :: 0x4 +FKF_CONFIRMHOTKEY :: 0x8 +FKF_HOTKEYSOUND :: 0x10 +FKF_INDICATOR :: 0x20 +FKF_CLICKON :: 0x40 diff --git a/core/sys/windows/userenv.odin b/core/sys/windows/userenv.odin index 92bc09a7e..2a2209d2c 100644 --- a/core/sys/windows/userenv.odin +++ b/core/sys/windows/userenv.odin @@ -1,9 +1,9 @@ -// +build windows +#+build windows package sys_windows foreign import userenv "system:Userenv.lib" -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign userenv { GetUserProfileDirectoryW :: proc(hToken: HANDLE, lpProfileDir: LPWSTR, diff --git a/core/sys/windows/util.odin b/core/sys/windows/util.odin index 9c9d8f7b4..b3eb800bc 100644 --- a/core/sys/windows/util.odin +++ b/core/sys/windows/util.odin @@ -1,27 +1,84 @@ -// +build windows +#+build windows package sys_windows -import "core:runtime" -import "core:intrinsics" +import "base:runtime" +import "base:intrinsics" L :: intrinsics.constant_utf16_cstring -LOWORD :: #force_inline proc "contextless" (x: DWORD) -> WORD { +// https://learn.microsoft.com/en-us/windows/win32/winmsg/makeword +MAKEWORD :: #force_inline proc "contextless" (#any_int a, b: int) -> WORD { + return WORD(BYTE(DWORD_PTR(a) & 0xff)) | (WORD(BYTE(DWORD_PTR(b) & 0xff)) << 8) +} + +// https://learn.microsoft.com/en-us/windows/win32/winmsg/makelong +MAKELONG :: #force_inline proc "contextless" (#any_int a, b: int) -> LONG { + return LONG(WORD(DWORD_PTR(a) & 0xffff)) | (LONG(WORD(DWORD_PTR(b) & 0xffff)) << 16) +} + +// https://learn.microsoft.com/en-us/windows/win32/winmsg/loword +LOWORD :: #force_inline proc "contextless" (#any_int x: int) -> WORD { return WORD(x & 0xffff) } -HIWORD :: #force_inline proc "contextless" (x: DWORD) -> WORD { +// https://learn.microsoft.com/en-us/windows/win32/winmsg/hiword +HIWORD :: #force_inline proc "contextless" (#any_int x: int) -> WORD { return WORD(x >> 16) } +// https://learn.microsoft.com/en-us/windows/win32/winmsg/lobyte +LOBYTE :: #force_inline proc "contextless" (w: WORD) -> BYTE { + return BYTE((DWORD_PTR(w)) & 0xff) +} + +// https://learn.microsoft.com/en-us/windows/win32/winmsg/hibyte +HIBYTE :: #force_inline proc "contextless" (w: WORD) -> BYTE { + return BYTE(((DWORD_PTR(w)) >> 8) & 0xff) +} + +// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-makewparam +MAKEWPARAM :: #force_inline proc "contextless" (#any_int l, h: int) -> WPARAM { + return WPARAM(MAKELONG(l, h)) +} + +// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-makelparam +MAKELPARAM :: #force_inline proc "contextless" (#any_int l, h: int) -> LPARAM { + return LPARAM(MAKELONG(l, h)) +} + +// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-makelresult +MAKELRESULT :: #force_inline proc "contextless" (#any_int l, h: int) -> LRESULT { + return LRESULT(MAKELONG(l, h)) +} + +// https://learn.microsoft.com/en-us/windows/win32/api/windowsx/nf-windowsx-get_x_lparam GET_X_LPARAM :: #force_inline proc "contextless" (lp: LPARAM) -> c_int { return cast(c_int)cast(c_short)LOWORD(cast(DWORD)lp) } +// https://learn.microsoft.com/en-us/windows/win32/api/windowsx/nf-windowsx-get_y_lparam GET_Y_LPARAM :: #force_inline proc "contextless" (lp: LPARAM) -> c_int { return cast(c_int)cast(c_short)HIWORD(cast(DWORD)lp) } +// https://learn.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-makelcid +MAKELCID :: #force_inline proc "contextless" (lgid, srtid: WORD) -> LCID { + return (DWORD(WORD(srtid)) << 16) | DWORD(WORD(lgid)) +} + +// https://learn.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-makelangid +MAKELANGID :: #force_inline proc "contextless" (p, s: WORD) -> DWORD { + return DWORD(WORD(s)) << 10 | DWORD(WORD(p)) +} + +LANGIDFROMLCID :: #force_inline proc "contextless" (lcid: LCID) -> LANGID { + return LANGID(lcid) +} + +// this one gave me trouble as it do not mask the values. +// the _ in the name is also off comparing to the c code +// i can't find any usage in the odin repo +@(deprecated = "use MAKEWORD") MAKE_WORD :: #force_inline proc "contextless" (x, y: WORD) -> WORD { return x << 8 | y } @@ -53,8 +110,8 @@ utf8_to_utf16 :: proc(s: string, allocator := context.temp_allocator) -> []u16 { return text[:n] } utf8_to_wstring :: proc(s: string, allocator := context.temp_allocator) -> wstring { - if res := utf8_to_utf16(s, allocator); res != nil { - return &res[0] + if res := utf8_to_utf16(s, allocator); len(res) > 0 { + return raw_data(res) } return nil } @@ -202,7 +259,7 @@ get_computer_name_and_account_sid :: proc(username: string) -> (computer_name: s username_w := utf8_to_utf16(username, context.temp_allocator) cbsid: DWORD computer_name_size: DWORD - pe_use := SID_TYPE.User + pe_use := SID_NAME_USE.SidTypeUser res := LookupAccountNameW( nil, // Look on this computer first @@ -244,7 +301,7 @@ get_sid :: proc(username: string, sid: ^SID) -> (ok: bool) { username_w := utf8_to_utf16(username, context.temp_allocator) cbsid: DWORD computer_name_size: DWORD - pe_use := SID_TYPE.User + pe_use := SID_NAME_USE.SidTypeUser res := LookupAccountNameW( nil, // Look on this computer first diff --git a/core/sys/windows/ux_theme.odin b/core/sys/windows/ux_theme.odin index 39b489bc0..392cf1e18 100644 --- a/core/sys/windows/ux_theme.odin +++ b/core/sys/windows/ux_theme.odin @@ -1,12 +1,12 @@ -// +build windows +#+build windows package sys_windows foreign import uxtheme "system:UxTheme.lib" -MARGINS :: distinct [4]int +MARGINS :: distinct [4]i32 PMARGINS :: ^MARGINS -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign uxtheme { IsThemeActive :: proc() -> BOOL --- } diff --git a/core/sys/windows/wgl.odin b/core/sys/windows/wgl.odin index 77cff2fa9..8fea55c3d 100644 --- a/core/sys/windows/wgl.odin +++ b/core/sys/windows/wgl.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows import "core:c" @@ -66,7 +66,7 @@ GetExtensionsStringARBType :: #type proc "c" (HDC) -> cstring wglGetExtensionsStringARB: GetExtensionsStringARBType -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign Opengl32 { wglCreateContext :: proc(hdc: HDC) -> HGLRC --- wglMakeCurrent :: proc(hdc: HDC, HGLRC: HGLRC) -> BOOL --- diff --git a/core/sys/windows/wglext.odin b/core/sys/windows/wglext.odin index 0c4b51d65..4c76b39ec 100644 --- a/core/sys/windows/wglext.odin +++ b/core/sys/windows/wglext.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows // WGL_ARB_buffer_region diff --git a/core/sys/windows/window_messages.odin b/core/sys/windows/window_messages.odin index 888c5ccf9..d69771bdf 100644 --- a/core/sys/windows/window_messages.odin +++ b/core/sys/windows/window_messages.odin @@ -1,4 +1,4 @@ -// +build windows +#+build windows package sys_windows WM_NULL :: 0x0000 diff --git a/core/sys/windows/winerror.odin b/core/sys/windows/winerror.odin index f552b9a53..61a7d9d86 100644 --- a/core/sys/windows/winerror.odin +++ b/core/sys/windows/winerror.odin @@ -1,6 +1,189 @@ -// +build windows +#+build windows package sys_windows +// https://learn.microsoft.com/en-us/windows/win32/api/winerror/ + +// Values are 32 bit values laid out as follows: +// +// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 +// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +// +---+-+-+-----------------------+-------------------------------+ +// |Sev|C|R| Facility | Code | +// +---+-+-+-----------------------+-------------------------------+ +// +// where +// +// Sev - is the severity code +// +// 00 - Success +// 01 - Informational +// 10 - Warning +// 11 - Error +// +// C - is the Customer code flag +// +// R - is a reserved bit +// +// Facility - is the facility code +// +// Code - is the facility's status code + +// Define the facility codes +FACILITY :: enum DWORD { + NULL = 0, + RPC = 1, + DISPATCH = 2, + STORAGE = 3, + ITF = 4, + WIN32 = 7, + WINDOWS = 8, + SSPI = 9, + SECURITY = 9, + CONTROL = 10, + CERT = 11, + INTERNET = 12, + MEDIASERVER = 13, + MSMQ = 14, + SETUPAPI = 15, + SCARD = 16, + COMPLUS = 17, + AAF = 18, + URT = 19, + ACS = 20, + DPLAY = 21, + UMI = 22, + SXS = 23, + WINDOWS_CE = 24, + HTTP = 25, + USERMODE_COMMONLOG = 26, + WER = 27, + USERMODE_FILTER_MANAGER = 31, + BACKGROUNDCOPY = 32, + CONFIGURATION = 33, + WIA = 33, + STATE_MANAGEMENT = 34, + METADIRECTORY = 35, + WINDOWSUPDATE = 36, + DIRECTORYSERVICE = 37, + GRAPHICS = 38, + SHELL = 39, + NAP = 39, + TPM_SERVICES = 40, + TPM_SOFTWARE = 41, + UI = 42, + XAML = 43, + ACTION_QUEUE = 44, + PLA = 48, + WINDOWS_SETUP = 48, + FVE = 49, + FWP = 50, + WINRM = 51, + NDIS = 52, + USERMODE_HYPERVISOR = 53, + CMI = 54, + USERMODE_VIRTUALIZATION = 55, + USERMODE_VOLMGR = 56, + BCD = 57, + USERMODE_VHD = 58, + USERMODE_HNS = 59, + SDIAG = 60, + WEBSERVICES = 61, + WINPE = 61, + WPN = 62, + WINDOWS_STORE = 63, + INPUT = 64, + QUIC = 65, + EAP = 66, + IORING = 70, + WINDOWS_DEFENDER = 80, + OPC = 81, + XPS = 82, + MBN = 84, + POWERSHELL = 84, + RAS = 83, + P2P_INT = 98, + P2P = 99, + DAF = 100, + BLUETOOTH_ATT = 101, + AUDIO = 102, + STATEREPOSITORY = 103, + VISUALCPP = 109, + SCRIPT = 112, + PARSE = 113, + BLB = 120, + BLB_CLI = 121, + WSBAPP = 122, + BLBUI = 128, + USN = 129, + USERMODE_VOLSNAP = 130, + TIERING = 131, + WSB_ONLINE = 133, + ONLINE_ID = 134, + DEVICE_UPDATE_AGENT = 135, + DRVSERVICING = 136, + DLS = 153, + DELIVERY_OPTIMIZATION = 208, + USERMODE_SPACES = 231, + USER_MODE_SECURITY_CORE = 232, + USERMODE_LICENSING = 234, + SOS = 160, + OCP_UPDATE_AGENT = 173, + DEBUGGERS = 176, + SPP = 256, + RESTORE = 256, + DMSERVER = 256, + DEPLOYMENT_SERVICES_SERVER = 257, + DEPLOYMENT_SERVICES_IMAGING = 258, + DEPLOYMENT_SERVICES_MANAGEMENT = 259, + DEPLOYMENT_SERVICES_UTIL = 260, + DEPLOYMENT_SERVICES_BINLSVC = 261, + DEPLOYMENT_SERVICES_PXE = 263, + DEPLOYMENT_SERVICES_TFTP = 264, + DEPLOYMENT_SERVICES_TRANSPORT_MANAGEMENT = 272, + DEPLOYMENT_SERVICES_DRIVER_PROVISIONING = 278, + DEPLOYMENT_SERVICES_MULTICAST_SERVER = 289, + DEPLOYMENT_SERVICES_MULTICAST_CLIENT = 290, + DEPLOYMENT_SERVICES_CONTENT_PROVIDER = 293, + HSP_SERVICES = 296, + HSP_SOFTWARE = 297, + LINGUISTIC_SERVICES = 305, + AUDIOSTREAMING = 1094, + TTD = 1490, + ACCELERATOR = 1536, + WMAAECMA = 1996, + DIRECTMUSIC = 2168, + DIRECT3D10 = 2169, + DXGI = 2170, + DXGI_DDI = 2171, + DIRECT3D11 = 2172, + DIRECT3D11_DEBUG = 2173, + DIRECT3D12 = 2174, + DIRECT3D12_DEBUG = 2175, + DXCORE = 2176, + PRESENTATION = 2177, + LEAP = 2184, + AUDCLNT = 2185, + WINCODEC_DWRITE_DWM = 2200, + WINML = 2192, + DIRECT2D = 2201, + DEFRAG = 2304, + USERMODE_SDBUS = 2305, + JSCRIPT = 2306, + PIDGENX = 2561, + EAS = 85, + WEB = 885, + WEB_SOCKET = 886, + MOBILE = 1793, + SQLITE = 1967, + SERVICE_FABRIC = 1968, + UTC = 1989, + WEP = 2049, + SYNCENGINE = 2050, + XBOX = 2339, + GAME = 2340, + PIX = 2748, +} + ERROR_SUCCESS : DWORD : 0 NO_ERROR :: 0 SEC_E_OK : HRESULT : 0x00000000 @@ -30,6 +213,7 @@ ERROR_BROKEN_PIPE : DWORD : 109 ERROR_CALL_NOT_IMPLEMENTED : DWORD : 120 ERROR_INSUFFICIENT_BUFFER : DWORD : 122 ERROR_INVALID_NAME : DWORD : 123 +ERROR_NEGATIVE_SEEK : DWORD : 131 ERROR_BAD_ARGUMENTS : DWORD : 160 ERROR_LOCK_FAILED : DWORD : 167 ERROR_ALREADY_EXISTS : DWORD : 183 @@ -42,9 +226,5532 @@ ERROR_TIMEOUT : DWORD : 1460 ERROR_DATATYPE_MISMATCH : DWORD : 1629 ERROR_UNSUPPORTED_TYPE : DWORD : 1630 ERROR_NOT_SAME_OBJECT : DWORD : 1656 -ERROR_PIPE_CONNECTED : DWORD : 0x80070217 +ERROR_PIPE_CONNECTED : DWORD : 535 ERROR_PIPE_BUSY : DWORD : 231 -E_NOTIMPL :: HRESULT(-0x7fff_bfff) // 0x8000_4001 +// https://learn.microsoft.com/en-us/windows/win32/seccrypto/common-hresult-values +S_OK :: 0x00000000 // Operation successful +E_NOTIMPL :: 0x80004001 // Not implemented +E_NOINTERFACE :: 0x80004002 // No such interface supported +E_POINTER :: 0x80004003 // Pointer that is not valid +E_ABORT :: 0x80004004 // Operation aborted +E_FAIL :: 0x80004005 // Unspecified failure +E_UNEXPECTED :: 0x8000FFFF // Unexpected failure +E_ACCESSDENIED :: 0x80070005 // General access denied error +E_HANDLE :: 0x80070006 // Handle that is not valid +E_OUTOFMEMORY :: 0x8007000E // Failed to allocate necessary memory +E_INVALIDARG :: 0x80070057 // One or more arguments are not valid -SUCCEEDED :: #force_inline proc(#any_int result: int) -> bool { return result >= 0 } +// Severity values +SEVERITY :: enum DWORD { + SUCCESS = 0, + ERROR = 1, +} + +// Generic test for success on any status value (non-negative numbers indicate success). +SUCCEEDED :: #force_inline proc "contextless" (#any_int result: int) -> bool { return result >= S_OK } +// and the inverse +FAILED :: #force_inline proc "contextless" (#any_int result: int) -> bool { return result < S_OK } + +// Generic test for error on any status value. +IS_ERROR :: #force_inline proc "contextless" (#any_int status: int) -> bool { return u32(status) >> 31 == u32(SEVERITY.ERROR) } + +// Return the code +HRESULT_CODE :: #force_inline proc "contextless" (#any_int hr: int) -> int { return int(u32(hr) & 0xFFFF) } + +// Return the facility +HRESULT_FACILITY :: #force_inline proc "contextless" (#any_int hr: int) -> FACILITY { return FACILITY((u32(hr) >> 16) & 0x1FFF) } + +// Return the severity +HRESULT_SEVERITY :: #force_inline proc "contextless" (#any_int hr: int) -> SEVERITY { return SEVERITY((u32(hr) >> 31) & 0x1) } + +// Create an HRESULT value from component pieces +MAKE_HRESULT :: #force_inline proc "contextless" (#any_int sev: int, #any_int fac: int, #any_int code: int) -> HRESULT { + return HRESULT((uint(sev)<<31) | (uint(fac)<<16) | (uint(code))) +} + +DECODE_HRESULT :: #force_inline proc "contextless" (#any_int hr: int) -> (SEVERITY, FACILITY, int) { + return HRESULT_SEVERITY(hr), HRESULT_FACILITY(hr), HRESULT_CODE(hr) +} + +// aka ERROR or WIN32_ERROR to hint the WIN32 facility +System_Error :: enum DWORD { + // The operation completed successfully. + SUCCESS = 0x0, + // Incorrect function. + INVALID_FUNCTION = 0x1, + // The system cannot find the file specified. + FILE_NOT_FOUND = 0x2, + // The system cannot find the path specified. + PATH_NOT_FOUND = 0x3, + // The system cannot open the file. + TOO_MANY_OPEN_FILES = 0x4, + // Access is denied. + ACCESS_DENIED = 0x5, + // The handle is invalid. + INVALID_HANDLE = 0x6, + // The storage control blocks were destroyed. + ARENA_TRASHED = 0x7, + // Not enough memory resources are available to process this command. + NOT_ENOUGH_MEMORY = 0x8, + // The storage control block address is invalid. + INVALID_BLOCK = 0x9, + // The environment is incorrect. + BAD_ENVIRONMENT = 0xA, + // An attempt was made to load a program with an incorrect format. + BAD_FORMAT = 0xB, + // The access code is invalid. + INVALID_ACCESS = 0xC, + // The data is invalid. + INVALID_DATA = 0xD, + // Not enough storage is available to complete this operation. + OUTOFMEMORY = 0xE, + // The system cannot find the drive specified. + INVALID_DRIVE = 0xF, + // The directory cannot be removed. + CURRENT_DIRECTORY = 0x10, + // The system cannot move the file to a different disk drive. + NOT_SAME_DEVICE = 0x11, + // There are no more files. + NO_MORE_FILES = 0x12, + // The media is write protected. + WRITE_PROTECT = 0x13, + // The system cannot find the device specified. + BAD_UNIT = 0x14, + // The device is not ready. + NOT_READY = 0x15, + // The device does not recognize the command. + BAD_COMMAND = 0x16, + // Data error cyclic redundancy check. + CRC = 0x17, + // The program issued a command but the command length is incorrect. + BAD_LENGTH = 0x18, + // The drive cannot locate a specific area or track on the disk. + SEEK = 0x19, + // The specified disk or diskette cannot be accessed. + NOT_DOS_DISK = 0x1A, + // The drive cannot find the sector requested. + SECTOR_NOT_FOUND = 0x1B, + // The printer is out of paper. + OUT_OF_PAPER = 0x1C, + // The system cannot write to the specified device. + WRITE_FAULT = 0x1D, + // The system cannot read from the specified device. + READ_FAULT = 0x1E, + // A device attached to the system is not functioning. + GEN_FAILURE = 0x1F, + // The process cannot access the file because it is being used by another process. + SHARING_VIOLATION = 0x20, + // The process cannot access the file because another process has locked a portion of the file. + LOCK_VIOLATION = 0x21, + // The wrong diskette is in the drive. Insert %2 Volume Serial Number: %3 into drive %1. + WRONG_DISK = 0x22, + // Too many files opened for sharing. + SHARING_BUFFER_EXCEEDED = 0x24, + // Reached the end of the file. + HANDLE_EOF = 0x26, + // The disk is full. + HANDLE_DISK_FULL = 0x27, + // The request is not supported. + NOT_SUPPORTED = 0x32, + // Windows cannot find the network path. Verify that the network path is correct and the destination computer is not busy or turned off. If Windows still cannot find the network path, contact your network administrator. + REM_NOT_LIST = 0x33, + // You were not connected because a duplicate name exists on the network. If joining a domain, go to System in Control Panel to change the computer name and try again. If joining a workgroup, choose another workgroup name. + DUP_NAME = 0x34, + // The network path was not found. + BAD_NETPATH = 0x35, + // The network is busy. + NETWORK_BUSY = 0x36, + // The specified network resource or device is no longer available. + DEV_NOT_EXIST = 0x37, + // The network BIOS command limit has been reached. + TOO_MANY_CMDS = 0x38, + // A network adapter hardware error occurred. + ADAP_HDW_ERR = 0x39, + // The specified server cannot perform the requested operation. + BAD_NET_RESP = 0x3A, + // An unexpected network error occurred. + UNEXP_NET_ERR = 0x3B, + // The remote adapter is not compatible. + BAD_REM_ADAP = 0x3C, + // The printer queue is full. + PRINTQ_FULL = 0x3D, + // Space to store the file waiting to be printed is not available on the server. + NO_SPOOL_SPACE = 0x3E, + // Your file waiting to be printed was deleted. + PRINT_CANCELLED = 0x3F, + // The specified network name is no longer available. + NETNAME_DELETED = 0x40, + // Network access is denied. + NETWORK_ACCESS_DENIED = 0x41, + // The network resource type is not correct. + BAD_DEV_TYPE = 0x42, + // The network name cannot be found. + BAD_NET_NAME = 0x43, + // The name limit for the local computer network adapter card was exceeded. + TOO_MANY_NAMES = 0x44, + // The network BIOS session limit was exceeded. + TOO_MANY_SESS = 0x45, + // The remote server has been paused or is in the process of being started. + SHARING_PAUSED = 0x46, + // No more connections can be made to this remote computer at this time because there are already as many connections as the computer can accept. + REQ_NOT_ACCEP = 0x47, + // The specified printer or disk device has been paused. + REDIR_PAUSED = 0x48, + // The file exists. + FILE_EXISTS = 0x50, + // The directory or file cannot be created. + CANNOT_MAKE = 0x52, + // Fail on INT 24. + FAIL_I24 = 0x53, + // Storage to process this request is not available. + OUT_OF_STRUCTURES = 0x54, + // The local device name is already in use. + ALREADY_ASSIGNED = 0x55, + // The specified network password is not correct. + INVALID_PASSWORD = 0x56, + // The parameter is incorrect. + INVALID_PARAMETER = 0x57, + // A write fault occurred on the network. + NET_WRITE_FAULT = 0x58, + // The system cannot start another process at this time. + NO_PROC_SLOTS = 0x59, + // Cannot create another system semaphore. + TOO_MANY_SEMAPHORES = 0x64, + // The exclusive semaphore is owned by another process. + EXCL_SEM_ALREADY_OWNED = 0x65, + // The semaphore is set and cannot be closed. + SEM_IS_SET = 0x66, + // The semaphore cannot be set again. + TOO_MANY_SEM_REQUESTS = 0x67, + // Cannot request exclusive semaphores at interrupt time. + INVALID_AT_INTERRUPT_TIME = 0x68, + // The previous ownership of this semaphore has ended. + SEM_OWNER_DIED = 0x69, + // Insert the diskette for drive %1. + SEM_USER_LIMIT = 0x6A, + // The program stopped because an alternate diskette was not inserted. + DISK_CHANGE = 0x6B, + // The disk is in use or locked by another process. + DRIVE_LOCKED = 0x6C, + // The pipe has been ended. + BROKEN_PIPE = 0x6D, + // The system cannot open the device or file specified. + OPEN_FAILED = 0x6E, + // The file name is too long. + BUFFER_OVERFLOW = 0x6F, + // There is not enough space on the disk. + DISK_FULL = 0x70, + // No more internal file identifiers available. + NO_MORE_SEARCH_HANDLES = 0x71, + // The target internal file identifier is incorrect. + INVALID_TARGET_HANDLE = 0x72, + // The IOCTL call made by the application program is not correct. + INVALID_CATEGORY = 0x75, + // The verify-on-write switch parameter value is not correct. + INVALID_VERIFY_SWITCH = 0x76, + // The system does not support the command requested. + BAD_DRIVER_LEVEL = 0x77, + // This function is not supported on this system. + CALL_NOT_IMPLEMENTED = 0x78, + // The semaphore timeout period has expired. + SEM_TIMEOUT = 0x79, + // The data area passed to a system call is too small. + INSUFFICIENT_BUFFER = 0x7A, + // The filename, directory name, or volume label syntax is incorrect. + INVALID_NAME = 0x7B, + // The system call level is not correct. + INVALID_LEVEL = 0x7C, + // The disk has no volume label. + NO_VOLUME_LABEL = 0x7D, + // The specified module could not be found. + MOD_NOT_FOUND = 0x7E, + // The specified procedure could not be found. + PROC_NOT_FOUND = 0x7F, + // There are no child processes to wait for. + WAIT_NO_CHILDREN = 0x80, + // The %1 application cannot be run in Win32 mode. + CHILD_NOT_COMPLETE = 0x81, + // Attempt to use a file handle to an open disk partition for an operation other than raw disk I/O. + DIRECT_ACCESS_HANDLE = 0x82, + // An attempt was made to move the file pointer before the beginning of the file. + NEGATIVE_SEEK = 0x83, + // The file pointer cannot be set on the specified device or file. + SEEK_ON_DEVICE = 0x84, + // A JOIN or SUBST command cannot be used for a drive that contains previously joined drives. + IS_JOIN_TARGET = 0x85, + // An attempt was made to use a JOIN or SUBST command on a drive that has already been joined. + IS_JOINED = 0x86, + // An attempt was made to use a JOIN or SUBST command on a drive that has already been substituted. + IS_SUBSTED = 0x87, + // The system tried to delete the JOIN of a drive that is not joined. + NOT_JOINED = 0x88, + // The system tried to delete the substitution of a drive that is not substituted. + NOT_SUBSTED = 0x89, + // The system tried to join a drive to a directory on a joined drive. + JOIN_TO_JOIN = 0x8A, + // The system tried to substitute a drive to a directory on a substituted drive. + SUBST_TO_SUBST = 0x8B, + // The system tried to join a drive to a directory on a substituted drive. + JOIN_TO_SUBST = 0x8C, + // The system tried to SUBST a drive to a directory on a joined drive. + SUBST_TO_JOIN = 0x8D, + // The system cannot perform a JOIN or SUBST at this time. + BUSY_DRIVE = 0x8E, + // The system cannot join or substitute a drive to or for a directory on the same drive. + SAME_DRIVE = 0x8F, + // The directory is not a subdirectory of the root directory. + DIR_NOT_ROOT = 0x90, + // The directory is not empty. + DIR_NOT_EMPTY = 0x91, + // The path specified is being used in a substitute. + IS_SUBST_PATH = 0x92, + // Not enough resources are available to process this command. + IS_JOIN_PATH = 0x93, + // The path specified cannot be used at this time. + PATH_BUSY = 0x94, + // An attempt was made to join or substitute a drive for which a directory on the drive is the target of a previous substitute. + IS_SUBST_TARGET = 0x95, + // System trace information was not specified in your CONFIG.SYS file, or tracing is disallowed. + SYSTEM_TRACE = 0x96, + // The number of specified semaphore events for DosMuxSemWait is not correct. + INVALID_EVENT_COUNT = 0x97, + // DosMuxSemWait did not execute; too many semaphores are already set. + TOO_MANY_MUXWAITERS = 0x98, + // The DosMuxSemWait list is not correct. + INVALID_LIST_FORMAT = 0x99, + // The volume label you entered exceeds the label character limit of the target file system. + LABEL_TOO_LONG = 0x9A, + // Cannot create another thread. + TOO_MANY_TCBS = 0x9B, + // The recipient process has refused the signal. + SIGNAL_REFUSED = 0x9C, + // The segment is already discarded and cannot be locked. + DISCARDED = 0x9D, + // The segment is already unlocked. + NOT_LOCKED = 0x9E, + // The address for the thread ID is not correct. + BAD_THREADID_ADDR = 0x9F, + // One or more arguments are not correct. + BAD_ARGUMENTS = 0xA0, + // The specified path is invalid. + BAD_PATHNAME = 0xA1, + // A signal is already pending. + SIGNAL_PENDING = 0xA2, + // No more threads can be created in the system. + MAX_THRDS_REACHED = 0xA4, + // Unable to lock a region of a file. + LOCK_FAILED = 0xA7, + // The requested resource is in use. + BUSY = 0xAA, + // Device's command support detection is in progress. + DEVICE_SUPPORT_IN_PROGRESS = 0xAB, + // A lock request was not outstanding for the supplied cancel region. + CANCEL_VIOLATION = 0xAD, + // The file system does not support atomic changes to the lock type. + ATOMIC_LOCKS_NOT_SUPPORTED = 0xAE, + // The system detected a segment number that was not correct. + INVALID_SEGMENT_NUMBER = 0xB4, + // The operating system cannot run %1. + INVALID_ORDINAL = 0xB6, + // Cannot create a file when that file already exists. + ALREADY_EXISTS = 0xB7, + // The flag passed is not correct. + INVALID_FLAG_NUMBER = 0xBA, + // The specified system semaphore name was not found. + SEM_NOT_FOUND = 0xBB, + // The operating system cannot run %1. + INVALID_STARTING_CODESEG = 0xBC, + // The operating system cannot run %1. + INVALID_STACKSEG = 0xBD, + // The operating system cannot run %1. + INVALID_MODULETYPE = 0xBE, + // Cannot run %1 in Win32 mode. + INVALID_EXE_SIGNATURE = 0xBF, + // The operating system cannot run %1. + EXE_MARKED_INVALID = 0xC0, + // %1 is not a valid Win32 application. + BAD_EXE_FORMAT = 0xC1, + // The operating system cannot run %1. + ITERATED_DATA_EXCEEDS_64k = 0xC2, + // The operating system cannot run %1. + INVALID_MINALLOCSIZE = 0xC3, + // The operating system cannot run this application program. + DYNLINK_FROM_INVALID_RING = 0xC4, + // The operating system is not presently configured to run this application. + IOPL_NOT_ENABLED = 0xC5, + // The operating system cannot run %1. + INVALID_SEGDPL = 0xC6, + // The operating system cannot run this application program. + AUTODATASEG_EXCEEDS_64k = 0xC7, + // The code segment cannot be greater than or equal to 64K. + RING2SEG_MUST_BE_MOVABLE = 0xC8, + // The operating system cannot run %1. + RELOC_CHAIN_XEEDS_SEGLIM = 0xC9, + // The operating system cannot run %1. + INFLOOP_IN_RELOC_CHAIN = 0xCA, + // The system could not find the environment option that was entered. + ENVVAR_NOT_FOUND = 0xCB, + // No process in the command subtree has a signal handler. + NO_SIGNAL_SENT = 0xCD, + // The filename or extension is too long. + FILENAME_EXCED_RANGE = 0xCE, + // The ring 2 stack is in use. + RING2_STACK_IN_USE = 0xCF, + // The global filename characters, * or ?, are entered incorrectly or too many global filename characters are specified. + META_EXPANSION_TOO_LONG = 0xD0, + // The signal being posted is not correct. + INVALID_SIGNAL_NUMBER = 0xD1, + // The signal handler cannot be set. + THREAD_1_INACTIVE = 0xD2, + // The segment is locked and cannot be reallocated. + LOCKED = 0xD4, + // Too many dynamic-link modules are attached to this program or dynamic-link module. + TOO_MANY_MODULES = 0xD6, + // Cannot nest calls to LoadModule. + NESTING_NOT_ALLOWED = 0xD7, + // This version of %1 is not compatible with the version of Windows you're running. Check your computer's system information and then contact the software publisher. + EXE_MACHINE_TYPE_MISMATCH = 0xD8, + // The image file %1 is signed, unable to modify. + EXE_CANNOT_MODIFY_SIGNED_BINARY = 0xD9, + // The image file %1 is strong signed, unable to modify. + EXE_CANNOT_MODIFY_STRONG_SIGNED_BINARY = 0xDA, + // This file is checked out or locked for editing by another user. + FILE_CHECKED_OUT = 0xDC, + // The file must be checked out before saving changes. + CHECKOUT_REQUIRED = 0xDD, + // The file type being saved or retrieved has been blocked. + BAD_FILE_TYPE = 0xDE, + // The file size exceeds the limit allowed and cannot be saved. + FILE_TOO_LARGE = 0xDF, + // Access Denied. Before opening files in this location, you must first add the web site to your trusted sites list, browse to the web site, and select the option to login automatically. + FORMS_AUTH_REQUIRED = 0xE0, + // Operation did not complete successfully because the file contains a virus or potentially unwanted software. + VIRUS_INFECTED = 0xE1, + // This file contains a virus or potentially unwanted software and cannot be opened. Due to the nature of this virus or potentially unwanted software, the file has been removed from this location. + VIRUS_DELETED = 0xE2, + // The pipe is local. + PIPE_LOCAL = 0xE5, + // The pipe state is invalid. + BAD_PIPE = 0xE6, + // All pipe instances are busy. + PIPE_BUSY = 0xE7, + // The pipe is being closed. + NO_DATA = 0xE8, + // No process is on the other end of the pipe. + PIPE_NOT_CONNECTED = 0xE9, + // More data is available. + MORE_DATA = 0xEA, + // The session was canceled. + VC_DISCONNECTED = 0xF0, + // The specified extended attribute name was invalid. + INVALID_EA_NAME = 0xFE, + // The extended attributes are inconsistent. + EA_LIST_INCONSISTENT = 0xFF, + // The wait operation timed out. + WAIT_TIMEOUT = 0x102, + // No more data is available. + NO_MORE_ITEMS = 0x103, + // The copy functions cannot be used. + CANNOT_COPY = 0x10A, + // The directory name is invalid. + DIRECTORY = 0x10B, + // The extended attributes did not fit in the buffer. + EAS_DIDNT_FIT = 0x113, + // The extended attribute file on the mounted file system is corrupt. + EA_FILE_CORRUPT = 0x114, + // The extended attribute table file is full. + EA_TABLE_FULL = 0x115, + // The specified extended attribute handle is invalid. + INVALID_EA_HANDLE = 0x116, + // The mounted file system does not support extended attributes. + EAS_NOT_SUPPORTED = 0x11A, + // Attempt to release mutex not owned by caller. + NOT_OWNER = 0x120, + // Too many posts were made to a semaphore. + TOO_MANY_POSTS = 0x12A, + // Only part of a ReadProcessMemory or WriteProcessMemory request was completed. + PARTIAL_COPY = 0x12B, + // The oplock request is denied. + OPLOCK_NOT_GRANTED = 0x12C, + // An invalid oplock acknowledgment was received by the system. + INVALID_OPLOCK_PROTOCOL = 0x12D, + // The volume is too fragmented to complete this operation. + DISK_TOO_FRAGMENTED = 0x12E, + // The file cannot be opened because it is in the process of being deleted. + DELETE_PENDING = 0x12F, + // Short name settings may not be changed on this volume due to the global registry setting. + INCOMPATIBLE_WITH_GLOBAL_SHORT_NAME_REGISTRY_SETTING = 0x130, + // Short names are not enabled on this volume. + SHORT_NAMES_NOT_ENABLED_ON_VOLUME = 0x131, + // The security stream for the given volume is in an inconsistent state. Please run CHKDSK on the volume. + SECURITY_STREAM_IS_INCONSISTENT = 0x132, + // A requested file lock operation cannot be processed due to an invalid byte range. + INVALID_LOCK_RANGE = 0x133, + // The subsystem needed to support the image type is not present. + IMAGE_SUBSYSTEM_NOT_PRESENT = 0x134, + // The specified file already has a notification GUID associated with it. + NOTIFICATION_GUID_ALREADY_DEFINED = 0x135, + // An invalid exception handler routine has been detected. + INVALID_EXCEPTION_HANDLER = 0x136, + // Duplicate privileges were specified for the token. + DUPLICATE_PRIVILEGES = 0x137, + // No ranges for the specified operation were able to be processed. + NO_RANGES_PROCESSED = 0x138, + // Operation is not allowed on a file system internal file. + NOT_ALLOWED_ON_SYSTEM_FILE = 0x139, + // The physical resources of this disk have been exhausted. + DISK_RESOURCES_EXHAUSTED = 0x13A, + // The token representing the data is invalid. + INVALID_TOKEN = 0x13B, + // The device does not support the command feature. + DEVICE_FEATURE_NOT_SUPPORTED = 0x13C, + // The system cannot find message text for message number 0x%1 in the message file for %2. + MR_MID_NOT_FOUND = 0x13D, + // The scope specified was not found. + SCOPE_NOT_FOUND = 0x13E, + // The Central Access Policy specified is not defined on the target machine. + UNDEFINED_SCOPE = 0x13F, + // The Central Access Policy obtained from Active Directory is invalid. + INVALID_CAP = 0x140, + // The device is unreachable. + DEVICE_UNREACHABLE = 0x141, + // The target device has insufficient resources to complete the operation. + DEVICE_NO_RESOURCES = 0x142, + // A data integrity checksum error occurred. Data in the file stream is corrupt. + DATA_CHECKSUM_ERROR = 0x143, + // An attempt was made to modify both a KERNEL and normal Extended Attribute EA in the same operation. + INTERMIXED_KERNEL_EA_OPERATION = 0x144, + // Device does not support file-level TRIM. + FILE_LEVEL_TRIM_NOT_SUPPORTED = 0x146, + // The command specified a data offset that does not align to the device's granularity/alignment. + OFFSET_ALIGNMENT_VIOLATION = 0x147, + // The command specified an invalid field in its parameter list. + INVALID_FIELD_IN_PARAMETER_LIST = 0x148, + // An operation is currently in progress with the device. + OPERATION_IN_PROGRESS = 0x149, + // An attempt was made to send down the command via an invalid path to the target device. + BAD_DEVICE_PATH = 0x14A, + // The command specified a number of descriptors that exceeded the maximum supported by the device. + TOO_MANY_DESCRIPTORS = 0x14B, + // Scrub is disabled on the specified file. + SCRUB_DATA_DISABLED = 0x14C, + // The storage device does not provide redundancy. + NOT_REDUNDANT_STORAGE = 0x14D, + // An operation is not supported on a resident file. + RESIDENT_FILE_NOT_SUPPORTED = 0x14E, + // An operation is not supported on a compressed file. + COMPRESSED_FILE_NOT_SUPPORTED = 0x14F, + // An operation is not supported on a directory. + DIRECTORY_NOT_SUPPORTED = 0x150, + // The specified copy of the requested data could not be read. + NOT_READ_FROM_COPY = 0x151, + // No action was taken as a system reboot is required. + FAIL_NOACTION_REBOOT = 0x15E, + // The shutdown operation failed. + FAIL_SHUTDOWN = 0x15F, + // The restart operation failed. + FAIL_RESTART = 0x160, + // The maximum number of sessions has been reached. + MAX_SESSIONS_REACHED = 0x161, + // The thread is already in background processing mode. + THREAD_MODE_ALREADY_BACKGROUND = 0x190, + // The thread is not in background processing mode. + THREAD_MODE_NOT_BACKGROUND = 0x191, + // The process is already in background processing mode. + PROCESS_MODE_ALREADY_BACKGROUND = 0x192, + // The process is not in background processing mode. + PROCESS_MODE_NOT_BACKGROUND = 0x193, + // Attempt to access invalid address. + INVALID_ADDRESS = 0x1E7, + + // User profile cannot be loaded. + USER_PROFILE_LOAD = 0x1F4, + // Arithmetic result exceeded 32 bits. + ARITHMETIC_OVERFLOW = 0x216, + // There is a process on other end of the pipe. + PIPE_CONNECTED = 0x217, + // Waiting for a process to open the other end of the pipe. + PIPE_LISTENING = 0x218, + // Application verifier has found an error in the current process. + VERIFIER_STOP = 0x219, + // An error occurred in the ABIOS subsystem. + ABIOS_ERROR = 0x21A, + // A warning occurred in the WX86 subsystem. + WX86_WARNING = 0x21B, + // An error occurred in the WX86 subsystem. + WX86_ERROR = 0x21C, + // An attempt was made to cancel or set a timer that has an associated APC and the subject thread is not the thread that originally set the timer with an associated APC routine. + TIMER_NOT_CANCELED = 0x21D, + // Unwind exception code. + UNWIND = 0x21E, + // An invalid or unaligned stack was encountered during an unwind operation. + BAD_STACK = 0x21F, + // An invalid unwind target was encountered during an unwind operation. + INVALID_UNWIND_TARGET = 0x220, + // Invalid Object Attributes specified to NtCreatePort or invalid Port Attributes specified to NtConnectPort + INVALID_PORT_ATTRIBUTES = 0x221, + // Length of message passed to NtRequestPort or NtRequestWaitReplyPort was longer than the maximum message allowed by the port. + PORT_MESSAGE_TOO_LONG = 0x222, + // An attempt was made to lower a quota limit below the current usage. + INVALID_QUOTA_LOWER = 0x223, + // An attempt was made to attach to a device that was already attached to another device. + DEVICE_ALREADY_ATTACHED = 0x224, + // An attempt was made to execute an instruction at an unaligned address and the host system does not support unaligned instruction references. + INSTRUCTION_MISALIGNMENT = 0x225, + // Profiling not started. + PROFILING_NOT_STARTED = 0x226, + // Profiling not stopped. + PROFILING_NOT_STOPPED = 0x227, + // The passed ACL did not contain the minimum required information. + COULD_NOT_INTERPRET = 0x228, + // The number of active profiling objects is at the maximum and no more may be started. + PROFILING_AT_LIMIT = 0x229, + // Used to indicate that an operation cannot continue without blocking for I/O. + CANT_WAIT = 0x22A, + // Indicates that a thread attempted to terminate itself by default (called NtTerminateThread with NULL) and it was the last thread in the current process. + CANT_TERMINATE_SELF = 0x22B, + // If an MM error is returned which is not defined in the standard FsRtl filter, it is converted to one of the following errors which is guaranteed to be in the filter. In this case information is lost, however, the filter correctly handles the exception. + UNEXPECTED_MM_CREATE_ERR = 0x22C, + // If an MM error is returned which is not defined in the standard FsRtl filter, it is converted to one of the following errors which is guaranteed to be in the filter. In this case information is lost, however, the filter correctly handles the exception. + UNEXPECTED_MM_MAP_ERROR = 0x22D, + // If an MM error is returned which is not defined in the standard FsRtl filter, it is converted to one of the following errors which is guaranteed to be in the filter. In this case information is lost, however, the filter correctly handles the exception. + UNEXPECTED_MM_EXTEND_ERR = 0x22E, + // A malformed function table was encountered during an unwind operation. + BAD_FUNCTION_TABLE = 0x22F, + // Indicates that an attempt was made to assign protection to a file system file or directory and one of the SIDs in the security descriptor could not be translated into a GUID that could be stored by the file system. This causes the protection attempt to fail, which may cause a file creation attempt to fail. + NO_GUID_TRANSLATION = 0x230, + // Indicates that an attempt was made to grow an LDT by setting its size, or that the size was not an even number of selectors. + INVALID_LDT_SIZE = 0x231, + // Indicates that the starting value for the LDT information was not an integral multiple of the selector size. + INVALID_LDT_OFFSET = 0x233, + // Indicates that the user supplied an invalid descriptor when trying to set up Ldt descriptors. + INVALID_LDT_DESCRIPTOR = 0x234, + // Indicates a process has too many threads to perform the requested action. For example, assignment of a primary token may only be performed when a process has zero or one threads. + TOO_MANY_THREADS = 0x235, + // An attempt was made to operate on a thread within a specific process, but the thread specified is not in the process specified. + THREAD_NOT_IN_PROCESS = 0x236, + // Page file quota was exceeded. + PAGEFILE_QUOTA_EXCEEDED = 0x237, + // The Netlogon service cannot start because another Netlogon service running in the domain conflicts with the specified role. + LOGON_SERVER_CONFLICT = 0x238, + // The SAM database on a Windows Server is significantly out of synchronization with the copy on the Domain Controller. A complete synchronization is required. + SYNCHRONIZATION_REQUIRED = 0x239, + // The NtCreateFile API failed. This error should never be returned to an application, it is a place holder for the Windows Lan Manager Redirector to use in its internal error mapping routines. + NET_OPEN_FAILED = 0x23A, + // {Privilege Failed} The I/O permissions for the process could not be changed. + IO_PRIVILEGE_FAILED = 0x23B, + // {Application Exit by CTRL+C} The application terminated as a result of a CTRL+C. + CONTROL_C_EXIT = 0x23C, + // {Missing System File} The required system file %hs is bad or missing. + MISSING_SYSTEMFILE = 0x23D, + // {Application Error} The exception %s (0x%08lx) occurred in the application at location 0x%08lx. + UNHANDLED_EXCEPTION = 0x23E, + // {Application Error} The application was unable to start correctly (0x%lx). Click OK to close the application. + APP_INIT_FAILURE = 0x23F, + // {Unable to Create Paging File} The creation of the paging file %hs failed (%lx). The requested size was %ld. + PAGEFILE_CREATE_FAILED = 0x240, + // Windows cannot verify the digital signature for this file. A recent hardware or software change might have installed a file that is signed incorrectly or damaged, or that might be malicious software from an unknown source. + INVALID_IMAGE_HASH = 0x241, + // {No Paging File Specified} No paging file was specified in the system configuration. + NO_PAGEFILE = 0x242, + // {EXCEPTION} A real-mode application issued a floating-point instruction and floating-point hardware is not present. + ILLEGAL_FLOAT_CONTEXT = 0x243, + // An event pair synchronization operation was performed using the thread specific client/server event pair object, but no event pair object was associated with the thread. + NO_EVENT_PAIR = 0x244, + // A Windows Server has an incorrect configuration. + DOMAIN_CTRLR_CONFIG_ERROR = 0x245, + // An illegal character was encountered. For a multi-byte character set this includes a lead byte without a succeeding trail byte. For the Unicode character set this includes the characters 0xFFFF and 0xFFFE. + ILLEGAL_CHARACTER = 0x246, + // The Unicode character is not defined in the Unicode character set installed on the system. + UNDEFINED_CHARACTER = 0x247, + // The paging file cannot be created on a floppy diskette. + FLOPPY_VOLUME = 0x248, + // The system BIOS failed to connect a system interrupt to the device or bus for which the device is connected. + BIOS_FAILED_TO_CONNECT_INTERRUPT = 0x249, + // This operation is only allowed for the Primary Domain Controller of the domain. + BACKUP_CONTROLLER = 0x24A, + // An attempt was made to acquire a mutant such that its maximum count would have been exceeded. + MUTANT_LIMIT_EXCEEDED = 0x24B, + // A volume has been accessed for which a file system driver is required that has not yet been loaded. + FS_DRIVER_REQUIRED = 0x24C, + // {Registry File Failure} The registry cannot load the hive (file): %hs or its log or alternate. It is corrupt, absent, or not writable. + CANNOT_LOAD_REGISTRY_FILE = 0x24D, + // {Unexpected Failure in DebugActiveProcess} An unexpected failure occurred while processing a DebugActiveProcess API request. You may choose OK to terminate the process, or Cancel to ignore the error. + DEBUG_ATTACH_FAILED = 0x24E, + // {Fatal System Error} The %hs system process terminated unexpectedly with a status of 0x%08x (0x%08x 0x%08x). The system has been shut down. + SYSTEM_PROCESS_TERMINATED = 0x24F, + // {Data Not Accepted} The TDI client could not handle the data received during an indication. + DATA_NOT_ACCEPTED = 0x250, + // NTVDM encountered a hard error. + VDM_HARD_ERROR = 0x251, + // {Cancel Timeout} The driver %hs failed to complete a cancelled I/O request in the allotted time. + DRIVER_CANCEL_TIMEOUT = 0x252, + // {Reply Message Mismatch} An attempt was made to reply to an LPC message, but the thread specified by the client ID in the message was not waiting on that message. + REPLY_MESSAGE_MISMATCH = 0x253, + // {Delayed Write Failed} Windows was unable to save all the data for the file %hs. The data has been lost. This error may be caused by a failure of your computer hardware or network connection. Please try to save this file elsewhere. + LOST_WRITEBEHIND_DATA = 0x254, + // The parameter(s) passed to the server in the client/server shared memory window were invalid. Too much data may have been put in the shared memory window. + CLIENT_SERVER_PARAMETERS_INVALID = 0x255, + // The stream is not a tiny stream. + NOT_TINY_STREAM = 0x256, + // The request must be handled by the stack overflow code. + STACK_OVERFLOW_READ = 0x257, + // Internal OFS status codes indicating how an allocation operation is handled. Either it is retried after the containing onode is moved or the extent stream is converted to a large stream. + CONVERT_TO_LARGE = 0x258, + // The attempt to find the object found an object matching by ID on the volume but it is out of the scope of the handle used for the operation. + FOUND_OUT_OF_SCOPE = 0x259, + // The bucket array must be grown. Retry transaction after doing so. + ALLOCATE_BUCKET = 0x25A, + // The user/kernel marshalling buffer has overflowed. + MARSHALL_OVERFLOW = 0x25B, + // The supplied variant structure contains invalid data. + INVALID_VARIANT = 0x25C, + // The specified buffer contains ill-formed data. + BAD_COMPRESSION_BUFFER = 0x25D, + // {Audit Failed} An attempt to generate a security audit failed. + AUDIT_FAILED = 0x25E, + // The timer resolution was not previously set by the current process. + TIMER_RESOLUTION_NOT_SET = 0x25F, + // There is insufficient account information to log you on. + INSUFFICIENT_LOGON_INFO = 0x260, + // {Invalid DLL Entrypoint} The dynamic link library %hs is not written correctly. The stack pointer has been left in an inconsistent state. The entrypoint should be declared as WINAPI or STDCALL. Select YES to fail the DLL load. Select NO to continue execution. Selecting NO may cause the application to operate incorrectly. + BAD_DLL_ENTRYPOINT = 0x261, + // {Invalid Service Callback Entrypoint} The %hs service is not written correctly. The stack pointer has been left in an inconsistent state. The callback entrypoint should be declared as WINAPI or STDCALL. Selecting OK will cause the service to continue operation. However, the service process may operate incorrectly. + BAD_SERVICE_ENTRYPOINT = 0x262, + // There is an IP address conflict with another system on the network. + IP_ADDRESS_CONFLICT1 = 0x263, + // There is an IP address conflict with another system on the network. + IP_ADDRESS_CONFLICT2 = 0x264, + // {Low On Registry Space} The system has reached the maximum size allowed for the system part of the registry. Additional storage requests will be ignored. + REGISTRY_QUOTA_LIMIT = 0x265, + // A callback return system service cannot be executed when no callback is active. + NO_CALLBACK_ACTIVE = 0x266, + // The password provided is too short to meet the policy of your user account. Please choose a longer password. + PWD_TOO_SHORT = 0x267, + // The policy of your user account does not allow you to change passwords too frequently. This is done to prevent users from changing back to a familiar, but potentially discovered, password. If you feel your password has been compromised then please contact your administrator immediately to have a new one assigned. + PWD_TOO_RECENT = 0x268, + // You have attempted to change your password to one that you have used in the past. The policy of your user account does not allow this. Please select a password that you have not previously used. + PWD_HISTORY_CONFLICT = 0x269, + // The specified compression format is unsupported. + UNSUPPORTED_COMPRESSION = 0x26A, + // The specified hardware profile configuration is invalid. + INVALID_HW_PROFILE = 0x26B, + // The specified Plug and Play registry device path is invalid. + INVALID_PLUGPLAY_DEVICE_PATH = 0x26C, + // The specified quota list is internally inconsistent with its descriptor. + QUOTA_LIST_INCONSISTENT = 0x26D, + // {Windows Evaluation Notification} The evaluation period for this installation of Windows has expired. This system will shutdown in 1 hour. To restore access to this installation of Windows, please upgrade this installation using a licensed distribution of this product. + EVALUATION_EXPIRATION = 0x26E, + // {Illegal System DLL Relocation} The system DLL %hs was relocated in memory. The application will not run properly. The relocation occurred because the DLL %hs occupied an address range reserved for Windows system DLLs. The vendor supplying the DLL should be contacted for a new DLL. + ILLEGAL_DLL_RELOCATION = 0x26F, + // {DLL Initialization Failed} The application failed to initialize because the window station is shutting down. + DLL_INIT_FAILED_LOGOFF = 0x270, + // The validation process needs to continue on to the next step. + VALIDATE_CONTINUE = 0x271, + // There are no more matches for the current index enumeration. + NO_MORE_MATCHES = 0x272, + // The range could not be added to the range list because of a conflict. + RANGE_LIST_CONFLICT = 0x273, + // The server process is running under a SID different than that required by client. + SERVER_SID_MISMATCH = 0x274, + // A group marked use for deny only cannot be enabled. + CANT_ENABLE_DENY_ONLY = 0x275, + // {EXCEPTION} Multiple floating point faults. + FLOAT_MULTIPLE_FAULTS = 0x276, + // {EXCEPTION} Multiple floating point traps. + FLOAT_MULTIPLE_TRAPS = 0x277, + // The requested interface is not supported. + NOINTERFACE = 0x278, + // {System Standby Failed} The driver %hs does not support standby mode. Updating this driver may allow the system to go to standby mode. + DRIVER_FAILED_SLEEP = 0x279, + // The system file %1 has become corrupt and has been replaced. + CORRUPT_SYSTEM_FILE = 0x27A, + // {Virtual Memory Minimum Too Low} Your system is low on virtual memory. Windows is increasing the size of your virtual memory paging file. During this process, memory requests for some applications may be denied. For more information, see Help. + COMMITMENT_MINIMUM = 0x27B, + // A device was removed so enumeration must be restarted. + PNP_RESTART_ENUMERATION = 0x27C, + // {Fatal System Error} The system image %s is not properly signed. The file has been replaced with the signed file. The system has been shut down. + SYSTEM_IMAGE_BAD_SIGNATURE = 0x27D, + // Device will not start without a reboot. + PNP_REBOOT_REQUIRED = 0x27E, + // There is not enough power to complete the requested operation. + INSUFFICIENT_POWER = 0x27F, + // MULTIPLE_FAULT_VIOLATION = , = 0x281, + MULTIPLE_FAULT_VIOLATION = 0x280, // The system is in the process of shutting down. + // An attempt to remove a processes DebugPort was made, but a port was not already associated with the process. + PORT_NOT_SET = 0x282, + // This version of Windows is not compatible with the behavior version of directory forest, domain or domain controller. + DS_VERSION_CHECK_FAILURE = 0x283, + // The specified range could not be found in the range list. + RANGE_NOT_FOUND = 0x284, + // The driver was not loaded because the system is booting into safe mode. + NOT_SAFE_MODE_DRIVER = 0x286, + // The driver was not loaded because it failed its initialization call. + FAILED_DRIVER_ENTRY = 0x287, + // The "%hs" encountered an error while applying power or reading the device configuration. This may be caused by a failure of your hardware or by a poor connection. + DEVICE_ENUMERATION_ERROR = 0x288, + // The create operation failed because the name contained at least one mount point which resolves to a volume to which the specified device object is not attached. + MOUNT_POINT_NOT_RESOLVED = 0x289, + // The device object parameter is either not a valid device object or is not attached to the volume specified by the file name. + INVALID_DEVICE_OBJECT_PARAMETER = 0x28A, + // A Machine Check Error has occurred. Please check the system eventlog for additional information. + MCA_OCCURED = 0x28B, + // There was error [%2] processing the driver database. + DRIVER_DATABASE_ERROR = 0x28C, + // System hive size has exceeded its limit. + SYSTEM_HIVE_TOO_LARGE = 0x28D, + // The driver could not be loaded because a previous version of the driver is still in memory. + DRIVER_FAILED_PRIOR_UNLOAD = 0x28E, + // {Volume Shadow Copy Service} Please wait while the Volume Shadow Copy Service prepares volume %hs for hibernation. + VOLSNAP_PREPARE_HIBERNATE = 0x28F, + // The system has failed to hibernate (The error code is %hs). Hibernation will be disabled until the system is restarted. + HIBERNATION_FAILURE = 0x290, + // The password provided is too long to meet the policy of your user account. Please choose a shorter password. + PWD_TOO_LONG = 0x291, + // The requested operation could not be completed due to a file system limitation. + FILE_SYSTEM_LIMITATION = 0x299, + // An assertion failure has occurred. + ASSERTION_FAILURE = 0x29C, + // An error occurred in the ACPI subsystem. + ACPI_ERROR = 0x29D, + // WOW Assertion Error. + WOW_ASSERTION = 0x29E, + // A device is missing in the system BIOS MPS table. This device will not be used. Please contact your system vendor for system BIOS update. + PNP_BAD_MPS_TABLE = 0x29F, + // A translator failed to translate resources. + PNP_TRANSLATION_FAILED = 0x2A0, + // A IRQ translator failed to translate resources. + PNP_IRQ_TRANSLATION_FAILED = 0x2A1, + // Driver %2 returned invalid ID for a child device (%3). + PNP_INVALID_ID = 0x2A2, + // {Kernel Debugger Awakened} the system debugger was awakened by an interrupt. + WAKE_SYSTEM_DEBUGGER = 0x2A3, + // {Handles Closed} Handles to objects have been automatically closed as a result of the requested operation. + HANDLES_CLOSED = 0x2A4, + // {Too Much Information} The specified access control list (ACL) contained more information than was expected. + EXTRANEOUS_INFORMATION = 0x2A5, + // This warning level status indicates that the transaction state already exists for the registry sub-tree, but that a transaction commit was previously aborted. The commit has NOT been completed, but has not been rolled back either (so it may still be committed if desired). + RXACT_COMMIT_NECESSARY = 0x2A6, + // {Media Changed} The media may have changed. + MEDIA_CHECK = 0x2A7, + // {GUID Substitution} During the translation of a global identifier (GUID) to a Windows security ID SID, no administratively-defined GUID prefix was found. A substitute prefix was used, which will not compromise system security. However, this may provide a more restrictive access than intended. + GUID_SUBSTITUTION_MADE = 0x2A8, + // The create operation stopped after reaching a symbolic link. + STOPPED_ON_SYMLINK = 0x2A9, + // A long jump has been executed. + LONGJUMP = 0x2AA, + // The Plug and Play query operation was not successful. + PLUGPLAY_QUERY_VETOED = 0x2AB, + // A frame consolidation has been executed. + UNWIND_CONSOLIDATE = 0x2AC, + // {Registry Hive Recovered} Registry hive (file): %hs was corrupted and it has been recovered. Some data might have been lost. + REGISTRY_HIVE_RECOVERED = 0x2AD, + // The application is attempting to run executable code from the module %hs. This may be insecure. An alternative, %hs, is available. Should the application use the secure module %hs? + DLL_MIGHT_BE_INSECURE = 0x2AE, + // The application is loading executable code from the module %hs. This is secure, but may be incompatible with previous releases of the operating system. An alternative, %hs, is available. Should the application use the secure module %hs? + DLL_MIGHT_BE_INCOMPATIBLE = 0x2AF, + // Debugger did not handle the exception. + DBG_EXCEPTION_NOT_HANDLED = 0x2B0, + // Debugger will reply later. + DBG_REPLY_LATER = 0x2B1, + // Debugger cannot provide handle. + DBG_UNABLE_TO_PROVIDE_HANDLE = 0x2B2, + // Debugger terminated thread. + DBG_TERMINATE_THREAD = 0x2B3, + // Debugger terminated process. + DBG_TERMINATE_PROCESS = 0x2B4, + // Debugger got control C. + DBG_CONTROL_C = 0x2B5, + // Debugger printed exception on control C. + DBG_PRINTEXCEPTION_C = 0x2B6, + // Debugger received RIP exception. + DBG_RIPEXCEPTION = 0x2B7, + // Debugger received control break. + DBG_CONTROL_BREAK = 0x2B8, + // Debugger command communication exception. + DBG_COMMAND_EXCEPTION = 0x2B9, + // {Object Exists} An attempt was made to create an object and the object name already existed. + OBJECT_NAME_EXISTS = 0x2BA, + // {Thread Suspended} A thread termination occurred while the thread was suspended. The thread was resumed, and termination proceeded. + THREAD_WAS_SUSPENDED = 0x2BB, + // {Image Relocated} An image file could not be mapped at the address specified in the image file. Local fixups must be performed on this image. + IMAGE_NOT_AT_BASE = 0x2BC, + // This informational level status indicates that a specified registry sub-tree transaction state did not yet exist and had to be created. + RXACT_STATE_CREATED = 0x2BD, + // {Segment Load} A virtual DOS machine (VDM) is loading, unloading, or moving an MS-DOS or Win16 program segment image. An exception is raised so a debugger can load, unload or track symbols and breakpoints within these 16-bit segments. + SEGMENT_NOTIFICATION = 0x2BE, + // {Invalid Current Directory} The process cannot switch to the startup current directory %hs. Select OK to set current directory to %hs, or select CANCEL to exit. + BAD_CURRENT_DIRECTORY = 0x2BF, + // {Redundant Read} To satisfy a read request, the NT fault-tolerant file system successfully read the requested data from a redundant copy. This was done because the file system encountered a failure on a member of the fault-tolerant volume, but was unable to reassign the failing area of the device. + FT_READ_RECOVERY_FROM_BACKUP = 0x2C0, + // {Redundant Write} To satisfy a write request, the NT fault-tolerant file system successfully wrote a redundant copy of the information. This was done because the file system encountered a failure on a member of the fault-tolerant volume, but was not able to reassign the failing area of the device. + FT_WRITE_RECOVERY = 0x2C1, + // {Machine Type Mismatch} The image file %hs is valid, but is for a machine type other than the current machine. Select OK to continue, or CANCEL to fail the DLL load. + IMAGE_MACHINE_TYPE_MISMATCH = 0x2C2, + // {Partial Data Received} The network transport returned partial data to its client. The remaining data will be sent later. + RECEIVE_PARTIAL = 0x2C3, + // {Expedited Data Received} The network transport returned data to its client that was marked as expedited by the remote system. + RECEIVE_EXPEDITED = 0x2C4, + // {Partial Expedited Data Received} The network transport returned partial data to its client and this data was marked as expedited by the remote system. The remaining data will be sent later. + RECEIVE_PARTIAL_EXPEDITED = 0x2C5, + // {TDI Event Done} The TDI indication has completed successfully. + EVENT_DONE = 0x2C6, + // {TDI Event Pending} The TDI indication has entered the pending state. + EVENT_PENDING = 0x2C7, + // Checking file system on %wZ. + CHECKING_FILE_SYSTEM = 0x2C8, + // {Fatal Application Exit} %hs. + FATAL_APP_EXIT = 0x2C9, + // The specified registry key is referenced by a predefined handle. + PREDEFINED_HANDLE = 0x2CA, + // {Page Unlocked} The page protection of a locked page was changed to 'No Access' and the page was unlocked from memory and from the process. + WAS_UNLOCKED = 0x2CB, + // %hs + SERVICE_NOTIFICATION = 0x2CC, + // {Page Locked} One of the pages to lock was already locked. + WAS_LOCKED = 0x2CD, + // Application popup: %1 : %2 + LOG_HARD_ERROR = 0x2CE, + // ALREADY_WIN32 = , = 0x2D0, + ALREADY_WIN32 = 0x2CF, // {Machine Type Mismatch} The image file %hs is valid, but is for a machine type other than the current machine. + // A yield execution was performed and no thread was available to run. + NO_YIELD_PERFORMED = 0x2D1, + // The resumable flag to a timer API was ignored. + TIMER_RESUME_IGNORED = 0x2D2, + // The arbiter has deferred arbitration of these resources to its parent. + ARBITRATION_UNHANDLED = 0x2D3, + // The inserted CardBus device cannot be started because of a configuration error on "%hs". + CARDBUS_NOT_SUPPORTED = 0x2D4, + // The CPUs in this multiprocessor system are not all the same revision level. To use all processors the operating system restricts itself to the features of the least capable processor in the system. Should problems occur with this system, contact the CPU manufacturer to see if this mix of processors is supported. + MP_PROCESSOR_MISMATCH = 0x2D5, + // The system was put into hibernation. + HIBERNATED = 0x2D6, + // The system was resumed from hibernation. + RESUME_HIBERNATION = 0x2D7, + // Windows has detected that the system firmware (BIOS) was updated [previous firmware date = %2, current firmware date %3]. + FIRMWARE_UPDATED = 0x2D8, + // A device driver is leaking locked I/O pages causing system degradation. The system has automatically enabled tracking code in order to try and catch the culprit. + DRIVERS_LEAKING_LOCKED_PAGES = 0x2D9, + // The system has awoken. + WAKE_SYSTEM = 0x2DA, + // WAIT_1 = , = 0x2DC, + WAIT_1 = 0x2DB, // WAIT_2 = , = 0x2DD, // WAIT_3 = , = 0x2DE, // WAIT_63 = , = 0x2DF, // ABANDONED_WAIT_0 = , = 0x2E0, // ABANDONED_WAIT_63 = , = 0x2E1, // USER_APC = , = 0x2E2, // KERNEL_APC = , = 0x2E3, // ALERTED = , = 0x2E4, // The requested operation requires elevation. + // A reparse should be performed by the Object Manager since the name of the file resulted in a symbolic link. + REPARSE = 0x2E5, + // An open/create operation completed while an oplock break is underway. + OPLOCK_BREAK_IN_PROGRESS = 0x2E6, + // A new volume has been mounted by a file system. + VOLUME_MOUNTED = 0x2E7, + // This success level status indicates that the transaction state already exists for the registry sub-tree, but that a transaction commit was previously aborted. The commit has now been completed. + RXACT_COMMITTED = 0x2E8, + // This indicates that a notify change request has been completed due to closing the handle which made the notify change request. + NOTIFY_CLEANUP = 0x2E9, + // {Connect Failure on Primary Transport} An attempt was made to connect to the remote server %hs on the primary transport, but the connection failed. The computer WAS able to connect on a secondary transport. + PRIMARY_TRANSPORT_CONNECT_FAILED = 0x2EA, + // Page fault was a transition fault. + PAGE_FAULT_TRANSITION = 0x2EB, + // Page fault was a demand zero fault. + PAGE_FAULT_DEMAND_ZERO = 0x2EC, + // Page fault was a demand zero fault. + PAGE_FAULT_COPY_ON_WRITE = 0x2ED, + // Page fault was a demand zero fault. + PAGE_FAULT_GUARD_PAGE = 0x2EE, + // Page fault was satisfied by reading from a secondary storage device. + PAGE_FAULT_PAGING_FILE = 0x2EF, + // Cached page was locked during operation. + CACHE_PAGE_LOCKED = 0x2F0, + // Crash dump exists in paging file. + CRASH_DUMP = 0x2F1, + // Specified buffer contains all zeros. + BUFFER_ALL_ZEROS = 0x2F2, + // A reparse should be performed by the Object Manager since the name of the file resulted in a symbolic link. + REPARSE_OBJECT = 0x2F3, + // The device has succeeded a query-stop and its resource requirements have changed. + RESOURCE_REQUIREMENTS_CHANGED = 0x2F4, + // The translator has translated these resources into the global space and no further translations should be performed. + TRANSLATION_COMPLETE = 0x2F5, + // A process being terminated has no threads to terminate. + NOTHING_TO_TERMINATE = 0x2F6, + // The specified process is not part of a job. + PROCESS_NOT_IN_JOB = 0x2F7, + // The specified process is part of a job. + PROCESS_IN_JOB = 0x2F8, + // {Volume Shadow Copy Service} The system is now ready for hibernation. + VOLSNAP_HIBERNATE_READY = 0x2F9, + // A file system or file system filter driver has successfully completed an FsFilter operation. + FSFILTER_OP_COMPLETED_SUCCESSFULLY = 0x2FA, + // The specified interrupt vector was already connected. + INTERRUPT_VECTOR_ALREADY_CONNECTED = 0x2FB, + // The specified interrupt vector is still connected. + INTERRUPT_STILL_CONNECTED = 0x2FC, + // An operation is blocked waiting for an oplock. + WAIT_FOR_OPLOCK = 0x2FD, + // Debugger handled exception. + DBG_EXCEPTION_HANDLED = 0x2FE, + // Debugger continued. + DBG_CONTINUE = 0x2FF, + // An exception occurred in a user mode callback and the kernel callback frame should be removed. + CALLBACK_POP_STACK = 0x300, + // Compression is disabled for this volume. + COMPRESSION_DISABLED = 0x301, + // The data provider cannot fetch backwards through a result set. + CANTFETCHBACKWARDS = 0x302, + // The data provider cannot scroll backwards through a result set. + CANTSCROLLBACKWARDS = 0x303, + // The data provider requires that previously fetched data is released before asking for more data. + ROWSNOTRELEASED = 0x304, + // The data provider was not able to interpret the flags set for a column binding in an accessor. + BAD_ACCESSOR_FLAGS = 0x305, + // One or more errors occurred while processing the request. + ERRORS_ENCOUNTERED = 0x306, + // The implementation is not capable of performing the request. + NOT_CAPABLE = 0x307, + // The client of a component requested an operation which is not valid given the state of the component instance. + REQUEST_OUT_OF_SEQUENCE = 0x308, + // A version number could not be parsed. + VERSION_PARSE_ERROR = 0x309, + // The iterator's start position is invalid. + BADSTARTPOSITION = 0x30A, + // The hardware has reported an uncorrectable memory error. + MEMORY_HARDWARE = 0x30B, + // The attempted operation required self healing to be enabled. + DISK_REPAIR_DISABLED = 0x30C, + // The Desktop heap encountered an error while allocating session memory. There is more information in the system event log. + INSUFFICIENT_RESOURCE_FOR_SPECIFIED_SHARED_SECTION_SIZE = 0x30D, + // The system power state is transitioning from %2 to %3. + SYSTEM_POWERSTATE_TRANSITION = 0x30E, + // The system power state is transitioning from %2 to %3 but could enter %4. + SYSTEM_POWERSTATE_COMPLEX_TRANSITION = 0x30F, + // A thread is getting dispatched with MCA EXCEPTION because of MCA. + MCA_EXCEPTION = 0x310, + // Access to %1 is monitored by policy rule %2. + ACCESS_AUDIT_BY_POLICY = 0x311, + // Access to %1 has been restricted by your Administrator by policy rule %2. + ACCESS_DISABLED_NO_SAFER_UI_BY_POLICY = 0x312, + // A valid hibernation file has been invalidated and should be abandoned. + ABANDON_HIBERFILE = 0x313, + // {Delayed Write Failed} Windows was unable to save all the data for the file %hs; the data has been lost. This error may be caused by network connectivity issues. Please try to save this file elsewhere. + LOST_WRITEBEHIND_DATA_NETWORK_DISCONNECTED = 0x314, + // {Delayed Write Failed} Windows was unable to save all the data for the file %hs; the data has been lost. This error was returned by the server on which the file exists. Please try to save this file elsewhere. + LOST_WRITEBEHIND_DATA_NETWORK_SERVER_ERROR = 0x315, + // {Delayed Write Failed} Windows was unable to save all the data for the file %hs; the data has been lost. This error may be caused if the device has been removed or the media is write-protected. + LOST_WRITEBEHIND_DATA_LOCAL_DISK_ERROR = 0x316, + // The resources required for this device conflict with the MCFG table. + BAD_MCFG_TABLE = 0x317, + // The volume repair could not be performed while it is online. Please schedule to take the volume offline so that it can be repaired. + DISK_REPAIR_REDIRECTED = 0x318, + // The volume repair was not successful. + DISK_REPAIR_UNSUCCESSFUL = 0x319, + // One of the volume corruption logs is full. Further corruptions that may be detected won't be logged. + CORRUPT_LOG_OVERFULL = 0x31A, + // One of the volume corruption logs is internally corrupted and needs to be recreated. The volume may contain undetected corruptions and must be scanned. + CORRUPT_LOG_CORRUPTED = 0x31B, + // One of the volume corruption logs is unavailable for being operated on. + CORRUPT_LOG_UNAVAILABLE = 0x31C, + // One of the volume corruption logs was deleted while still having corruption records in them. The volume contains detected corruptions and must be scanned. + CORRUPT_LOG_DELETED_FULL = 0x31D, + // One of the volume corruption logs was cleared by chkdsk and no longer contains real corruptions. + CORRUPT_LOG_CLEARED = 0x31E, + // Orphaned files exist on the volume but could not be recovered because no more new names could be created in the recovery directory. Files must be moved from the recovery directory. + ORPHAN_NAME_EXHAUSTED = 0x31F, + // The oplock that was associated with this handle is now associated with a different handle. + OPLOCK_SWITCHED_TO_NEW_HANDLE = 0x320, + // An oplock of the requested level cannot be granted. An oplock of a lower level may be available. + CANNOT_GRANT_REQUESTED_OPLOCK = 0x321, + // The operation did not complete successfully because it would cause an oplock to be broken. The caller has requested that existing oplocks not be broken. + CANNOT_BREAK_OPLOCK = 0x322, + // The handle with which this oplock was associated has been closed. The oplock is now broken. + OPLOCK_HANDLE_CLOSED = 0x323, + // The specified access control entry (ACE) does not contain a condition. + NO_ACE_CONDITION = 0x324, + // The specified access control entry (ACE) contains an invalid condition. + INVALID_ACE_CONDITION = 0x325, + // Access to the specified file handle has been revoked. + FILE_HANDLE_REVOKED = 0x326, + // An image file was mapped at a different address from the one specified in the image file but fixups will still be automatically performed on the image. + IMAGE_AT_DIFFERENT_BASE = 0x327, + // Access to the extended attribute was denied. + EA_ACCESS_DENIED = 0x3E2, + // The I/O operation has been aborted because of either a thread exit or an application request. + OPERATION_ABORTED = 0x3E3, + // Overlapped I/O event is not in a signaled state. + IO_INCOMPLETE = 0x3E4, + // Overlapped I/O operation is in progress. + IO_PENDING = 0x3E5, + // Invalid access to memory location. + NOACCESS = 0x3E6, + // Error performing inpage operation. + SWAPERROR = 0x3E7, + + // Recursion too deep; the stack overflowed. + STACK_OVERFLOW = 0x3E9, + // The window cannot act on the sent message. + INVALID_MESSAGE = 0x3EA, + // Cannot complete this function. + CAN_NOT_COMPLETE = 0x3EB, + // Invalid flags. + INVALID_FLAGS = 0x3EC, + // The volume does not contain a recognized file system. Please make sure that all required file system drivers are loaded and that the volume is not corrupted. + UNRECOGNIZED_VOLUME = 0x3ED, + // The volume for a file has been externally altered so that the opened file is no longer valid. + FILE_INVALID = 0x3EE, + // The requested operation cannot be performed in full-screen mode. + FULLSCREEN_MODE = 0x3EF, + // An attempt was made to reference a token that does not exist. + NO_TOKEN = 0x3F0, + // The configuration registry database is corrupt. + BADDB = 0x3F1, + // The configuration registry key is invalid. + BADKEY = 0x3F2, + // The configuration registry key could not be opened. + CANTOPEN = 0x3F3, + // The configuration registry key could not be read. + CANTREAD = 0x3F4, + // The configuration registry key could not be written. + CANTWRITE = 0x3F5, + // One of the files in the registry database had to be recovered by use of a log or alternate copy. The recovery was successful. + REGISTRY_RECOVERED = 0x3F6, + // The registry is corrupted. The structure of one of the files containing registry data is corrupted, or the system's memory image of the file is corrupted, or the file could not be recovered because the alternate copy or log was absent or corrupted. + REGISTRY_CORRUPT = 0x3F7, + // An I/O operation initiated by the registry failed unrecoverably. The registry could not read in, or write out, or flush, one of the files that contain the system's image of the registry. + REGISTRY_IO_FAILED = 0x3F8, + // The system has attempted to load or restore a file into the registry, but the specified file is not in a registry file format. + NOT_REGISTRY_FILE = 0x3F9, + // Illegal operation attempted on a registry key that has been marked for deletion. + KEY_DELETED = 0x3FA, + // System could not allocate the required space in a registry log. + NO_LOG_SPACE = 0x3FB, + // Cannot create a symbolic link in a registry key that already has subkeys or values. + KEY_HAS_CHILDREN = 0x3FC, + // Cannot create a stable subkey under a volatile parent key. + CHILD_MUST_BE_VOLATILE = 0x3FD, + // A notify change request is being completed and the information is not being returned in the caller's buffer. The caller now needs to enumerate the files to find the changes. + NOTIFY_ENUM_DIR = 0x3FE, + // A stop control has been sent to a service that other running services are dependent on. + DEPENDENT_SERVICES_RUNNING = 0x41B, + // The requested control is not valid for this service. + INVALID_SERVICE_CONTROL = 0x41C, + // The service did not respond to the start or control request in a timely fashion. + SERVICE_REQUEST_TIMEOUT = 0x41D, + // A thread could not be created for the service. + SERVICE_NO_THREAD = 0x41E, + // The service database is locked. + SERVICE_DATABASE_LOCKED = 0x41F, + // An instance of the service is already running. + SERVICE_ALREADY_RUNNING = 0x420, + // The account name is invalid or does not exist, or the password is invalid for the account name specified. + INVALID_SERVICE_ACCOUNT = 0x421, + // The service cannot be started, either because it is disabled or because it has no enabled devices associated with it. + SERVICE_DISABLED = 0x422, + // Circular service dependency was specified. + CIRCULAR_DEPENDENCY = 0x423, + // The specified service does not exist as an installed service. + SERVICE_DOES_NOT_EXIST = 0x424, + // The service cannot accept control messages at this time. + SERVICE_CANNOT_ACCEPT_CTRL = 0x425, + // The service has not been started. + SERVICE_NOT_ACTIVE = 0x426, + // The service process could not connect to the service controller. + FAILED_SERVICE_CONTROLLER_CONNECT = 0x427, + // An exception occurred in the service when handling the control request. + EXCEPTION_IN_SERVICE = 0x428, + // The database specified does not exist. + DATABASE_DOES_NOT_EXIST = 0x429, + // The service has returned a service-specific error code. + SERVICE_SPECIFIC_ERROR = 0x42A, + // The process terminated unexpectedly. + PROCESS_ABORTED = 0x42B, + // The dependency service or group failed to start. + SERVICE_DEPENDENCY_FAIL = 0x42C, + // The service did not start due to a logon failure. + SERVICE_LOGON_FAILED = 0x42D, + // After starting, the service hung in a start-pending state. + SERVICE_START_HANG = 0x42E, + // The specified service database lock is invalid. + INVALID_SERVICE_LOCK = 0x42F, + // The specified service has been marked for deletion. + SERVICE_MARKED_FOR_DELETE = 0x430, + // The specified service already exists. + SERVICE_EXISTS = 0x431, + // The system is currently running with the last-known-good configuration. + ALREADY_RUNNING_LKG = 0x432, + // The dependency service does not exist or has been marked for deletion. + SERVICE_DEPENDENCY_DELETED = 0x433, + // The current boot has already been accepted for use as the last-known-good control set. + BOOT_ALREADY_ACCEPTED = 0x434, + // No attempts to start the service have been made since the last boot. + SERVICE_NEVER_STARTED = 0x435, + // The name is already in use as either a service name or a service display name. + DUPLICATE_SERVICE_NAME = 0x436, + // The account specified for this service is different from the account specified for other services running in the same process. + DIFFERENT_SERVICE_ACCOUNT = 0x437, + // Failure actions can only be set for Win32 services, not for drivers. + CANNOT_DETECT_DRIVER_FAILURE = 0x438, + // This service runs in the same process as the service control manager. Therefore, the service control manager cannot take action if this service's process terminates unexpectedly. + CANNOT_DETECT_PROCESS_ABORT = 0x439, + // No recovery program has been configured for this service. + NO_RECOVERY_PROGRAM = 0x43A, + // The executable program that this service is configured to run in does not implement the service. + SERVICE_NOT_IN_EXE = 0x43B, + // This service cannot be started in Safe Mode. + NOT_SAFEBOOT_SERVICE = 0x43C, + // The physical end of the tape has been reached. + END_OF_MEDIA = 0x44C, + // A tape access reached a filemark. + FILEMARK_DETECTED = 0x44D, + // The beginning of the tape or a partition was encountered. + BEGINNING_OF_MEDIA = 0x44E, + // A tape access reached the end of a set of files. + SETMARK_DETECTED = 0x44F, + // No more data is on the tape. + NO_DATA_DETECTED = 0x450, + // Tape could not be partitioned. + PARTITION_FAILURE = 0x451, + // When accessing a new tape of a multivolume partition, the current block size is incorrect. + INVALID_BLOCK_LENGTH = 0x452, + // Tape partition information could not be found when loading a tape. + DEVICE_NOT_PARTITIONED = 0x453, + // Unable to lock the media eject mechanism. + UNABLE_TO_LOCK_MEDIA = 0x454, + // Unable to unload the media. + UNABLE_TO_UNLOAD_MEDIA = 0x455, + // The media in the drive may have changed. + MEDIA_CHANGED = 0x456, + // The I/O bus was reset. + BUS_RESET = 0x457, + // No media in drive. + NO_MEDIA_IN_DRIVE = 0x458, + // No mapping for the Unicode character exists in the target multi-byte code page. + NO_UNICODE_TRANSLATION = 0x459, + // A dynamic link library (DLL) initialization routine failed. + DLL_INIT_FAILED = 0x45A, + // A system shutdown is in progress. + SHUTDOWN_IN_PROGRESS = 0x45B, + // Unable to abort the system shutdown because no shutdown was in progress. + NO_SHUTDOWN_IN_PROGRESS = 0x45C, + // The request could not be performed because of an I/O device error. + IO_DEVICE = 0x45D, + // No serial device was successfully initialized. The serial driver will unload. + SERIAL_NO_DEVICE = 0x45E, + // Unable to open a device that was sharing an interrupt request (IRQ) with other devices. At least one other device that uses that IRQ was already opened. + IRQ_BUSY = 0x45F, + // A serial I/O operation was completed by another write to the serial port. The IOCTL_SERIAL_XOFF_COUNTER reached zero.) + MORE_WRITES = 0x460, + // A serial I/O operation completed because the timeout period expired. The IOCTL_SERIAL_XOFF_COUNTER did not reach zero.) + COUNTER_TIMEOUT = 0x461, + // No ID address mark was found on the floppy disk. + FLOPPY_ID_MARK_NOT_FOUND = 0x462, + // Mismatch between the floppy disk sector ID field and the floppy disk controller track address. + FLOPPY_WRONG_CYLINDER = 0x463, + // The floppy disk controller reported an error that is not recognized by the floppy disk driver. + FLOPPY_UNKNOWN_ERROR = 0x464, + // The floppy disk controller returned inconsistent results in its registers. + FLOPPY_BAD_REGISTERS = 0x465, + // While accessing the hard disk, a recalibrate operation failed, even after retries. + DISK_RECALIBRATE_FAILED = 0x466, + // While accessing the hard disk, a disk operation failed even after retries. + DISK_OPERATION_FAILED = 0x467, + // While accessing the hard disk, a disk controller reset was needed, but even that failed. + DISK_RESET_FAILED = 0x468, + // Physical end of tape encountered. + EOM_OVERFLOW = 0x469, + // Not enough server storage is available to process this command. + NOT_ENOUGH_SERVER_MEMORY = 0x46A, + // A potential deadlock condition has been detected. + POSSIBLE_DEADLOCK = 0x46B, + // The base address or the file offset specified does not have the proper alignment. + MAPPED_ALIGNMENT = 0x46C, + // An attempt to change the system power state was vetoed by another application or driver. + SET_POWER_STATE_VETOED = 0x474, + // The system BIOS failed an attempt to change the system power state. + SET_POWER_STATE_FAILED = 0x475, + // An attempt was made to create more links on a file than the file system supports. + TOO_MANY_LINKS = 0x476, + // The specified program requires a newer version of Windows. + OLD_WIN_VERSION = 0x47E, + // The specified program is not a Windows or MS-DOS program. + APP_WRONG_OS = 0x47F, + // Cannot start more than one instance of the specified program. + SINGLE_INSTANCE_APP = 0x480, + // The specified program was written for an earlier version of Windows. + RMODE_APP = 0x481, + // One of the library files needed to run this application is damaged. + INVALID_DLL = 0x482, + // No application is associated with the specified file for this operation. + NO_ASSOCIATION = 0x483, + // An error occurred in sending the command to the application. + DDE_FAIL = 0x484, + // One of the library files needed to run this application cannot be found. + DLL_NOT_FOUND = 0x485, + // The current process has used all of its system allowance of handles for Window Manager objects. + NO_MORE_USER_HANDLES = 0x486, + // The message can be used only with synchronous operations. + MESSAGE_SYNC_ONLY = 0x487, + // The indicated source element has no media. + SOURCE_ELEMENT_EMPTY = 0x488, + // The indicated destination element already contains media. + DESTINATION_ELEMENT_FULL = 0x489, + // The indicated element does not exist. + ILLEGAL_ELEMENT_ADDRESS = 0x48A, + // The indicated element is part of a magazine that is not present. + MAGAZINE_NOT_PRESENT = 0x48B, + // The indicated device requires reinitialization due to hardware errors. + DEVICE_REINITIALIZATION_NEEDED = 0x48C, + // The device has indicated that cleaning is required before further operations are attempted. + DEVICE_REQUIRES_CLEANING = 0x48D, + // The device has indicated that its door is open. + DEVICE_DOOR_OPEN = 0x48E, + // The device is not connected. + DEVICE_NOT_CONNECTED = 0x48F, + // Element not found. + NOT_FOUND = 0x490, + // There was no match for the specified key in the index. + NO_MATCH = 0x491, + // The property set specified does not exist on the object. + SET_NOT_FOUND = 0x492, + // The point passed to GetMouseMovePoints is not in the buffer. + POINT_NOT_FOUND = 0x493, + // The tracking (workstation) service is not running. + NO_TRACKING_SERVICE = 0x494, + // The Volume ID could not be found. + NO_VOLUME_ID = 0x495, + // Unable to remove the file to be replaced. + UNABLE_TO_REMOVE_REPLACED = 0x497, + // Unable to move the replacement file to the file to be replaced. The file to be replaced has retained its original name. + UNABLE_TO_MOVE_REPLACEMENT = 0x498, + // Unable to move the replacement file to the file to be replaced. The file to be replaced has been renamed using the backup name. + UNABLE_TO_MOVE_REPLACEMENT_2 = 0x499, + // The volume change journal is being deleted. + JOURNAL_DELETE_IN_PROGRESS = 0x49A, + // The volume change journal is not active. + JOURNAL_NOT_ACTIVE = 0x49B, + // A file was found, but it may not be the correct file. + POTENTIAL_FILE_FOUND = 0x49C, + // The journal entry has been deleted from the journal. + JOURNAL_ENTRY_DELETED = 0x49D, + // A system shutdown has already been scheduled. + SHUTDOWN_IS_SCHEDULED = 0x4A6, + // The system shutdown cannot be initiated because there are other users logged on to the computer. + SHUTDOWN_USERS_LOGGED_ON = 0x4A7, + // The specified device name is invalid. + BAD_DEVICE = 0x4B0, + // The device is not currently connected but it is a remembered connection. + CONNECTION_UNAVAIL = 0x4B1, + // The local device name has a remembered connection to another network resource. + DEVICE_ALREADY_REMEMBERED = 0x4B2, + // The network path was either typed incorrectly, does not exist, or the network provider is not currently available. Please try retyping the path or contact your network administrator. + NO_NET_OR_BAD_PATH = 0x4B3, + // The specified network provider name is invalid. + BAD_PROVIDER = 0x4B4, + // Unable to open the network connection profile. + CANNOT_OPEN_PROFILE = 0x4B5, + // The network connection profile is corrupted. + BAD_PROFILE = 0x4B6, + // Cannot enumerate a noncontainer. + NOT_CONTAINER = 0x4B7, + // An extended error has occurred. + EXTENDED_ERROR = 0x4B8, + // The format of the specified group name is invalid. + INVALID_GROUPNAME = 0x4B9, + // The format of the specified computer name is invalid. + INVALID_COMPUTERNAME = 0x4BA, + // The format of the specified event name is invalid. + INVALID_EVENTNAME = 0x4BB, + // The format of the specified domain name is invalid. + INVALID_DOMAINNAME = 0x4BC, + // The format of the specified service name is invalid. + INVALID_SERVICENAME = 0x4BD, + // The format of the specified network name is invalid. + INVALID_NETNAME = 0x4BE, + // The format of the specified share name is invalid. + INVALID_SHARENAME = 0x4BF, + // The format of the specified password is invalid. + INVALID_PASSWORDNAME = 0x4C0, + // The format of the specified message name is invalid. + INVALID_MESSAGENAME = 0x4C1, + // The format of the specified message destination is invalid. + INVALID_MESSAGEDEST = 0x4C2, + // Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again. + SESSION_CREDENTIAL_CONFLICT = 0x4C3, + // An attempt was made to establish a session to a network server, but there are already too many sessions established to that server. + REMOTE_SESSION_LIMIT_EXCEEDED = 0x4C4, + // The workgroup or domain name is already in use by another computer on the network. + DUP_DOMAINNAME = 0x4C5, + // The network is not present or not started. + NO_NETWORK = 0x4C6, + // The operation was canceled by the user. + CANCELLED = 0x4C7, + // The requested operation cannot be performed on a file with a user-mapped section open. + USER_MAPPED_FILE = 0x4C8, + // The remote computer refused the network connection. + CONNECTION_REFUSED = 0x4C9, + // The network connection was gracefully closed. + GRACEFUL_DISCONNECT = 0x4CA, + // The network transport endpoint already has an address associated with it. + ADDRESS_ALREADY_ASSOCIATED = 0x4CB, + // An address has not yet been associated with the network endpoint. + ADDRESS_NOT_ASSOCIATED = 0x4CC, + // An operation was attempted on a nonexistent network connection. + CONNECTION_INVALID = 0x4CD, + // An invalid operation was attempted on an active network connection. + CONNECTION_ACTIVE = 0x4CE, + // The network location cannot be reached. For information about network troubleshooting, see Windows Help. + NETWORK_UNREACHABLE = 0x4CF, + // The network location cannot be reached. For information about network troubleshooting, see Windows Help. + HOST_UNREACHABLE = 0x4D0, + // The network location cannot be reached. For information about network troubleshooting, see Windows Help. + PROTOCOL_UNREACHABLE = 0x4D1, + // No service is operating at the destination network endpoint on the remote system. + PORT_UNREACHABLE = 0x4D2, + // The request was aborted. + REQUEST_ABORTED = 0x4D3, + // The network connection was aborted by the local system. + CONNECTION_ABORTED = 0x4D4, + // The operation could not be completed. A retry should be performed. + RETRY = 0x4D5, + // A connection to the server could not be made because the limit on the number of concurrent connections for this account has been reached. + CONNECTION_COUNT_LIMIT = 0x4D6, + // Attempting to log in during an unauthorized time of day for this account. + LOGIN_TIME_RESTRICTION = 0x4D7, + // The account is not authorized to log in from this station. + LOGIN_WKSTA_RESTRICTION = 0x4D8, + // The network address could not be used for the operation requested. + INCORRECT_ADDRESS = 0x4D9, + // The service is already registered. + ALREADY_REGISTERED = 0x4DA, + // The specified service does not exist. + SERVICE_NOT_FOUND = 0x4DB, + // The operation being requested was not performed because the user has not been authenticated. + NOT_AUTHENTICATED = 0x4DC, + // The operation being requested was not performed because the user has not logged on to the network. The specified service does not exist. + NOT_LOGGED_ON = 0x4DD, + // Continue with work in progress. + CONTINUE = 0x4DE, + // An attempt was made to perform an initialization operation when initialization has already been completed. + ALREADY_INITIALIZED = 0x4DF, + // No more local devices. + NO_MORE_DEVICES = 0x4E0, + // The specified site does not exist. + NO_SUCH_SITE = 0x4E1, + // A domain controller with the specified name already exists. + DOMAIN_CONTROLLER_EXISTS = 0x4E2, + // This operation is supported only when you are connected to the server. + ONLY_IF_CONNECTED = 0x4E3, + // The group policy framework should call the extension even if there are no changes. + OVERRIDE_NOCHANGES = 0x4E4, + // The specified user does not have a valid profile. + BAD_USER_PROFILE = 0x4E5, + // This operation is not supported on a computer running Windows Server 2003 for Small Business Server. + NOT_SUPPORTED_ON_SBS = 0x4E6, + // The server machine is shutting down. + SERVER_SHUTDOWN_IN_PROGRESS = 0x4E7, + // The remote system is not available. For information about network troubleshooting, see Windows Help. + HOST_DOWN = 0x4E8, + // The security identifier provided is not from an account domain. + NON_ACCOUNT_SID = 0x4E9, + // The security identifier provided does not have a domain component. + NON_DOMAIN_SID = 0x4EA, + // AppHelp dialog canceled thus preventing the application from starting. + APPHELP_BLOCK = 0x4EB, + // This program is blocked by group policy. For more information, contact your system administrator. + ACCESS_DISABLED_BY_POLICY = 0x4EC, + // A program attempt to use an invalid register value. Normally caused by an uninitialized register. This error is Itanium specific. + REG_NAT_CONSUMPTION = 0x4ED, + // The share is currently offline or does not exist. + CSCSHARE_OFFLINE = 0x4EE, + // The Kerberos protocol encountered an error while validating the KDC certificate during smartcard logon. There is more information in the system event log. + PKINIT_FAILURE = 0x4EF, + // The Kerberos protocol encountered an error while attempting to utilize the smartcard subsystem. + SMARTCARD_SUBSYSTEM_FAILURE = 0x4F0, + // The system cannot contact a domain controller to service the authentication request. Please try again later. + DOWNGRADE_DETECTED = 0x4F1, + // The machine is locked and cannot be shut down without the force option. + MACHINE_LOCKED = 0x4F7, + // An application-defined callback gave invalid data when called. + CALLBACK_SUPPLIED_INVALID_DATA = 0x4F9, + // The group policy framework should call the extension in the synchronous foreground policy refresh. + SYNC_FOREGROUND_REFRESH_REQUIRED = 0x4FA, + // This driver has been blocked from loading. + DRIVER_BLOCKED = 0x4FB, + // A dynamic link library (DLL) referenced a module that was neither a DLL nor the process's executable image. + INVALID_IMPORT_OF_NON_DLL = 0x4FC, + // Windows cannot open this program since it has been disabled. + ACCESS_DISABLED_WEBBLADE = 0x4FD, + // Windows cannot open this program because the license enforcement system has been tampered with or become corrupted. + ACCESS_DISABLED_WEBBLADE_TAMPER = 0x4FE, + // A transaction recover failed. + RECOVERY_FAILURE = 0x4FF, + // The current thread has already been converted to a fiber. + ALREADY_FIBER = 0x500, + // The current thread has already been converted from a fiber. + ALREADY_THREAD = 0x501, + // The system detected an overrun of a stack-based buffer in this application. This overrun could potentially allow a malicious user to gain control of this application. + STACK_BUFFER_OVERRUN = 0x502, + // Data present in one of the parameters is more than the function can operate on. + PARAMETER_QUOTA_EXCEEDED = 0x503, + // An attempt to do an operation on a debug object failed because the object is in the process of being deleted. + DEBUGGER_INACTIVE = 0x504, + // An attempt to delay-load a .dll or get a function address in a delay-loaded .dll failed. + DELAY_LOAD_FAILED = 0x505, + // %1 is a 16-bit application. You do not have permissions to execute 16-bit applications. Check your permissions with your system administrator. + VDM_DISALLOWED = 0x506, + // Insufficient information exists to identify the cause of failure. + UNIDENTIFIED_ERROR = 0x507, + // The parameter passed to a C runtime function is incorrect. + INVALID_CRUNTIME_PARAMETER = 0x508, + // The operation occurred beyond the valid data length of the file. + BEYOND_VDL = 0x509, + // The service start failed since one or more services in the same process have an incompatible service SID type setting. A service with restricted service SID type can only coexist in the same process with other services with a restricted SID type. If the service SID type for this service was just configured, the hosting process must be restarted in order to start this service. + // On Windows Server 2003 and Windows XP, an unrestricted service cannot coexist in the same process with other services. The service with the unrestricted service SID type must be moved to an owned process in order to start this service. + INCOMPATIBLE_SERVICE_SID_TYPE = 0x50A, + // The process hosting the driver for this device has been terminated. + DRIVER_PROCESS_TERMINATED = 0x50B, + // An operation attempted to exceed an implementation-defined limit. + IMPLEMENTATION_LIMIT = 0x50C, + // Either the target process, or the target thread's containing process, is a protected process. + PROCESS_IS_PROTECTED = 0x50D, + // The service notification client is lagging too far behind the current state of services in the machine. + SERVICE_NOTIFY_CLIENT_LAGGING = 0x50E, + // The requested file operation failed because the storage quota was exceeded. To free up disk space, move files to a different location or delete unnecessary files. For more information, contact your system administrator. + DISK_QUOTA_EXCEEDED = 0x50F, + // The requested file operation failed because the storage policy blocks that type of file. For more information, contact your system administrator. + CONTENT_BLOCKED = 0x510, + // A privilege that the service requires to function properly does not exist in the service account configuration. You may use the Services Microsoft Management Console (MMC) snap-in (services.msc) and the Local Security Settings MMC snap-in (secpol.msc) to view the service configuration and the account configuration. + INCOMPATIBLE_SERVICE_PRIVILEGE = 0x511, + // A thread involved in this operation appears to be unresponsive. + APP_HANG = 0x512, + // Indicates a particular Security ID may not be assigned as the label of an object. + INVALID_LABEL = 0x513, + + // Not all privileges or groups referenced are assigned to the caller. + NOT_ALL_ASSIGNED = 0x514, + // Some mapping between account names and security IDs was not done. + SOME_NOT_MAPPED = 0x515, + // No system quota limits are specifically set for this account. + NO_QUOTAS_FOR_ACCOUNT = 0x516, + // No encryption key is available. A well-known encryption key was returned. + LOCAL_USER_SESSION_KEY = 0x517, + // The password is too complex to be converted to a LAN Manager password. The LAN Manager password returned is a NULL string. + NULL_LM_PASSWORD = 0x518, + // The revision level is unknown. + UNKNOWN_REVISION = 0x519, + // Indicates two revision levels are incompatible. + REVISION_MISMATCH = 0x51A, + // This security ID may not be assigned as the owner of this object. + INVALID_OWNER = 0x51B, + // This security ID may not be assigned as the primary group of an object. + INVALID_PRIMARY_GROUP = 0x51C, + // An attempt has been made to operate on an impersonation token by a thread that is not currently impersonating a client. + NO_IMPERSONATION_TOKEN = 0x51D, + // The group may not be disabled. + CANT_DISABLE_MANDATORY = 0x51E, + // There are currently no logon servers available to service the logon request. + NO_LOGON_SERVERS = 0x51F, + // A specified logon session does not exist. It may already have been terminated. + NO_SUCH_LOGON_SESSION = 0x520, + // A specified privilege does not exist. + NO_SUCH_PRIVILEGE = 0x521, + // A required privilege is not held by the client. + PRIVILEGE_NOT_HELD = 0x522, + // The name provided is not a properly formed account name. + INVALID_ACCOUNT_NAME = 0x523, + // The specified account already exists. + USER_EXISTS = 0x524, + // The specified account does not exist. + NO_SUCH_USER = 0x525, + // The specified group already exists. + GROUP_EXISTS = 0x526, + // The specified group does not exist. + NO_SUCH_GROUP = 0x527, + // Either the specified user account is already a member of the specified group, or the specified group cannot be deleted because it contains a member. + MEMBER_IN_GROUP = 0x528, + // The specified user account is not a member of the specified group account. + MEMBER_NOT_IN_GROUP = 0x529, + // This operation is disallowed as it could result in an administration account being disabled, deleted or unable to log on. + LAST_ADMIN = 0x52A, + // Unable to update the password. The value provided as the current password is incorrect. + WRONG_PASSWORD = 0x52B, + // Unable to update the password. The value provided for the new password contains values that are not allowed in passwords. + ILL_FORMED_PASSWORD = 0x52C, + // Unable to update the password. The value provided for the new password does not meet the length, complexity, or history requirements of the domain. + PASSWORD_RESTRICTION = 0x52D, + // The user name or password is incorrect. + LOGON_FAILURE = 0x52E, + // Account restrictions are preventing this user from signing in. For example: blank passwords aren't allowed, sign-in times are limited, or a policy restriction has been enforced. + ACCOUNT_RESTRICTION = 0x52F, + // Your account has time restrictions that keep you from signing in right now. + INVALID_LOGON_HOURS = 0x530, + // This user isn't allowed to sign in to this computer. + INVALID_WORKSTATION = 0x531, + // The password for this account has expired. + PASSWORD_EXPIRED = 0x532, + // This user can't sign in because this account is currently disabled. + ACCOUNT_DISABLED = 0x533, + // No mapping between account names and security IDs was done. + NONE_MAPPED = 0x534, + // Too many local user identifiers (LUIDs) were requested at one time. + TOO_MANY_LUIDS_REQUESTED = 0x535, + // No more local user identifiers (LUIDs) are available. + LUIDS_EXHAUSTED = 0x536, + // The subauthority part of a security ID is invalid for this particular use. + INVALID_SUB_AUTHORITY = 0x537, + // The access control list (ACL) structure is invalid. + INVALID_ACL = 0x538, + // The security ID structure is invalid. + INVALID_SID = 0x539, + // The security descriptor structure is invalid. + INVALID_SECURITY_DESCR = 0x53A, + // The inherited access control list (ACL) or access control entry (ACE) could not be built. + BAD_INHERITANCE_ACL = 0x53C, + // The server is currently disabled. + SERVER_DISABLED = 0x53D, + // The server is currently enabled. + SERVER_NOT_DISABLED = 0x53E, + // The value provided was an invalid value for an identifier authority. + INVALID_ID_AUTHORITY = 0x53F, + // No more memory is available for security information updates. + ALLOTTED_SPACE_EXCEEDED = 0x540, + // The specified attributes are invalid, or incompatible with the attributes for the group as a whole. + INVALID_GROUP_ATTRIBUTES = 0x541, + // Either a required impersonation level was not provided, or the provided impersonation level is invalid. + BAD_IMPERSONATION_LEVEL = 0x542, + // Cannot open an anonymous level security token. + CANT_OPEN_ANONYMOUS = 0x543, + // The validation information class requested was invalid. + BAD_VALIDATION_CLASS = 0x544, + // The type of the token is inappropriate for its attempted use. + BAD_TOKEN_TYPE = 0x545, + // Unable to perform a security operation on an object that has no associated security. + NO_SECURITY_ON_OBJECT = 0x546, + // Configuration information could not be read from the domain controller, either because the machine is unavailable, or access has been denied. + CANT_ACCESS_DOMAIN_INFO = 0x547, + // The security account manager (SAM) or local security authority (LSA) server was in the wrong state to perform the security operation. + INVALID_SERVER_STATE = 0x548, + // The domain was in the wrong state to perform the security operation. + INVALID_DOMAIN_STATE = 0x549, + // This operation is only allowed for the Primary Domain Controller of the domain. + INVALID_DOMAIN_ROLE = 0x54A, + // The specified domain either does not exist or could not be contacted. + NO_SUCH_DOMAIN = 0x54B, + // The specified domain already exists. + DOMAIN_EXISTS = 0x54C, + // An attempt was made to exceed the limit on the number of domains per server. + DOMAIN_LIMIT_EXCEEDED = 0x54D, + // Unable to complete the requested operation because of either a catastrophic media failure or a data structure corruption on the disk. + INTERNAL_DB_CORRUPTION = 0x54E, + // An internal error occurred. + INTERNAL_ERROR = 0x54F, + // Generic access types were contained in an access mask which should already be mapped to nongeneric types. + GENERIC_NOT_MAPPED = 0x550, + // A security descriptor is not in the right format (absolute or self-relative). + BAD_DESCRIPTOR_FORMAT = 0x551, + // The requested action is restricted for use by logon processes only. The calling process has not registered as a logon process. + NOT_LOGON_PROCESS = 0x552, + // Cannot start a new logon session with an ID that is already in use. + LOGON_SESSION_EXISTS = 0x553, + // A specified authentication package is unknown. + NO_SUCH_PACKAGE = 0x554, + // The logon session is not in a state that is consistent with the requested operation. + BAD_LOGON_SESSION_STATE = 0x555, + // The logon session ID is already in use. + LOGON_SESSION_COLLISION = 0x556, + // A logon request contained an invalid logon type value. + INVALID_LOGON_TYPE = 0x557, + // Unable to impersonate using a named pipe until data has been read from that pipe. + CANNOT_IMPERSONATE = 0x558, + // The transaction state of a registry subtree is incompatible with the requested operation. + RXACT_INVALID_STATE = 0x559, + // An internal security database corruption has been encountered. + RXACT_COMMIT_FAILURE = 0x55A, + // Cannot perform this operation on built-in accounts. + SPECIAL_ACCOUNT = 0x55B, + // Cannot perform this operation on this built-in special group. + SPECIAL_GROUP = 0x55C, + // Cannot perform this operation on this built-in special user. + SPECIAL_USER = 0x55D, + // The user cannot be removed from a group because the group is currently the user's primary group. + MEMBERS_PRIMARY_GROUP = 0x55E, + // The token is already in use as a primary token. + TOKEN_ALREADY_IN_USE = 0x55F, + // The specified local group does not exist. + NO_SUCH_ALIAS = 0x560, + // The specified account name is not a member of the group. + MEMBER_NOT_IN_ALIAS = 0x561, + // The specified account name is already a member of the group. + MEMBER_IN_ALIAS = 0x562, + // The specified local group already exists. + ALIAS_EXISTS = 0x563, + // Logon failure: the user has not been granted the requested logon type at this computer. + LOGON_NOT_GRANTED = 0x564, + // The maximum number of secrets that may be stored in a single system has been exceeded. + TOO_MANY_SECRETS = 0x565, + // The length of a secret exceeds the maximum length allowed. + SECRET_TOO_LONG = 0x566, + // The local security authority database contains an internal inconsistency. + INTERNAL_DB_ERROR = 0x567, + // During a logon attempt, the user's security context accumulated too many security IDs. + TOO_MANY_CONTEXT_IDS = 0x568, + // Logon failure: the user has not been granted the requested logon type at this computer. + LOGON_TYPE_NOT_GRANTED = 0x569, + // A cross-encrypted password is necessary to change a user password. + NT_CROSS_ENCRYPTION_REQUIRED = 0x56A, + // A member could not be added to or removed from the local group because the member does not exist. + NO_SUCH_MEMBER = 0x56B, + // A new member could not be added to a local group because the member has the wrong account type. + INVALID_MEMBER = 0x56C, + // Too many security IDs have been specified. + TOO_MANY_SIDS = 0x56D, + // A cross-encrypted password is necessary to change this user password. + LM_CROSS_ENCRYPTION_REQUIRED = 0x56E, + // Indicates an ACL contains no inheritable components. + NO_INHERITANCE = 0x56F, + // The file or directory is corrupted and unreadable. + FILE_CORRUPT = 0x570, + // The disk structure is corrupted and unreadable. + DISK_CORRUPT = 0x571, + // There is no user session key for the specified logon session. + NO_USER_SESSION_KEY = 0x572, + // The service being accessed is licensed for a particular number of connections. No more connections can be made to the service at this time because there are already as many connections as the service can accept. + LICENSE_QUOTA_EXCEEDED = 0x573, + // The target account name is incorrect. + WRONG_TARGET_NAME = 0x574, + // Mutual Authentication failed. The server's password is out of date at the domain controller. + MUTUAL_AUTH_FAILED = 0x575, + // There is a time and/or date difference between the client and server. + TIME_SKEW = 0x576, + // This operation cannot be performed on the current domain. + CURRENT_DOMAIN_NOT_ALLOWED = 0x577, + // Invalid window handle. + INVALID_WINDOW_HANDLE = 0x578, + // Invalid menu handle. + INVALID_MENU_HANDLE = 0x579, + // Invalid cursor handle. + INVALID_CURSOR_HANDLE = 0x57A, + // Invalid accelerator table handle. + INVALID_ACCEL_HANDLE = 0x57B, + // Invalid hook handle. + INVALID_HOOK_HANDLE = 0x57C, + // Invalid handle to a multiple-window position structure. + INVALID_DWP_HANDLE = 0x57D, + // Cannot create a top-level child window. + TLW_WITH_WSCHILD = 0x57E, + // Cannot find window class. + CANNOT_FIND_WND_CLASS = 0x57F, + // Invalid window; it belongs to other thread. + WINDOW_OF_OTHER_THREAD = 0x580, + // Hot key is already registered. + HOTKEY_ALREADY_REGISTERED = 0x581, + // Class already exists. + CLASS_ALREADY_EXISTS = 0x582, + // Class does not exist. + CLASS_DOES_NOT_EXIST = 0x583, + // Class still has open windows. + CLASS_HAS_WINDOWS = 0x584, + // Invalid index. + INVALID_INDEX = 0x585, + // Invalid icon handle. + INVALID_ICON_HANDLE = 0x586, + // Using private DIALOG window words. + PRIVATE_DIALOG_INDEX = 0x587, + // The list box identifier was not found. + LISTBOX_ID_NOT_FOUND = 0x588, + // No wildcards were found. + NO_WILDCARD_CHARACTERS = 0x589, + // Thread does not have a clipboard open. + CLIPBOARD_NOT_OPEN = 0x58A, + // Hot key is not registered. + HOTKEY_NOT_REGISTERED = 0x58B, + // The window is not a valid dialog window. + WINDOW_NOT_DIALOG = 0x58C, + // Control ID not found. + CONTROL_ID_NOT_FOUND = 0x58D, + // Invalid message for a combo box because it does not have an edit control. + INVALID_COMBOBOX_MESSAGE = 0x58E, + // The window is not a combo box. + WINDOW_NOT_COMBOBOX = 0x58F, + // Height must be less than 256. + INVALID_EDIT_HEIGHT = 0x590, + // Invalid device context (DC) handle. + DC_NOT_FOUND = 0x591, + // Invalid hook procedure type. + INVALID_HOOK_FILTER = 0x592, + // Invalid hook procedure. + INVALID_FILTER_PROC = 0x593, + // Cannot set nonlocal hook without a module handle. + HOOK_NEEDS_HMOD = 0x594, + // This hook procedure can only be set globally. + GLOBAL_ONLY_HOOK = 0x595, + // The journal hook procedure is already installed. + JOURNAL_HOOK_SET = 0x596, + // The hook procedure is not installed. + HOOK_NOT_INSTALLED = 0x597, + // Invalid message for single-selection list box. + INVALID_LB_MESSAGE = 0x598, + // LB_SETCOUNT sent to non-lazy list box. + SETCOUNT_ON_BAD_LB = 0x599, + // This list box does not support tab stops. + LB_WITHOUT_TABSTOPS = 0x59A, + // Cannot destroy object created by another thread. + DESTROY_OBJECT_OF_OTHER_THREAD = 0x59B, + // Child windows cannot have menus. + CHILD_WINDOW_MENU = 0x59C, + // The window does not have a system menu. + NO_SYSTEM_MENU = 0x59D, + // Invalid message box style. + INVALID_MSGBOX_STYLE = 0x59E, + // Invalid system-wide (SPI_*) parameter. + INVALID_SPI_VALUE = 0x59F, + // Screen already locked. + SCREEN_ALREADY_LOCKED = 0x5A0, + // All handles to windows in a multiple-window position structure must have the same parent. + HWNDS_HAVE_DIFF_PARENT = 0x5A1, + // The window is not a child window. + NOT_CHILD_WINDOW = 0x5A2, + // Invalid GW_* command. + INVALID_GW_COMMAND = 0x5A3, + // Invalid thread identifier. + INVALID_THREAD_ID = 0x5A4, + // Cannot process a message from a window that is not a multiple document interface (MDI) window. + NON_MDICHILD_WINDOW = 0x5A5, + // Popup menu already active. + POPUP_ALREADY_ACTIVE = 0x5A6, + // The window does not have scroll bars. + NO_SCROLLBARS = 0x5A7, + // Scroll bar range cannot be greater than MAXLONG. + INVALID_SCROLLBAR_RANGE = 0x5A8, + // Cannot show or remove the window in the way specified. + INVALID_SHOWWIN_COMMAND = 0x5A9, + // Insufficient system resources exist to complete the requested service. + NO_SYSTEM_RESOURCES = 0x5AA, + // Insufficient system resources exist to complete the requested service. + NONPAGED_SYSTEM_RESOURCES = 0x5AB, + // Insufficient system resources exist to complete the requested service. + PAGED_SYSTEM_RESOURCES = 0x5AC, + // Insufficient quota to complete the requested service. + WORKING_SET_QUOTA = 0x5AD, + // Insufficient quota to complete the requested service. + PAGEFILE_QUOTA = 0x5AE, + // The paging file is too small for this operation to complete. + COMMITMENT_LIMIT = 0x5AF, + // A menu item was not found. + MENU_ITEM_NOT_FOUND = 0x5B0, + // Invalid keyboard layout handle. + INVALID_KEYBOARD_HANDLE = 0x5B1, + // Hook type not allowed. + HOOK_TYPE_NOT_ALLOWED = 0x5B2, + // This operation requires an interactive window station. + REQUIRES_INTERACTIVE_WINDOWSTATION = 0x5B3, + // This operation returned because the timeout period expired. + TIMEOUT = 0x5B4, + // Invalid monitor handle. + INVALID_MONITOR_HANDLE = 0x5B5, + // Incorrect size argument. + INCORRECT_SIZE = 0x5B6, + // The symbolic link cannot be followed because its type is disabled. + SYMLINK_CLASS_DISABLED = 0x5B7, + // This application does not support the current operation on symbolic links. + SYMLINK_NOT_SUPPORTED = 0x5B8, + // Windows was unable to parse the requested XML data. + XML_PARSE_ERROR = 0x5B9, + // An error was encountered while processing an XML digital signature. + XMLDSIG_ERROR = 0x5BA, + // This application must be restarted. + RESTART_APPLICATION = 0x5BB, + // The caller made the connection request in the wrong routing compartment. + WRONG_COMPARTMENT = 0x5BC, + // There was an AuthIP failure when attempting to connect to the remote host. + AUTHIP_FAILURE = 0x5BD, + // Insufficient NVRAM resources exist to complete the requested service. A reboot might be required. + NO_NVRAM_RESOURCES = 0x5BE, + // Unable to finish the requested operation because the specified process is not a GUI process. + NOT_GUI_PROCESS = 0x5BF, + // The event log file is corrupted. + EVENTLOG_FILE_CORRUPT = 0x5DC, + // No event log file could be opened, so the event logging service did not start. + EVENTLOG_CANT_START = 0x5DD, + // The event log file is full. + LOG_FILE_FULL = 0x5DE, + // The event log file has changed between read operations. + EVENTLOG_FILE_CHANGED = 0x5DF, + // The specified task name is invalid. + INVALID_TASK_NAME = 0x60E, + // The specified task index is invalid. + INVALID_TASK_INDEX = 0x60F, + // The specified thread is already joining a task. + THREAD_ALREADY_IN_TASK = 0x610, + // The Windows Installer Service could not be accessed. This can occur if the Windows Installer is not correctly installed. Contact your support personnel for assistance. + INSTALL_SERVICE_FAILURE = 0x641, + // User cancelled installation. + INSTALL_USEREXIT = 0x642, + // Fatal error during installation. + INSTALL_FAILURE = 0x643, + // Installation suspended, incomplete. + INSTALL_SUSPEND = 0x644, + // This action is only valid for products that are currently installed. + UNKNOWN_PRODUCT = 0x645, + // Feature ID not registered. + UNKNOWN_FEATURE = 0x646, + // Component ID not registered. + UNKNOWN_COMPONENT = 0x647, + // Unknown property. + UNKNOWN_PROPERTY = 0x648, + // Handle is in an invalid state. + INVALID_HANDLE_STATE = 0x649, + // The configuration data for this product is corrupt. Contact your support personnel. + BAD_CONFIGURATION = 0x64A, + // Component qualifier not present. + INDEX_ABSENT = 0x64B, + // The installation source for this product is not available. Verify that the source exists and that you can access it. + INSTALL_SOURCE_ABSENT = 0x64C, + // This installation package cannot be installed by the Windows Installer service. You must install a Windows service pack that contains a newer version of the Windows Installer service. + INSTALL_PACKAGE_VERSION = 0x64D, + // Product is uninstalled. + PRODUCT_UNINSTALLED = 0x64E, + // SQL query syntax invalid or unsupported. + BAD_QUERY_SYNTAX = 0x64F, + // Record field does not exist. + INVALID_FIELD = 0x650, + // The device has been removed. + DEVICE_REMOVED = 0x651, + // Another installation is already in progress. Complete that installation before proceeding with this install. + INSTALL_ALREADY_RUNNING = 0x652, + // This installation package could not be opened. Verify that the package exists and that you can access it, or contact the application vendor to verify that this is a valid Windows Installer package. + INSTALL_PACKAGE_OPEN_FAILED = 0x653, + // This installation package could not be opened. Contact the application vendor to verify that this is a valid Windows Installer package. + INSTALL_PACKAGE_INVALID = 0x654, + // There was an error starting the Windows Installer service user interface. Contact your support personnel. + INSTALL_UI_FAILURE = 0x655, + // Error opening installation log file. Verify that the specified log file location exists and that you can write to it. + INSTALL_LOG_FAILURE = 0x656, + // The language of this installation package is not supported by your system. + INSTALL_LANGUAGE_UNSUPPORTED = 0x657, + // Error applying transforms. Verify that the specified transform paths are valid. + INSTALL_TRANSFORM_FAILURE = 0x658, + // This installation is forbidden by system policy. Contact your system administrator. + INSTALL_PACKAGE_REJECTED = 0x659, + // Function could not be executed. + FUNCTION_NOT_CALLED = 0x65A, + // Function failed during execution. + FUNCTION_FAILED = 0x65B, + // Invalid or unknown table specified. + INVALID_TABLE = 0x65C, + // Data supplied is of wrong type. + DATATYPE_MISMATCH = 0x65D, + // Data of this type is not supported. + UNSUPPORTED_TYPE = 0x65E, + // The Windows Installer service failed to start. Contact your support personnel. + CREATE_FAILED = 0x65F, + // The Temp folder is on a drive that is full or is inaccessible. Free up space on the drive or verify that you have write permission on the Temp folder. + INSTALL_TEMP_UNWRITABLE = 0x660, + // This installation package is not supported by this processor type. Contact your product vendor. + INSTALL_PLATFORM_UNSUPPORTED = 0x661, + // Component not used on this computer. + INSTALL_NOTUSED = 0x662, + // This update package could not be opened. Verify that the update package exists and that you can access it, or contact the application vendor to verify that this is a valid Windows Installer update package. + PATCH_PACKAGE_OPEN_FAILED = 0x663, + // This update package could not be opened. Contact the application vendor to verify that this is a valid Windows Installer update package. + PATCH_PACKAGE_INVALID = 0x664, + // This update package cannot be processed by the Windows Installer service. You must install a Windows service pack that contains a newer version of the Windows Installer service. + PATCH_PACKAGE_UNSUPPORTED = 0x665, + // Another version of this product is already installed. Installation of this version cannot continue. To configure or remove the existing version of this product, use Add/Remove Programs on the Control Panel. + PRODUCT_VERSION = 0x666, + // Invalid command line argument. Consult the Windows Installer SDK for detailed command line help. + INVALID_COMMAND_LINE = 0x667, + // Only administrators have permission to add, remove, or configure server software during a Terminal services remote session. If you want to install or configure software on the server, contact your network administrator. + INSTALL_REMOTE_DISALLOWED = 0x668, + // The requested operation completed successfully. The system will be restarted so the changes can take effect. + SUCCESS_REBOOT_INITIATED = 0x669, + // The upgrade cannot be installed by the Windows Installer service because the program to be upgraded may be missing, or the upgrade may update a different version of the program. Verify that the program to be upgraded exists on your computer and that you have the correct upgrade. + PATCH_TARGET_NOT_FOUND = 0x66A, + // The update package is not permitted by software restriction policy. + PATCH_PACKAGE_REJECTED = 0x66B, + // One or more customizations are not permitted by software restriction policy. + INSTALL_TRANSFORM_REJECTED = 0x66C, + // The Windows Installer does not permit installation from a Remote Desktop Connection. + INSTALL_REMOTE_PROHIBITED = 0x66D, + // Uninstallation of the update package is not supported. + PATCH_REMOVAL_UNSUPPORTED = 0x66E, + // The update is not applied to this product. + UNKNOWN_PATCH = 0x66F, + // No valid sequence could be found for the set of updates. + PATCH_NO_SEQUENCE = 0x670, + // Update removal was disallowed by policy. + PATCH_REMOVAL_DISALLOWED = 0x671, + // The XML update data is invalid. + INVALID_PATCH_XML = 0x672, + // Windows Installer does not permit updating of managed advertised products. At least one feature of the product must be installed before applying the update. + PATCH_MANAGED_ADVERTISED_PRODUCT = 0x673, + // The Windows Installer service is not accessible in Safe Mode. Please try again when your computer is not in Safe Mode or you can use System Restore to return your machine to a previous good state. + INSTALL_SERVICE_SAFEBOOT = 0x674, + // A fail fast exception occurred. Exception handlers will not be invoked and the process will be terminated immediately. + FAIL_FAST_EXCEPTION = 0x675, + // The app that you are trying to run is not supported on this version of Windows. + INSTALL_REJECTED = 0x676, + + // The string binding is invalid. + RPC_S_INVALID_STRING_BINDING = 0x6A4, + // The binding handle is not the correct type. + RPC_S_WRONG_KIND_OF_BINDING = 0x6A5, + // The binding handle is invalid. + RPC_S_INVALID_BINDING = 0x6A6, + // The RPC protocol sequence is not supported. + RPC_S_PROTSEQ_NOT_SUPPORTED = 0x6A7, + // The RPC protocol sequence is invalid. + RPC_S_INVALID_RPC_PROTSEQ = 0x6A8, + // The string universal unique identifier (UUID) is invalid. + RPC_S_INVALID_STRING_UUID = 0x6A9, + // The endpoint format is invalid. + RPC_S_INVALID_ENDPOINT_FORMAT = 0x6AA, + // The network address is invalid. + RPC_S_INVALID_NET_ADDR = 0x6AB, + // No endpoint was found. + RPC_S_NO_ENDPOINT_FOUND = 0x6AC, + // The timeout value is invalid. + RPC_S_INVALID_TIMEOUT = 0x6AD, + // The object universal unique identifier (UUID) was not found. + RPC_S_OBJECT_NOT_FOUND = 0x6AE, + // The object universal unique identifier (UUID) has already been registered. + RPC_S_ALREADY_REGISTERED = 0x6AF, + // The type universal unique identifier (UUID) has already been registered. + RPC_S_TYPE_ALREADY_REGISTERED = 0x6B0, + // The RPC server is already listening. + RPC_S_ALREADY_LISTENING = 0x6B1, + // No protocol sequences have been registered. + RPC_S_NO_PROTSEQS_REGISTERED = 0x6B2, + // The RPC server is not listening. + RPC_S_NOT_LISTENING = 0x6B3, + // The manager type is unknown. + RPC_S_UNKNOWN_MGR_TYPE = 0x6B4, + // The interface is unknown. + RPC_S_UNKNOWN_IF = 0x6B5, + // There are no bindings. + RPC_S_NO_BINDINGS = 0x6B6, + // There are no protocol sequences. + RPC_S_NO_PROTSEQS = 0x6B7, + // The endpoint cannot be created. + RPC_S_CANT_CREATE_ENDPOINT = 0x6B8, + // Not enough resources are available to complete this operation. + RPC_S_OUT_OF_RESOURCES = 0x6B9, + // The RPC server is unavailable. + RPC_S_SERVER_UNAVAILABLE = 0x6BA, + // The RPC server is too busy to complete this operation. + RPC_S_SERVER_TOO_BUSY = 0x6BB, + // The network options are invalid. + RPC_S_INVALID_NETWORK_OPTIONS = 0x6BC, + // There are no remote procedure calls active on this thread. + RPC_S_NO_CALL_ACTIVE = 0x6BD, + // The remote procedure call failed. + RPC_S_CALL_FAILED = 0x6BE, + // The remote procedure call failed and did not execute. + RPC_S_CALL_FAILED_DNE = 0x6BF, + // A remote procedure call (RPC) protocol error occurred. + RPC_S_PROTOCOL_ERROR = 0x6C0, + // Access to the HTTP proxy is denied. + RPC_S_PROXY_ACCESS_DENIED = 0x6C1, + // The transfer syntax is not supported by the RPC server. + RPC_S_UNSUPPORTED_TRANS_SYN = 0x6C2, + // The universal unique identifier (UUID) type is not supported. + RPC_S_UNSUPPORTED_TYPE = 0x6C4, + // The tag is invalid. + RPC_S_INVALID_TAG = 0x6C5, + // The array bounds are invalid. + RPC_S_INVALID_BOUND = 0x6C6, + // The binding does not contain an entry name. + RPC_S_NO_ENTRY_NAME = 0x6C7, + // The name syntax is invalid. + RPC_S_INVALID_NAME_SYNTAX = 0x6C8, + // The name syntax is not supported. + RPC_S_UNSUPPORTED_NAME_SYNTAX = 0x6C9, + // No network address is available to use to construct a universal unique identifier (UUID). + RPC_S_UUID_NO_ADDRESS = 0x6CB, + // The endpoint is a duplicate. + RPC_S_DUPLICATE_ENDPOINT = 0x6CC, + // The authentication type is unknown. + RPC_S_UNKNOWN_AUTHN_TYPE = 0x6CD, + // The maximum number of calls is too small. + RPC_S_MAX_CALLS_TOO_SMALL = 0x6CE, + // The string is too long. + RPC_S_STRING_TOO_LONG = 0x6CF, + // The RPC protocol sequence was not found. + RPC_S_PROTSEQ_NOT_FOUND = 0x6D0, + // The procedure number is out of range. + RPC_S_PROCNUM_OUT_OF_RANGE = 0x6D1, + // The binding does not contain any authentication information. + RPC_S_BINDING_HAS_NO_AUTH = 0x6D2, + // The authentication service is unknown. + RPC_S_UNKNOWN_AUTHN_SERVICE = 0x6D3, + // The authentication level is unknown. + RPC_S_UNKNOWN_AUTHN_LEVEL = 0x6D4, + // The security context is invalid. + RPC_S_INVALID_AUTH_IDENTITY = 0x6D5, + // The authorization service is unknown. + RPC_S_UNKNOWN_AUTHZ_SERVICE = 0x6D6, + // The entry is invalid. + EPT_S_INVALID_ENTRY = 0x6D7, + // The server endpoint cannot perform the operation. + EPT_S_CANT_PERFORM_OP = 0x6D8, + // There are no more endpoints available from the endpoint mapper. + EPT_S_NOT_REGISTERED = 0x6D9, + // No interfaces have been exported. + RPC_S_NOTHING_TO_EXPORT = 0x6DA, + // The entry name is incomplete. + RPC_S_INCOMPLETE_NAME = 0x6DB, + // The version option is invalid. + RPC_S_INVALID_VERS_OPTION = 0x6DC, + // There are no more members. + RPC_S_NO_MORE_MEMBERS = 0x6DD, + // There is nothing to unexport. + RPC_S_NOT_ALL_OBJS_UNEXPORTED = 0x6DE, + // The interface was not found. + RPC_S_INTERFACE_NOT_FOUND = 0x6DF, + // The entry already exists. + RPC_S_ENTRY_ALREADY_EXISTS = 0x6E0, + // The entry is not found. + RPC_S_ENTRY_NOT_FOUND = 0x6E1, + // The name service is unavailable. + RPC_S_NAME_SERVICE_UNAVAILABLE = 0x6E2, + // The network address family is invalid. + RPC_S_INVALID_NAF_ID = 0x6E3, + // The requested operation is not supported. + RPC_S_CANNOT_SUPPORT = 0x6E4, + // No security context is available to allow impersonation. + RPC_S_NO_CONTEXT_AVAILABLE = 0x6E5, + // An internal error occurred in a remote procedure call (RPC). + RPC_S_INTERNAL_ERROR = 0x6E6, + // The RPC server attempted an integer division by zero. + RPC_S_ZERO_DIVIDE = 0x6E7, + // An addressing error occurred in the RPC server. + RPC_S_ADDRESS_ERROR = 0x6E8, + // A floating-point operation at the RPC server caused a division by zero. + RPC_S_FP_DIV_ZERO = 0x6E9, + // A floating-point underflow occurred at the RPC server. + RPC_S_FP_UNDERFLOW = 0x6EA, + // A floating-point overflow occurred at the RPC server. + RPC_S_FP_OVERFLOW = 0x6EB, + // The list of RPC servers available for the binding of auto handles has been exhausted. + RPC_X_NO_MORE_ENTRIES = 0x6EC, + // Unable to open the character translation table file. + RPC_X_SS_CHAR_TRANS_OPEN_FAIL = 0x6ED, + // The file containing the character translation table has fewer than 512 bytes. + RPC_X_SS_CHAR_TRANS_SHORT_FILE = 0x6EE, + // A null context handle was passed from the client to the host during a remote procedure call. + RPC_X_SS_IN_NULL_CONTEXT = 0x6EF, + // The context handle changed during a remote procedure call. + RPC_X_SS_CONTEXT_DAMAGED = 0x6F1, + // The binding handles passed to a remote procedure call do not match. + RPC_X_SS_HANDLES_MISMATCH = 0x6F2, + // The stub is unable to get the remote procedure call handle. + RPC_X_SS_CANNOT_GET_CALL_HANDLE = 0x6F3, + // A null reference pointer was passed to the stub. + RPC_X_NULL_REF_POINTER = 0x6F4, + // The enumeration value is out of range. + RPC_X_ENUM_VALUE_OUT_OF_RANGE = 0x6F5, + // The byte count is too small. + RPC_X_BYTE_COUNT_TOO_SMALL = 0x6F6, + // The stub received bad data. + RPC_X_BAD_STUB_DATA = 0x6F7, + // The supplied user buffer is not valid for the requested operation. + INVALID_USER_BUFFER = 0x6F8, + // The disk media is not recognized. It may not be formatted. + UNRECOGNIZED_MEDIA = 0x6F9, + // The workstation does not have a trust secret. + NO_TRUST_LSA_SECRET = 0x6FA, + // The security database on the server does not have a computer account for this workstation trust relationship. + NO_TRUST_SAM_ACCOUNT = 0x6FB, + // The trust relationship between the primary domain and the trusted domain failed. + TRUSTED_DOMAIN_FAILURE = 0x6FC, + // The trust relationship between this workstation and the primary domain failed. + TRUSTED_RELATIONSHIP_FAILURE = 0x6FD, + // The network logon failed. + TRUST_FAILURE = 0x6FE, + // A remote procedure call is already in progress for this thread. + RPC_S_CALL_IN_PROGRESS = 0x6FF, + // An attempt was made to logon, but the network logon service was not started. + NETLOGON_NOT_STARTED = 0x700, + // The user's account has expired. + ACCOUNT_EXPIRED = 0x701, + // The redirector is in use and cannot be unloaded. + REDIRECTOR_HAS_OPEN_HANDLES = 0x702, + // The specified printer driver is already installed. + PRINTER_DRIVER_ALREADY_INSTALLED = 0x703, + // The specified port is unknown. + UNKNOWN_PORT = 0x704, + // The printer driver is unknown. + UNKNOWN_PRINTER_DRIVER = 0x705, + // The print processor is unknown. + UNKNOWN_PRINTPROCESSOR = 0x706, + // The specified separator file is invalid. + INVALID_SEPARATOR_FILE = 0x707, + // The specified priority is invalid. + INVALID_PRIORITY = 0x708, + // The printer name is invalid. + INVALID_PRINTER_NAME = 0x709, + // The printer already exists. + PRINTER_ALREADY_EXISTS = 0x70A, + // The printer command is invalid. + INVALID_PRINTER_COMMAND = 0x70B, + // The specified datatype is invalid. + INVALID_DATATYPE = 0x70C, + // The environment specified is invalid. + INVALID_ENVIRONMENT = 0x70D, + // There are no more bindings. + RPC_S_NO_MORE_BINDINGS = 0x70E, + // The account used is an interdomain trust account. Use your global user account or local user account to access this server. + NOLOGON_INTERDOMAIN_TRUST_ACCOUNT = 0x70F, + // The account used is a computer account. Use your global user account or local user account to access this server. + NOLOGON_WORKSTATION_TRUST_ACCOUNT = 0x710, + // The account used is a server trust account. Use your global user account or local user account to access this server. + NOLOGON_SERVER_TRUST_ACCOUNT = 0x711, + // The name or security ID (SID) of the domain specified is inconsistent with the trust information for that domain. + DOMAIN_TRUST_INCONSISTENT = 0x712, + // The server is in use and cannot be unloaded. + SERVER_HAS_OPEN_HANDLES = 0x713, + // The specified image file did not contain a resource section. + RESOURCE_DATA_NOT_FOUND = 0x714, + // The specified resource type cannot be found in the image file. + RESOURCE_TYPE_NOT_FOUND = 0x715, + // The specified resource name cannot be found in the image file. + RESOURCE_NAME_NOT_FOUND = 0x716, + // The specified resource language ID cannot be found in the image file. + RESOURCE_LANG_NOT_FOUND = 0x717, + // Not enough quota is available to process this command. + NOT_ENOUGH_QUOTA = 0x718, + // No interfaces have been registered. + RPC_S_NO_INTERFACES = 0x719, + // The remote procedure call was cancelled. + RPC_S_CALL_CANCELLED = 0x71A, + // The binding handle does not contain all required information. + RPC_S_BINDING_INCOMPLETE = 0x71B, + // A communications failure occurred during a remote procedure call. + RPC_S_COMM_FAILURE = 0x71C, + // The requested authentication level is not supported. + RPC_S_UNSUPPORTED_AUTHN_LEVEL = 0x71D, + // No principal name registered. + RPC_S_NO_PRINC_NAME = 0x71E, + // The error specified is not a valid Windows RPC error code. + RPC_S_NOT_RPC_ERROR = 0x71F, + // A UUID that is valid only on this computer has been allocated. + RPC_S_UUID_LOCAL_ONLY = 0x720, + // A security package specific error occurred. + RPC_S_SEC_PKG_ERROR = 0x721, + // Thread is not canceled. + RPC_S_NOT_CANCELLED = 0x722, + // Invalid operation on the encoding/decoding handle. + RPC_X_INVALID_ES_ACTION = 0x723, + // Incompatible version of the serializing package. + RPC_X_WRONG_ES_VERSION = 0x724, + // Incompatible version of the RPC stub. + RPC_X_WRONG_STUB_VERSION = 0x725, + // The RPC pipe object is invalid or corrupted. + RPC_X_INVALID_PIPE_OBJECT = 0x726, + // An invalid operation was attempted on an RPC pipe object. + RPC_X_WRONG_PIPE_ORDER = 0x727, + // Unsupported RPC pipe version. + RPC_X_WRONG_PIPE_VERSION = 0x728, + // HTTP proxy server rejected the connection because the cookie authentication failed. + RPC_S_COOKIE_AUTH_FAILED = 0x729, + // The group member was not found. + RPC_S_GROUP_MEMBER_NOT_FOUND = 0x76A, + // The endpoint mapper database entry could not be created. + EPT_S_CANT_CREATE = 0x76B, + // The object universal unique identifier (UUID) is the nil UUID. + RPC_S_INVALID_OBJECT = 0x76C, + // The specified time is invalid. + INVALID_TIME = 0x76D, + // The specified form name is invalid. + INVALID_FORM_NAME = 0x76E, + // The specified form size is invalid. + INVALID_FORM_SIZE = 0x76F, + // The specified printer handle is already being waited on. + ALREADY_WAITING = 0x770, + // The specified printer has been deleted. + PRINTER_DELETED = 0x771, + // The state of the printer is invalid. + INVALID_PRINTER_STATE = 0x772, + // The user's password must be changed before signing in. + PASSWORD_MUST_CHANGE = 0x773, + // Could not find the domain controller for this domain. + DOMAIN_CONTROLLER_NOT_FOUND = 0x774, + // The referenced account is currently locked out and may not be logged on to. + ACCOUNT_LOCKED_OUT = 0x775, + // The object exporter specified was not found. + OR_INVALID_OXID = 0x776, + // The object specified was not found. + OR_INVALID_OID = 0x777, + // The object resolver set specified was not found. + OR_INVALID_SET = 0x778, + // Some data remains to be sent in the request buffer. + RPC_S_SEND_INCOMPLETE = 0x779, + // Invalid asynchronous remote procedure call handle. + RPC_S_INVALID_ASYNC_HANDLE = 0x77A, + // Invalid asynchronous RPC call handle for this operation. + RPC_S_INVALID_ASYNC_CALL = 0x77B, + // The RPC pipe object has already been closed. + RPC_X_PIPE_CLOSED = 0x77C, + // The RPC call completed before all pipes were processed. + RPC_X_PIPE_DISCIPLINE_ERROR = 0x77D, + // No more data is available from the RPC pipe. + RPC_X_PIPE_EMPTY = 0x77E, + // No site name is available for this machine. + NO_SITENAME = 0x77F, + // The file cannot be accessed by the system. + CANT_ACCESS_FILE = 0x780, + // The name of the file cannot be resolved by the system. + CANT_RESOLVE_FILENAME = 0x781, + // The entry is not of the expected type. + RPC_S_ENTRY_TYPE_MISMATCH = 0x782, + // Not all object UUIDs could be exported to the specified entry. + RPC_S_NOT_ALL_OBJS_EXPORTED = 0x783, + // Interface could not be exported to the specified entry. + RPC_S_INTERFACE_NOT_EXPORTED = 0x784, + // The specified profile entry could not be added. + RPC_S_PROFILE_NOT_ADDED = 0x785, + // The specified profile element could not be added. + RPC_S_PRF_ELT_NOT_ADDED = 0x786, + // The specified profile element could not be removed. + RPC_S_PRF_ELT_NOT_REMOVED = 0x787, + // The group element could not be added. + RPC_S_GRP_ELT_NOT_ADDED = 0x788, + // The group element could not be removed. + RPC_S_GRP_ELT_NOT_REMOVED = 0x789, + // The printer driver is not compatible with a policy enabled on your computer that blocks NT 4.0 drivers. + KM_DRIVER_BLOCKED = 0x78A, + // The context has expired and can no longer be used. + CONTEXT_EXPIRED = 0x78B, + // The current user's delegated trust creation quota has been exceeded. + PER_USER_TRUST_QUOTA_EXCEEDED = 0x78C, + // The total delegated trust creation quota has been exceeded. + ALL_USER_TRUST_QUOTA_EXCEEDED = 0x78D, + // The current user's delegated trust deletion quota has been exceeded. + USER_DELETE_TRUST_QUOTA_EXCEEDED = 0x78E, + // The computer you are signing into is protected by an authentication firewall. The specified account is not allowed to authenticate to the computer. + AUTHENTICATION_FIREWALL_FAILED = 0x78F, + // Remote connections to the Print Spooler are blocked by a policy set on your machine. + REMOTE_PRINT_CONNECTIONS_BLOCKED = 0x790, + // Authentication failed because NTLM authentication has been disabled. + NTLM_BLOCKED = 0x791, + // Logon Failure: EAS policy requires that the user change their password before this operation can be performed. + PASSWORD_CHANGE_REQUIRED = 0x792, + // The pixel format is invalid. + INVALID_PIXEL_FORMAT = 0x7D0, + // The specified driver is invalid. + BAD_DRIVER = 0x7D1, + // The window style or class attribute is invalid for this operation. + INVALID_WINDOW_STYLE = 0x7D2, + // The requested metafile operation is not supported. + METAFILE_NOT_SUPPORTED = 0x7D3, + // The requested transformation operation is not supported. + TRANSFORM_NOT_SUPPORTED = 0x7D4, + // The requested clipping operation is not supported. + CLIPPING_NOT_SUPPORTED = 0x7D5, + // The specified color management module is invalid. + INVALID_CMM = 0x7DA, + // The specified color profile is invalid. + INVALID_PROFILE = 0x7DB, + // The specified tag was not found. + TAG_NOT_FOUND = 0x7DC, + // A required tag is not present. + TAG_NOT_PRESENT = 0x7DD, + // The specified tag is already present. + DUPLICATE_TAG = 0x7DE, + // The specified color profile is not associated with the specified device. + PROFILE_NOT_ASSOCIATED_WITH_DEVICE = 0x7DF, + // The specified color profile was not found. + PROFILE_NOT_FOUND = 0x7E0, + // The specified color space is invalid. + INVALID_COLORSPACE = 0x7E1, + // Image Color Management is not enabled. + ICM_NOT_ENABLED = 0x7E2, + // There was an error while deleting the color transform. + DELETING_ICM_XFORM = 0x7E3, + // The specified color transform is invalid. + INVALID_TRANSFORM = 0x7E4, + // The specified transform does not match the bitmap's color space. + COLORSPACE_MISMATCH = 0x7E5, + // The specified named color index is not present in the profile. + INVALID_COLORINDEX = 0x7E6, + // The specified profile is intended for a device of a different type than the specified device. + PROFILE_DOES_NOT_MATCH_DEVICE = 0x7E7, + // The network connection was made successfully, but the user had to be prompted for a password other than the one originally specified. + CONNECTED_OTHER_PASSWORD = 0x83C, + // The network connection was made successfully using default credentials. + CONNECTED_OTHER_PASSWORD_DEFAULT = 0x83D, + // The specified username is invalid. + BAD_USERNAME = 0x89A, + // This network connection does not exist. + NOT_CONNECTED = 0x8CA, + // This network connection has files open or requests pending. + OPEN_FILES = 0x961, + // Active connections still exist. + ACTIVE_CONNECTIONS = 0x962, + // The device is in use by an active process and cannot be disconnected. + DEVICE_IN_USE = 0x964, + // The specified print monitor is unknown. + UNKNOWN_PRINT_MONITOR = 0xBB8, + // The specified printer driver is currently in use. + PRINTER_DRIVER_IN_USE = 0xBB9, + // The spool file was not found. + SPOOL_FILE_NOT_FOUND = 0xBBA, + // A StartDocPrinter call was not issued. + SPL_NO_STARTDOC = 0xBBB, + // An AddJob call was not issued. + SPL_NO_ADDJOB = 0xBBC, + // The specified print processor has already been installed. + PRINT_PROCESSOR_ALREADY_INSTALLED = 0xBBD, + // The specified print monitor has already been installed. + PRINT_MONITOR_ALREADY_INSTALLED = 0xBBE, + // The specified print monitor does not have the required functions. + INVALID_PRINT_MONITOR = 0xBBF, + // The specified print monitor is currently in use. + PRINT_MONITOR_IN_USE = 0xBC0, + // The requested operation is not allowed when there are jobs queued to the printer. + PRINTER_HAS_JOBS_QUEUED = 0xBC1, + // The requested operation is successful. Changes will not be effective until the system is rebooted. + SUCCESS_REBOOT_REQUIRED = 0xBC2, + // The requested operation is successful. Changes will not be effective until the service is restarted. + SUCCESS_RESTART_REQUIRED = 0xBC3, + // No printers were found. + PRINTER_NOT_FOUND = 0xBC4, + // The printer driver is known to be unreliable. + PRINTER_DRIVER_WARNED = 0xBC5, + // The printer driver is known to harm the system. + PRINTER_DRIVER_BLOCKED = 0xBC6, + // The specified printer driver package is currently in use. + PRINTER_DRIVER_PACKAGE_IN_USE = 0xBC7, + // Unable to find a core driver package that is required by the printer driver package. + CORE_DRIVER_PACKAGE_NOT_FOUND = 0xBC8, + // The requested operation failed. A system reboot is required to roll back changes made. + FAIL_REBOOT_REQUIRED = 0xBC9, + // The requested operation failed. A system reboot has been initiated to roll back changes made. + FAIL_REBOOT_INITIATED = 0xBCA, + // The specified printer driver was not found on the system and needs to be downloaded. + PRINTER_DRIVER_DOWNLOAD_NEEDED = 0xBCB, + // The requested print job has failed to print. A print system update requires the job to be resubmitted. + PRINT_JOB_RESTART_REQUIRED = 0xBCC, + // The printer driver does not contain a valid manifest, or contains too many manifests. + INVALID_PRINTER_DRIVER_MANIFEST = 0xBCD, + // The specified printer cannot be shared. + PRINTER_NOT_SHAREABLE = 0xBCE, + // The operation was paused. + REQUEST_PAUSED = 0xBEA, + // Reissue the given operation as a cached IO operation. + IO_REISSUE_AS_CACHED = 0xF6E, + + // WINS encountered an error while processing the command. + WINS_INTERNAL = 0xFA0, + // The local WINS cannot be deleted. + CAN_NOT_DEL_LOCAL_WINS = 0xFA1, + // The importation from the file failed. + STATIC_INIT = 0xFA2, + // The backup failed. Was a full backup done before? + INC_BACKUP = 0xFA3, + // The backup failed. Check the directory to which you are backing the database. + FULL_BACKUP = 0xFA4, + // The name does not exist in the WINS database. + REC_NON_EXISTENT = 0xFA5, + // Replication with a nonconfigured partner is not allowed. + RPL_NOT_ALLOWED = 0xFA6, + // The version of the supplied content information is not supported. + PEERDIST_ERROR_CONTENTINFO_VERSION_UNSUPPORTED = 0xFD2, + // The supplied content information is malformed. + PEERDIST_ERROR_CANNOT_PARSE_CONTENTINFO = 0xFD3, + // The requested data cannot be found in local or peer caches. + PEERDIST_ERROR_MISSING_DATA = 0xFD4, + // No more data is available or required. + PEERDIST_ERROR_NO_MORE = 0xFD5, + // The supplied object has not been initialized. + PEERDIST_ERROR_NOT_INITIALIZED = 0xFD6, + // The supplied object has already been initialized. + PEERDIST_ERROR_ALREADY_INITIALIZED = 0xFD7, + // A shutdown operation is already in progress. + PEERDIST_ERROR_SHUTDOWN_IN_PROGRESS = 0xFD8, + // The supplied object has already been invalidated. + PEERDIST_ERROR_INVALIDATED = 0xFD9, + // An element already exists and was not replaced. + PEERDIST_ERROR_ALREADY_EXISTS = 0xFDA, + // Cannot cancel the requested operation as it has already been completed. + PEERDIST_ERROR_OPERATION_NOTFOUND = 0xFDB, + // Can not perform the reqested operation because it has already been carried out. + PEERDIST_ERROR_ALREADY_COMPLETED = 0xFDC, + // An operation accessed data beyond the bounds of valid data. + PEERDIST_ERROR_OUT_OF_BOUNDS = 0xFDD, + // The requested version is not supported. + PEERDIST_ERROR_VERSION_UNSUPPORTED = 0xFDE, + // A configuration value is invalid. + PEERDIST_ERROR_INVALID_CONFIGURATION = 0xFDF, + // The SKU is not licensed. + PEERDIST_ERROR_NOT_LICENSED = 0xFE0, + // PeerDist Service is still initializing and will be available shortly. + PEERDIST_ERROR_SERVICE_UNAVAILABLE = 0xFE1, + // Communication with one or more computers will be temporarily blocked due to recent errors. + PEERDIST_ERROR_TRUST_FAILURE = 0xFE2, + // The DHCP client has obtained an IP address that is already in use on the network. The local interface will be disabled until the DHCP client can obtain a new address. + DHCP_ADDRESS_CONFLICT = 0x1004, + // The GUID passed was not recognized as valid by a WMI data provider. + WMI_GUID_NOT_FOUND = 0x1068, + // The instance name passed was not recognized as valid by a WMI data provider. + WMI_INSTANCE_NOT_FOUND = 0x1069, + // The data item ID passed was not recognized as valid by a WMI data provider. + WMI_ITEMID_NOT_FOUND = 0x106A, + // The WMI request could not be completed and should be retried. + WMI_TRY_AGAIN = 0x106B, + // The WMI data provider could not be located. + WMI_DP_NOT_FOUND = 0x106C, + // The WMI data provider references an instance set that has not been registered. + WMI_UNRESOLVED_INSTANCE_REF = 0x106D, + // The WMI data block or event notification has already been enabled. + WMI_ALREADY_ENABLED = 0x106E, + // The WMI data block is no longer available. + WMI_GUID_DISCONNECTED = 0x106F, + // The WMI data service is not available. + WMI_SERVER_UNAVAILABLE = 0x1070, + // The WMI data provider failed to carry out the request. + WMI_DP_FAILED = 0x1071, + // The WMI MOF information is not valid. + WMI_INVALID_MOF = 0x1072, + // The WMI registration information is not valid. + WMI_INVALID_REGINFO = 0x1073, + // The WMI data block or event notification has already been disabled. + WMI_ALREADY_DISABLED = 0x1074, + // The WMI data item or data block is read only. + WMI_READ_ONLY = 0x1075, + // The WMI data item or data block could not be changed. + WMI_SET_FAILURE = 0x1076, + // This operation is only valid in the context of an app container. + NOT_APPCONTAINER = 0x109A, + // This application can only run in the context of an app container. + APPCONTAINER_REQUIRED = 0x109B, + // This functionality is not supported in the context of an app container. + NOT_SUPPORTED_IN_APPCONTAINER = 0x109C, + // The length of the SID supplied is not a valid length for app container SIDs. + INVALID_PACKAGE_SID_LENGTH = 0x109D, + // The media identifier does not represent a valid medium. + INVALID_MEDIA = 0x10CC, + // The library identifier does not represent a valid library. + INVALID_LIBRARY = 0x10CD, + // The media pool identifier does not represent a valid media pool. + INVALID_MEDIA_POOL = 0x10CE, + // The drive and medium are not compatible or exist in different libraries. + DRIVE_MEDIA_MISMATCH = 0x10CF, + // The medium currently exists in an offline library and must be online to perform this operation. + MEDIA_OFFLINE = 0x10D0, + // The operation cannot be performed on an offline library. + LIBRARY_OFFLINE = 0x10D1, + // The library, drive, or media pool is empty. + EMPTY = 0x10D2, + // The library, drive, or media pool must be empty to perform this operation. + NOT_EMPTY = 0x10D3, + // No media is currently available in this media pool or library. + MEDIA_UNAVAILABLE = 0x10D4, + // A resource required for this operation is disabled. + RESOURCE_DISABLED = 0x10D5, + // The media identifier does not represent a valid cleaner. + INVALID_CLEANER = 0x10D6, + // The drive cannot be cleaned or does not support cleaning. + UNABLE_TO_CLEAN = 0x10D7, + // The object identifier does not represent a valid object. + OBJECT_NOT_FOUND = 0x10D8, + // Unable to read from or write to the database. + DATABASE_FAILURE = 0x10D9, + // The database is full. + DATABASE_FULL = 0x10DA, + // The medium is not compatible with the device or media pool. + MEDIA_INCOMPATIBLE = 0x10DB, + // The resource required for this operation does not exist. + RESOURCE_NOT_PRESENT = 0x10DC, + // The operation identifier is not valid. + INVALID_OPERATION = 0x10DD, + // The media is not mounted or ready for use. + MEDIA_NOT_AVAILABLE = 0x10DE, + // The device is not ready for use. + DEVICE_NOT_AVAILABLE = 0x10DF, + // The operator or administrator has refused the request. + REQUEST_REFUSED = 0x10E0, + // The drive identifier does not represent a valid drive. + INVALID_DRIVE_OBJECT = 0x10E1, + // Library is full. No slot is available for use. + LIBRARY_FULL = 0x10E2, + // The transport cannot access the medium. + MEDIUM_NOT_ACCESSIBLE = 0x10E3, + // Unable to load the medium into the drive. + UNABLE_TO_LOAD_MEDIUM = 0x10E4, + // Unable to retrieve the drive status. + UNABLE_TO_INVENTORY_DRIVE = 0x10E5, + // Unable to retrieve the slot status. + UNABLE_TO_INVENTORY_SLOT = 0x10E6, + // Unable to retrieve status about the transport. + UNABLE_TO_INVENTORY_TRANSPORT = 0x10E7, + // Cannot use the transport because it is already in use. + TRANSPORT_FULL = 0x10E8, + // Unable to open or close the inject/eject port. + CONTROLLING_IEPORT = 0x10E9, + // Unable to eject the medium because it is in a drive. + UNABLE_TO_EJECT_MOUNTED_MEDIA = 0x10EA, + // A cleaner slot is already reserved. + CLEANER_SLOT_SET = 0x10EB, + // A cleaner slot is not reserved. + CLEANER_SLOT_NOT_SET = 0x10EC, + // The cleaner cartridge has performed the maximum number of drive cleanings. + CLEANER_CARTRIDGE_SPENT = 0x10ED, + // Unexpected on-medium identifier. + UNEXPECTED_OMID = 0x10EE, + // The last remaining item in this group or resource cannot be deleted. + CANT_DELETE_LAST_ITEM = 0x10EF, + // The message provided exceeds the maximum size allowed for this parameter. + MESSAGE_EXCEEDS_MAX_SIZE = 0x10F0, + // The volume contains system or paging files. + VOLUME_CONTAINS_SYS_FILES = 0x10F1, + // The media type cannot be removed from this library since at least one drive in the library reports it can support this media type. + INDIGENOUS_TYPE = 0x10F2, + // This offline media cannot be mounted on this system since no enabled drives are present which can be used. + NO_SUPPORTING_DRIVES = 0x10F3, + // A cleaner cartridge is present in the tape library. + CLEANER_CARTRIDGE_INSTALLED = 0x10F4, + // Cannot use the inject/eject port because it is not empty. + IEPORT_FULL = 0x10F5, + // This file is currently not available for use on this computer. + FILE_OFFLINE = 0x10FE, + // The remote storage service is not operational at this time. + REMOTE_STORAGE_NOT_ACTIVE = 0x10FF, + // The remote storage service encountered a media error. + REMOTE_STORAGE_MEDIA_ERROR = 0x1100, + // The file or directory is not a reparse point. + NOT_A_REPARSE_POINT = 0x1126, + // The reparse point attribute cannot be set because it conflicts with an existing attribute. + REPARSE_ATTRIBUTE_CONFLICT = 0x1127, + // The data present in the reparse point buffer is invalid. + INVALID_REPARSE_DATA = 0x1128, + // The tag present in the reparse point buffer is invalid. + REPARSE_TAG_INVALID = 0x1129, + // There is a mismatch between the tag specified in the request and the tag present in the reparse point. + REPARSE_TAG_MISMATCH = 0x112A, + // Fast Cache data not found. + APP_DATA_NOT_FOUND = 0x1130, + // Fast Cache data expired. + APP_DATA_EXPIRED = 0x1131, + // Fast Cache data corrupt. + APP_DATA_CORRUPT = 0x1132, + // Fast Cache data has exceeded its max size and cannot be updated. + APP_DATA_LIMIT_EXCEEDED = 0x1133, + // Fast Cache has been ReArmed and requires a reboot until it can be updated. + APP_DATA_REBOOT_REQUIRED = 0x1134, + // Secure Boot detected that rollback of protected data has been attempted. + SECUREBOOT_ROLLBACK_DETECTED = 0x1144, + // The value is protected by Secure Boot policy and cannot be modified or deleted. + SECUREBOOT_POLICY_VIOLATION = 0x1145, + // The Secure Boot policy is invalid. + SECUREBOOT_INVALID_POLICY = 0x1146, + // A new Secure Boot policy did not contain the current publisher on its update list. + SECUREBOOT_POLICY_PUBLISHER_NOT_FOUND = 0x1147, + // The Secure Boot policy is either not signed or is signed by a non-trusted signer. + SECUREBOOT_POLICY_NOT_SIGNED = 0x1148, + // Secure Boot is not enabled on this machine. + SECUREBOOT_NOT_ENABLED = 0x1149, + // Secure Boot requires that certain files and drivers are not replaced by other files or drivers. + SECUREBOOT_FILE_REPLACED = 0x114A, + // The copy offload read operation is not supported by a filter. + OFFLOAD_READ_FLT_NOT_SUPPORTED = 0x1158, + // The copy offload write operation is not supported by a filter. + OFFLOAD_WRITE_FLT_NOT_SUPPORTED = 0x1159, + // The copy offload read operation is not supported for the file. + OFFLOAD_READ_FILE_NOT_SUPPORTED = 0x115A, + // The copy offload write operation is not supported for the file. + OFFLOAD_WRITE_FILE_NOT_SUPPORTED = 0x115B, + // Single Instance Storage is not available on this volume. + VOLUME_NOT_SIS_ENABLED = 0x1194, + // The operation cannot be completed because other resources are dependent on this resource. + DEPENDENT_RESOURCE_EXISTS = 0x1389, + // The cluster resource dependency cannot be found. + DEPENDENCY_NOT_FOUND = 0x138A, + // The cluster resource cannot be made dependent on the specified resource because it is already dependent. + DEPENDENCY_ALREADY_EXISTS = 0x138B, + // The cluster resource is not online. + RESOURCE_NOT_ONLINE = 0x138C, + // A cluster node is not available for this operation. + HOST_NODE_NOT_AVAILABLE = 0x138D, + // The cluster resource is not available. + RESOURCE_NOT_AVAILABLE = 0x138E, + // The cluster resource could not be found. + RESOURCE_NOT_FOUND = 0x138F, + // The cluster is being shut down. + SHUTDOWN_CLUSTER = 0x1390, + // A cluster node cannot be evicted from the cluster unless the node is down or it is the last node. + CANT_EVICT_ACTIVE_NODE = 0x1391, + // The object already exists. + OBJECT_ALREADY_EXISTS = 0x1392, + // The object is already in the list. + OBJECT_IN_LIST = 0x1393, + // The cluster group is not available for any new requests. + GROUP_NOT_AVAILABLE = 0x1394, + // The cluster group could not be found. + GROUP_NOT_FOUND = 0x1395, + // The operation could not be completed because the cluster group is not online. + GROUP_NOT_ONLINE = 0x1396, + // The operation failed because either the specified cluster node is not the owner of the resource, or the node is not a possible owner of the resource. + HOST_NODE_NOT_RESOURCE_OWNER = 0x1397, + // The operation failed because either the specified cluster node is not the owner of the group, or the node is not a possible owner of the group. + HOST_NODE_NOT_GROUP_OWNER = 0x1398, + // The cluster resource could not be created in the specified resource monitor. + RESMON_CREATE_FAILED = 0x1399, + // The cluster resource could not be brought online by the resource monitor. + RESMON_ONLINE_FAILED = 0x139A, + // The operation could not be completed because the cluster resource is online. + RESOURCE_ONLINE = 0x139B, + // The cluster resource could not be deleted or brought offline because it is the quorum resource. + QUORUM_RESOURCE = 0x139C, + // The cluster could not make the specified resource a quorum resource because it is not capable of being a quorum resource. + NOT_QUORUM_CAPABLE = 0x139D, + // The cluster software is shutting down. + CLUSTER_SHUTTING_DOWN = 0x139E, + // The group or resource is not in the correct state to perform the requested operation. + INVALID_STATE = 0x139F, + // The properties were stored but not all changes will take effect until the next time the resource is brought online. + RESOURCE_PROPERTIES_STORED = 0x13A0, + // The cluster could not make the specified resource a quorum resource because it does not belong to a shared storage class. + NOT_QUORUM_CLASS = 0x13A1, + // The cluster resource could not be deleted since it is a core resource. + CORE_RESOURCE = 0x13A2, + // The quorum resource failed to come online. + QUORUM_RESOURCE_ONLINE_FAILED = 0x13A3, + // The quorum log could not be created or mounted successfully. + QUORUMLOG_OPEN_FAILED = 0x13A4, + // The cluster log is corrupt. + CLUSTERLOG_CORRUPT = 0x13A5, + // The record could not be written to the cluster log since it exceeds the maximum size. + CLUSTERLOG_RECORD_EXCEEDS_MAXSIZE = 0x13A6, + // The cluster log exceeds its maximum size. + CLUSTERLOG_EXCEEDS_MAXSIZE = 0x13A7, + // No checkpoint record was found in the cluster log. + CLUSTERLOG_CHKPOINT_NOT_FOUND = 0x13A8, + // The minimum required disk space needed for logging is not available. + CLUSTERLOG_NOT_ENOUGH_SPACE = 0x13A9, + // The cluster node failed to take control of the quorum resource because the resource is owned by another active node. + QUORUM_OWNER_ALIVE = 0x13AA, + // A cluster network is not available for this operation. + NETWORK_NOT_AVAILABLE = 0x13AB, + // A cluster node is not available for this operation. + NODE_NOT_AVAILABLE = 0x13AC, + // All cluster nodes must be running to perform this operation. + ALL_NODES_NOT_AVAILABLE = 0x13AD, + // A cluster resource failed. + RESOURCE_FAILED = 0x13AE, + // The cluster node is not valid. + CLUSTER_INVALID_NODE = 0x13AF, + // The cluster node already exists. + CLUSTER_NODE_EXISTS = 0x13B0, + // A node is in the process of joining the cluster. + CLUSTER_JOIN_IN_PROGRESS = 0x13B1, + // The cluster node was not found. + CLUSTER_NODE_NOT_FOUND = 0x13B2, + // The cluster local node information was not found. + CLUSTER_LOCAL_NODE_NOT_FOUND = 0x13B3, + // The cluster network already exists. + CLUSTER_NETWORK_EXISTS = 0x13B4, + // The cluster network was not found. + CLUSTER_NETWORK_NOT_FOUND = 0x13B5, + // The cluster network interface already exists. + CLUSTER_NETINTERFACE_EXISTS = 0x13B6, + // The cluster network interface was not found. + CLUSTER_NETINTERFACE_NOT_FOUND = 0x13B7, + // The cluster request is not valid for this object. + CLUSTER_INVALID_REQUEST = 0x13B8, + // The cluster network provider is not valid. + CLUSTER_INVALID_NETWORK_PROVIDER = 0x13B9, + // The cluster node is down. + CLUSTER_NODE_DOWN = 0x13BA, + // The cluster node is not reachable. + CLUSTER_NODE_UNREACHABLE = 0x13BB, + // The cluster node is not a member of the cluster. + CLUSTER_NODE_NOT_MEMBER = 0x13BC, + // A cluster join operation is not in progress. + CLUSTER_JOIN_NOT_IN_PROGRESS = 0x13BD, + // The cluster network is not valid. + CLUSTER_INVALID_NETWORK = 0x13BE, + // The cluster node is up. + CLUSTER_NODE_UP = 0x13C0, + // The cluster IP address is already in use. + CLUSTER_IPADDR_IN_USE = 0x13C1, + // The cluster node is not paused. + CLUSTER_NODE_NOT_PAUSED = 0x13C2, + // No cluster security context is available. + CLUSTER_NO_SECURITY_CONTEXT = 0x13C3, + // The cluster network is not configured for internal cluster communication. + CLUSTER_NETWORK_NOT_INTERNAL = 0x13C4, + // The cluster node is already up. + CLUSTER_NODE_ALREADY_UP = 0x13C5, + // The cluster node is already down. + CLUSTER_NODE_ALREADY_DOWN = 0x13C6, + // The cluster network is already online. + CLUSTER_NETWORK_ALREADY_ONLINE = 0x13C7, + // The cluster network is already offline. + CLUSTER_NETWORK_ALREADY_OFFLINE = 0x13C8, + // The cluster node is already a member of the cluster. + CLUSTER_NODE_ALREADY_MEMBER = 0x13C9, + // The cluster network is the only one configured for internal cluster communication between two or more active cluster nodes. The internal communication capability cannot be removed from the network. + CLUSTER_LAST_INTERNAL_NETWORK = 0x13CA, + // One or more cluster resources depend on the network to provide service to clients. The client access capability cannot be removed from the network. + CLUSTER_NETWORK_HAS_DEPENDENTS = 0x13CB, + // This operation cannot be performed on the cluster resource as it the quorum resource. You may not bring the quorum resource offline or modify its possible owners list. + INVALID_OPERATION_ON_QUORUM = 0x13CC, + // The cluster quorum resource is not allowed to have any dependencies. + DEPENDENCY_NOT_ALLOWED = 0x13CD, + // The cluster node is paused. + CLUSTER_NODE_PAUSED = 0x13CE, + // The cluster resource cannot be brought online. The owner node cannot run this resource. + NODE_CANT_HOST_RESOURCE = 0x13CF, + // The cluster node is not ready to perform the requested operation. + CLUSTER_NODE_NOT_READY = 0x13D0, + // The cluster node is shutting down. + CLUSTER_NODE_SHUTTING_DOWN = 0x13D1, + // The cluster join operation was aborted. + CLUSTER_JOIN_ABORTED = 0x13D2, + // The cluster join operation failed due to incompatible software versions between the joining node and its sponsor. + CLUSTER_INCOMPATIBLE_VERSIONS = 0x13D3, + // This resource cannot be created because the cluster has reached the limit on the number of resources it can monitor. + CLUSTER_MAXNUM_OF_RESOURCES_EXCEEDED = 0x13D4, + // The system configuration changed during the cluster join or form operation. The join or form operation was aborted. + CLUSTER_SYSTEM_CONFIG_CHANGED = 0x13D5, + // The specified resource type was not found. + CLUSTER_RESOURCE_TYPE_NOT_FOUND = 0x13D6, + // The specified node does not support a resource of this type. This may be due to version inconsistencies or due to the absence of the resource DLL on this node. + CLUSTER_RESTYPE_NOT_SUPPORTED = 0x13D7, + // The specified resource name is not supported by this resource DLL. This may be due to a bad (or changed) name supplied to the resource DLL. + CLUSTER_RESNAME_NOT_FOUND = 0x13D8, + // No authentication package could be registered with the RPC server. + CLUSTER_NO_RPC_PACKAGES_REGISTERED = 0x13D9, + // You cannot bring the group online because the owner of the group is not in the preferred list for the group. To change the owner node for the group, move the group. + CLUSTER_OWNER_NOT_IN_PREFLIST = 0x13DA, + // The join operation failed because the cluster database sequence number has changed or is incompatible with the locker node. This may happen during a join operation if the cluster database was changing during the join. + CLUSTER_DATABASE_SEQMISMATCH = 0x13DB, + // The resource monitor will not allow the fail operation to be performed while the resource is in its current state. This may happen if the resource is in a pending state. + RESMON_INVALID_STATE = 0x13DC, + // A non locker code got a request to reserve the lock for making global updates. + CLUSTER_GUM_NOT_LOCKER = 0x13DD, + // The quorum disk could not be located by the cluster service. + QUORUM_DISK_NOT_FOUND = 0x13DE, + // The backed up cluster database is possibly corrupt. + DATABASE_BACKUP_CORRUPT = 0x13DF, + // A DFS root already exists in this cluster node. + CLUSTER_NODE_ALREADY_HAS_DFS_ROOT = 0x13E0, + // An attempt to modify a resource property failed because it conflicts with another existing property. + RESOURCE_PROPERTY_UNCHANGEABLE = 0x13E1, + // An operation was attempted that is incompatible with the current membership state of the node. + CLUSTER_MEMBERSHIP_INVALID_STATE = 0x1702, + // The quorum resource does not contain the quorum log. + CLUSTER_QUORUMLOG_NOT_FOUND = 0x1703, + // The membership engine requested shutdown of the cluster service on this node. + CLUSTER_MEMBERSHIP_HALT = 0x1704, + // The join operation failed because the cluster instance ID of the joining node does not match the cluster instance ID of the sponsor node. + CLUSTER_INSTANCE_ID_MISMATCH = 0x1705, + // A matching cluster network for the specified IP address could not be found. + CLUSTER_NETWORK_NOT_FOUND_FOR_IP = 0x1706, + // The actual data type of the property did not match the expected data type of the property. + CLUSTER_PROPERTY_DATA_TYPE_MISMATCH = 0x1707, + // The cluster node was evicted from the cluster successfully, but the node was not cleaned up. To determine what cleanup steps failed and how to recover, see the Failover Clustering application event log using Event Viewer. + CLUSTER_EVICT_WITHOUT_CLEANUP = 0x1708, + // Two or more parameter values specified for a resource's properties are in conflict. + CLUSTER_PARAMETER_MISMATCH = 0x1709, + // This computer cannot be made a member of a cluster. + NODE_CANNOT_BE_CLUSTERED = 0x170A, + // This computer cannot be made a member of a cluster because it does not have the correct version of Windows installed. + CLUSTER_WRONG_OS_VERSION = 0x170B, + // A cluster cannot be created with the specified cluster name because that cluster name is already in use. Specify a different name for the cluster. + CLUSTER_CANT_CREATE_DUP_CLUSTER_NAME = 0x170C, + // The cluster configuration action has already been committed. + CLUSCFG_ALREADY_COMMITTED = 0x170D, + // The cluster configuration action could not be rolled back. + CLUSCFG_ROLLBACK_FAILED = 0x170E, + // The drive letter assigned to a system disk on one node conflicted with the drive letter assigned to a disk on another node. + CLUSCFG_SYSTEM_DISK_DRIVE_LETTER_CONFLICT = 0x170F, + // One or more nodes in the cluster are running a version of Windows that does not support this operation. + CLUSTER_OLD_VERSION = 0x1710, + // The name of the corresponding computer account doesn't match the Network Name for this resource. + CLUSTER_MISMATCHED_COMPUTER_ACCT_NAME = 0x1711, + // No network adapters are available. + CLUSTER_NO_NET_ADAPTERS = 0x1712, + // The cluster node has been poisoned. + CLUSTER_POISONED = 0x1713, + // The group is unable to accept the request since it is moving to another node. + CLUSTER_GROUP_MOVING = 0x1714, + // The resource type cannot accept the request since is too busy performing another operation. + CLUSTER_RESOURCE_TYPE_BUSY = 0x1715, + // The call to the cluster resource DLL timed out. + RESOURCE_CALL_TIMED_OUT = 0x1716, + // The address is not valid for an IPv6 Address resource. A global IPv6 address is required, and it must match a cluster network. Compatibility addresses are not permitted. + INVALID_CLUSTER_IPV6_ADDRESS = 0x1717, + // An internal cluster error occurred. A call to an invalid function was attempted. + CLUSTER_INTERNAL_INVALID_FUNCTION = 0x1718, + // A parameter value is out of acceptable range. + CLUSTER_PARAMETER_OUT_OF_BOUNDS = 0x1719, + // A network error occurred while sending data to another node in the cluster. The number of bytes transmitted was less than required. + CLUSTER_PARTIAL_SEND = 0x171A, + // An invalid cluster registry operation was attempted. + CLUSTER_REGISTRY_INVALID_FUNCTION = 0x171B, + // An input string of characters is not properly terminated. + CLUSTER_INVALID_STRING_TERMINATION = 0x171C, + // An input string of characters is not in a valid format for the data it represents. + CLUSTER_INVALID_STRING_FORMAT = 0x171D, + // An internal cluster error occurred. A cluster database transaction was attempted while a transaction was already in progress. + CLUSTER_DATABASE_TRANSACTION_IN_PROGRESS = 0x171E, + // An internal cluster error occurred. There was an attempt to commit a cluster database transaction while no transaction was in progress. + CLUSTER_DATABASE_TRANSACTION_NOT_IN_PROGRESS = 0x171F, + // An internal cluster error occurred. Data was not properly initialized. + CLUSTER_NULL_DATA = 0x1720, + // An error occurred while reading from a stream of data. An unexpected number of bytes was returned. + CLUSTER_PARTIAL_READ = 0x1721, + // An error occurred while writing to a stream of data. The required number of bytes could not be written. + CLUSTER_PARTIAL_WRITE = 0x1722, + // An error occurred while deserializing a stream of cluster data. + CLUSTER_CANT_DESERIALIZE_DATA = 0x1723, + // One or more property values for this resource are in conflict with one or more property values associated with its dependent resource(s). + DEPENDENT_RESOURCE_PROPERTY_CONFLICT = 0x1724, + // A quorum of cluster nodes was not present to form a cluster. + CLUSTER_NO_QUORUM = 0x1725, + // The cluster network is not valid for an IPv6 Address resource, or it does not match the configured address. + CLUSTER_INVALID_IPV6_NETWORK = 0x1726, + // The cluster network is not valid for an IPv6 Tunnel resource. Check the configuration of the IP Address resource on which the IPv6 Tunnel resource depends. + CLUSTER_INVALID_IPV6_TUNNEL_NETWORK = 0x1727, + // Quorum resource cannot reside in the Available Storage group. + QUORUM_NOT_ALLOWED_IN_THIS_GROUP = 0x1728, + // The dependencies for this resource are nested too deeply. + DEPENDENCY_TREE_TOO_COMPLEX = 0x1729, + // The call into the resource DLL raised an unhandled exception. + EXCEPTION_IN_RESOURCE_CALL = 0x172A, + // The RHS process failed to initialize. + CLUSTER_RHS_FAILED_INITIALIZATION = 0x172B, + // The Failover Clustering feature is not installed on this node. + CLUSTER_NOT_INSTALLED = 0x172C, + // The resources must be online on the same node for this operation. + CLUSTER_RESOURCES_MUST_BE_ONLINE_ON_THE_SAME_NODE = 0x172D, + // A new node can not be added since this cluster is already at its maximum number of nodes. + CLUSTER_MAX_NODES_IN_CLUSTER = 0x172E, + // This cluster can not be created since the specified number of nodes exceeds the maximum allowed limit. + CLUSTER_TOO_MANY_NODES = 0x172F, + // An attempt to use the specified cluster name failed because an enabled computer object with the given name already exists in the domain. + CLUSTER_OBJECT_ALREADY_USED = 0x1730, + // This cluster cannot be destroyed. It has non-core application groups which must be deleted before the cluster can be destroyed. + NONCORE_GROUPS_FOUND = 0x1731, + // File share associated with file share witness resource cannot be hosted by this cluster or any of its nodes. + FILE_SHARE_RESOURCE_CONFLICT = 0x1732, + // Eviction of this node is invalid at this time. Due to quorum requirements node eviction will result in cluster shutdown. If it is the last node in the cluster, destroy cluster command should be used. + CLUSTER_EVICT_INVALID_REQUEST = 0x1733, + // Only one instance of this resource type is allowed in the cluster. + CLUSTER_SINGLETON_RESOURCE = 0x1734, + // Only one instance of this resource type is allowed per resource group. + CLUSTER_GROUP_SINGLETON_RESOURCE = 0x1735, + // The resource failed to come online due to the failure of one or more provider resources. + CLUSTER_RESOURCE_PROVIDER_FAILED = 0x1736, + // The resource has indicated that it cannot come online on any node. + CLUSTER_RESOURCE_CONFIGURATION_ERROR = 0x1737, + // The current operation cannot be performed on this group at this time. + CLUSTER_GROUP_BUSY = 0x1738, + // The directory or file is not located on a cluster shared volume. + CLUSTER_NOT_SHARED_VOLUME = 0x1739, + // The Security Descriptor does not meet the requirements for a cluster. + CLUSTER_INVALID_SECURITY_DESCRIPTOR = 0x173A, + // There is one or more shared volumes resources configured in the cluster. Those resources must be moved to available storage in order for operation to succeed. + CLUSTER_SHARED_VOLUMES_IN_USE = 0x173B, + // This group or resource cannot be directly manipulated. Use shared volume APIs to perform desired operation. + CLUSTER_USE_SHARED_VOLUMES_API = 0x173C, + // Back up is in progress. Please wait for backup completion before trying this operation again. + CLUSTER_BACKUP_IN_PROGRESS = 0x173D, + // The path does not belong to a cluster shared volume. + NON_CSV_PATH = 0x173E, + // The cluster shared volume is not locally mounted on this node. + CSV_VOLUME_NOT_LOCAL = 0x173F, + // The cluster watchdog is terminating. + CLUSTER_WATCHDOG_TERMINATING = 0x1740, + // A resource vetoed a move between two nodes because they are incompatible. + CLUSTER_RESOURCE_VETOED_MOVE_INCOMPATIBLE_NODES = 0x1741, + // The request is invalid either because node weight cannot be changed while the cluster is in disk-only quorum mode, or because changing the node weight would violate the minimum cluster quorum requirements. + CLUSTER_INVALID_NODE_WEIGHT = 0x1742, + // The resource vetoed the call. + CLUSTER_RESOURCE_VETOED_CALL = 0x1743, + // Resource could not start or run because it could not reserve sufficient system resources. + RESMON_SYSTEM_RESOURCES_LACKING = 0x1744, + // A resource vetoed a move between two nodes because the destination currently does not have enough resources to complete the operation. + CLUSTER_RESOURCE_VETOED_MOVE_NOT_ENOUGH_RESOURCES_ON_DESTINATION = 0x1745, + // A resource vetoed a move between two nodes because the source currently does not have enough resources to complete the operation. + CLUSTER_RESOURCE_VETOED_MOVE_NOT_ENOUGH_RESOURCES_ON_SOURCE = 0x1746, + // The requested operation can not be completed because the group is queued for an operation. + CLUSTER_GROUP_QUEUED = 0x1747, + // The requested operation can not be completed because a resource has locked status. + CLUSTER_RESOURCE_LOCKED_STATUS = 0x1748, + // The resource cannot move to another node because a cluster shared volume vetoed the operation. + CLUSTER_SHARED_VOLUME_FAILOVER_NOT_ALLOWED = 0x1749, + // A node drain is already in progress. + // This value was also named ERROR_CLUSTER_NODE_EVACUATION_IN_PROGRESS + CLUSTER_NODE_DRAIN_IN_PROGRESS = 0x174A, + // Clustered storage is not connected to the node. + CLUSTER_DISK_NOT_CONNECTED = 0x174B, + // The disk is not configured in a way to be used with CSV. CSV disks must have at least one partition that is formatted with NTFS. + DISK_NOT_CSV_CAPABLE = 0x174C, + // The resource must be part of the Available Storage group to complete this action. + RESOURCE_NOT_IN_AVAILABLE_STORAGE = 0x174D, + // CSVFS failed operation as volume is in redirected mode. + CLUSTER_SHARED_VOLUME_REDIRECTED = 0x174E, + // CSVFS failed operation as volume is not in redirected mode. + CLUSTER_SHARED_VOLUME_NOT_REDIRECTED = 0x174F, + // Cluster properties cannot be returned at this time. + CLUSTER_CANNOT_RETURN_PROPERTIES = 0x1750, + // The clustered disk resource contains software snapshot diff area that are not supported for Cluster Shared Volumes. + CLUSTER_RESOURCE_CONTAINS_UNSUPPORTED_DIFF_AREA_FOR_SHARED_VOLUMES = 0x1751, + // The operation cannot be completed because the resource is in maintenance mode. + CLUSTER_RESOURCE_IS_IN_MAINTENANCE_MODE = 0x1752, + // The operation cannot be completed because of cluster affinity conflicts. + CLUSTER_AFFINITY_CONFLICT = 0x1753, + // The operation cannot be completed because the resource is a replica virtual machine. + CLUSTER_RESOURCE_IS_REPLICA_VIRTUAL_MACHINE = 0x1754, + + // The specified file could not be encrypted. + ENCRYPTION_FAILED = 0x1770, + // The specified file could not be decrypted. + DECRYPTION_FAILED = 0x1771, + // The specified file is encrypted and the user does not have the ability to decrypt it. + FILE_ENCRYPTED = 0x1772, + // There is no valid encryption recovery policy configured for this system. + NO_RECOVERY_POLICY = 0x1773, + // The required encryption driver is not loaded for this system. + NO_EFS = 0x1774, + // The file was encrypted with a different encryption driver than is currently loaded. + WRONG_EFS = 0x1775, + // There are no EFS keys defined for the user. + NO_USER_KEYS = 0x1776, + // The specified file is not encrypted. + FILE_NOT_ENCRYPTED = 0x1777, + // The specified file is not in the defined EFS export format. + NOT_EXPORT_FORMAT = 0x1778, + // The specified file is read only. + FILE_READ_ONLY = 0x1779, + // The directory has been disabled for encryption. + DIR_EFS_DISALLOWED = 0x177A, + // The server is not trusted for remote encryption operation. + EFS_SERVER_NOT_TRUSTED = 0x177B, + // Recovery policy configured for this system contains invalid recovery certificate. + BAD_RECOVERY_POLICY = 0x177C, + // The encryption algorithm used on the source file needs a bigger key buffer than the one on the destination file. + EFS_ALG_BLOB_TOO_BIG = 0x177D, + // The disk partition does not support file encryption. + VOLUME_NOT_SUPPORT_EFS = 0x177E, + // This machine is disabled for file encryption. + EFS_DISABLED = 0x177F, + // A newer system is required to decrypt this encrypted file. + EFS_VERSION_NOT_SUPPORT = 0x1780, + // The remote server sent an invalid response for a file being opened with Client Side Encryption. + CS_ENCRYPTION_INVALID_SERVER_RESPONSE = 0x1781, + // Client Side Encryption is not supported by the remote server even though it claims to support it. + CS_ENCRYPTION_UNSUPPORTED_SERVER = 0x1782, + // File is encrypted and should be opened in Client Side Encryption mode. + CS_ENCRYPTION_EXISTING_ENCRYPTED_FILE = 0x1783, + // A new encrypted file is being created and a $EFS needs to be provided. + CS_ENCRYPTION_NEW_ENCRYPTED_FILE = 0x1784, + // The SMB client requested a CSE FSCTL on a non-CSE file. + CS_ENCRYPTION_FILE_NOT_CSE = 0x1785, + // The requested operation was blocked by policy. For more information, contact your system administrator. + ENCRYPTION_POLICY_DENIES_OPERATION = 0x1786, + // The list of servers for this workgroup is not currently available. + NO_BROWSER_SERVERS_FOUND = 0x17E6, + // The Task Scheduler service must be configured to run in the System account to function properly. Individual tasks may be configured to run in other accounts. + SCHED_E_SERVICE_NOT_LOCALSYSTEM = 0x1838, + // Log service encountered an invalid log sector. + LOG_SECTOR_INVALID = 0x19C8, + // Log service encountered a log sector with invalid block parity. + LOG_SECTOR_PARITY_INVALID = 0x19C9, + // Log service encountered a remapped log sector. + LOG_SECTOR_REMAPPED = 0x19CA, + // Log service encountered a partial or incomplete log block. + LOG_BLOCK_INCOMPLETE = 0x19CB, + // Log service encountered an attempt access data outside the active log range. + LOG_INVALID_RANGE = 0x19CC, + // Log service user marshalling buffers are exhausted. + LOG_BLOCKS_EXHAUSTED = 0x19CD, + // Log service encountered an attempt read from a marshalling area with an invalid read context. + LOG_READ_CONTEXT_INVALID = 0x19CE, + // Log service encountered an invalid log restart area. + LOG_RESTART_INVALID = 0x19CF, + // Log service encountered an invalid log block version. + LOG_BLOCK_VERSION = 0x19D0, + // Log service encountered an invalid log block. + LOG_BLOCK_INVALID = 0x19D1, + // Log service encountered an attempt to read the log with an invalid read mode. + LOG_READ_MODE_INVALID = 0x19D2, + // Log service encountered a log stream with no restart area. + LOG_NO_RESTART = 0x19D3, + // Log service encountered a corrupted metadata file. + LOG_METADATA_CORRUPT = 0x19D4, + // Log service encountered a metadata file that could not be created by the log file system. + LOG_METADATA_INVALID = 0x19D5, + // Log service encountered a metadata file with inconsistent data. + LOG_METADATA_INCONSISTENT = 0x19D6, + // Log service encountered an attempt to erroneous allocate or dispose reservation space. + LOG_RESERVATION_INVALID = 0x19D7, + // Log service cannot delete log file or file system container. + LOG_CANT_DELETE = 0x19D8, + // Log service has reached the maximum allowable containers allocated to a log file. + LOG_CONTAINER_LIMIT_EXCEEDED = 0x19D9, + // Log service has attempted to read or write backward past the start of the log. + LOG_START_OF_LOG = 0x19DA, + // Log policy could not be installed because a policy of the same type is already present. + LOG_POLICY_ALREADY_INSTALLED = 0x19DB, + // Log policy in question was not installed at the time of the request. + LOG_POLICY_NOT_INSTALLED = 0x19DC, + // The installed set of policies on the log is invalid. + LOG_POLICY_INVALID = 0x19DD, + // A policy on the log in question prevented the operation from completing. + LOG_POLICY_CONFLICT = 0x19DE, + // Log space cannot be reclaimed because the log is pinned by the archive tail. + LOG_PINNED_ARCHIVE_TAIL = 0x19DF, + // Log record is not a record in the log file. + LOG_RECORD_NONEXISTENT = 0x19E0, + // Number of reserved log records or the adjustment of the number of reserved log records is invalid. + LOG_RECORDS_RESERVED_INVALID = 0x19E1, + // Reserved log space or the adjustment of the log space is invalid. + LOG_SPACE_RESERVED_INVALID = 0x19E2, + // An new or existing archive tail or base of the active log is invalid. + LOG_TAIL_INVALID = 0x19E3, + // Log space is exhausted. + LOG_FULL = 0x19E4, + // The log could not be set to the requested size. + COULD_NOT_RESIZE_LOG = 0x19E5, + // Log is multiplexed, no direct writes to the physical log is allowed. + LOG_MULTIPLEXED = 0x19E6, + // The operation failed because the log is a dedicated log. + LOG_DEDICATED = 0x19E7, + // The operation requires an archive context. + LOG_ARCHIVE_NOT_IN_PROGRESS = 0x19E8, + // Log archival is in progress. + LOG_ARCHIVE_IN_PROGRESS = 0x19E9, + // The operation requires a non-ephemeral log, but the log is ephemeral. + LOG_EPHEMERAL = 0x19EA, + // The log must have at least two containers before it can be read from or written to. + LOG_NOT_ENOUGH_CONTAINERS = 0x19EB, + // A log client has already registered on the stream. + LOG_CLIENT_ALREADY_REGISTERED = 0x19EC, + // A log client has not been registered on the stream. + LOG_CLIENT_NOT_REGISTERED = 0x19ED, + // A request has already been made to handle the log full condition. + LOG_FULL_HANDLER_IN_PROGRESS = 0x19EE, + // Log service encountered an error when attempting to read from a log container. + LOG_CONTAINER_READ_FAILED = 0x19EF, + // Log service encountered an error when attempting to write to a log container. + LOG_CONTAINER_WRITE_FAILED = 0x19F0, + // Log service encountered an error when attempting open a log container. + LOG_CONTAINER_OPEN_FAILED = 0x19F1, + // Log service encountered an invalid container state when attempting a requested action. + LOG_CONTAINER_STATE_INVALID = 0x19F2, + // Log service is not in the correct state to perform a requested action. + LOG_STATE_INVALID = 0x19F3, + // Log space cannot be reclaimed because the log is pinned. + LOG_PINNED = 0x19F4, + // Log metadata flush failed. + LOG_METADATA_FLUSH_FAILED = 0x19F5, + // Security on the log and its containers is inconsistent. + LOG_INCONSISTENT_SECURITY = 0x19F6, + // Records were appended to the log or reservation changes were made, but the log could not be flushed. + LOG_APPENDED_FLUSH_FAILED = 0x19F7, + // The log is pinned due to reservation consuming most of the log space. Free some reserved records to make space available. + LOG_PINNED_RESERVATION = 0x19F8, + // The transaction handle associated with this operation is not valid. + INVALID_TRANSACTION = 0x1A2C, + // The requested operation was made in the context of a transaction that is no longer active. + TRANSACTION_NOT_ACTIVE = 0x1A2D, + // The requested operation is not valid on the Transaction object in its current state. + TRANSACTION_REQUEST_NOT_VALID = 0x1A2E, + // The caller has called a response API, but the response is not expected because the TM did not issue the corresponding request to the caller. + TRANSACTION_NOT_REQUESTED = 0x1A2F, + // It is too late to perform the requested operation, since the Transaction has already been aborted. + TRANSACTION_ALREADY_ABORTED = 0x1A30, + // It is too late to perform the requested operation, since the Transaction has already been committed. + TRANSACTION_ALREADY_COMMITTED = 0x1A31, + // The Transaction Manager was unable to be successfully initialized. Transacted operations are not supported. + TM_INITIALIZATION_FAILED = 0x1A32, + // The specified ResourceManager made no changes or updates to the resource under this transaction. + RESOURCEMANAGER_READ_ONLY = 0x1A33, + // The resource manager has attempted to prepare a transaction that it has not successfully joined. + TRANSACTION_NOT_JOINED = 0x1A34, + // The Transaction object already has a superior enlistment, and the caller attempted an operation that would have created a new superior. Only a single superior enlistment is allow. + TRANSACTION_SUPERIOR_EXISTS = 0x1A35, + // The RM tried to register a protocol that already exists. + CRM_PROTOCOL_ALREADY_EXISTS = 0x1A36, + // The attempt to propagate the Transaction failed. + TRANSACTION_PROPAGATION_FAILED = 0x1A37, + // The requested propagation protocol was not registered as a CRM. + CRM_PROTOCOL_NOT_FOUND = 0x1A38, + // The buffer passed in to PushTransaction or PullTransaction is not in a valid format. + TRANSACTION_INVALID_MARSHALL_BUFFER = 0x1A39, + // The current transaction context associated with the thread is not a valid handle to a transaction object. + CURRENT_TRANSACTION_NOT_VALID = 0x1A3A, + // The specified Transaction object could not be opened, because it was not found. + TRANSACTION_NOT_FOUND = 0x1A3B, + // The specified ResourceManager object could not be opened, because it was not found. + RESOURCEMANAGER_NOT_FOUND = 0x1A3C, + // The specified Enlistment object could not be opened, because it was not found. + ENLISTMENT_NOT_FOUND = 0x1A3D, + // The specified TransactionManager object could not be opened, because it was not found. + TRANSACTIONMANAGER_NOT_FOUND = 0x1A3E, + // The object specified could not be created or opened, because its associated TransactionManager is not online. The TransactionManager must be brought fully Online by calling RecoverTransactionManager to recover to the end of its LogFile before objects in its Transaction or ResourceManager namespaces can be opened. In addition, errors in writing records to its LogFile can cause a TransactionManager to go offline. + TRANSACTIONMANAGER_NOT_ONLINE = 0x1A3F, + // The specified TransactionManager was unable to create the objects contained in its logfile in the Ob namespace. Therefore, the TransactionManager was unable to recover. + TRANSACTIONMANAGER_RECOVERY_NAME_COLLISION = 0x1A40, + // The call to create a superior Enlistment on this Transaction object could not be completed, because the Transaction object specified for the enlistment is a subordinate branch of the Transaction. Only the root of the Transaction can be enlisted on as a superior. + TRANSACTION_NOT_ROOT = 0x1A41, + // Because the associated transaction manager or resource manager has been closed, the handle is no longer valid. + TRANSACTION_OBJECT_EXPIRED = 0x1A42, + // The specified operation could not be performed on this Superior enlistment, because the enlistment was not created with the corresponding completion response in the NotificationMask. + TRANSACTION_RESPONSE_NOT_ENLISTED = 0x1A43, + // The specified operation could not be performed, because the record that would be logged was too long. This can occur because of two conditions: either there are too many Enlistments on this Transaction, or the combined RecoveryInformation being logged on behalf of those Enlistments is too long. + TRANSACTION_RECORD_TOO_LONG = 0x1A44, + // Implicit transaction are not supported. + IMPLICIT_TRANSACTION_NOT_SUPPORTED = 0x1A45, + // The kernel transaction manager had to abort or forget the transaction because it blocked forward progress. + TRANSACTION_INTEGRITY_VIOLATED = 0x1A46, + // The TransactionManager identity that was supplied did not match the one recorded in the TransactionManager's log file. + TRANSACTIONMANAGER_IDENTITY_MISMATCH = 0x1A47, + // This snapshot operation cannot continue because a transactional resource manager cannot be frozen in its current state. Please try again. + RM_CANNOT_BE_FROZEN_FOR_SNAPSHOT = 0x1A48, + // The transaction cannot be enlisted on with the specified EnlistmentMask, because the transaction has already completed the PrePrepare phase. In order to ensure correctness, the ResourceManager must switch to a write- through mode and cease caching data within this transaction. Enlisting for only subsequent transaction phases may still succeed. + TRANSACTION_MUST_WRITETHROUGH = 0x1A49, + // The transaction does not have a superior enlistment. + TRANSACTION_NO_SUPERIOR = 0x1A4A, + // The attempt to commit the Transaction completed, but it is possible that some portion of the transaction tree did not commit successfully due to heuristics. Therefore it is possible that some data modified in the transaction may not have committed, resulting in transactional inconsistency. If possible, check the consistency of the associated data. + HEURISTIC_DAMAGE_POSSIBLE = 0x1A4B, + // The function attempted to use a name that is reserved for use by another transaction. + TRANSACTIONAL_CONFLICT = 0x1A90, + // Transaction support within the specified resource manager is not started or was shut down due to an error. + RM_NOT_ACTIVE = 0x1A91, + // The metadata of the RM has been corrupted. The RM will not function. + RM_METADATA_CORRUPT = 0x1A92, + // The specified directory does not contain a resource manager. + DIRECTORY_NOT_RM = 0x1A93, + // The remote server or share does not support transacted file operations. + TRANSACTIONS_UNSUPPORTED_REMOTE = 0x1A95, + // The requested log size is invalid. + LOG_RESIZE_INVALID_SIZE = 0x1A96, + // The object (file, stream, link) corresponding to the handle has been deleted by a Transaction Savepoint Rollback. + OBJECT_NO_LONGER_EXISTS = 0x1A97, + // The specified file miniversion was not found for this transacted file open. + STREAM_MINIVERSION_NOT_FOUND = 0x1A98, + // The specified file miniversion was found but has been invalidated. Most likely cause is a transaction savepoint rollback. + STREAM_MINIVERSION_NOT_VALID = 0x1A99, + // A miniversion may only be opened in the context of the transaction that created it. + MINIVERSION_INACCESSIBLE_FROM_SPECIFIED_TRANSACTION = 0x1A9A, + // It is not possible to open a miniversion with modify access. + CANT_OPEN_MINIVERSION_WITH_MODIFY_INTENT = 0x1A9B, + // It is not possible to create any more miniversions for this stream. + CANT_CREATE_MORE_STREAM_MINIVERSIONS = 0x1A9C, + // The remote server sent mismatching version number or Fid for a file opened with transactions. + REMOTE_FILE_VERSION_MISMATCH = 0x1A9E, + // The handle has been invalidated by a transaction. The most likely cause is the presence of memory mapping on a file or an open handle when the transaction ended or rolled back to savepoint. + HANDLE_NO_LONGER_VALID = 0x1A9F, + // There is no transaction metadata on the file. + NO_TXF_METADATA = 0x1AA0, + // The log data is corrupt. + LOG_CORRUPTION_DETECTED = 0x1AA1, + // The file can't be recovered because there is a handle still open on it. + CANT_RECOVER_WITH_HANDLE_OPEN = 0x1AA2, + // The transaction outcome is unavailable because the resource manager responsible for it has disconnected. + RM_DISCONNECTED = 0x1AA3, + // The request was rejected because the enlistment in question is not a superior enlistment. + ENLISTMENT_NOT_SUPERIOR = 0x1AA4, + // The transactional resource manager is already consistent. Recovery is not needed. + RECOVERY_NOT_NEEDED = 0x1AA5, + // The transactional resource manager has already been started. + RM_ALREADY_STARTED = 0x1AA6, + // The file cannot be opened transactionally, because its identity depends on the outcome of an unresolved transaction. + FILE_IDENTITY_NOT_PERSISTENT = 0x1AA7, + // The operation cannot be performed because another transaction is depending on the fact that this property will not change. + CANT_BREAK_TRANSACTIONAL_DEPENDENCY = 0x1AA8, + // The operation would involve a single file with two transactional resource managers and is therefore not allowed. + CANT_CROSS_RM_BOUNDARY = 0x1AA9, + // The $Txf directory must be empty for this operation to succeed. + TXF_DIR_NOT_EMPTY = 0x1AAA, + // The operation would leave a transactional resource manager in an inconsistent state and is therefore not allowed. + INDOUBT_TRANSACTIONS_EXIST = 0x1AAB, + // The operation could not be completed because the transaction manager does not have a log. + TM_VOLATILE = 0x1AAC, + // A rollback could not be scheduled because a previously scheduled rollback has already executed or been queued for execution. + ROLLBACK_TIMER_EXPIRED = 0x1AAD, + // The transactional metadata attribute on the file or directory is corrupt and unreadable. + TXF_ATTRIBUTE_CORRUPT = 0x1AAE, + // The encryption operation could not be completed because a transaction is active. + EFS_NOT_ALLOWED_IN_TRANSACTION = 0x1AAF, + // This object is not allowed to be opened in a transaction. + TRANSACTIONAL_OPEN_NOT_ALLOWED = 0x1AB0, + // An attempt to create space in the transactional resource manager's log failed. The failure status has been recorded in the event log. + LOG_GROWTH_FAILED = 0x1AB1, + // Memory mapping (creating a mapped section) a remote file under a transaction is not supported. + TRANSACTED_MAPPING_UNSUPPORTED_REMOTE = 0x1AB2, + // Transaction metadata is already present on this file and cannot be superseded. + TXF_METADATA_ALREADY_PRESENT = 0x1AB3, + // A transaction scope could not be entered because the scope handler has not been initialized. + TRANSACTION_SCOPE_CALLBACKS_NOT_SET = 0x1AB4, + // Promotion was required in order to allow the resource manager to enlist, but the transaction was set to disallow it. + TRANSACTION_REQUIRED_PROMOTION = 0x1AB5, + // This file is open for modification in an unresolved transaction and may be opened for execute only by a transacted reader. + CANNOT_EXECUTE_FILE_IN_TRANSACTION = 0x1AB6, + // The request to thaw frozen transactions was ignored because transactions had not previously been frozen. + TRANSACTIONS_NOT_FROZEN = 0x1AB7, + // Transactions cannot be frozen because a freeze is already in progress. + TRANSACTION_FREEZE_IN_PROGRESS = 0x1AB8, + // The target volume is not a snapshot volume. This operation is only valid on a volume mounted as a snapshot. + NOT_SNAPSHOT_VOLUME = 0x1AB9, + // The savepoint operation failed because files are open on the transaction. This is not permitted. + NO_SAVEPOINT_WITH_OPEN_FILES = 0x1ABA, + // Windows has discovered corruption in a file, and that file has since been repaired. Data loss may have occurred. + DATA_LOST_REPAIR = 0x1ABB, + // The sparse operation could not be completed because a transaction is active on the file. + SPARSE_NOT_ALLOWED_IN_TRANSACTION = 0x1ABC, + // The call to create a TransactionManager object failed because the Tm Identity stored in the logfile does not match the Tm Identity that was passed in as an argument. + TM_IDENTITY_MISMATCH = 0x1ABD, + // I/O was attempted on a section object that has been floated as a result of a transaction ending. There is no valid data. + FLOATED_SECTION = 0x1ABE, + // The transactional resource manager cannot currently accept transacted work due to a transient condition such as low resources. + CANNOT_ACCEPT_TRANSACTED_WORK = 0x1ABF, + // The transactional resource manager had too many tranactions outstanding that could not be aborted. The transactional resource manger has been shut down. + CANNOT_ABORT_TRANSACTIONS = 0x1AC0, + // The operation could not be completed due to bad clusters on disk. + BAD_CLUSTERS = 0x1AC1, + // The compression operation could not be completed because a transaction is active on the file. + COMPRESSION_NOT_ALLOWED_IN_TRANSACTION = 0x1AC2, + // The operation could not be completed because the volume is dirty. Please run chkdsk and try again. + VOLUME_DIRTY = 0x1AC3, + // The link tracking operation could not be completed because a transaction is active. + NO_LINK_TRACKING_IN_TRANSACTION = 0x1AC4, + // This operation cannot be performed in a transaction. + OPERATION_NOT_SUPPORTED_IN_TRANSACTION = 0x1AC5, + // The handle is no longer properly associated with its transaction. It may have been opened in a transactional resource manager that was subsequently forced to restart. Please close the handle and open a new one. + EXPIRED_HANDLE = 0x1AC6, + // The specified operation could not be performed because the resource manager is not enlisted in the transaction. + TRANSACTION_NOT_ENLISTED = 0x1AC7, + // The specified session name is invalid. + CTX_WINSTATION_NAME_INVALID = 0x1B59, + // The specified protocol driver is invalid. + CTX_INVALID_PD = 0x1B5A, + // The specified protocol driver was not found in the system path. + CTX_PD_NOT_FOUND = 0x1B5B, + // The specified terminal connection driver was not found in the system path. + CTX_WD_NOT_FOUND = 0x1B5C, + // A registry key for event logging could not be created for this session. + CTX_CANNOT_MAKE_EVENTLOG_ENTRY = 0x1B5D, + // A service with the same name already exists on the system. + CTX_SERVICE_NAME_COLLISION = 0x1B5E, + // A close operation is pending on the session. + CTX_CLOSE_PENDING = 0x1B5F, + // There are no free output buffers available. + CTX_NO_OUTBUF = 0x1B60, + // The MODEM.INF file was not found. + CTX_MODEM_INF_NOT_FOUND = 0x1B61, + // The modem name was not found in MODEM.INF. + CTX_INVALID_MODEMNAME = 0x1B62, + // The modem did not accept the command sent to it. Verify that the configured modem name matches the attached modem. + CTX_MODEM_RESPONSE_ERROR = 0x1B63, + // The modem did not respond to the command sent to it. Verify that the modem is properly cabled and powered on. + CTX_MODEM_RESPONSE_TIMEOUT = 0x1B64, + // Carrier detect has failed or carrier has been dropped due to disconnect. + CTX_MODEM_RESPONSE_NO_CARRIER = 0x1B65, + // Dial tone not detected within the required time. Verify that the phone cable is properly attached and functional. + CTX_MODEM_RESPONSE_NO_DIALTONE = 0x1B66, + // Busy signal detected at remote site on callback. + CTX_MODEM_RESPONSE_BUSY = 0x1B67, + // Voice detected at remote site on callback. + CTX_MODEM_RESPONSE_VOICE = 0x1B68, + // Transport driver error. + CTX_TD_ERROR = 0x1B69, + // The specified session cannot be found. + CTX_WINSTATION_NOT_FOUND = 0x1B6E, + // The specified session name is already in use. + CTX_WINSTATION_ALREADY_EXISTS = 0x1B6F, + // The task you are trying to do can't be completed because Remote Desktop Services is currently busy. Please try again in a few minutes. Other users should still be able to log on. + CTX_WINSTATION_BUSY = 0x1B70, + // An attempt has been made to connect to a session whose video mode is not supported by the current client. + CTX_BAD_VIDEO_MODE = 0x1B71, + // The application attempted to enable DOS graphics mode. DOS graphics mode is not supported. + CTX_GRAPHICS_INVALID = 0x1B7B, + // Your interactive logon privilege has been disabled. Please contact your administrator. + CTX_LOGON_DISABLED = 0x1B7D, + // The requested operation can be performed only on the system console. This is most often the result of a driver or system DLL requiring direct console access. + CTX_NOT_CONSOLE = 0x1B7E, + // The client failed to respond to the server connect message. + CTX_CLIENT_QUERY_TIMEOUT = 0x1B80, + // Disconnecting the console session is not supported. + CTX_CONSOLE_DISCONNECT = 0x1B81, + // Reconnecting a disconnected session to the console is not supported. + CTX_CONSOLE_CONNECT = 0x1B82, + // The request to control another session remotely was denied. + CTX_SHADOW_DENIED = 0x1B84, + // The requested session access is denied. + CTX_WINSTATION_ACCESS_DENIED = 0x1B85, + // The specified terminal connection driver is invalid. + CTX_INVALID_WD = 0x1B89, + // The requested session cannot be controlled remotely. This may be because the session is disconnected or does not currently have a user logged on. + CTX_SHADOW_INVALID = 0x1B8A, + // The requested session is not configured to allow remote control. + CTX_SHADOW_DISABLED = 0x1B8B, + // Your request to connect to this Terminal Server has been rejected. Your Terminal Server client license number is currently being used by another user. Please call your system administrator to obtain a unique license number. + CTX_CLIENT_LICENSE_IN_USE = 0x1B8C, + // Your request to connect to this Terminal Server has been rejected. Your Terminal Server client license number has not been entered for this copy of the Terminal Server client. Please contact your system administrator. + CTX_CLIENT_LICENSE_NOT_SET = 0x1B8D, + // The number of connections to this computer is limited and all connections are in use right now. Try connecting later or contact your system administrator. + CTX_LICENSE_NOT_AVAILABLE = 0x1B8E, + // The client you are using is not licensed to use this system. Your logon request is denied. + CTX_LICENSE_CLIENT_INVALID = 0x1B8F, + // The system license has expired. Your logon request is denied. + CTX_LICENSE_EXPIRED = 0x1B90, + // Remote control could not be terminated because the specified session is not currently being remotely controlled. + CTX_SHADOW_NOT_RUNNING = 0x1B91, + // The remote control of the console was terminated because the display mode was changed. Changing the display mode in a remote control session is not supported. + CTX_SHADOW_ENDED_BY_MODE_CHANGE = 0x1B92, + // Activation has already been reset the maximum number of times for this installation. Your activation timer will not be cleared. + ACTIVATION_COUNT_EXCEEDED = 0x1B93, + // Remote logins are currently disabled. + CTX_WINSTATIONS_DISABLED = 0x1B94, + // You do not have the proper encryption level to access this Session. + CTX_ENCRYPTION_LEVEL_REQUIRED = 0x1B95, + // The user %s\\%s is currently logged on to this computer. Only the current user or an administrator can log on to this computer. + CTX_SESSION_IN_USE = 0x1B96, + // The user %s\\%s is already logged on to the console of this computer. You do not have permission to log in at this time. To resolve this issue, contact %s\\%s and have them log off. + CTX_NO_FORCE_LOGOFF = 0x1B97, + // Unable to log you on because of an account restriction. + CTX_ACCOUNT_RESTRICTION = 0x1B98, + // The RDP protocol component %2 detected an error in the protocol stream and has disconnected the client. + RDP_PROTOCOL_ERROR = 0x1B99, + // The Client Drive Mapping Service Has Connected on Terminal Connection. + CTX_CDM_CONNECT = 0x1B9A, + // The Client Drive Mapping Service Has Disconnected on Terminal Connection. + CTX_CDM_DISCONNECT = 0x1B9B, + // The Terminal Server security layer detected an error in the protocol stream and has disconnected the client. + CTX_SECURITY_LAYER_ERROR = 0x1B9C, + // The target session is incompatible with the current session. + TS_INCOMPATIBLE_SESSIONS = 0x1B9D, + // Windows can't connect to your session because a problem occurred in the Windows video subsystem. Try connecting again later, or contact the server administrator for assistance. + TS_VIDEO_SUBSYSTEM_ERROR = 0x1B9E, + // The file replication service API was called incorrectly. + FRS_ERR_INVALID_API_SEQUENCE = 0x1F41, + // The file replication service cannot be started. + FRS_ERR_STARTING_SERVICE = 0x1F42, + // The file replication service cannot be stopped. + FRS_ERR_STOPPING_SERVICE = 0x1F43, + // The file replication service API terminated the request. The event log may have more information. + FRS_ERR_INTERNAL_API = 0x1F44, + // The file replication service terminated the request. The event log may have more information. + FRS_ERR_INTERNAL = 0x1F45, + // The file replication service cannot be contacted. The event log may have more information. + FRS_ERR_SERVICE_COMM = 0x1F46, + // The file replication service cannot satisfy the request because the user has insufficient privileges. The event log may have more information. + FRS_ERR_INSUFFICIENT_PRIV = 0x1F47, + // The file replication service cannot satisfy the request because authenticated RPC is not available. The event log may have more information. + FRS_ERR_AUTHENTICATION = 0x1F48, + // The file replication service cannot satisfy the request because the user has insufficient privileges on the domain controller. The event log may have more information. + FRS_ERR_PARENT_INSUFFICIENT_PRIV = 0x1F49, + // The file replication service cannot satisfy the request because authenticated RPC is not available on the domain controller. The event log may have more information. + FRS_ERR_PARENT_AUTHENTICATION = 0x1F4A, + // The file replication service cannot communicate with the file replication service on the domain controller. The event log may have more information. + FRS_ERR_CHILD_TO_PARENT_COMM = 0x1F4B, + // The file replication service on the domain controller cannot communicate with the file replication service on this computer. The event log may have more information. + FRS_ERR_PARENT_TO_CHILD_COMM = 0x1F4C, + // The file replication service cannot populate the system volume because of an internal error. The event log may have more information. + FRS_ERR_SYSVOL_POPULATE = 0x1F4D, + // The file replication service cannot populate the system volume because of an internal timeout. The event log may have more information. + FRS_ERR_SYSVOL_POPULATE_TIMEOUT = 0x1F4E, + // The file replication service cannot process the request. The system volume is busy with a previous request. + FRS_ERR_SYSVOL_IS_BUSY = 0x1F4F, + // The file replication service cannot stop replicating the system volume because of an internal error. The event log may have more information. + FRS_ERR_SYSVOL_DEMOTE = 0x1F50, + // The file replication service detected an invalid parameter. + FRS_ERR_INVALID_SERVICE_PARAMETER = 0x1F51, + + + // An error occurred while installing the directory service. For more information, see the event log. + DS_NOT_INSTALLED = 0x2008, + // The directory service evaluated group memberships locally. + DS_MEMBERSHIP_EVALUATED_LOCALLY = 0x2009, + // The specified directory service attribute or value does not exist. + DS_NO_ATTRIBUTE_OR_VALUE = 0x200A, + // The attribute syntax specified to the directory service is invalid. + DS_INVALID_ATTRIBUTE_SYNTAX = 0x200B, + // The attribute type specified to the directory service is not defined. + DS_ATTRIBUTE_TYPE_UNDEFINED = 0x200C, + // The specified directory service attribute or value already exists. + DS_ATTRIBUTE_OR_VALUE_EXISTS = 0x200D, + // The directory service is busy. + DS_BUSY = 0x200E, + // The directory service is unavailable. + DS_UNAVAILABLE = 0x200F, + // The directory service was unable to allocate a relative identifier. + DS_NO_RIDS_ALLOCATED = 0x2010, + // The directory service has exhausted the pool of relative identifiers. + DS_NO_MORE_RIDS = 0x2011, + // The requested operation could not be performed because the directory service is not the master for that type of operation. + DS_INCORRECT_ROLE_OWNER = 0x2012, + // The directory service was unable to initialize the subsystem that allocates relative identifiers. + DS_RIDMGR_INIT_ERROR = 0x2013, + // The requested operation did not satisfy one or more constraints associated with the class of the object. + DS_OBJ_CLASS_VIOLATION = 0x2014, + // The directory service can perform the requested operation only on a leaf object. + DS_CANT_ON_NON_LEAF = 0x2015, + // The directory service cannot perform the requested operation on the RDN attribute of an object. + DS_CANT_ON_RDN = 0x2016, + // The directory service detected an attempt to modify the object class of an object. + DS_CANT_MOD_OBJ_CLASS = 0x2017, + // The requested cross-domain move operation could not be performed. + DS_CROSS_DOM_MOVE_ERROR = 0x2018, + // Unable to contact the global catalog server. + DS_GC_NOT_AVAILABLE = 0x2019, + // The policy object is shared and can only be modified at the root. + SHARED_POLICY = 0x201A, + // The policy object does not exist. + POLICY_OBJECT_NOT_FOUND = 0x201B, + // The requested policy information is only in the directory service. + POLICY_ONLY_IN_DS = 0x201C, + // A domain controller promotion is currently active. + PROMOTION_ACTIVE = 0x201D, + // A domain controller promotion is not currently active. + NO_PROMOTION_ACTIVE = 0x201E, + // An operations error occurred. + DS_OPERATIONS_ERROR = 0x2020, + // A protocol error occurred. + DS_PROTOCOL_ERROR = 0x2021, + // The time limit for this request was exceeded. + DS_TIMELIMIT_EXCEEDED = 0x2022, + // The size limit for this request was exceeded. + DS_SIZELIMIT_EXCEEDED = 0x2023, + // The administrative limit for this request was exceeded. + DS_ADMIN_LIMIT_EXCEEDED = 0x2024, + // The compare response was false. + DS_COMPARE_FALSE = 0x2025, + // The compare response was true. + DS_COMPARE_TRUE = 0x2026, + // The requested authentication method is not supported by the server. + DS_AUTH_METHOD_NOT_SUPPORTED = 0x2027, + // A more secure authentication method is required for this server. + DS_STRONG_AUTH_REQUIRED = 0x2028, + // Inappropriate authentication. + DS_INAPPROPRIATE_AUTH = 0x2029, + // The authentication mechanism is unknown. + DS_AUTH_UNKNOWN = 0x202A, + // A referral was returned from the server. + DS_REFERRAL = 0x202B, + // The server does not support the requested critical extension. + DS_UNAVAILABLE_CRIT_EXTENSION = 0x202C, + // This request requires a secure connection. + DS_CONFIDENTIALITY_REQUIRED = 0x202D, + // Inappropriate matching. + DS_INAPPROPRIATE_MATCHING = 0x202E, + // A constraint violation occurred. + DS_CONSTRAINT_VIOLATION = 0x202F, + // There is no such object on the server. + DS_NO_SUCH_OBJECT = 0x2030, + // There is an alias problem. + DS_ALIAS_PROBLEM = 0x2031, + // An invalid dn syntax has been specified. + DS_INVALID_DN_SYNTAX = 0x2032, + // The object is a leaf object. + DS_IS_LEAF = 0x2033, + // There is an alias dereferencing problem. + DS_ALIAS_DEREF_PROBLEM = 0x2034, + // The server is unwilling to process the request. + DS_UNWILLING_TO_PERFORM = 0x2035, + // A loop has been detected. + DS_LOOP_DETECT = 0x2036, + // There is a naming violation. + DS_NAMING_VIOLATION = 0x2037, + // The result set is too large. + DS_OBJECT_RESULTS_TOO_LARGE = 0x2038, + // The operation affects multiple DSAs. + DS_AFFECTS_MULTIPLE_DSAS = 0x2039, + // The server is not operational. + DS_SERVER_DOWN = 0x203A, + // A local error has occurred. + DS_LOCAL_ERROR = 0x203B, + // An encoding error has occurred. + DS_ENCODING_ERROR = 0x203C, + // A decoding error has occurred. + DS_DECODING_ERROR = 0x203D, + // The search filter cannot be recognized. + DS_FILTER_UNKNOWN = 0x203E, + // One or more parameters are illegal. + DS_PARAM_ERROR = 0x203F, + // The specified method is not supported. + DS_NOT_SUPPORTED = 0x2040, + // No results were returned. + DS_NO_RESULTS_RETURNED = 0x2041, + // The specified control is not supported by the server. + DS_CONTROL_NOT_FOUND = 0x2042, + // A referral loop was detected by the client. + DS_CLIENT_LOOP = 0x2043, + // The preset referral limit was exceeded. + DS_REFERRAL_LIMIT_EXCEEDED = 0x2044, + // The search requires a SORT control. + DS_SORT_CONTROL_MISSING = 0x2045, + // The search results exceed the offset range specified. + DS_OFFSET_RANGE_ERROR = 0x2046, + // The directory service detected the subsystem that allocates relative identifiers is disabled. This can occur as a protective mechanism when the system determines a significant portion of relative identifiers (RIDs) have been exhausted. Please see https://go.microsoft.com/fwlink/p/?linkid=228610 for recommended diagnostic steps and the procedure to re-enable account creation. + DS_RIDMGR_DISABLED = 0x2047, + // The root object must be the head of a naming context. The root object cannot have an instantiated parent. + DS_ROOT_MUST_BE_NC = 0x206D, + // The add replica operation cannot be performed. The naming context must be writeable in order to create the replica. + DS_ADD_REPLICA_INHIBITED = 0x206E, + // A reference to an attribute that is not defined in the schema occurred. + DS_ATT_NOT_DEF_IN_SCHEMA = 0x206F, + // The maximum size of an object has been exceeded. + DS_MAX_OBJ_SIZE_EXCEEDED = 0x2070, + // An attempt was made to add an object to the directory with a name that is already in use. + DS_OBJ_STRING_NAME_EXISTS = 0x2071, + // An attempt was made to add an object of a class that does not have an RDN defined in the schema. + DS_NO_RDN_DEFINED_IN_SCHEMA = 0x2072, + // An attempt was made to add an object using an RDN that is not the RDN defined in the schema. + DS_RDN_DOESNT_MATCH_SCHEMA = 0x2073, + // None of the requested attributes were found on the objects. + DS_NO_REQUESTED_ATTS_FOUND = 0x2074, + // The user buffer is too small. + DS_USER_BUFFER_TO_SMALL = 0x2075, + // The attribute specified in the operation is not present on the object. + DS_ATT_IS_NOT_ON_OBJ = 0x2076, + // Illegal modify operation. Some aspect of the modification is not permitted. + DS_ILLEGAL_MOD_OPERATION = 0x2077, + // The specified object is too large. + DS_OBJ_TOO_LARGE = 0x2078, + // The specified instance type is not valid. + DS_BAD_INSTANCE_TYPE = 0x2079, + // The operation must be performed at a master DSA. + DS_MASTERDSA_REQUIRED = 0x207A, + // The object class attribute must be specified. + DS_OBJECT_CLASS_REQUIRED = 0x207B, + // A required attribute is missing. + DS_MISSING_REQUIRED_ATT = 0x207C, + // An attempt was made to modify an object to include an attribute that is not legal for its class. + DS_ATT_NOT_DEF_FOR_CLASS = 0x207D, + // The specified attribute is already present on the object. + DS_ATT_ALREADY_EXISTS = 0x207E, + // The specified attribute is not present, or has no values. + DS_CANT_ADD_ATT_VALUES = 0x2080, + // Multiple values were specified for an attribute that can have only one value. + DS_SINGLE_VALUE_CONSTRAINT = 0x2081, + // A value for the attribute was not in the acceptable range of values. + DS_RANGE_CONSTRAINT = 0x2082, + // The specified value already exists. + DS_ATT_VAL_ALREADY_EXISTS = 0x2083, + // The attribute cannot be removed because it is not present on the object. + DS_CANT_REM_MISSING_ATT = 0x2084, + // The attribute value cannot be removed because it is not present on the object. + DS_CANT_REM_MISSING_ATT_VAL = 0x2085, + // The specified root object cannot be a subref. + DS_ROOT_CANT_BE_SUBREF = 0x2086, + // Chaining is not permitted. + DS_NO_CHAINING = 0x2087, + // Chained evaluation is not permitted. + DS_NO_CHAINED_EVAL = 0x2088, + // The operation could not be performed because the object's parent is either uninstantiated or deleted. + DS_NO_PARENT_OBJECT = 0x2089, + // Having a parent that is an alias is not permitted. Aliases are leaf objects. + DS_PARENT_IS_AN_ALIAS = 0x208A, + // The object and parent must be of the same type, either both masters or both replicas. + DS_CANT_MIX_MASTER_AND_REPS = 0x208B, + // The operation cannot be performed because child objects exist. This operation can only be performed on a leaf object. + DS_CHILDREN_EXIST = 0x208C, + // Directory object not found. + DS_OBJ_NOT_FOUND = 0x208D, + // The aliased object is missing. + DS_ALIASED_OBJ_MISSING = 0x208E, + // The object name has bad syntax. + DS_BAD_NAME_SYNTAX = 0x208F, + // It is not permitted for an alias to refer to another alias. + DS_ALIAS_POINTS_TO_ALIAS = 0x2090, + // The alias cannot be dereferenced. + DS_CANT_DEREF_ALIAS = 0x2091, + // The operation is out of scope. + DS_OUT_OF_SCOPE = 0x2092, + // The operation cannot continue because the object is in the process of being removed. + DS_OBJECT_BEING_REMOVED = 0x2093, + // The DSA object cannot be deleted. + DS_CANT_DELETE_DSA_OBJ = 0x2094, + // A directory service error has occurred. + DS_GENERIC_ERROR = 0x2095, + // The operation can only be performed on an internal master DSA object. + DS_DSA_MUST_BE_INT_MASTER = 0x2096, + // The object must be of class DSA. + DS_CLASS_NOT_DSA = 0x2097, + // Insufficient access rights to perform the operation. + DS_INSUFF_ACCESS_RIGHTS = 0x2098, + // The object cannot be added because the parent is not on the list of possible superiors. + DS_ILLEGAL_SUPERIOR = 0x2099, + // Access to the attribute is not permitted because the attribute is owned by the Security Accounts Manager (SAM). + DS_ATTRIBUTE_OWNED_BY_SAM = 0x209A, + // The name has too many parts. + DS_NAME_TOO_MANY_PARTS = 0x209B, + // The name is too long. + DS_NAME_TOO_LONG = 0x209C, + // The name value is too long. + DS_NAME_VALUE_TOO_LONG = 0x209D, + // The directory service encountered an error parsing a name. + DS_NAME_UNPARSEABLE = 0x209E, + // The directory service cannot get the attribute type for a name. + DS_NAME_TYPE_UNKNOWN = 0x209F, + // The name does not identify an object; the name identifies a phantom. + DS_NOT_AN_OBJECT = 0x20A0, + // The security descriptor is too short. + DS_SEC_DESC_TOO_SHORT = 0x20A1, + // The security descriptor is invalid. + DS_SEC_DESC_INVALID = 0x20A2, + // Failed to create name for deleted object. + DS_NO_DELETED_NAME = 0x20A3, + // The parent of a new subref must exist. + DS_SUBREF_MUST_HAVE_PARENT = 0x20A4, + // The object must be a naming context. + DS_NCNAME_MUST_BE_NC = 0x20A5, + // It is not permitted to add an attribute which is owned by the system. + DS_CANT_ADD_SYSTEM_ONLY = 0x20A6, + // The class of the object must be structural; you cannot instantiate an abstract class. + DS_CLASS_MUST_BE_CONCRETE = 0x20A7, + // The schema object could not be found. + DS_INVALID_DMD = 0x20A8, + // A local object with this GUID (dead or alive) already exists. + DS_OBJ_GUID_EXISTS = 0x20A9, + // The operation cannot be performed on a back link. + DS_NOT_ON_BACKLINK = 0x20AA, + // The cross reference for the specified naming context could not be found. + DS_NO_CROSSREF_FOR_NC = 0x20AB, + // The operation could not be performed because the directory service is shutting down. + DS_SHUTTING_DOWN = 0x20AC, + // The directory service request is invalid. + DS_UNKNOWN_OPERATION = 0x20AD, + // The role owner attribute could not be read. + DS_INVALID_ROLE_OWNER = 0x20AE, + // The requested FSMO operation failed. The current FSMO holder could not be contacted. + DS_COULDNT_CONTACT_FSMO = 0x20AF, + // Modification of a DN across a naming context is not permitted. + DS_CROSS_NC_DN_RENAME = 0x20B0, + // The attribute cannot be modified because it is owned by the system. + DS_CANT_MOD_SYSTEM_ONLY = 0x20B1, + // Only the replicator can perform this function. + DS_REPLICATOR_ONLY = 0x20B2, + // The specified class is not defined. + DS_OBJ_CLASS_NOT_DEFINED = 0x20B3, + // The specified class is not a subclass. + DS_OBJ_CLASS_NOT_SUBCLASS = 0x20B4, + // The name reference is invalid. + DS_NAME_REFERENCE_INVALID = 0x20B5, + // A cross reference already exists. + DS_CROSS_REF_EXISTS = 0x20B6, + // It is not permitted to delete a master cross reference. + DS_CANT_DEL_MASTER_CROSSREF = 0x20B7, + // Subtree notifications are only supported on NC heads. + DS_SUBTREE_NOTIFY_NOT_NC_HEAD = 0x20B8, + // Notification filter is too complex. + DS_NOTIFY_FILTER_TOO_COMPLEX = 0x20B9, + // Schema update failed: duplicate RDN. + DS_DUP_RDN = 0x20BA, + // Schema update failed: duplicate OID. + DS_DUP_OID = 0x20BB, + // Schema update failed: duplicate MAPI identifier. + DS_DUP_MAPI_ID = 0x20BC, + // Schema update failed: duplicate schema-id GUID. + DS_DUP_SCHEMA_ID_GUID = 0x20BD, + // Schema update failed: duplicate LDAP display name. + DS_DUP_LDAP_DISPLAY_NAME = 0x20BE, + // Schema update failed: range-lower less than range upper. + DS_SEMANTIC_ATT_TEST = 0x20BF, + // Schema update failed: syntax mismatch. + DS_SYNTAX_MISMATCH = 0x20C0, + // Schema deletion failed: attribute is used in must-contain. + DS_EXISTS_IN_MUST_HAVE = 0x20C1, + // Schema deletion failed: attribute is used in may-contain. + DS_EXISTS_IN_MAY_HAVE = 0x20C2, + // Schema update failed: attribute in may-contain does not exist. + DS_NONEXISTENT_MAY_HAVE = 0x20C3, + // Schema update failed: attribute in must-contain does not exist. + DS_NONEXISTENT_MUST_HAVE = 0x20C4, + // Schema update failed: class in aux-class list does not exist or is not an auxiliary class. + DS_AUX_CLS_TEST_FAIL = 0x20C5, + // Schema update failed: class in poss-superiors does not exist. + DS_NONEXISTENT_POSS_SUP = 0x20C6, + // Schema update failed: class in subclassof list does not exist or does not satisfy hierarchy rules. + DS_SUB_CLS_TEST_FAIL = 0x20C7, + // Schema update failed: Rdn-Att-Id has wrong syntax. + DS_BAD_RDN_ATT_ID_SYNTAX = 0x20C8, + // Schema deletion failed: class is used as auxiliary class. + DS_EXISTS_IN_AUX_CLS = 0x20C9, + // Schema deletion failed: class is used as sub class. + DS_EXISTS_IN_SUB_CLS = 0x20CA, + // Schema deletion failed: class is used as poss superior. + DS_EXISTS_IN_POSS_SUP = 0x20CB, + // Schema update failed in recalculating validation cache. + DS_RECALCSCHEMA_FAILED = 0x20CC, + // The tree deletion is not finished. The request must be made again to continue deleting the tree. + DS_TREE_DELETE_NOT_FINISHED = 0x20CD, + // The requested delete operation could not be performed. + DS_CANT_DELETE = 0x20CE, + // Cannot read the governs class identifier for the schema record. + DS_ATT_SCHEMA_REQ_ID = 0x20CF, + // The attribute schema has bad syntax. + DS_BAD_ATT_SCHEMA_SYNTAX = 0x20D0, + // The attribute could not be cached. + DS_CANT_CACHE_ATT = 0x20D1, + // The class could not be cached. + DS_CANT_CACHE_CLASS = 0x20D2, + // The attribute could not be removed from the cache. + DS_CANT_REMOVE_ATT_CACHE = 0x20D3, + // The class could not be removed from the cache. + DS_CANT_REMOVE_CLASS_CACHE = 0x20D4, + // The distinguished name attribute could not be read. + DS_CANT_RETRIEVE_DN = 0x20D5, + // No superior reference has been configured for the directory service. The directory service is therefore unable to issue referrals to objects outside this forest. + DS_MISSING_SUPREF = 0x20D6, + // The instance type attribute could not be retrieved. + DS_CANT_RETRIEVE_INSTANCE = 0x20D7, + // An internal error has occurred. + DS_CODE_INCONSISTENCY = 0x20D8, + // A database error has occurred. + DS_DATABASE_ERROR = 0x20D9, + // The attribute GOVERNSID is missing. + DS_GOVERNSID_MISSING = 0x20DA, + // An expected attribute is missing. + DS_MISSING_EXPECTED_ATT = 0x20DB, + // The specified naming context is missing a cross reference. + DS_NCNAME_MISSING_CR_REF = 0x20DC, + // A security checking error has occurred. + DS_SECURITY_CHECKING_ERROR = 0x20DD, + // The schema is not loaded. + DS_SCHEMA_NOT_LOADED = 0x20DE, + // Schema allocation failed. Please check if the machine is running low on memory. + DS_SCHEMA_ALLOC_FAILED = 0x20DF, + // Failed to obtain the required syntax for the attribute schema. + DS_ATT_SCHEMA_REQ_SYNTAX = 0x20E0, + // The global catalog verification failed. The global catalog is not available or does not support the operation. Some part of the directory is currently not available. + DS_GCVERIFY_ERROR = 0x20E1, + // The replication operation failed because of a schema mismatch between the servers involved. + DS_DRA_SCHEMA_MISMATCH = 0x20E2, + // The DSA object could not be found. + DS_CANT_FIND_DSA_OBJ = 0x20E3, + // The naming context could not be found. + DS_CANT_FIND_EXPECTED_NC = 0x20E4, + // The naming context could not be found in the cache. + DS_CANT_FIND_NC_IN_CACHE = 0x20E5, + // The child object could not be retrieved. + DS_CANT_RETRIEVE_CHILD = 0x20E6, + // The modification was not permitted for security reasons. + DS_SECURITY_ILLEGAL_MODIFY = 0x20E7, + // The operation cannot replace the hidden record. + DS_CANT_REPLACE_HIDDEN_REC = 0x20E8, + // The hierarchy file is invalid. + DS_BAD_HIERARCHY_FILE = 0x20E9, + // The attempt to build the hierarchy table failed. + DS_BUILD_HIERARCHY_TABLE_FAILED = 0x20EA, + // The directory configuration parameter is missing from the registry. + DS_CONFIG_PARAM_MISSING = 0x20EB, + // The attempt to count the address book indices failed. + DS_COUNTING_AB_INDICES_FAILED = 0x20EC, + // The allocation of the hierarchy table failed. + DS_HIERARCHY_TABLE_MALLOC_FAILED = 0x20ED, + // The directory service encountered an internal failure. + DS_INTERNAL_FAILURE = 0x20EE, + // The directory service encountered an unknown failure. + DS_UNKNOWN_ERROR = 0x20EF, + // A root object requires a class of 'top'. + DS_ROOT_REQUIRES_CLASS_TOP = 0x20F0, + // This directory server is shutting down, and cannot take ownership of new floating single-master operation roles. + DS_REFUSING_FSMO_ROLES = 0x20F1, + // The directory service is missing mandatory configuration information, and is unable to determine the ownership of floating single-master operation roles. + DS_MISSING_FSMO_SETTINGS = 0x20F2, + // The directory service was unable to transfer ownership of one or more floating single-master operation roles to other servers. + DS_UNABLE_TO_SURRENDER_ROLES = 0x20F3, + // The replication operation failed. + DS_DRA_GENERIC = 0x20F4, + // An invalid parameter was specified for this replication operation. + DS_DRA_INVALID_PARAMETER = 0x20F5, + // The directory service is too busy to complete the replication operation at this time. + DS_DRA_BUSY = 0x20F6, + // The distinguished name specified for this replication operation is invalid. + DS_DRA_BAD_DN = 0x20F7, + // The naming context specified for this replication operation is invalid. + DS_DRA_BAD_NC = 0x20F8, + // The distinguished name specified for this replication operation already exists. + DS_DRA_DN_EXISTS = 0x20F9, + // The replication system encountered an internal error. + DS_DRA_INTERNAL_ERROR = 0x20FA, + // The replication operation encountered a database inconsistency. + DS_DRA_INCONSISTENT_DIT = 0x20FB, + // The server specified for this replication operation could not be contacted. + DS_DRA_CONNECTION_FAILED = 0x20FC, + // The replication operation encountered an object with an invalid instance type. + DS_DRA_BAD_INSTANCE_TYPE = 0x20FD, + // The replication operation failed to allocate memory. + DS_DRA_OUT_OF_MEM = 0x20FE, + // The replication operation encountered an error with the mail system. + DS_DRA_MAIL_PROBLEM = 0x20FF, + // The replication reference information for the target server already exists. + DS_DRA_REF_ALREADY_EXISTS = 0x2100, + // The replication reference information for the target server does not exist. + DS_DRA_REF_NOT_FOUND = 0x2101, + // The naming context cannot be removed because it is replicated to another server. + DS_DRA_OBJ_IS_REP_SOURCE = 0x2102, + // The replication operation encountered a database error. + DS_DRA_DB_ERROR = 0x2103, + // The naming context is in the process of being removed or is not replicated from the specified server. + DS_DRA_NO_REPLICA = 0x2104, + // Replication access was denied. + DS_DRA_ACCESS_DENIED = 0x2105, + // The requested operation is not supported by this version of the directory service. + DS_DRA_NOT_SUPPORTED = 0x2106, + // The replication remote procedure call was cancelled. + DS_DRA_RPC_CANCELLED = 0x2107, + // The source server is currently rejecting replication requests. + DS_DRA_SOURCE_DISABLED = 0x2108, + // The destination server is currently rejecting replication requests. + DS_DRA_SINK_DISABLED = 0x2109, + // The replication operation failed due to a collision of object names. + DS_DRA_NAME_COLLISION = 0x210A, + // The replication source has been reinstalled. + DS_DRA_SOURCE_REINSTALLED = 0x210B, + // The replication operation failed because a required parent object is missing. + DS_DRA_MISSING_PARENT = 0x210C, + // The replication operation was preempted. + DS_DRA_PREEMPTED = 0x210D, + // The replication synchronization attempt was abandoned because of a lack of updates. + DS_DRA_ABANDON_SYNC = 0x210E, + // The replication operation was terminated because the system is shutting down. + DS_DRA_SHUTDOWN = 0x210F, + // Synchronization attempt failed because the destination DC is currently waiting to synchronize new partial attributes from source. This condition is normal if a recent schema change modified the partial attribute set. The destination partial attribute set is not a subset of source partial attribute set. + DS_DRA_INCOMPATIBLE_PARTIAL_SET = 0x2110, + // The replication synchronization attempt failed because a master replica attempted to sync from a partial replica. + DS_DRA_SOURCE_IS_PARTIAL_REPLICA = 0x2111, + // The server specified for this replication operation was contacted, but that server was unable to contact an additional server needed to complete the operation. + DS_DRA_EXTN_CONNECTION_FAILED = 0x2112, + // The version of the directory service schema of the source forest is not compatible with the version of directory service on this computer. + DS_INSTALL_SCHEMA_MISMATCH = 0x2113, + // Schema update failed: An attribute with the same link identifier already exists. + DS_DUP_LINK_ID = 0x2114, + // Name translation: Generic processing error. + DS_NAME_ERROR_RESOLVING = 0x2115, + // Name translation: Could not find the name or insufficient right to see name. + DS_NAME_ERROR_NOT_FOUND = 0x2116, + // Name translation: Input name mapped to more than one output name. + DS_NAME_ERROR_NOT_UNIQUE = 0x2117, + // Name translation: Input name found, but not the associated output format. + DS_NAME_ERROR_NO_MAPPING = 0x2118, + // Name translation: Unable to resolve completely, only the domain was found. + DS_NAME_ERROR_DOMAIN_ONLY = 0x2119, + // Name translation: Unable to perform purely syntactical mapping at the client without going out to the wire. + DS_NAME_ERROR_NO_SYNTACTICAL_MAPPING = 0x211A, + // Modification of a constructed attribute is not allowed. + DS_CONSTRUCTED_ATT_MOD = 0x211B, + // The OM-Object-Class specified is incorrect for an attribute with the specified syntax. + DS_WRONG_OM_OBJ_CLASS = 0x211C, + // The replication request has been posted; waiting for reply. + DS_DRA_REPL_PENDING = 0x211D, + // The requested operation requires a directory service, and none was available. + DS_DS_REQUIRED = 0x211E, + // The LDAP display name of the class or attribute contains non-ASCII characters. + DS_INVALID_LDAP_DISPLAY_NAME = 0x211F, + // The requested search operation is only supported for base searches. + DS_NON_BASE_SEARCH = 0x2120, + // The search failed to retrieve attributes from the database. + DS_CANT_RETRIEVE_ATTS = 0x2121, + // The schema update operation tried to add a backward link attribute that has no corresponding forward link. + DS_BACKLINK_WITHOUT_LINK = 0x2122, + // Source and destination of a cross-domain move do not agree on the object's epoch number. Either source or destination does not have the latest version of the object. + DS_EPOCH_MISMATCH = 0x2123, + // Source and destination of a cross-domain move do not agree on the object's current name. Either source or destination does not have the latest version of the object. + DS_SRC_NAME_MISMATCH = 0x2124, + // Source and destination for the cross-domain move operation are identical. Caller should use local move operation instead of cross-domain move operation. + DS_SRC_AND_DST_NC_IDENTICAL = 0x2125, + // Source and destination for a cross-domain move are not in agreement on the naming contexts in the forest. Either source or destination does not have the latest version of the Partitions container. + DS_DST_NC_MISMATCH = 0x2126, + // Destination of a cross-domain move is not authoritative for the destination naming context. + DS_NOT_AUTHORITIVE_FOR_DST_NC = 0x2127, + // Source and destination of a cross-domain move do not agree on the identity of the source object. Either source or destination does not have the latest version of the source object. + DS_SRC_GUID_MISMATCH = 0x2128, + // Object being moved across-domains is already known to be deleted by the destination server. The source server does not have the latest version of the source object. + DS_CANT_MOVE_DELETED_OBJECT = 0x2129, + // Another operation which requires exclusive access to the PDC FSMO is already in progress. + DS_PDC_OPERATION_IN_PROGRESS = 0x212A, + // A cross-domain move operation failed such that two versions of the moved object exist - one each in the source and destination domains. The destination object needs to be removed to restore the system to a consistent state. + DS_CROSS_DOMAIN_CLEANUP_REQD = 0x212B, + // This object may not be moved across domain boundaries either because cross-domain moves for this class are disallowed, or the object has some special characteristics, e.g.: trust account or restricted RID, which prevent its move. + DS_ILLEGAL_XDOM_MOVE_OPERATION = 0x212C, + // Can't move objects with memberships across domain boundaries as once moved, this would violate the membership conditions of the account group. Remove the object from any account group memberships and retry. + DS_CANT_WITH_ACCT_GROUP_MEMBERSHPS = 0x212D, + // A naming context head must be the immediate child of another naming context head, not of an interior node. + DS_NC_MUST_HAVE_NC_PARENT = 0x212E, + // The directory cannot validate the proposed naming context name because it does not hold a replica of the naming context above the proposed naming context. Please ensure that the domain naming master role is held by a server that is configured as a global catalog server, and that the server is up to date with its replication partners. (Applies only to Windows 2000 Domain Naming masters.) + DS_CR_IMPOSSIBLE_TO_VALIDATE = 0x212F, + // Destination domain must be in native mode. + DS_DST_DOMAIN_NOT_NATIVE = 0x2130, + // The operation cannot be performed because the server does not have an infrastructure container in the domain of interest. + DS_MISSING_INFRASTRUCTURE_CONTAINER = 0x2131, + // Cross-domain move of non-empty account groups is not allowed. + DS_CANT_MOVE_ACCOUNT_GROUP = 0x2132, + // Cross-domain move of non-empty resource groups is not allowed. + DS_CANT_MOVE_RESOURCE_GROUP = 0x2133, + // The search flags for the attribute are invalid. The ANR bit is valid only on attributes of Unicode or Teletex strings. + DS_INVALID_SEARCH_FLAG = 0x2134, + // Tree deletions starting at an object which has an NC head as a descendant are not allowed. + DS_NO_TREE_DELETE_ABOVE_NC = 0x2135, + // The directory service failed to lock a tree in preparation for a tree deletion because the tree was in use. + DS_COULDNT_LOCK_TREE_FOR_DELETE = 0x2136, + // The directory service failed to identify the list of objects to delete while attempting a tree deletion. + DS_COULDNT_IDENTIFY_OBJECTS_FOR_TREE_DELETE = 0x2137, + // Security Accounts Manager initialization failed because of the following error: %1. Error Status: 0x%2. Please shutdown this system and reboot into Directory Services Restore Mode, check the event log for more detailed information. + DS_SAM_INIT_FAILURE = 0x2138, + // Only an administrator can modify the membership list of an administrative group. + DS_SENSITIVE_GROUP_VIOLATION = 0x2139, + // Cannot change the primary group ID of a domain controller account. + DS_CANT_MOD_PRIMARYGROUPID = 0x213A, + // An attempt is made to modify the base schema. + DS_ILLEGAL_BASE_SCHEMA_MOD = 0x213B, + // Adding a new mandatory attribute to an existing class, deleting a mandatory attribute from an existing class, or adding an optional attribute to the special class Top that is not a backlink attribute (directly or through inheritance, for example, by adding or deleting an auxiliary class) is not allowed. + DS_NONSAFE_SCHEMA_CHANGE = 0x213C, + // Schema update is not allowed on this DC because the DC is not the schema FSMO Role Owner. + DS_SCHEMA_UPDATE_DISALLOWED = 0x213D, + // An object of this class cannot be created under the schema container. You can only create attribute-schema and class-schema objects under the schema container. + DS_CANT_CREATE_UNDER_SCHEMA = 0x213E, + // The replica/child install failed to get the objectVersion attribute on the schema container on the source DC. Either the attribute is missing on the schema container or the credentials supplied do not have permission to read it. + DS_INSTALL_NO_SRC_SCH_VERSION = 0x213F, + // The replica/child install failed to read the objectVersion attribute in the SCHEMA section of the file schema.ini in the system32 directory. + DS_INSTALL_NO_SCH_VERSION_IN_INIFILE = 0x2140, + // The specified group type is invalid. + DS_INVALID_GROUP_TYPE = 0x2141, + // You cannot nest global groups in a mixed domain if the group is security-enabled. + DS_NO_NEST_GLOBALGROUP_IN_MIXEDDOMAIN = 0x2142, + // You cannot nest local groups in a mixed domain if the group is security-enabled. + DS_NO_NEST_LOCALGROUP_IN_MIXEDDOMAIN = 0x2143, + // A global group cannot have a local group as a member. + DS_GLOBAL_CANT_HAVE_LOCAL_MEMBER = 0x2144, + // A global group cannot have a universal group as a member. + DS_GLOBAL_CANT_HAVE_UNIVERSAL_MEMBER = 0x2145, + // A universal group cannot have a local group as a member. + DS_UNIVERSAL_CANT_HAVE_LOCAL_MEMBER = 0x2146, + // A global group cannot have a cross-domain member. + DS_GLOBAL_CANT_HAVE_CROSSDOMAIN_MEMBER = 0x2147, + // A local group cannot have another cross domain local group as a member. + DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER = 0x2148, + // A group with primary members cannot change to a security-disabled group. + DS_HAVE_PRIMARY_MEMBERS = 0x2149, + // The schema cache load failed to convert the string default SD on a class-schema object. + DS_STRING_SD_CONVERSION_FAILED = 0x214A, + // Only DSAs configured to be Global Catalog servers should be allowed to hold the Domain Naming Master FSMO role. (Applies only to Windows 2000 servers.) + DS_NAMING_MASTER_GC = 0x214B, + // The DSA operation is unable to proceed because of a DNS lookup failure. + DS_DNS_LOOKUP_FAILURE = 0x214C, + // While processing a change to the DNS Host Name for an object, the Service Principal Name values could not be kept in sync. + DS_COULDNT_UPDATE_SPNS = 0x214D, + // The Security Descriptor attribute could not be read. + DS_CANT_RETRIEVE_SD = 0x214E, + // The object requested was not found, but an object with that key was found. + DS_KEY_NOT_UNIQUE = 0x214F, + // The syntax of the linked attribute being added is incorrect. Forward links can only have syntax 2.5.5.1, 2.5.5.7, and 2.5.5.14, and backlinks can only have syntax 2.5.5.1. + DS_WRONG_LINKED_ATT_SYNTAX = 0x2150, + // Security Account Manager needs to get the boot password. + DS_SAM_NEED_BOOTKEY_PASSWORD = 0x2151, + // Security Account Manager needs to get the boot key from floppy disk. + DS_SAM_NEED_BOOTKEY_FLOPPY = 0x2152, + // Directory Service cannot start. + DS_CANT_START = 0x2153, + // Directory Services could not start. + DS_INIT_FAILURE = 0x2154, + // The connection between client and server requires packet privacy or better. + DS_NO_PKT_PRIVACY_ON_CONNECTION = 0x2155, + // The source domain may not be in the same forest as destination. + DS_SOURCE_DOMAIN_IN_FOREST = 0x2156, + // The destination domain must be in the forest. + DS_DESTINATION_DOMAIN_NOT_IN_FOREST = 0x2157, + // The operation requires that destination domain auditing be enabled. + DS_DESTINATION_AUDITING_NOT_ENABLED = 0x2158, + // The operation couldn't locate a DC for the source domain. + DS_CANT_FIND_DC_FOR_SRC_DOMAIN = 0x2159, + // The source object must be a group or user. + DS_SRC_OBJ_NOT_GROUP_OR_USER = 0x215A, + // The source object's SID already exists in destination forest. + DS_SRC_SID_EXISTS_IN_FOREST = 0x215B, + // The source and destination object must be of the same type. + DS_SRC_AND_DST_OBJECT_CLASS_MISMATCH = 0x215C, + // Security Accounts Manager initialization failed because of the following error: %1. Error Status: 0x%2. Click OK to shut down the system and reboot into Safe Mode. Check the event log for detailed information. + SAM_INIT_FAILURE = 0x215D, + // Schema information could not be included in the replication request. + DS_DRA_SCHEMA_INFO_SHIP = 0x215E, + // The replication operation could not be completed due to a schema incompatibility. + DS_DRA_SCHEMA_CONFLICT = 0x215F, + // The replication operation could not be completed due to a previous schema incompatibility. + DS_DRA_EARLIER_SCHEMA_CONFLICT = 0x2160, + // The replication update could not be applied because either the source or the destination has not yet received information regarding a recent cross-domain move operation. + DS_DRA_OBJ_NC_MISMATCH = 0x2161, + // The requested domain could not be deleted because there exist domain controllers that still host this domain. + DS_NC_STILL_HAS_DSAS = 0x2162, + // The requested operation can be performed only on a global catalog server. + DS_GC_REQUIRED = 0x2163, + // A local group can only be a member of other local groups in the same domain. + DS_LOCAL_MEMBER_OF_LOCAL_ONLY = 0x2164, + // Foreign security principals cannot be members of universal groups. + DS_NO_FPO_IN_UNIVERSAL_GROUPS = 0x2165, + // The attribute is not allowed to be replicated to the GC because of security reasons. + DS_CANT_ADD_TO_GC = 0x2166, + // The checkpoint with the PDC could not be taken because there too many modifications being processed currently. + DS_NO_CHECKPOINT_WITH_PDC = 0x2167, + // The operation requires that source domain auditing be enabled. + DS_SOURCE_AUDITING_NOT_ENABLED = 0x2168, + // Security principal objects can only be created inside domain naming contexts. + DS_CANT_CREATE_IN_NONDOMAIN_NC = 0x2169, + // A Service Principal Name (SPN) could not be constructed because the provided hostname is not in the necessary format. + DS_INVALID_NAME_FOR_SPN = 0x216A, + // A Filter was passed that uses constructed attributes. + DS_FILTER_USES_CONTRUCTED_ATTRS = 0x216B, + // The unicodePwd attribute value must be enclosed in double quotes. + DS_UNICODEPWD_NOT_IN_QUOTES = 0x216C, + // Your computer could not be joined to the domain. You have exceeded the maximum number of computer accounts you are allowed to create in this domain. Contact your system administrator to have this limit reset or increased. + DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED = 0x216D, + // For security reasons, the operation must be run on the destination DC. + DS_MUST_BE_RUN_ON_DST_DC = 0x216E, + // For security reasons, the source DC must be NT4SP4 or greater. + DS_SRC_DC_MUST_BE_SP4_OR_GREATER = 0x216F, + // Critical Directory Service System objects cannot be deleted during tree delete operations. The tree delete may have been partially performed. + DS_CANT_TREE_DELETE_CRITICAL_OBJ = 0x2170, + // Directory Services could not start because of the following error: %1. Error Status: 0x%2. Please click OK to shutdown the system. You can use the recovery console to diagnose the system further. + DS_INIT_FAILURE_CONSOLE = 0x2171, + // Security Accounts Manager initialization failed because of the following error: %1. Error Status: 0x%2. Please click OK to shutdown the system. You can use the recovery console to diagnose the system further. + DS_SAM_INIT_FAILURE_CONSOLE = 0x2172, + // The version of the operating system is incompatible with the current AD DS forest functional level or AD LDS Configuration Set functional level. You must upgrade to a new version of the operating system before this server can become an AD DS Domain Controller or add an AD LDS Instance in this AD DS Forest or AD LDS Configuration Set. + DS_FOREST_VERSION_TOO_HIGH = 0x2173, + // The version of the operating system installed is incompatible with the current domain functional level. You must upgrade to a new version of the operating system before this server can become a domain controller in this domain. + DS_DOMAIN_VERSION_TOO_HIGH = 0x2174, + // The version of the operating system installed on this server no longer supports the current AD DS Forest functional level or AD LDS Configuration Set functional level. You must raise the AD DS Forest functional level or AD LDS Configuration Set functional level before this server can become an AD DS Domain Controller or an AD LDS Instance in this Forest or Configuration Set. + DS_FOREST_VERSION_TOO_LOW = 0x2175, + // The version of the operating system installed on this server no longer supports the current domain functional level. You must raise the domain functional level before this server can become a domain controller in this domain. + DS_DOMAIN_VERSION_TOO_LOW = 0x2176, + // The version of the operating system installed on this server is incompatible with the functional level of the domain or forest. + DS_INCOMPATIBLE_VERSION = 0x2177, + // The functional level of the domain (or forest) cannot be raised to the requested value, because there exist one or more domain controllers in the domain (or forest) that are at a lower incompatible functional level. + DS_LOW_DSA_VERSION = 0x2178, + // The forest functional level cannot be raised to the requested value since one or more domains are still in mixed domain mode. All domains in the forest must be in native mode, for you to raise the forest functional level. + DS_NO_BEHAVIOR_VERSION_IN_MIXEDDOMAIN = 0x2179, + // The sort order requested is not supported. + DS_NOT_SUPPORTED_SORT_ORDER = 0x217A, + // The requested name already exists as a unique identifier. + DS_NAME_NOT_UNIQUE = 0x217B, + // The machine account was created pre-NT4. The account needs to be recreated. + DS_MACHINE_ACCOUNT_CREATED_PRENT4 = 0x217C, + // The database is out of version store. + DS_OUT_OF_VERSION_STORE = 0x217D, + // Unable to continue operation because multiple conflicting controls were used. + DS_INCOMPATIBLE_CONTROLS_USED = 0x217E, + // Unable to find a valid security descriptor reference domain for this partition. + DS_NO_REF_DOMAIN = 0x217F, + // Schema update failed: The link identifier is reserved. + DS_RESERVED_LINK_ID = 0x2180, + // Schema update failed: There are no link identifiers available. + DS_LINK_ID_NOT_AVAILABLE = 0x2181, + // An account group cannot have a universal group as a member. + DS_AG_CANT_HAVE_UNIVERSAL_MEMBER = 0x2182, + // Rename or move operations on naming context heads or read-only objects are not allowed. + DS_MODIFYDN_DISALLOWED_BY_INSTANCE_TYPE = 0x2183, + // Move operations on objects in the schema naming context are not allowed. + DS_NO_OBJECT_MOVE_IN_SCHEMA_NC = 0x2184, + // A system flag has been set on the object and does not allow the object to be moved or renamed. + DS_MODIFYDN_DISALLOWED_BY_FLAG = 0x2185, + // This object is not allowed to change its grandparent container. Moves are not forbidden on this object, but are restricted to sibling containers. + DS_MODIFYDN_WRONG_GRANDPARENT = 0x2186, + // Unable to resolve completely, a referral to another forest is generated. + DS_NAME_ERROR_TRUST_REFERRAL = 0x2187, + // The requested action is not supported on standard server. + NOT_SUPPORTED_ON_STANDARD_SERVER = 0x2188, + // Could not access a partition of the directory service located on a remote server. Make sure at least one server is running for the partition in question. + DS_CANT_ACCESS_REMOTE_PART_OF_AD = 0x2189, + // The directory cannot validate the proposed naming context (or partition) name because it does not hold a replica nor can it contact a replica of the naming context above the proposed naming context. Please ensure that the parent naming context is properly registered in DNS, and at least one replica of this naming context is reachable by the Domain Naming master. + DS_CR_IMPOSSIBLE_TO_VALIDATE_V2 = 0x218A, + // The thread limit for this request was exceeded. + DS_THREAD_LIMIT_EXCEEDED = 0x218B, + // The Global catalog server is not in the closest site. + DS_NOT_CLOSEST = 0x218C, + // The DS cannot derive a service principal name (SPN) with which to mutually authenticate the target server because the corresponding server object in the local DS database has no serverReference attribute. + DS_CANT_DERIVE_SPN_WITHOUT_SERVER_REF = 0x218D, + // The Directory Service failed to enter single user mode. + DS_SINGLE_USER_MODE_FAILED = 0x218E, + // The Directory Service cannot parse the script because of a syntax error. + DS_NTDSCRIPT_SYNTAX_ERROR = 0x218F, + // The Directory Service cannot process the script because of an error. + DS_NTDSCRIPT_PROCESS_ERROR = 0x2190, + // The directory service cannot perform the requested operation because the servers involved are of different replication epochs (which is usually related to a domain rename that is in progress). + DS_DIFFERENT_REPL_EPOCHS = 0x2191, + // The directory service binding must be renegotiated due to a change in the server extensions information. + DS_DRS_EXTENSIONS_CHANGED = 0x2192, + // Operation not allowed on a disabled cross ref. + DS_REPLICA_SET_CHANGE_NOT_ALLOWED_ON_DISABLED_CR = 0x2193, + // Schema update failed: No values for msDS-IntId are available. + DS_NO_MSDS_INTID = 0x2194, + // Schema update failed: Duplicate msDS-INtId. Retry the operation. + DS_DUP_MSDS_INTID = 0x2195, + // Schema deletion failed: attribute is used in rDNAttID. + DS_EXISTS_IN_RDNATTID = 0x2196, + // The directory service failed to authorize the request. + DS_AUTHORIZATION_FAILED = 0x2197, + // The Directory Service cannot process the script because it is invalid. + DS_INVALID_SCRIPT = 0x2198, + // The remote create cross reference operation failed on the Domain Naming Master FSMO. The operation's error is in the extended data. + DS_REMOTE_CROSSREF_OP_FAILED = 0x2199, + // A cross reference is in use locally with the same name. + DS_CROSS_REF_BUSY = 0x219A, + // The DS cannot derive a service principal name (SPN) with which to mutually authenticate the target server because the server's domain has been deleted from the forest. + DS_CANT_DERIVE_SPN_FOR_DELETED_DOMAIN = 0x219B, + // Writeable NCs prevent this DC from demoting. + DS_CANT_DEMOTE_WITH_WRITEABLE_NC = 0x219C, + // The requested object has a non-unique identifier and cannot be retrieved. + DS_DUPLICATE_ID_FOUND = 0x219D, + // Insufficient attributes were given to create an object. This object may not exist because it may have been deleted and already garbage collected. + DS_INSUFFICIENT_ATTR_TO_CREATE_OBJECT = 0x219E, + // The group cannot be converted due to attribute restrictions on the requested group type. + DS_GROUP_CONVERSION_ERROR = 0x219F, + // Cross-domain move of non-empty basic application groups is not allowed. + DS_CANT_MOVE_APP_BASIC_GROUP = 0x21A0, + // Cross-domain move of non-empty query based application groups is not allowed. + DS_CANT_MOVE_APP_QUERY_GROUP = 0x21A1, + // The FSMO role ownership could not be verified because its directory partition has not replicated successfully with at least one replication partner. + DS_ROLE_NOT_VERIFIED = 0x21A2, + // The target container for a redirection of a well known object container cannot already be a special container. + DS_WKO_CONTAINER_CANNOT_BE_SPECIAL = 0x21A3, + // The Directory Service cannot perform the requested operation because a domain rename operation is in progress. + DS_DOMAIN_RENAME_IN_PROGRESS = 0x21A4, + // The directory service detected a child partition below the requested partition name. The partition hierarchy must be created in a top down method. + DS_EXISTING_AD_CHILD_NC = 0x21A5, + // The directory service cannot replicate with this server because the time since the last replication with this server has exceeded the tombstone lifetime. + DS_REPL_LIFETIME_EXCEEDED = 0x21A6, + // The requested operation is not allowed on an object under the system container. + DS_DISALLOWED_IN_SYSTEM_CONTAINER = 0x21A7, + // The LDAP servers network send queue has filled up because the client is not processing the results of its requests fast enough. No more requests will be processed until the client catches up. If the client does not catch up then it will be disconnected. + DS_LDAP_SEND_QUEUE_FULL = 0x21A8, + // The scheduled replication did not take place because the system was too busy to execute the request within the schedule window. The replication queue is overloaded. Consider reducing the number of partners or decreasing the scheduled replication frequency. + DS_DRA_OUT_SCHEDULE_WINDOW = 0x21A9, + // At this time, it cannot be determined if the branch replication policy is available on the hub domain controller. Please retry at a later time to account for replication latencies. + DS_POLICY_NOT_KNOWN = 0x21AA, + // The site settings object for the specified site does not exist. + NO_SITE_SETTINGS_OBJECT = 0x21AB, + // The local account store does not contain secret material for the specified account. + NO_SECRETS = 0x21AC, + // Could not find a writable domain controller in the domain. + NO_WRITABLE_DC_FOUND = 0x21AD, + // The server object for the domain controller does not exist. + DS_NO_SERVER_OBJECT = 0x21AE, + // The NTDS Settings object for the domain controller does not exist. + DS_NO_NTDSA_OBJECT = 0x21AF, + // The requested search operation is not supported for ASQ searches. + DS_NON_ASQ_SEARCH = 0x21B0, + // A required audit event could not be generated for the operation. + DS_AUDIT_FAILURE = 0x21B1, + // The search flags for the attribute are invalid. The subtree index bit is valid only on single valued attributes. + DS_INVALID_SEARCH_FLAG_SUBTREE = 0x21B2, + // The search flags for the attribute are invalid. The tuple index bit is valid only on attributes of Unicode strings. + DS_INVALID_SEARCH_FLAG_TUPLE = 0x21B3, + // The address books are nested too deeply. Failed to build the hierarchy table. + DS_HIERARCHY_TABLE_TOO_DEEP = 0x21B4, + // The specified up-to-date-ness vector is corrupt. + DS_DRA_CORRUPT_UTD_VECTOR = 0x21B5, + // The request to replicate secrets is denied. + DS_DRA_SECRETS_DENIED = 0x21B6, + // Schema update failed: The MAPI identifier is reserved. + DS_RESERVED_MAPI_ID = 0x21B7, + // Schema update failed: There are no MAPI identifiers available. + DS_MAPI_ID_NOT_AVAILABLE = 0x21B8, + // The replication operation failed because the required attributes of the local krbtgt object are missing. + DS_DRA_MISSING_KRBTGT_SECRET = 0x21B9, + // The domain name of the trusted domain already exists in the forest. + DS_DOMAIN_NAME_EXISTS_IN_FOREST = 0x21BA, + // The flat name of the trusted domain already exists in the forest. + DS_FLAT_NAME_EXISTS_IN_FOREST = 0x21BB, + // The User Principal Name (UPN) is invalid. + INVALID_USER_PRINCIPAL_NAME = 0x21BC, + // OID mapped groups cannot have members. + DS_OID_MAPPED_GROUP_CANT_HAVE_MEMBERS = 0x21BD, + // The specified OID cannot be found. + DS_OID_NOT_FOUND = 0x21BE, + // The replication operation failed because the target object referred by a link value is recycled. + DS_DRA_RECYCLED_TARGET = 0x21BF, + // The redirect operation failed because the target object is in a NC different from the domain NC of the current domain controller. + DS_DISALLOWED_NC_REDIRECT = 0x21C0, + // The functional level of the AD LDS configuration set cannot be lowered to the requested value. + DS_HIGH_ADLDS_FFL = 0x21C1, + // The functional level of the domain (or forest) cannot be lowered to the requested value. + DS_HIGH_DSA_VERSION = 0x21C2, + // The functional level of the AD LDS configuration set cannot be raised to the requested value, because there exist one or more ADLDS instances that are at a lower incompatible functional level. + DS_LOW_ADLDS_FFL = 0x21C3, + // The domain join cannot be completed because the SID of the domain you attempted to join was identical to the SID of this machine. This is a symptom of an improperly cloned operating system install. You should run sysprep on this machine in order to generate a new machine SID. Please see https://go.microsoft.com/fwlink/p/?linkid=168895 for more information. + DOMAIN_SID_SAME_AS_LOCAL_WORKSTATION = 0x21C4, + // The undelete operation failed because the Sam Account Name or Additional Sam Account Name of the object being undeleted conflicts with an existing live object. + DS_UNDELETE_SAM_VALIDATION_FAILED = 0x21C5, + // The system is not authoritative for the specified account and therefore cannot complete the operation. Please retry the operation using the provider associated with this account. If this is an online provider please use the provider's online site. + INCORRECT_ACCOUNT_TYPE = 0x21C6, + + + // DNS server unable to interpret format. + DNS_ERROR_RCODE_FORMAT_ERROR = 0x2329, + // DNS server failure. + DNS_ERROR_RCODE_SERVER_FAILURE = 0x232A, + // DNS name does not exist. + DNS_ERROR_RCODE_NAME_ERROR = 0x232B, + // DNS request not supported by name server. + DNS_ERROR_RCODE_NOT_IMPLEMENTED = 0x232C, + // DNS operation refused. + DNS_ERROR_RCODE_REFUSED = 0x232D, + // DNS name that ought not exist, does exist. + DNS_ERROR_RCODE_YXDOMAIN = 0x232E, + // DNS RR set that ought not exist, does exist. + DNS_ERROR_RCODE_YXRRSET = 0x232F, + // DNS RR set that ought to exist, does not exist. + DNS_ERROR_RCODE_NXRRSET = 0x2330, + // DNS server not authoritative for zone. + DNS_ERROR_RCODE_NOTAUTH = 0x2331, + // DNS name in update or prereq is not in zone. + DNS_ERROR_RCODE_NOTZONE = 0x2332, + // DNS signature failed to verify. + DNS_ERROR_RCODE_BADSIG = 0x2338, + // DNS bad key. + DNS_ERROR_RCODE_BADKEY = 0x2339, + // DNS signature validity expired. + DNS_ERROR_RCODE_BADTIME = 0x233A, + // Only the DNS server acting as the key master for the zone may perform this operation. + DNS_ERROR_KEYMASTER_REQUIRED = 0x238D, + // This operation is not allowed on a zone that is signed or has signing keys. + DNS_ERROR_NOT_ALLOWED_ON_SIGNED_ZONE = 0x238E, + // NSEC3 is not compatible with the RSA-SHA-1 algorithm. Choose a different algorithm or use NSEC. + // This value was also named DNS_ERROR_INVALID_NSEC3_PARAMETERS + DNS_ERROR_NSEC3_INCOMPATIBLE_WITH_RSA_SHA1 = 0x238F, + // The zone does not have enough signing keys. There must be at least one key signing key (KSK) and at least one zone signing key (ZSK). + DNS_ERROR_NOT_ENOUGH_SIGNING_KEY_DESCRIPTORS = 0x2390, + // The specified algorithm is not supported. + DNS_ERROR_UNSUPPORTED_ALGORITHM = 0x2391, + // The specified key size is not supported. + DNS_ERROR_INVALID_KEY_SIZE = 0x2392, + // One or more of the signing keys for a zone are not accessible to the DNS server. Zone signing will not be operational until this error is resolved. + DNS_ERROR_SIGNING_KEY_NOT_ACCESSIBLE = 0x2393, + // The specified key storage provider does not support DPAPI++ data protection. Zone signing will not be operational until this error is resolved. + DNS_ERROR_KSP_DOES_NOT_SUPPORT_PROTECTION = 0x2394, + // An unexpected DPAPI++ error was encountered. Zone signing will not be operational until this error is resolved. + DNS_ERROR_UNEXPECTED_DATA_PROTECTION_ERROR = 0x2395, + // An unexpected crypto error was encountered. Zone signing may not be operational until this error is resolved. + DNS_ERROR_UNEXPECTED_CNG_ERROR = 0x2396, + // The DNS server encountered a signing key with an unknown version. Zone signing will not be operational until this error is resolved. + DNS_ERROR_UNKNOWN_SIGNING_PARAMETER_VERSION = 0x2397, + // The specified key service provider cannot be opened by the DNS server. + DNS_ERROR_KSP_NOT_ACCESSIBLE = 0x2398, + // The DNS server cannot accept any more signing keys with the specified algorithm and KSK flag value for this zone. + DNS_ERROR_TOO_MANY_SKDS = 0x2399, + // The specified rollover period is invalid. + DNS_ERROR_INVALID_ROLLOVER_PERIOD = 0x239A, + // The specified initial rollover offset is invalid. + DNS_ERROR_INVALID_INITIAL_ROLLOVER_OFFSET = 0x239B, + // The specified signing key is already in process of rolling over keys. + DNS_ERROR_ROLLOVER_IN_PROGRESS = 0x239C, + // The specified signing key does not have a standby key to revoke. + DNS_ERROR_STANDBY_KEY_NOT_PRESENT = 0x239D, + // This operation is not allowed on a zone signing key (ZSK). + DNS_ERROR_NOT_ALLOWED_ON_ZSK = 0x239E, + // This operation is not allowed on an active signing key. + DNS_ERROR_NOT_ALLOWED_ON_ACTIVE_SKD = 0x239F, + // The specified signing key is already queued for rollover. + DNS_ERROR_ROLLOVER_ALREADY_QUEUED = 0x23A0, + // This operation is not allowed on an unsigned zone. + DNS_ERROR_NOT_ALLOWED_ON_UNSIGNED_ZONE = 0x23A1, + // This operation could not be completed because the DNS server listed as the current key master for this zone is down or misconfigured. Resolve the problem on the current key master for this zone or use another DNS server to seize the key master role. + DNS_ERROR_BAD_KEYMASTER = 0x23A2, + // The specified signature validity period is invalid. + DNS_ERROR_INVALID_SIGNATURE_VALIDITY_PERIOD = 0x23A3, + // The specified NSEC3 iteration count is higher than allowed by the minimum key length used in the zone. + DNS_ERROR_INVALID_NSEC3_ITERATION_COUNT = 0x23A4, + // This operation could not be completed because the DNS server has been configured with DNSSEC features disabled. Enable DNSSEC on the DNS server. + DNS_ERROR_DNSSEC_IS_DISABLED = 0x23A5, + // This operation could not be completed because the XML stream received is empty or syntactically invalid. + DNS_ERROR_INVALID_XML = 0x23A6, + // This operation completed, but no trust anchors were added because all of the trust anchors received were either invalid, unsupported, expired, or would not become valid in less than 30 days. + DNS_ERROR_NO_VALID_TRUST_ANCHORS = 0x23A7, + // The specified signing key is not waiting for parental DS update. + DNS_ERROR_ROLLOVER_NOT_POKEABLE = 0x23A8, + // Hash collision detected during NSEC3 signing. Specify a different user-provided salt, or use a randomly generated salt, and attempt to sign the zone again. + DNS_ERROR_NSEC3_NAME_COLLISION = 0x23A9, + // NSEC is not compatible with the NSEC3-RSA-SHA-1 algorithm. Choose a different algorithm or use NSEC3. + DNS_ERROR_NSEC_INCOMPATIBLE_WITH_NSEC3_RSA_SHA1 = 0x23AA, + // No records found for given DNS query. + DNS_INFO_NO_RECORDS = 0x251D, + // Bad DNS packet. + DNS_ERROR_BAD_PACKET = 0x251E, + // No DNS packet. + DNS_ERROR_NO_PACKET = 0x251F, + // DNS error, check rcode. + DNS_ERROR_RCODE = 0x2520, + // Unsecured DNS packet. + DNS_ERROR_UNSECURE_PACKET = 0x2521, + // DNS query request is pending. + DNS_REQUEST_PENDING = 0x2522, + // Invalid DNS type. + DNS_ERROR_INVALID_TYPE = 0x254F, + // Invalid IP address. + DNS_ERROR_INVALID_IP_ADDRESS = 0x2550, + // Invalid property. + DNS_ERROR_INVALID_PROPERTY = 0x2551, + // Try DNS operation again later. + DNS_ERROR_TRY_AGAIN_LATER = 0x2552, + // Record for given name and type is not unique. + DNS_ERROR_NOT_UNIQUE = 0x2553, + // DNS name does not comply with RFC specifications. + DNS_ERROR_NON_RFC_NAME = 0x2554, + // DNS name is a fully-qualified DNS name. + DNS_STATUS_FQDN = 0x2555, + // DNS name is dotted (multi-label). + DNS_STATUS_DOTTED_NAME = 0x2556, + // DNS name is a single-part name. + DNS_STATUS_SINGLE_PART_NAME = 0x2557, + // DNS name contains an invalid character. + DNS_ERROR_INVALID_NAME_CHAR = 0x2558, + // DNS name is entirely numeric. + DNS_ERROR_NUMERIC_NAME = 0x2559, + // The operation requested is not permitted on a DNS root server. + DNS_ERROR_NOT_ALLOWED_ON_ROOT_SERVER = 0x255A, + // The record could not be created because this part of the DNS namespace has been delegated to another server. + DNS_ERROR_NOT_ALLOWED_UNDER_DELEGATION = 0x255B, + // The DNS server could not find a set of root hints. + DNS_ERROR_CANNOT_FIND_ROOT_HINTS = 0x255C, + // The DNS server found root hints but they were not consistent across all adapters. + DNS_ERROR_INCONSISTENT_ROOT_HINTS = 0x255D, + // The specified value is too small for this parameter. + DNS_ERROR_DWORD_VALUE_TOO_SMALL = 0x255E, + // The specified value is too large for this parameter. + DNS_ERROR_DWORD_VALUE_TOO_LARGE = 0x255F, + // This operation is not allowed while the DNS server is loading zones in the background. Please try again later. + DNS_ERROR_BACKGROUND_LOADING = 0x2560, + // The operation requested is not permitted on against a DNS server running on a read-only DC. + DNS_ERROR_NOT_ALLOWED_ON_RODC = 0x2561, + // No data is allowed to exist underneath a DNAME record. + DNS_ERROR_NOT_ALLOWED_UNDER_DNAME = 0x2562, + // This operation requires credentials delegation. + DNS_ERROR_DELEGATION_REQUIRED = 0x2563, + // Name resolution policy table has been corrupted. DNS resolution will fail until it is fixed. Contact your network administrator. + DNS_ERROR_INVALID_POLICY_TABLE = 0x2564, + // DNS zone does not exist. + DNS_ERROR_ZONE_DOES_NOT_EXIST = 0x2581, + // DNS zone information not available. + DNS_ERROR_NO_ZONE_INFO = 0x2582, + // Invalid operation for DNS zone. + DNS_ERROR_INVALID_ZONE_OPERATION = 0x2583, + // Invalid DNS zone configuration. + DNS_ERROR_ZONE_CONFIGURATION_ERROR = 0x2584, + // DNS zone has no start of authority (SOA) record. + DNS_ERROR_ZONE_HAS_NO_SOA_RECORD = 0x2585, + // DNS zone has no Name Server (NS) record. + DNS_ERROR_ZONE_HAS_NO_NS_RECORDS = 0x2586, + // DNS zone is locked. + DNS_ERROR_ZONE_LOCKED = 0x2587, + // DNS zone creation failed. + DNS_ERROR_ZONE_CREATION_FAILED = 0x2588, + // DNS zone already exists. + DNS_ERROR_ZONE_ALREADY_EXISTS = 0x2589, + // DNS automatic zone already exists. + DNS_ERROR_AUTOZONE_ALREADY_EXISTS = 0x258A, + // Invalid DNS zone type. + DNS_ERROR_INVALID_ZONE_TYPE = 0x258B, + // Secondary DNS zone requires master IP address. + DNS_ERROR_SECONDARY_REQUIRES_MASTER_IP = 0x258C, + // DNS zone not secondary. + DNS_ERROR_ZONE_NOT_SECONDARY = 0x258D, + // Need secondary IP address. + DNS_ERROR_NEED_SECONDARY_ADDRESSES = 0x258E, + // WINS initialization failed. + DNS_ERROR_WINS_INIT_FAILED = 0x258F, + // Need WINS servers. + DNS_ERROR_NEED_WINS_SERVERS = 0x2590, + // NBTSTAT initialization call failed. + DNS_ERROR_NBSTAT_INIT_FAILED = 0x2591, + // Invalid delete of start of authority (SOA). + DNS_ERROR_SOA_DELETE_INVALID = 0x2592, + // A conditional forwarding zone already exists for that name. + DNS_ERROR_FORWARDER_ALREADY_EXISTS = 0x2593, + // This zone must be configured with one or more master DNS server IP addresses. + DNS_ERROR_ZONE_REQUIRES_MASTER_IP = 0x2594, + // The operation cannot be performed because this zone is shut down. + DNS_ERROR_ZONE_IS_SHUTDOWN = 0x2595, + // This operation cannot be performed because the zone is currently being signed. Please try again later. + DNS_ERROR_ZONE_LOCKED_FOR_SIGNING = 0x2596, + // Primary DNS zone requires datafile. + DNS_ERROR_PRIMARY_REQUIRES_DATAFILE = 0x25B3, + // Invalid datafile name for DNS zone. + DNS_ERROR_INVALID_DATAFILE_NAME = 0x25B4, + // Failed to open datafile for DNS zone. + DNS_ERROR_DATAFILE_OPEN_FAILURE = 0x25B5, + // Failed to write datafile for DNS zone. + DNS_ERROR_FILE_WRITEBACK_FAILED = 0x25B6, + // Failure while reading datafile for DNS zone. + DNS_ERROR_DATAFILE_PARSING = 0x25B7, + // DNS record does not exist. + DNS_ERROR_RECORD_DOES_NOT_EXIST = 0x25E5, + // DNS record format error. + DNS_ERROR_RECORD_FORMAT = 0x25E6, + // Node creation failure in DNS. + DNS_ERROR_NODE_CREATION_FAILED = 0x25E7, + // Unknown DNS record type. + DNS_ERROR_UNKNOWN_RECORD_TYPE = 0x25E8, + // DNS record timed out. + DNS_ERROR_RECORD_TIMED_OUT = 0x25E9, + // Name not in DNS zone. + DNS_ERROR_NAME_NOT_IN_ZONE = 0x25EA, + // CNAME loop detected. + DNS_ERROR_CNAME_LOOP = 0x25EB, + // Node is a CNAME DNS record. + DNS_ERROR_NODE_IS_CNAME = 0x25EC, + // A CNAME record already exists for given name. + DNS_ERROR_CNAME_COLLISION = 0x25ED, + // Record only at DNS zone root. + DNS_ERROR_RECORD_ONLY_AT_ZONE_ROOT = 0x25EE, + // DNS record already exists. + DNS_ERROR_RECORD_ALREADY_EXISTS = 0x25EF, + // Secondary DNS zone data error. + DNS_ERROR_SECONDARY_DATA = 0x25F0, + // Could not create DNS cache data. + DNS_ERROR_NO_CREATE_CACHE_DATA = 0x25F1, + // DNS name does not exist. + DNS_ERROR_NAME_DOES_NOT_EXIST = 0x25F2, + // Could not create pointer (PTR) record. + DNS_WARNING_PTR_CREATE_FAILED = 0x25F3, + // DNS domain was undeleted. + DNS_WARNING_DOMAIN_UNDELETED = 0x25F4, + // The directory service is unavailable. + DNS_ERROR_DS_UNAVAILABLE = 0x25F5, + // DNS zone already exists in the directory service. + DNS_ERROR_DS_ZONE_ALREADY_EXISTS = 0x25F6, + // DNS server not creating or reading the boot file for the directory service integrated DNS zone. + DNS_ERROR_NO_BOOTFILE_IF_DS_ZONE = 0x25F7, + // Node is a DNAME DNS record. + DNS_ERROR_NODE_IS_DNAME = 0x25F8, + // A DNAME record already exists for given name. + DNS_ERROR_DNAME_COLLISION = 0x25F9, + // An alias loop has been detected with either CNAME or DNAME records. + DNS_ERROR_ALIAS_LOOP = 0x25FA, + // DNS AXFR (zone transfer) complete. + DNS_INFO_AXFR_COMPLETE = 0x2617, + // DNS zone transfer failed. + DNS_ERROR_AXFR = 0x2618, + // Added local WINS server. + DNS_INFO_ADDED_LOCAL_WINS = 0x2619, + // Secure update call needs to continue update request. + DNS_STATUS_CONTINUE_NEEDED = 0x2649, + // TCP/IP network protocol not installed. + DNS_ERROR_NO_TCPIP = 0x267B, + // No DNS servers configured for local system. + DNS_ERROR_NO_DNS_SERVERS = 0x267C, + // The specified directory partition does not exist. + DNS_ERROR_DP_DOES_NOT_EXIST = 0x26AD, + // The specified directory partition already exists. + DNS_ERROR_DP_ALREADY_EXISTS = 0x26AE, + // This DNS server is not enlisted in the specified directory partition. + DNS_ERROR_DP_NOT_ENLISTED = 0x26AF, + // This DNS server is already enlisted in the specified directory partition. + DNS_ERROR_DP_ALREADY_ENLISTED = 0x26B0, + // The directory partition is not available at this time. Please wait a few minutes and try again. + DNS_ERROR_DP_NOT_AVAILABLE = 0x26B1, + // The operation failed because the domain naming master FSMO role could not be reached. The domain controller holding the domain naming master FSMO role is down or unable to service the request or is not running Windows Server 2003 or later. + DNS_ERROR_DP_FSMO_ERROR = 0x26B2, + // A blocking operation was interrupted by a call to WSACancelBlockingCall. + WSAEINTR = 0x2714, + // The file handle supplied is not valid. + WSAEBADF = 0x2719, + // An attempt was made to access a socket in a way forbidden by its access permissions. + WSAEACCES = 0x271D, + // The system detected an invalid pointer address in attempting to use a pointer argument in a call. + WSAEFAULT = 0x271E, + // An invalid argument was supplied. + WSAEINVAL = 0x2726, + // Too many open sockets. + WSAEMFILE = 0x2728, + // A non-blocking socket operation could not be completed immediately. + WSAEWOULDBLOCK = 0x2733, + // A blocking operation is currently executing. + WSAEINPROGRESS = 0x2734, + // An operation was attempted on a non-blocking socket that already had an operation in progress. + WSAEALREADY = 0x2735, + // An operation was attempted on something that is not a socket. + WSAENOTSOCK = 0x2736, + // A required address was omitted from an operation on a socket. + WSAEDESTADDRREQ = 0x2737, + // A message sent on a datagram socket was larger than the internal message buffer or some other network limit, or the buffer used to receive a datagram into was smaller than the datagram itself. + WSAEMSGSIZE = 0x2738, + // A protocol was specified in the socket function call that does not support the semantics of the socket type requested. + WSAEPROTOTYPE = 0x2739, + // An unknown, invalid, or unsupported option or level was specified in a getsockopt or setsockopt call. + WSAENOPROTOOPT = 0x273A, + // The requested protocol has not been configured into the system, or no implementation for it exists. + WSAEPROTONOSUPPORT = 0x273B, + // The support for the specified socket type does not exist in this address family. + WSAESOCKTNOSUPPORT = 0x273C, + // The attempted operation is not supported for the type of object referenced. + WSAEOPNOTSUPP = 0x273D, + // The protocol family has not been configured into the system or no implementation for it exists. + WSAEPFNOSUPPORT = 0x273E, + // An address incompatible with the requested protocol was used. + WSAEAFNOSUPPORT = 0x273F, + // Only one usage of each socket address (protocol/network address/port) is normally permitted. + WSAEADDRINUSE = 0x2740, + // The requested address is not valid in its context. + WSAEADDRNOTAVAIL = 0x2741, + // A socket operation encountered a dead network. + WSAENETDOWN = 0x2742, + // A socket operation was attempted to an unreachable network. + WSAENETUNREACH = 0x2743, + // The connection has been broken due to keep-alive activity detecting a failure while the operation was in progress. + WSAENETRESET = 0x2744, + // An established connection was aborted by the software in your host machine. + WSAECONNABORTED = 0x2745, + // An existing connection was forcibly closed by the remote host. + WSAECONNRESET = 0x2746, + // An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full. + WSAENOBUFS = 0x2747, + // A connect request was made on an already connected socket. + WSAEISCONN = 0x2748, + // A request to send or receive data was disallowed because the socket is not connected and (when sending on a datagram socket using a sendto call) no address was supplied. + WSAENOTCONN = 0x2749, + // A request to send or receive data was disallowed because the socket had already been shut down in that direction with a previous shutdown call. + WSAESHUTDOWN = 0x274A, + // Too many references to some kernel object. + WSAETOOMANYREFS = 0x274B, + // A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. + WSAETIMEDOUT = 0x274C, + // No connection could be made because the target machine actively refused it. + WSAECONNREFUSED = 0x274D, + // Cannot translate name. + WSAELOOP = 0x274E, + // Name component or name was too long. + WSAENAMETOOLONG = 0x274F, + // A socket operation failed because the destination host was down. + WSAEHOSTDOWN = 0x2750, + // A socket operation was attempted to an unreachable host. + WSAEHOSTUNREACH = 0x2751, + // Cannot remove a directory that is not empty. + WSAENOTEMPTY = 0x2752, + // A Windows Sockets implementation may have a limit on the number of applications that may use it simultaneously. + WSAEPROCLIM = 0x2753, + // Ran out of quota. + WSAEUSERS = 0x2754, + // Ran out of disk quota. + WSAEDQUOT = 0x2755, + // File handle reference is no longer available. + WSAESTALE = 0x2756, + // Item is not available locally. + WSAEREMOTE = 0x2757, + // WSAStartup cannot function at this time because the underlying system it uses to provide network services is currently unavailable. = = 0x276C, + WSASYSNOTREADY = 0x276B, // The Windows Sockets version requested is not supported. + // Either the application has not called WSAStartup, or WSAStartup failed. + WSANOTINITIALISED = 0x276D, + // Returned by WSARecv or WSARecvFrom to indicate the remote party has initiated a graceful shutdown sequence. + WSAEDISCON = 0x2775, + // No more results can be returned by WSALookupServiceNext. + WSAENOMORE = 0x2776, + // A call to WSALookupServiceEnd was made while this call was still processing. The call has been canceled. + WSAECANCELLED = 0x2777, + // The procedure call table is invalid. + WSAEINVALIDPROCTABLE = 0x2778, + // The requested service provider is invalid. + WSAEINVALIDPROVIDER = 0x2779, + // The requested service provider could not be loaded or initialized. + WSAEPROVIDERFAILEDINIT = 0x277A, + // A system call has failed. + WSASYSCALLFAILURE = 0x277B, + // No such service is known. The service cannot be found in the specified name space. + WSASERVICE_NOT_FOUND = 0x277C, + // The specified class was not found. + WSATYPE_NOT_FOUND = 0x277D, + // No more results can be returned by WSALookupServiceNext. + WSA_E_NO_MORE = 0x277E, + // A call to WSALookupServiceEnd was made while this call was still processing. The call has been canceled. + WSA_E_CANCELLED = 0x277F, + // A database query failed because it was actively refused. + WSAEREFUSED = 0x2780, + // No such host is known. + WSAHOST_NOT_FOUND = 0x2AF9, + // This is usually a temporary error during hostname resolution and means that the local server did not receive a response from an authoritative server. + WSATRY_AGAIN = 0x2AFA, + // A non-recoverable error occurred during a database lookup. + WSANO_RECOVERY = 0x2AFB, + // The requested name is valid, but no data of the requested type was found. + WSANO_DATA = 0x2AFC, + // At least one reserve has arrived. + WSA_QOS_RECEIVERS = 0x2AFD, + // At least one path has arrived. + WSA_QOS_SENDERS = 0x2AFE, + // There are no senders. + WSA_QOS_NO_SENDERS = 0x2AFF, + // There are no receivers. + WSA_QOS_NO_RECEIVERS = 0x2B00, + // Reserve has been confirmed. + WSA_QOS_REQUEST_CONFIRMED = 0x2B01, + // Error due to lack of resources. + WSA_QOS_ADMISSION_FAILURE = 0x2B02, + // Rejected for administrative reasons - bad credentials. + WSA_QOS_POLICY_FAILURE = 0x2B03, + // Unknown or conflicting style. + WSA_QOS_BAD_STYLE = 0x2B04, + // Problem with some part of the filterspec or providerspecific buffer in general. + WSA_QOS_BAD_OBJECT = 0x2B05, + // Problem with some part of the flowspec. + WSA_QOS_TRAFFIC_CTRL_ERROR = 0x2B06, + // General QOS error. + WSA_QOS_GENERIC_ERROR = 0x2B07, + // An invalid or unrecognized service type was found in the flowspec. + WSA_QOS_ESERVICETYPE = 0x2B08, + // An invalid or inconsistent flowspec was found in the QOS structure. + WSA_QOS_EFLOWSPEC = 0x2B09, + // Invalid QOS provider-specific buffer. + WSA_QOS_EPROVSPECBUF = 0x2B0A, + // An invalid QOS filter style was used. + WSA_QOS_EFILTERSTYLE = 0x2B0B, + // An invalid QOS filter type was used. + WSA_QOS_EFILTERTYPE = 0x2B0C, + // An incorrect number of QOS FILTERSPECs were specified in the FLOWDESCRIPTOR. + WSA_QOS_EFILTERCOUNT = 0x2B0D, + // An object with an invalid ObjectLength field was specified in the QOS provider-specific buffer. + WSA_QOS_EOBJLENGTH = 0x2B0E, + // An incorrect number of flow descriptors was specified in the QOS structure. + WSA_QOS_EFLOWCOUNT = 0x2B0F, + // An unrecognized object was found in the QOS provider-specific buffer. + WSA_QOS_EUNKOWNPSOBJ = 0x2B10, + // An invalid policy object was found in the QOS provider-specific buffer. + WSA_QOS_EPOLICYOBJ = 0x2B11, + // An invalid QOS flow descriptor was found in the flow descriptor list. + WSA_QOS_EFLOWDESC = 0x2B12, + // An invalid or inconsistent flowspec was found in the QOS provider specific buffer. + WSA_QOS_EPSFLOWSPEC = 0x2B13, + // An invalid FILTERSPEC was found in the QOS provider-specific buffer. + WSA_QOS_EPSFILTERSPEC = 0x2B14, + // An invalid shape discard mode object was found in the QOS provider specific buffer. + WSA_QOS_ESDMODEOBJ = 0x2B15, + // An invalid shaping rate object was found in the QOS provider-specific buffer. + WSA_QOS_ESHAPERATEOBJ = 0x2B16, + // A reserved policy element was found in the QOS provider-specific buffer. + WSA_QOS_RESERVED_PETYPE = 0x2B17, + // No such host is known securely. + WSA_SECURE_HOST_NOT_FOUND = 0x2B18, + // Name based IPSEC policy could not be added. + WSA_IPSEC_NAME_POLICY_ERROR = 0x2B19, + + // See Internet Error Codes and WinInet.h. + INTERNET_ = 0x2EE0, + // See WinHTTP Error Codes and Winhttp.h. + WINHTTP_ = 0x2EE1, + // The specified quick mode policy already exists. + IPSEC_QM_POLICY_EXISTS = 0x32C8, + // The specified quick mode policy was not found. + IPSEC_QM_POLICY_NOT_FOUND = 0x32C9, + // The specified quick mode policy is being used. + IPSEC_QM_POLICY_IN_USE = 0x32CA, + // The specified main mode policy already exists. + IPSEC_MM_POLICY_EXISTS = 0x32CB, + // The specified main mode policy was not found. + IPSEC_MM_POLICY_NOT_FOUND = 0x32CC, + // The specified main mode policy is being used. + IPSEC_MM_POLICY_IN_USE = 0x32CD, + // The specified main mode filter already exists. + IPSEC_MM_FILTER_EXISTS = 0x32CE, + // The specified main mode filter was not found. + IPSEC_MM_FILTER_NOT_FOUND = 0x32CF, + // The specified transport mode filter already exists. + IPSEC_TRANSPORT_FILTER_EXISTS = 0x32D0, + // The specified transport mode filter does not exist. + IPSEC_TRANSPORT_FILTER_NOT_FOUND = 0x32D1, + // The specified main mode authentication list exists. + IPSEC_MM_AUTH_EXISTS = 0x32D2, + // The specified main mode authentication list was not found. + IPSEC_MM_AUTH_NOT_FOUND = 0x32D3, + // The specified main mode authentication list is being used. + IPSEC_MM_AUTH_IN_USE = 0x32D4, + // The specified default main mode policy was not found. + IPSEC_DEFAULT_MM_POLICY_NOT_FOUND = 0x32D5, + // The specified default main mode authentication list was not found. + IPSEC_DEFAULT_MM_AUTH_NOT_FOUND = 0x32D6, + // The specified default quick mode policy was not found. + IPSEC_DEFAULT_QM_POLICY_NOT_FOUND = 0x32D7, + // The specified tunnel mode filter exists. + IPSEC_TUNNEL_FILTER_EXISTS = 0x32D8, + // The specified tunnel mode filter was not found. + IPSEC_TUNNEL_FILTER_NOT_FOUND = 0x32D9, + // The Main Mode filter is pending deletion. + IPSEC_MM_FILTER_PENDING_DELETION = 0x32DA, + // The transport filter is pending deletion. + IPSEC_TRANSPORT_FILTER_PENDING_DELETION = 0x32DB, + // The tunnel filter is pending deletion. + IPSEC_TUNNEL_FILTER_PENDING_DELETION = 0x32DC, + // The Main Mode policy is pending deletion. + IPSEC_MM_POLICY_PENDING_DELETION = 0x32DD, + // The Main Mode authentication bundle is pending deletion. + IPSEC_MM_AUTH_PENDING_DELETION = 0x32DE, + // The Quick Mode policy is pending deletion. + IPSEC_QM_POLICY_PENDING_DELETION = 0x32DF, + // The Main Mode policy was successfully added, but some of the requested offers are not supported. + WARNING_IPSEC_MM_POLICY_PRUNED = 0x32E0, + // The Quick Mode policy was successfully added, but some of the requested offers are not supported. + WARNING_IPSEC_QM_POLICY_PRUNED = 0x32E1, + // IPSEC_IKE_NEG_STATUS_BEGIN = = 0x35E9, + IPSEC_IKE_NEG_STATUS_BEGIN = 0x35E8, // IKE authentication credentials are unacceptable. + // IKE security attributes are unacceptable. + IPSEC_IKE_ATTRIB_FAIL = 0x35EA, + // IKE Negotiation in progress. + IPSEC_IKE_NEGOTIATION_PENDING = 0x35EB, + // General processing error. + IPSEC_IKE_GENERAL_PROCESSING_ERROR = 0x35EC, + // Negotiation timed out. + IPSEC_IKE_TIMED_OUT = 0x35ED, + // IKE failed to find valid machine certificate. Contact your Network Security Administrator about installing a valid certificate in the appropriate Certificate Store. + IPSEC_IKE_NO_CERT = 0x35EE, + // IKE SA deleted by peer before establishment completed. + IPSEC_IKE_SA_DELETED = 0x35EF, + // IKE SA deleted before establishment completed. + IPSEC_IKE_SA_REAPED = 0x35F0, + // Negotiation request sat in Queue too long. + IPSEC_IKE_MM_ACQUIRE_DROP = 0x35F1, + // Negotiation request sat in Queue too long. + IPSEC_IKE_QM_ACQUIRE_DROP = 0x35F2, + // Negotiation request sat in Queue too long. + IPSEC_IKE_QUEUE_DROP_MM = 0x35F3, + // Negotiation request sat in Queue too long. + IPSEC_IKE_QUEUE_DROP_NO_MM = 0x35F4, + // No response from peer. + IPSEC_IKE_DROP_NO_RESPONSE = 0x35F5, + // Negotiation took too long. + IPSEC_IKE_MM_DELAY_DROP = 0x35F6, + // Negotiation took too long. + IPSEC_IKE_QM_DELAY_DROP = 0x35F7, + // Unknown error occurred. + IPSEC_IKE_ERROR = 0x35F8, + // Certificate Revocation Check failed. + IPSEC_IKE_CRL_FAILED = 0x35F9, + // Invalid certificate key usage. + IPSEC_IKE_INVALID_KEY_USAGE = 0x35FA, + // Invalid certificate type. + IPSEC_IKE_INVALID_CERT_TYPE = 0x35FB, + // IKE negotiation failed because the machine certificate used does not have a private key. IPsec certificates require a private key. Contact your Network Security administrator about replacing with a certificate that has a private key. + IPSEC_IKE_NO_PRIVATE_KEY = 0x35FC, + // Simultaneous rekeys were detected. + IPSEC_IKE_SIMULTANEOUS_REKEY = 0x35FD, + // Failure in Diffie-Hellman computation. + IPSEC_IKE_DH_FAIL = 0x35FE, + // Don't know how to process critical payload. + IPSEC_IKE_CRITICAL_PAYLOAD_NOT_RECOGNIZED = 0x35FF, + // Invalid header. + IPSEC_IKE_INVALID_HEADER = 0x3600, + // No policy configured. + IPSEC_IKE_NO_POLICY = 0x3601, + // Failed to verify signature. + IPSEC_IKE_INVALID_SIGNATURE = 0x3602, + // Failed to authenticate using Kerberos. + IPSEC_IKE_KERBEROS_ERROR = 0x3603, + // Peer's certificate did not have a public key. + IPSEC_IKE_NO_PUBLIC_KEY = 0x3604, + // Error processing error payload. + IPSEC_IKE_PROCESS_ERR = 0x3605, + // Error processing SA payload. + IPSEC_IKE_PROCESS_ERR_SA = 0x3606, + // Error processing Proposal payload. + IPSEC_IKE_PROCESS_ERR_PROP = 0x3607, + // Error processing Transform payload. + IPSEC_IKE_PROCESS_ERR_TRANS = 0x3608, + // Error processing KE payload. + IPSEC_IKE_PROCESS_ERR_KE = 0x3609, + // Error processing ID payload. + IPSEC_IKE_PROCESS_ERR_ID = 0x360A, + // Error processing Cert payload. + IPSEC_IKE_PROCESS_ERR_CERT = 0x360B, + // Error processing Certificate Request payload. + IPSEC_IKE_PROCESS_ERR_CERT_REQ = 0x360C, + // Error processing Hash payload. + IPSEC_IKE_PROCESS_ERR_HASH = 0x360D, + // Error processing Signature payload. + IPSEC_IKE_PROCESS_ERR_SIG = 0x360E, + // Error processing Nonce payload. + IPSEC_IKE_PROCESS_ERR_NONCE = 0x360F, + // Error processing Notify payload. + IPSEC_IKE_PROCESS_ERR_NOTIFY = 0x3610, + // Error processing Delete Payload. + IPSEC_IKE_PROCESS_ERR_DELETE = 0x3611, + // Error processing VendorId payload. + IPSEC_IKE_PROCESS_ERR_VENDOR = 0x3612, + // Invalid payload received. + IPSEC_IKE_INVALID_PAYLOAD = 0x3613, + // Soft SA loaded. + IPSEC_IKE_LOAD_SOFT_SA = 0x3614, + // Soft SA torn down. + IPSEC_IKE_SOFT_SA_TORN_DOWN = 0x3615, + // Invalid cookie received. + IPSEC_IKE_INVALID_COOKIE = 0x3616, + // Peer failed to send valid machine certificate. + IPSEC_IKE_NO_PEER_CERT = 0x3617, + // Certification Revocation check of peer's certificate failed. + IPSEC_IKE_PEER_CRL_FAILED = 0x3618, + // New policy invalidated SAs formed with old policy. + IPSEC_IKE_POLICY_CHANGE = 0x3619, + // There is no available Main Mode IKE policy. + IPSEC_IKE_NO_MM_POLICY = 0x361A, + // Failed to enabled TCB privilege. + IPSEC_IKE_NOTCBPRIV = 0x361B, + // Failed to load SECURITY.DLL. + IPSEC_IKE_SECLOADFAIL = 0x361C, + // Failed to obtain security function table dispatch address from SSPI. + IPSEC_IKE_FAILSSPINIT = 0x361D, + // Failed to query Kerberos package to obtain max token size. + IPSEC_IKE_FAILQUERYSSP = 0x361E, + // Failed to obtain Kerberos server credentials for ISAKMP/ERROR_IPSEC_IKE service. Kerberos authentication will not function. The most likely reason for this is lack of domain membership. This is normal if your computer is a member of a workgroup. + IPSEC_IKE_SRVACQFAIL = 0x361F, + // Failed to determine SSPI principal name for ISAKMP/ERROR_IPSEC_IKE service (QueryCredentialsAttributes). + IPSEC_IKE_SRVQUERYCRED = 0x3620, + // Failed to obtain new SPI for the inbound SA from IPsec driver. The most common cause for this is that the driver does not have the correct filter. Check your policy to verify the filters. + IPSEC_IKE_GETSPIFAIL = 0x3621, + // Given filter is invalid. + IPSEC_IKE_INVALID_FILTER = 0x3622, + // Memory allocation failed. + IPSEC_IKE_OUT_OF_MEMORY = 0x3623, + // Failed to add Security Association to IPsec Driver. The most common cause for this is if the IKE negotiation took too long to complete. If the problem persists, reduce the load on the faulting machine. + IPSEC_IKE_ADD_UPDATE_KEY_FAILED = 0x3624, + // Invalid policy. + IPSEC_IKE_INVALID_POLICY = 0x3625, + // Invalid DOI. + IPSEC_IKE_UNKNOWN_DOI = 0x3626, + // Invalid situation. + IPSEC_IKE_INVALID_SITUATION = 0x3627, + // Diffie-Hellman failure. + IPSEC_IKE_DH_FAILURE = 0x3628, + // Invalid Diffie-Hellman group. + IPSEC_IKE_INVALID_GROUP = 0x3629, + // Error encrypting payload. + IPSEC_IKE_ENCRYPT = 0x362A, + // Error decrypting payload. + IPSEC_IKE_DECRYPT = 0x362B, + // Policy match error. + IPSEC_IKE_POLICY_MATCH = 0x362C, + // Unsupported ID. + IPSEC_IKE_UNSUPPORTED_ID = 0x362D, + // Hash verification failed. + IPSEC_IKE_INVALID_HASH = 0x362E, + // Invalid hash algorithm. + IPSEC_IKE_INVALID_HASH_ALG = 0x362F, + // Invalid hash size. + IPSEC_IKE_INVALID_HASH_SIZE = 0x3630, + // Invalid encryption algorithm. + IPSEC_IKE_INVALID_ENCRYPT_ALG = 0x3631, + // Invalid authentication algorithm. + IPSEC_IKE_INVALID_AUTH_ALG = 0x3632, + // Invalid certificate signature. + IPSEC_IKE_INVALID_SIG = 0x3633, + // Load failed. + IPSEC_IKE_LOAD_FAILED = 0x3634, + // Deleted via RPC call. + IPSEC_IKE_RPC_DELETE = 0x3635, + // Temporary state created to perform reinitialization. This is not a real failure. + IPSEC_IKE_BENIGN_REINIT = 0x3636, + // The lifetime value received in the Responder Lifetime Notify is below the Windows 2000 configured minimum value. Please fix the policy on the peer machine. + IPSEC_IKE_INVALID_RESPONDER_LIFETIME_NOTIFY = 0x3637, + // The recipient cannot handle version of IKE specified in the header. + IPSEC_IKE_INVALID_MAJOR_VERSION = 0x3638, + // Key length in certificate is too small for configured security requirements. + IPSEC_IKE_INVALID_CERT_KEYLEN = 0x3639, + // Max number of established MM SAs to peer exceeded. + IPSEC_IKE_MM_LIMIT = 0x363A, + // IKE received a policy that disables negotiation. + IPSEC_IKE_NEGOTIATION_DISABLED = 0x363B, + // Reached maximum quick mode limit for the main mode. New main mode will be started. + IPSEC_IKE_QM_LIMIT = 0x363C, + // Main mode SA lifetime expired or peer sent a main mode delete. + IPSEC_IKE_MM_EXPIRED = 0x363D, + // Main mode SA assumed to be invalid because peer stopped responding. + IPSEC_IKE_PEER_MM_ASSUMED_INVALID = 0x363E, + // Certificate doesn't chain to a trusted root in IPsec policy. + IPSEC_IKE_CERT_CHAIN_POLICY_MISMATCH = 0x363F, + // Received unexpected message ID. + IPSEC_IKE_UNEXPECTED_MESSAGE_ID = 0x3640, + // Received invalid authentication offers. + IPSEC_IKE_INVALID_AUTH_PAYLOAD = 0x3641, + // Sent DoS cookie notify to initiator. + IPSEC_IKE_DOS_COOKIE_SENT = 0x3642, + // IKE service is shutting down. + IPSEC_IKE_SHUTTING_DOWN = 0x3643, + // Could not verify binding between CGA address and certificate. + IPSEC_IKE_CGA_AUTH_FAILED = 0x3644, + // Error processing NatOA payload. + IPSEC_IKE_PROCESS_ERR_NATOA = 0x3645, + // Parameters of the main mode are invalid for this quick mode. + IPSEC_IKE_INVALID_MM_FOR_QM = 0x3646, + // Quick mode SA was expired by IPsec driver. + IPSEC_IKE_QM_EXPIRED = 0x3647, + // Too many dynamically added IKEEXT filters were detected. + IPSEC_IKE_TOO_MANY_FILTERS = 0x3648, + // IPSEC_IKE_NEG_STATUS_END = = 0x364A, + IPSEC_IKE_NEG_STATUS_END = 0x3649, // NAP reauth succeeded and must delete the dummy NAP IKEv2 tunnel. + // Error in assigning inner IP address to initiator in tunnel mode. + IPSEC_IKE_INNER_IP_ASSIGNMENT_FAILURE = 0x364B, + // Require configuration payload missing. + IPSEC_IKE_REQUIRE_CP_PAYLOAD_MISSING = 0x364C, + // A negotiation running as the security principle who issued the connection is in progress. + IPSEC_KEY_MODULE_IMPERSONATION_NEGOTIATION_PENDING = 0x364D, + // SA was deleted due to IKEv1/AuthIP co-existence suppress check. + IPSEC_IKE_COEXISTENCE_SUPPRESS = 0x364E, + // Incoming SA request was dropped due to peer IP address rate limiting. + IPSEC_IKE_RATELIMIT_DROP = 0x364F, + // Peer does not support MOBIKE. + IPSEC_IKE_PEER_DOESNT_SUPPORT_MOBIKE = 0x3650, + // SA establishment is not authorized. + IPSEC_IKE_AUTHORIZATION_FAILURE = 0x3651, + // SA establishment is not authorized because there is not a sufficiently strong PKINIT-based credential. + IPSEC_IKE_STRONG_CRED_AUTHORIZATION_FAILURE = 0x3652, + // SA establishment is not authorized. You may need to enter updated or different credentials such as a smartcard. + IPSEC_IKE_AUTHORIZATION_FAILURE_WITH_OPTIONAL_RETRY = 0x3653, + // SA establishment is not authorized because there is not a sufficiently strong PKINIT-based credential. This might be related to certificate-to-account mapping failure for the SA. + IPSEC_IKE_STRONG_CRED_AUTHORIZATION_AND_CERTMAP_FAILURE = 0x3654, + // IPSEC_IKE_NEG_STATUS_EXTENDED_END = = 0x3656, + IPSEC_IKE_NEG_STATUS_EXTENDED_END = 0x3655, // The SPI in the packet does not match a valid IPsec SA. + // Packet was received on an IPsec SA whose lifetime has expired. + IPSEC_SA_LIFETIME_EXPIRED = 0x3657, + // Packet was received on an IPsec SA that does not match the packet characteristics. + IPSEC_WRONG_SA = 0x3658, + // Packet sequence number replay check failed. + IPSEC_REPLAY_CHECK_FAILED = 0x3659, + // IPsec header and/or trailer in the packet is invalid. + IPSEC_INVALID_PACKET = 0x365A, + // IPsec integrity check failed. + IPSEC_INTEGRITY_CHECK_FAILED = 0x365B, + // IPsec dropped a clear text packet. + IPSEC_CLEAR_TEXT_DROP = 0x365C, + // IPsec dropped an incoming ESP packet in authenticated firewall mode. This drop is benign. + IPSEC_AUTH_FIREWALL_DROP = 0x365D, + // IPsec dropped a packet due to DoS throttling. + IPSEC_THROTTLE_DROP = 0x365E, + // IPsec DoS Protection matched an explicit block rule. + IPSEC_DOSP_BLOCK = 0x3665, + // IPsec DoS Protection received an IPsec specific multicast packet which is not allowed. + IPSEC_DOSP_RECEIVED_MULTICAST = 0x3666, + // IPsec DoS Protection received an incorrectly formatted packet. + IPSEC_DOSP_INVALID_PACKET = 0x3667, + // IPsec DoS Protection failed to look up state. + IPSEC_DOSP_STATE_LOOKUP_FAILED = 0x3668, + // IPsec DoS Protection failed to create state because the maximum number of entries allowed by policy has been reached. + IPSEC_DOSP_MAX_ENTRIES = 0x3669, + // IPsec DoS Protection received an IPsec negotiation packet for a keying module which is not allowed by policy. + IPSEC_DOSP_KEYMOD_NOT_ALLOWED = 0x366A, + // IPsec DoS Protection has not been enabled. + IPSEC_DOSP_NOT_INSTALLED = 0x366B, + // IPsec DoS Protection failed to create a per internal IP rate limit queue because the maximum number of queues allowed by policy has been reached. + IPSEC_DOSP_MAX_PER_IP_RATELIMIT_QUEUES = 0x366C, + // The requested section was not present in the activation context. + SXS_SECTION_NOT_FOUND = 0x36B0, + // The application has failed to start because its side-by-side configuration is incorrect. Please see the application event log or use the command-line sxstrace.exe tool for more detail. + SXS_CANT_GEN_ACTCTX = 0x36B1, + // The application binding data format is invalid. + SXS_INVALID_ACTCTXDATA_FORMAT = 0x36B2, + // The referenced assembly is not installed on your system. + SXS_ASSEMBLY_NOT_FOUND = 0x36B3, + // The manifest file does not begin with the required tag and format information. + SXS_MANIFEST_FORMAT_ERROR = 0x36B4, + // The manifest file contains one or more syntax errors. + SXS_MANIFEST_PARSE_ERROR = 0x36B5, + // The application attempted to activate a disabled activation context. + SXS_ACTIVATION_CONTEXT_DISABLED = 0x36B6, + // The requested lookup key was not found in any active activation context. + SXS_KEY_NOT_FOUND = 0x36B7, + // A component version required by the application conflicts with another component version already active. + SXS_VERSION_CONFLICT = 0x36B8, + // The type requested activation context section does not match the query API used. + SXS_WRONG_SECTION_TYPE = 0x36B9, + // Lack of system resources has required isolated activation to be disabled for the current thread of execution. + SXS_THREAD_QUERIES_DISABLED = 0x36BA, + // An attempt to set the process default activation context failed because the process default activation context was already set. + SXS_PROCESS_DEFAULT_ALREADY_SET = 0x36BB, + // The encoding group identifier specified is not recognized. + SXS_UNKNOWN_ENCODING_GROUP = 0x36BC, + // The encoding requested is not recognized. + SXS_UNKNOWN_ENCODING = 0x36BD, + // The manifest contains a reference to an invalid URI. + SXS_INVALID_XML_NAMESPACE_URI = 0x36BE, + // The application manifest contains a reference to a dependent assembly which is not installed. + SXS_ROOT_MANIFEST_DEPENDENCY_NOT_INSTALLED = 0x36BF, + // The manifest for an assembly used by the application has a reference to a dependent assembly which is not installed. + SXS_LEAF_MANIFEST_DEPENDENCY_NOT_INSTALLED = 0x36C0, + // The manifest contains an attribute for the assembly identity which is not valid. + SXS_INVALID_ASSEMBLY_IDENTITY_ATTRIBUTE = 0x36C1, + // The manifest is missing the required default namespace specification on the assembly element. + SXS_MANIFEST_MISSING_REQUIRED_DEFAULT_NAMESPACE = 0x36C2, + // The manifest has a default namespace specified on the assembly element but its value is not "urn:schemas-microsoft-com:asm.v1". + SXS_MANIFEST_INVALID_REQUIRED_DEFAULT_NAMESPACE = 0x36C3, + // The private manifest probed has crossed a path with an unsupported reparse point. + SXS_PRIVATE_MANIFEST_CROSS_PATH_WITH_REPARSE_POINT = 0x36C4, + // Two or more components referenced directly or indirectly by the application manifest have files by the same name. + SXS_DUPLICATE_DLL_NAME = 0x36C5, + // Two or more components referenced directly or indirectly by the application manifest have window classes with the same name. + SXS_DUPLICATE_WINDOWCLASS_NAME = 0x36C6, + // Two or more components referenced directly or indirectly by the application manifest have the same COM server CLSIDs. + SXS_DUPLICATE_CLSID = 0x36C7, + // Two or more components referenced directly or indirectly by the application manifest have proxies for the same COM interface IIDs. + SXS_DUPLICATE_IID = 0x36C8, + // Two or more components referenced directly or indirectly by the application manifest have the same COM type library TLBIDs. + SXS_DUPLICATE_TLBID = 0x36C9, + // Two or more components referenced directly or indirectly by the application manifest have the same COM ProgIDs. + SXS_DUPLICATE_PROGID = 0x36CA, + // Two or more components referenced directly or indirectly by the application manifest are different versions of the same component which is not permitted. + SXS_DUPLICATE_ASSEMBLY_NAME = 0x36CB, + // A component's file does not match the verification information present in the component manifest. + SXS_FILE_HASH_MISMATCH = 0x36CC, + // The policy manifest contains one or more syntax errors. + SXS_POLICY_PARSE_ERROR = 0x36CD, + // Manifest Parse Error : A string literal was expected, but no opening quote character was found. + SXS_XML_E_MISSINGQUOTE = 0x36CE, + // Manifest Parse Error : Incorrect syntax was used in a comment. + SXS_XML_E_COMMENTSYNTAX = 0x36CF, + // Manifest Parse Error : A name was started with an invalid character. + SXS_XML_E_BADSTARTNAMECHAR = 0x36D0, + // Manifest Parse Error : A name contained an invalid character. + SXS_XML_E_BADNAMECHAR = 0x36D1, + // Manifest Parse Error : A string literal contained an invalid character. + SXS_XML_E_BADCHARINSTRING = 0x36D2, + // Manifest Parse Error : Invalid syntax for an xml declaration. + SXS_XML_E_XMLDECLSYNTAX = 0x36D3, + // Manifest Parse Error : An Invalid character was found in text content. + SXS_XML_E_BADCHARDATA = 0x36D4, + // Manifest Parse Error : Required white space was missing. + SXS_XML_E_MISSINGWHITESPACE = 0x36D5, + // Manifest Parse Error : The character '>' was expected. + SXS_XML_E_EXPECTINGTAGEND = 0x36D6, + // Manifest Parse Error : A semi colon character was expected. + SXS_XML_E_MISSINGSEMICOLON = 0x36D7, + // Manifest Parse Error : Unbalanced parentheses. + SXS_XML_E_UNBALANCEDPAREN = 0x36D8, + // Manifest Parse Error : Internal error. + SXS_XML_E_INTERNALERROR = 0x36D9, + // Manifest Parse Error : Whitespace is not allowed at this location. + SXS_XML_E_UNEXPECTED_WHITESPACE = 0x36DA, + // Manifest Parse Error : End of file reached in invalid state for current encoding. + SXS_XML_E_INCOMPLETE_ENCODING = 0x36DB, + // Manifest Parse Error : Missing parenthesis. + SXS_XML_E_MISSING_PAREN = 0x36DC, + // Manifest Parse Error : A single or double closing quote character (\' or \") is missing. + SXS_XML_E_EXPECTINGCLOSEQUOTE = 0x36DD, + // Manifest Parse Error : Multiple colons are not allowed in a name. + SXS_XML_E_MULTIPLE_COLONS = 0x36DE, + // Manifest Parse Error : Invalid character for decimal digit. + SXS_XML_E_INVALID_DECIMAL = 0x36DF, + // Manifest Parse Error : Invalid character for hexadecimal digit. + SXS_XML_E_INVALID_HEXIDECIMAL = 0x36E0, + // Manifest Parse Error : Invalid unicode character value for this platform. + SXS_XML_E_INVALID_UNICODE = 0x36E1, + // Manifest Parse Error : Expecting whitespace or '?'. + SXS_XML_E_WHITESPACEORQUESTIONMARK = 0x36E2, + // Manifest Parse Error : End tag was not expected at this location. + SXS_XML_E_UNEXPECTEDENDTAG = 0x36E3, + // Manifest Parse Error : The following tags were not closed: %1. + SXS_XML_E_UNCLOSEDTAG = 0x36E4, + // Manifest Parse Error : Duplicate attribute. + SXS_XML_E_DUPLICATEATTRIBUTE = 0x36E5, + // Manifest Parse Error : Only one top level element is allowed in an XML document. + SXS_XML_E_MULTIPLEROOTS = 0x36E6, + // Manifest Parse Error : Invalid at the top level of the document. + SXS_XML_E_INVALIDATROOTLEVEL = 0x36E7, + // Manifest Parse Error : Invalid xml declaration. + SXS_XML_E_BADXMLDECL = 0x36E8, + // Manifest Parse Error : XML document must have a top level element. + SXS_XML_E_MISSINGROOT = 0x36E9, + // Manifest Parse Error : Unexpected end of file. + SXS_XML_E_UNEXPECTEDEOF = 0x36EA, + // Manifest Parse Error : Parameter entities cannot be used inside markup declarations in an internal subset. + SXS_XML_E_BADPEREFINSUBSET = 0x36EB, + // Manifest Parse Error : Element was not closed. + SXS_XML_E_UNCLOSEDSTARTTAG = 0x36EC, + // Manifest Parse Error : End element was missing the character '>'. + SXS_XML_E_UNCLOSEDENDTAG = 0x36ED, + // Manifest Parse Error : A string literal was not closed. + SXS_XML_E_UNCLOSEDSTRING = 0x36EE, + // Manifest Parse Error : A comment was not closed. + SXS_XML_E_UNCLOSEDCOMMENT = 0x36EF, + // Manifest Parse Error : A declaration was not closed. + SXS_XML_E_UNCLOSEDDECL = 0x36F0, + // Manifest Parse Error : A CDATA section was not closed. + SXS_XML_E_UNCLOSEDCDATA = 0x36F1, + // Manifest Parse Error : The namespace prefix is not allowed to start with the reserved string "xml". + SXS_XML_E_RESERVEDNAMESPACE = 0x36F2, + // Manifest Parse Error : System does not support the specified encoding. + SXS_XML_E_INVALIDENCODING = 0x36F3, + // Manifest Parse Error : Switch from current encoding to specified encoding not supported. + SXS_XML_E_INVALIDSWITCH = 0x36F4, + // Manifest Parse Error : The name 'xml' is reserved and must be lower case. + SXS_XML_E_BADXMLCASE = 0x36F5, + // Manifest Parse Error : The standalone attribute must have the value 'yes' or 'no'. + SXS_XML_E_INVALID_STANDALONE = 0x36F6, + // Manifest Parse Error : The standalone attribute cannot be used in external entities. + SXS_XML_E_UNEXPECTED_STANDALONE = 0x36F7, + // Manifest Parse Error : Invalid version number. + SXS_XML_E_INVALID_VERSION = 0x36F8, + // Manifest Parse Error : Missing equals sign between attribute and attribute value. + SXS_XML_E_MISSINGEQUALS = 0x36F9, + // Assembly Protection Error : Unable to recover the specified assembly. + SXS_PROTECTION_RECOVERY_FAILED = 0x36FA, + // Assembly Protection Error : The public key for an assembly was too short to be allowed. + SXS_PROTECTION_PUBLIC_KEY_TOO_SHORT = 0x36FB, + // Assembly Protection Error : The catalog for an assembly is not valid, or does not match the assembly's manifest. + SXS_PROTECTION_CATALOG_NOT_VALID = 0x36FC, + // An HRESULT could not be translated to a corresponding Win32 error code. + SXS_UNTRANSLATABLE_HRESULT = 0x36FD, + // Assembly Protection Error : The catalog for an assembly is missing. + SXS_PROTECTION_CATALOG_FILE_MISSING = 0x36FE, + // The supplied assembly identity is missing one or more attributes which must be present in this context. + SXS_MISSING_ASSEMBLY_IDENTITY_ATTRIBUTE = 0x36FF, + // The supplied assembly identity has one or more attribute names that contain characters not permitted in XML names. + SXS_INVALID_ASSEMBLY_IDENTITY_ATTRIBUTE_NAME = 0x3700, + // The referenced assembly could not be found. + SXS_ASSEMBLY_MISSING = 0x3701, + // The activation context activation stack for the running thread of execution is corrupt. + SXS_CORRUPT_ACTIVATION_STACK = 0x3702, + // The application isolation metadata for this process or thread has become corrupt. + SXS_CORRUPTION = 0x3703, + // The activation context being deactivated is not the most recently activated one. + SXS_EARLY_DEACTIVATION = 0x3704, + // The activation context being deactivated is not active for the current thread of execution. + SXS_INVALID_DEACTIVATION = 0x3705, + // The activation context being deactivated has already been deactivated. + SXS_MULTIPLE_DEACTIVATION = 0x3706, + // A component used by the isolation facility has requested to terminate the process. + SXS_PROCESS_TERMINATION_REQUESTED = 0x3707, + // A kernel mode component is releasing a reference on an activation context. + SXS_RELEASE_ACTIVATION_CONTEXT = 0x3708, + // The activation context of system default assembly could not be generated. + SXS_SYSTEM_DEFAULT_ACTIVATION_CONTEXT_EMPTY = 0x3709, + // The value of an attribute in an identity is not within the legal range. + SXS_INVALID_IDENTITY_ATTRIBUTE_VALUE = 0x370A, + // The name of an attribute in an identity is not within the legal range. + SXS_INVALID_IDENTITY_ATTRIBUTE_NAME = 0x370B, + // An identity contains two definitions for the same attribute. + SXS_IDENTITY_DUPLICATE_ATTRIBUTE = 0x370C, + // The identity string is malformed. This may be due to a trailing comma, more than two unnamed attributes, missing attribute name or missing attribute value. + SXS_IDENTITY_PARSE_ERROR = 0x370D, + // A string containing localized substitutable content was malformed. Either a dollar sign ($) was followed by something other than a left parenthesis or another dollar sign or an substitution's right parenthesis was not found. + MALFORMED_SUBSTITUTION_STRING = 0x370E, + // The public key token does not correspond to the public key specified. + SXS_INCORRECT_PUBLIC_KEY_TOKEN = 0x370F, + // A substitution string had no mapping. + UNMAPPED_SUBSTITUTION_STRING = 0x3710, + // The component must be locked before making the request. + SXS_ASSEMBLY_NOT_LOCKED = 0x3711, + // The component store has been corrupted. + SXS_COMPONENT_STORE_CORRUPT = 0x3712, + // An advanced installer failed during setup or servicing. + ADVANCED_INSTALLER_FAILED = 0x3713, + // The character encoding in the XML declaration did not match the encoding used in the document. + XML_ENCODING_MISMATCH = 0x3714, + // The identities of the manifests are identical but their contents are different. + SXS_MANIFEST_IDENTITY_SAME_BUT_CONTENTS_DIFFERENT = 0x3715, + // The component identities are different. + SXS_IDENTITIES_DIFFERENT = 0x3716, + // The assembly is not a deployment. + SXS_ASSEMBLY_IS_NOT_A_DEPLOYMENT = 0x3717, + // The file is not a part of the assembly. + SXS_FILE_NOT_PART_OF_ASSEMBLY = 0x3718, + // The size of the manifest exceeds the maximum allowed. + SXS_MANIFEST_TOO_BIG = 0x3719, + // The setting is not registered. + SXS_SETTING_NOT_REGISTERED = 0x371A, + // One or more required members of the transaction are not present. + SXS_TRANSACTION_CLOSURE_INCOMPLETE = 0x371B, + // The SMI primitive installer failed during setup or servicing. + SMI_PRIMITIVE_INSTALLER_FAILED = 0x371C, + // A generic command executable returned a result that indicates failure. + GENERIC_COMMAND_FAILED = 0x371D, + // A component is missing file verification information in its manifest. + SXS_FILE_HASH_MISSING = 0x371E, + // The specified channel path is invalid. + EVT_INVALID_CHANNEL_PATH = 0x3A98, + // The specified query is invalid. + EVT_INVALID_QUERY = 0x3A99, + // The publisher metadata cannot be found in the resource. + EVT_PUBLISHER_METADATA_NOT_FOUND = 0x3A9A, + // The template for an event definition cannot be found in the resource (error = %1). + EVT_EVENT_TEMPLATE_NOT_FOUND = 0x3A9B, + // The specified publisher name is invalid. + EVT_INVALID_PUBLISHER_NAME = 0x3A9C, + // The event data raised by the publisher is not compatible with the event template definition in the publisher's manifest. + EVT_INVALID_EVENT_DATA = 0x3A9D, + // The specified channel could not be found. Check channel configuration. + EVT_CHANNEL_NOT_FOUND = 0x3A9F, + // The specified xml text was not well-formed. See Extended Error for more details. + EVT_MALFORMED_XML_TEXT = 0x3AA0, + // The caller is trying to subscribe to a direct channel which is not allowed. The events for a direct channel go directly to a logfile and cannot be subscribed to. + EVT_SUBSCRIPTION_TO_DIRECT_CHANNEL = 0x3AA1, + // Configuration error. + EVT_CONFIGURATION_ERROR = 0x3AA2, + // The query result is stale / invalid. This may be due to the log being cleared or rolling over after the query result was created. Users should handle this code by releasing the query result object and reissuing the query. + EVT_QUERY_RESULT_STALE = 0x3AA3, + // Query result is currently at an invalid position. + EVT_QUERY_RESULT_INVALID_POSITION = 0x3AA4, + // Registered MSXML doesn't support validation. + EVT_NON_VALIDATING_MSXML = 0x3AA5, + // An expression can only be followed by a change of scope operation if it itself evaluates to a node set and is not already part of some other change of scope operation. + EVT_FILTER_ALREADYSCOPED = 0x3AA6, + // Can't perform a step operation from a term that does not represent an element set. + EVT_FILTER_NOTELTSET = 0x3AA7, + // Left hand side arguments to binary operators must be either attributes, nodes or variables and right hand side arguments must be constants. + EVT_FILTER_INVARG = 0x3AA8, + // A step operation must involve either a node test or, in the case of a predicate, an algebraic expression against which to test each node in the node set identified by the preceeding node set can be evaluated. + EVT_FILTER_INVTEST = 0x3AA9, + // This data type is currently unsupported. + EVT_FILTER_INVTYPE = 0x3AAA, + // A syntax error occurred at position %1!d!. + EVT_FILTER_PARSEERR = 0x3AAB, + // This operator is unsupported by this implementation of the filter. + EVT_FILTER_UNSUPPORTEDOP = 0x3AAC, + // The token encountered was unexpected. + EVT_FILTER_UNEXPECTEDTOKEN = 0x3AAD, + // The requested operation cannot be performed over an enabled direct channel. The channel must first be disabled before performing the requested operation. + EVT_INVALID_OPERATION_OVER_ENABLED_DIRECT_CHANNEL = 0x3AAE, + // Channel property %1!s! contains invalid value. The value has invalid type, is outside of valid range, can't be updated or is not supported by this type of channel. + EVT_INVALID_CHANNEL_PROPERTY_VALUE = 0x3AAF, + // Publisher property %1!s! contains invalid value. The value has invalid type, is outside of valid range, can't be updated or is not supported by this type of publisher. + EVT_INVALID_PUBLISHER_PROPERTY_VALUE = 0x3AB0, + // The channel fails to activate. + EVT_CHANNEL_CANNOT_ACTIVATE = 0x3AB1, + // The xpath expression exceeded supported complexity. Please symplify it or split it into two or more simple expressions. + EVT_FILTER_TOO_COMPLEX = 0x3AB2, + // the message resource is present but the message is not found in the string/message table. + EVT_MESSAGE_NOT_FOUND = 0x3AB3, + // The message id for the desired message could not be found. + EVT_MESSAGE_ID_NOT_FOUND = 0x3AB4, + // The substitution string for insert index (%1) could not be found. + EVT_UNRESOLVED_VALUE_INSERT = 0x3AB5, + // The description string for parameter reference (%1) could not be found. + EVT_UNRESOLVED_PARAMETER_INSERT = 0x3AB6, + // The maximum number of replacements has been reached. + EVT_MAX_INSERTS_REACHED = 0x3AB7, + // The event definition could not be found for event id (%1). + EVT_EVENT_DEFINITION_NOT_FOUND = 0x3AB8, + // The locale specific resource for the desired message is not present. + EVT_MESSAGE_LOCALE_NOT_FOUND = 0x3AB9, + // The resource is too old to be compatible. + EVT_VERSION_TOO_OLD = 0x3ABA, + // The resource is too new to be compatible. + EVT_VERSION_TOO_NEW = 0x3ABB, + // The channel at index %1!d! of the query can't be opened. + EVT_CANNOT_OPEN_CHANNEL_OF_QUERY = 0x3ABC, + // The publisher has been disabled and its resource is not available. This usually occurs when the publisher is in the process of being uninstalled or upgraded. + EVT_PUBLISHER_DISABLED = 0x3ABD, + // Attempted to create a numeric type that is outside of its valid range. + EVT_FILTER_OUT_OF_RANGE = 0x3ABE, + // The subscription fails to activate. + EC_SUBSCRIPTION_CANNOT_ACTIVATE = 0x3AE8, + // The log of the subscription is in disabled state, and cannot be used to forward events to. The log must first be enabled before the subscription can be activated. + EC_LOG_DISABLED = 0x3AE9, + // When forwarding events from local machine to itself, the query of the subscription can't contain target log of the subscription. + EC_CIRCULAR_FORWARDING = 0x3AEA, + // The credential store that is used to save credentials is full. + EC_CREDSTORE_FULL = 0x3AEB, + // The credential used by this subscription can't be found in credential store. + EC_CRED_NOT_FOUND = 0x3AEC, + // No active channel is found for the query. + EC_NO_ACTIVE_CHANNEL = 0x3AED, + // The resource loader failed to find MUI file. + MUI_FILE_NOT_FOUND = 0x3AFC, + // The resource loader failed to load MUI file because the file fail to pass validation. + MUI_INVALID_FILE = 0x3AFD, + // The RC Manifest is corrupted with garbage data or unsupported version or missing required item. + MUI_INVALID_RC_CONFIG = 0x3AFE, + // The RC Manifest has invalid culture name. + MUI_INVALID_LOCALE_NAME = 0x3AFF, + // The RC Manifest has invalid ultimatefallback name. + MUI_INVALID_ULTIMATEFALLBACK_NAME = 0x3B00, + // The resource loader cache doesn't have loaded MUI entry. + MUI_FILE_NOT_LOADED = 0x3B01, + // User stopped resource enumeration. + RESOURCE_ENUM_USER_STOP = 0x3B02, + // UI language installation failed. + MUI_INTLSETTINGS_UILANG_NOT_INSTALLED = 0x3B03, + // Locale installation failed. + MUI_INTLSETTINGS_INVALID_LOCALE_NAME = 0x3B04, + // A resource does not have default or neutral value. + MRM_RUNTIME_NO_DEFAULT_OR_NEUTRAL_RESOURCE = 0x3B06, + // Invalid PRI config file. + MRM_INVALID_PRICONFIG = 0x3B07, + // Invalid file type. + MRM_INVALID_FILE_TYPE = 0x3B08, + // Unknown qualifier. + MRM_UNKNOWN_QUALIFIER = 0x3B09, + // Invalid qualifier value. + MRM_INVALID_QUALIFIER_VALUE = 0x3B0A, + // No Candidate found. + MRM_NO_CANDIDATE = 0x3B0B, + // The ResourceMap or NamedResource has an item that does not have default or neutral resource.. + MRM_NO_MATCH_OR_DEFAULT_CANDIDATE = 0x3B0C, + // Invalid ResourceCandidate type. + MRM_RESOURCE_TYPE_MISMATCH = 0x3B0D, + // Duplicate Resource Map. + MRM_DUPLICATE_MAP_NAME = 0x3B0E, + // Duplicate Entry. + MRM_DUPLICATE_ENTRY = 0x3B0F, + // Invalid Resource Identifier. + MRM_INVALID_RESOURCE_IDENTIFIER = 0x3B10, + // Filepath too long. + MRM_FILEPATH_TOO_LONG = 0x3B11, + // Unsupported directory type. + MRM_UNSUPPORTED_DIRECTORY_TYPE = 0x3B12, + // Invalid PRI File. + MRM_INVALID_PRI_FILE = 0x3B16, + // NamedResource Not Found. + MRM_NAMED_RESOURCE_NOT_FOUND = 0x3B17, + // ResourceMap Not Found. + MRM_MAP_NOT_FOUND = 0x3B1F, + // Unsupported MRT profile type. + MRM_UNSUPPORTED_PROFILE_TYPE = 0x3B20, + // Invalid qualifier operator. + MRM_INVALID_QUALIFIER_OPERATOR = 0x3B21, + // Unable to determine qualifier value or qualifier value has not been set. + MRM_INDETERMINATE_QUALIFIER_VALUE = 0x3B22, + // Automerge is enabled in the PRI file. + MRM_AUTOMERGE_ENABLED = 0x3B23, + // Too many resources defined for package. + MRM_TOO_MANY_RESOURCES = 0x3B24, + // The monitor returned a DDC/CI capabilities string that did not comply with the ACCESS.bus 3.0, DDC/CI 1.1 or MCCS 2 Revision 1 specification. + MCA_INVALID_CAPABILITIES_STRING = 0x3B60, + // The monitor's VCP Version (0xDF) VCP code returned an invalid version value. + MCA_INVALID_VCP_VERSION = 0x3B61, + // The monitor does not comply with the MCCS specification it claims to support. + MCA_MONITOR_VIOLATES_MCCS_SPECIFICATION = 0x3B62, + // The MCCS version in a monitor's mccs_ver capability does not match the MCCS version the monitor reports when the VCP Version (0xDF) VCP code is used. + MCA_MCCS_VERSION_MISMATCH = 0x3B63, + // The Monitor Configuration API only works with monitors that support the MCCS 1.0 specification, MCCS 2.0 specification or the MCCS 2.0 Revision 1 specification. + MCA_UNSUPPORTED_MCCS_VERSION = 0x3B64, + // An internal Monitor Configuration API error occurred. + MCA_INTERNAL_ERROR = 0x3B65, + // The monitor returned an invalid monitor technology type. CRT, Plasma and LCD (TFT) are examples of monitor technology types. This error implies that the monitor violated the MCCS 2.0 or MCCS 2.0 Revision 1 specification. + MCA_INVALID_TECHNOLOGY_TYPE_RETURNED = 0x3B66, + // The caller of SetMonitorColorTemperature specified a color temperature that the current monitor did not support. This error implies that the monitor violated the MCCS 2.0 or MCCS 2.0 Revision 1 specification. + MCA_UNSUPPORTED_COLOR_TEMPERATURE = 0x3B67, + // The requested system device cannot be identified due to multiple indistinguishable devices potentially matching the identification criteria. + AMBIGUOUS_SYSTEM_DEVICE = 0x3B92, + // The requested system device cannot be found. + SYSTEM_DEVICE_NOT_FOUND = 0x3BC3, + // Hash generation for the specified hash version and hash type is not enabled on the server. + HASH_NOT_SUPPORTED = 0x3BC4, + // The hash requested from the server is not available or no longer valid. + HASH_NOT_PRESENT = 0x3BC5, + // The secondary interrupt controller instance that manages the specified interrupt is not registered. + SECONDARY_IC_PROVIDER_NOT_REGISTERED = 0x3BD9, + // The information supplied by the GPIO client driver is invalid. + GPIO_CLIENT_INFORMATION_INVALID = 0x3BDA, + // The version specified by the GPIO client driver is not supported. + GPIO_VERSION_NOT_SUPPORTED = 0x3BDB, + // The registration packet supplied by the GPIO client driver is not valid. + GPIO_INVALID_REGISTRATION_PACKET = 0x3BDC, + // The requested operation is not supported for the specified handle. + GPIO_OPERATION_DENIED = 0x3BDD, + // The requested connect mode conflicts with an existing mode on one or more of the specified pins. + GPIO_INCOMPATIBLE_CONNECT_MODE = 0x3BDE, + // The interrupt requested to be unmasked is not masked. + GPIO_INTERRUPT_ALREADY_UNMASKED = 0x3BDF, + // The requested run level switch cannot be completed successfully. + CANNOT_SWITCH_RUNLEVEL = 0x3C28, + // The service has an invalid run level setting. The run level for a service must not be higher than the run level of its dependent services. + INVALID_RUNLEVEL_SETTING = 0x3C29, + // The requested run level switch cannot be completed successfully since one or more services will not stop or restart within the specified timeout. + RUNLEVEL_SWITCH_TIMEOUT = 0x3C2A, + // A run level switch agent did not respond within the specified timeout. + RUNLEVEL_SWITCH_AGENT_TIMEOUT = 0x3C2B, + // A run level switch is currently in progress. + RUNLEVEL_SWITCH_IN_PROGRESS = 0x3C2C, + // One or more services failed to start during the service startup phase of a run level switch. + SERVICES_FAILED_AUTOSTART = 0x3C2D, + // The task stop request cannot be completed immediately since task needs more time to shutdown. + COM_TASK_STOP_PENDING = 0x3C8D, + // Package could not be opened. + INSTALL_OPEN_PACKAGE_FAILED = 0x3CF0, + // Package was not found. + INSTALL_PACKAGE_NOT_FOUND = 0x3CF1, + // Package data is invalid. + INSTALL_INVALID_PACKAGE = 0x3CF2, + // Package failed updates, dependency or conflict validation. + INSTALL_RESOLVE_DEPENDENCY_FAILED = 0x3CF3, + // There is not enough disk space on your computer. Please free up some space and try again. + INSTALL_OUT_OF_DISK_SPACE = 0x3CF4, + // There was a problem downloading your product. + INSTALL_NETWORK_FAILURE = 0x3CF5, + // Package could not be registered. + INSTALL_REGISTRATION_FAILURE = 0x3CF6, + // Package could not be unregistered. + INSTALL_DEREGISTRATION_FAILURE = 0x3CF7, + // User cancelled the install request. + INSTALL_CANCEL = 0x3CF8, + // Install failed. Please contact your software vendor. + INSTALL_FAILED = 0x3CF9, + // Removal failed. Please contact your software vendor. + REMOVE_FAILED = 0x3CFA, + // The provided package is already installed, and reinstallation of the package was blocked. Check the AppXDeployment-Server event log for details. + PACKAGE_ALREADY_EXISTS = 0x3CFB, + // The application cannot be started. Try reinstalling the application to fix the problem. + NEEDS_REMEDIATION = 0x3CFC, + // A Prerequisite for an install could not be satisfied. + INSTALL_PREREQUISITE_FAILED = 0x3CFD, + // The package repository is corrupted. + PACKAGE_REPOSITORY_CORRUPTED = 0x3CFE, + // To install this application you need either a Windows developer license or a sideloading-enabled system. + INSTALL_POLICY_FAILURE = 0x3CFF, + // The application cannot be started because it is currently updating. + PACKAGE_UPDATING = 0x3D00, + // The package deployment operation is blocked by policy. Please contact your system administrator. + DEPLOYMENT_BLOCKED_BY_POLICY = 0x3D01, + // The package could not be installed because resources it modifies are currently in use. + PACKAGES_IN_USE = 0x3D02, + // The package could not be recovered because necessary data for recovery have been corrupted. + RECOVERY_FILE_CORRUPT = 0x3D03, + // The signature is invalid. To register in developer mode, AppxSignature.p7x and AppxBlockMap.xml must be valid or should not be present. + INVALID_STAGED_SIGNATURE = 0x3D04, + // An error occurred while deleting the package's previously existing application data. + DELETING_EXISTING_APPLICATIONDATA_STORE_FAILED = 0x3D05, + // The package could not be installed because a higher version of this package is already installed. + INSTALL_PACKAGE_DOWNGRADE = 0x3D06, + // An error in a system binary was detected. Try refreshing the PC to fix the problem. + SYSTEM_NEEDS_REMEDIATION = 0x3D07, + // A corrupted CLR NGEN binary was detected on the system. + APPX_INTEGRITY_FAILURE_CLR_NGEN = 0x3D08, + // The operation could not be resumed because necessary data for recovery have been corrupted. + RESILIENCY_FILE_CORRUPT = 0x3D09, + // The package could not be installed because the Windows Firewall service is not running. Enable the Windows Firewall service and try again. + INSTALL_FIREWALL_SERVICE_NOT_RUNNING = 0x3D0A, + // The process has no package identity. + APPMODEL_ERROR_NO_PACKAGE = 0x3D54, + // The package runtime information is corrupted. + APPMODEL_ERROR_PACKAGE_RUNTIME_CORRUPT = 0x3D55, + // The package identity is corrupted. + APPMODEL_ERROR_PACKAGE_IDENTITY_CORRUPT = 0x3D56, + // The process has no application identity. + APPMODEL_ERROR_NO_APPLICATION = 0x3D57, + // Loading the state store failed. + STATE_LOAD_STORE_FAILED = 0x3DB8, + // Retrieving the state version for the application failed. + STATE_GET_VERSION_FAILED = 0x3DB9, + // Setting the state version for the application failed. + STATE_SET_VERSION_FAILED = 0x3DBA, + // Resetting the structured state of the application failed. + STATE_STRUCTURED_RESET_FAILED = 0x3DBB, + // State Manager failed to open the container. + STATE_OPEN_CONTAINER_FAILED = 0x3DBC, + // State Manager failed to create the container. + STATE_CREATE_CONTAINER_FAILED = 0x3DBD, + // State Manager failed to delete the container. + STATE_DELETE_CONTAINER_FAILED = 0x3DBE, + // State Manager failed to read the setting. + STATE_READ_SETTING_FAILED = 0x3DBF, + // State Manager failed to write the setting. + STATE_WRITE_SETTING_FAILED = 0x3DC0, + // State Manager failed to delete the setting. + STATE_DELETE_SETTING_FAILED = 0x3DC1, + // State Manager failed to query the setting. + STATE_QUERY_SETTING_FAILED = 0x3DC2, + // State Manager failed to read the composite setting. + STATE_READ_COMPOSITE_SETTING_FAILED = 0x3DC3, + // State Manager failed to write the composite setting. + STATE_WRITE_COMPOSITE_SETTING_FAILED = 0x3DC4, + // State Manager failed to enumerate the containers. + STATE_ENUMERATE_CONTAINER_FAILED = 0x3DC5, + // State Manager failed to enumerate the settings. + STATE_ENUMERATE_SETTINGS_FAILED = 0x3DC6, + // The size of the state manager composite setting value has exceeded the limit. + STATE_COMPOSITE_SETTING_VALUE_SIZE_LIMIT_EXCEEDED = 0x3DC7, + // The size of the state manager setting value has exceeded the limit. + STATE_SETTING_VALUE_SIZE_LIMIT_EXCEEDED = 0x3DC8, + // The length of the state manager setting name has exceeded the limit. + STATE_SETTING_NAME_SIZE_LIMIT_EXCEEDED = 0x3DC9, + // The length of the state manager container name has exceeded the limit. + STATE_CONTAINER_NAME_SIZE_LIMIT_EXCEEDED = 0x3DCA, + // This API cannot be used in the context of the caller's application type. + API_UNAVAILABLE = 0x3DE1, +} diff --git a/core/sys/windows/winmm.odin b/core/sys/windows/winmm.odin index 445470f6e..3c7ec80e7 100644 --- a/core/sys/windows/winmm.odin +++ b/core/sys/windows/winmm.odin @@ -1,16 +1,42 @@ -// +build windows +#+build windows package sys_windows foreign import winmm "system:Winmm.lib" MMRESULT :: UINT -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign winmm { timeGetDevCaps :: proc(ptc: LPTIMECAPS, cbtc: UINT) -> MMRESULT --- timeBeginPeriod :: proc(uPeriod: UINT) -> MMRESULT --- timeEndPeriod :: proc(uPeriod: UINT) -> MMRESULT --- timeGetTime :: proc() -> DWORD --- + + waveOutGetNumDevs :: proc() -> UINT --- + waveOutGetDevCapsW :: proc(uDeviceID: UINT_PTR, pwoc: LPWAVEOUTCAPSW, cbwoc: UINT) -> MMRESULT --- + waveOutGetVolume :: proc(hwo: HWAVEOUT, pdwVolume: LPDWORD) -> MMRESULT --- + waveOutSetVolume :: proc(hwo: HWAVEOUT, dwVolume: DWORD) -> MMRESULT --- + waveOutGetErrorTextW :: proc(mmrError: MMRESULT, pszText: LPWSTR, cchText: UINT) -> MMRESULT --- + waveOutOpen :: proc(phwo: LPHWAVEOUT, uDeviceID: UINT, pwfx: LPCWAVEFORMATEX, dwCallback: DWORD_PTR, dwInstance: DWORD_PTR, fdwOpen: DWORD) -> MMRESULT --- + waveOutClose :: proc(hwo: HWAVEOUT) -> MMRESULT --- + waveOutPrepareHeader :: proc(hwo: HWAVEOUT, pwh: LPWAVEHDR, cbwh: UINT) -> MMRESULT --- + waveOutUnprepareHeader :: proc(hwo: HWAVEOUT, pwh: LPWAVEHDR, cbwh: UINT) -> MMRESULT --- + waveOutWrite :: proc(hwo: HWAVEOUT, pwh: LPWAVEHDR, cbwh: UINT) -> MMRESULT --- + waveOutPause :: proc(hwo: HWAVEOUT) -> MMRESULT --- + waveOutRestart :: proc(hwo: HWAVEOUT) -> MMRESULT --- + waveOutReset :: proc(hwo: HWAVEOUT) -> MMRESULT --- + waveOutBreakLoop :: proc(hwo: HWAVEOUT) -> MMRESULT --- + waveOutGetPosition :: proc(hwo: HWAVEOUT, pmmt: LPMMTIME, cbmmt: UINT) -> MMRESULT --- + waveOutGetPitch :: proc(hwo: HWAVEOUT, pdwPitch: LPDWORD) -> MMRESULT --- + waveOutSetPitch :: proc(hwo: HWAVEOUT, pdwPitch: DWORD) -> MMRESULT --- + waveOutGetPlaybackRate :: proc(hwo: HWAVEOUT, pdwRate: LPDWORD) -> MMRESULT --- + waveOutSetPlaybackRate :: proc(hwo: HWAVEOUT, pdwRate: DWORD) -> MMRESULT --- + waveOutGetID :: proc(hwo: HWAVEOUT, puDeviceID: LPUINT) -> MMRESULT --- + + waveInGetNumDevs :: proc() -> UINT --- + waveInGetDevCapsW :: proc(uDeviceID: UINT_PTR, pwic: LPWAVEINCAPSW, cbwic: UINT) -> MMRESULT --- + + PlaySoundW :: proc(pszSound: LPCWSTR, hmod: HMODULE, fdwSound: DWORD) -> BOOL --- } LPTIMECAPS :: ^TIMECAPS @@ -169,4 +195,197 @@ MCIERR_NO_IDENTITY :: MCIERR_BASE + 94 MIXERR_INVALLINE :: (MIXERR_BASE + 0) MIXERR_INVALCONTROL :: (MIXERR_BASE + 1) MIXERR_INVALVALUE :: (MIXERR_BASE + 2) -MIXERR_LASTERROR :: (MIXERR_BASE + 2) \ No newline at end of file +MIXERR_LASTERROR :: (MIXERR_BASE + 2) + +/* waveform output */ +MM_WOM_OPEN :: 0x3BB +MM_WOM_CLOSE :: 0x3BC +MM_WOM_DONE :: 0x3BD +/* waveform input */ +MM_WIM_OPEN :: 0x3BE +MM_WIM_CLOSE :: 0x3BF +MM_WIM_DATA :: 0x3C0 + +WOM_OPEN :: MM_WOM_OPEN +WOM_CLOSE :: MM_WOM_CLOSE +WOM_DONE :: MM_WOM_DONE +WIM_OPEN :: MM_WIM_OPEN +WIM_CLOSE :: MM_WIM_CLOSE +WIM_DATA :: MM_WIM_DATA + +WAVE_MAPPER : UINT : 0xFFFFFFFF // -1 + +WAVE_FORMAT_QUERY :: 0x0001 +WAVE_ALLOWSYNC :: 0x0002 +WAVE_MAPPED :: 0x0004 +WAVE_FORMAT_DIRECT :: 0x0008 +WAVE_FORMAT_DIRECT_QUERY :: (WAVE_FORMAT_QUERY | WAVE_FORMAT_DIRECT) +WAVE_MAPPED_DEFAULT_COMMUNICATION_DEVICE :: 0x0010 + +WHDR_DONE :: 0x00000001 /* done bit */ +WHDR_PREPARED :: 0x00000002 /* set if this header has been prepared */ +WHDR_BEGINLOOP :: 0x00000004 /* loop start block */ +WHDR_ENDLOOP :: 0x00000008 /* loop end block */ +WHDR_INQUEUE :: 0x00000010 /* reserved for driver */ + +WAVECAPS_PITCH :: 0x0001 /* supports pitch control */ +WAVECAPS_PLAYBACKRATE :: 0x0002 /* supports playback rate control */ +WAVECAPS_VOLUME :: 0x0004 /* supports volume control */ +WAVECAPS_LRVOLUME :: 0x0008 /* separate left-right volume control */ +WAVECAPS_SYNC :: 0x0010 +WAVECAPS_SAMPLEACCURATE :: 0x0020 + +WAVE_INVALIDFORMAT :: 0x00000000 /* invalid format */ +WAVE_FORMAT_1M08 :: 0x00000001 /* 11.025 kHz, Mono, 8-bit */ +WAVE_FORMAT_1S08 :: 0x00000002 /* 11.025 kHz, Stereo, 8-bit */ +WAVE_FORMAT_1M16 :: 0x00000004 /* 11.025 kHz, Mono, 16-bit */ +WAVE_FORMAT_1S16 :: 0x00000008 /* 11.025 kHz, Stereo, 16-bit */ +WAVE_FORMAT_2M08 :: 0x00000010 /* 22.05 kHz, Mono, 8-bit */ +WAVE_FORMAT_2S08 :: 0x00000020 /* 22.05 kHz, Stereo, 8-bit */ +WAVE_FORMAT_2M16 :: 0x00000040 /* 22.05 kHz, Mono, 16-bit */ +WAVE_FORMAT_2S16 :: 0x00000080 /* 22.05 kHz, Stereo, 16-bit */ +WAVE_FORMAT_4M08 :: 0x00000100 /* 44.1 kHz, Mono, 8-bit */ +WAVE_FORMAT_4S08 :: 0x00000200 /* 44.1 kHz, Stereo, 8-bit */ +WAVE_FORMAT_4M16 :: 0x00000400 /* 44.1 kHz, Mono, 16-bit */ +WAVE_FORMAT_4S16 :: 0x00000800 /* 44.1 kHz, Stereo, 16-bit */ +WAVE_FORMAT_44M08 :: 0x00000100 /* 44.1 kHz, Mono, 8-bit */ +WAVE_FORMAT_44S08 :: 0x00000200 /* 44.1 kHz, Stereo, 8-bit */ +WAVE_FORMAT_44M16 :: 0x00000400 /* 44.1 kHz, Mono, 16-bit */ +WAVE_FORMAT_44S16 :: 0x00000800 /* 44.1 kHz, Stereo, 16-bit */ +WAVE_FORMAT_48M08 :: 0x00001000 /* 48 kHz, Mono, 8-bit */ +WAVE_FORMAT_48S08 :: 0x00002000 /* 48 kHz, Stereo, 8-bit */ +WAVE_FORMAT_48M16 :: 0x00004000 /* 48 kHz, Mono, 16-bit */ +WAVE_FORMAT_48S16 :: 0x00008000 /* 48 kHz, Stereo, 16-bit */ +WAVE_FORMAT_96M08 :: 0x00010000 /* 96 kHz, Mono, 8-bit */ +WAVE_FORMAT_96S08 :: 0x00020000 /* 96 kHz, Stereo, 8-bit */ +WAVE_FORMAT_96M16 :: 0x00040000 /* 96 kHz, Mono, 16-bit */ +WAVE_FORMAT_96S16 :: 0x00080000 /* 96 kHz, Stereo, 16-bit */ + +HWAVE :: distinct HANDLE +HWAVEIN :: distinct HANDLE +HWAVEOUT :: distinct HANDLE + +LPHWAVEIN :: ^HWAVEIN +LPHWAVEOUT :: ^HWAVEOUT + +// https://learn.microsoft.com/en-us/windows/win32/multimedia/multimedia-timer-structures +MMTIME :: struct { + wType: MMTIME_TYPE, + u: struct #raw_union { + ms: DWORD, + sample: DWORD, + cb: DWORD, + ticks: DWORD, + smpte: struct { + hour: BYTE, + min: BYTE, + sec: BYTE, + frame: BYTE, + fps: BYTE, + dummy: BYTE, + pad: [2]BYTE, + }, + midi: struct { + songptrpos: DWORD, + }, + }, +} +LPMMTIME :: ^MMTIME + +MMTIME_TYPE :: enum UINT { + /* time in milliseconds */ + TIME_MS = 0x0001, + /* number of wave samples */ + TIME_SAMPLES = 0x0002, + /* current byte offset */ + TIME_BYTES = 0x0004, + /* SMPTE time */ + TIME_SMPTE = 0x0008, + /* MIDI time */ + TIME_MIDI = 0x0010, + /* Ticks within MIDI stream */ + TIME_TICKS = 0x0020, +} + +MAXPNAMELEN :: 32 +MAXERRORLENGTH :: 256 +MMVERSION :: UINT + +/* flags for wFormatTag field of WAVEFORMAT */ +WAVE_FORMAT_PCM :: 1 + +WAVEFORMATEX :: struct { + wFormatTag: WORD, + nChannels: WORD, + nSamplesPerSec: DWORD, + nAvgBytesPerSec: DWORD, + nBlockAlign: WORD, + wBitsPerSample: WORD, + cbSize: WORD, +} +LPCWAVEFORMATEX :: ^WAVEFORMATEX + +WAVEHDR :: struct { + lpData: LPSTR, /* pointer to locked data buffer */ + dwBufferLength: DWORD, /* length of data buffer */ + dwBytesRecorded: DWORD, /* used for input only */ + dwUser: DWORD_PTR, /* for client's use */ + dwFlags: DWORD, /* assorted flags (see defines) */ + dwLoops: DWORD, /* loop control counter */ + lpNext: LPWAVEHDR, /* reserved for driver */ + reserved: DWORD_PTR, /* reserved for driver */ +} +LPWAVEHDR :: ^WAVEHDR + +WAVEINCAPSW :: struct { + wMid: WORD, /* manufacturer ID */ + wPid: WORD, /* product ID */ + vDriverVersion: MMVERSION, /* version of the driver */ + szPname: [MAXPNAMELEN]WCHAR, /* product name (NULL terminated string) */ + dwFormats: DWORD, /* formats supported */ + wChannels: WORD, /* number of channels supported */ + wReserved1: WORD, /* structure packing */ +} +LPWAVEINCAPSW :: ^WAVEINCAPSW + +WAVEOUTCAPSW :: struct { + wMid: WORD, /* manufacturer ID */ + wPid: WORD, /* product ID */ + vDriverVersion: MMVERSION, /* version of the driver */ + szPname: [MAXPNAMELEN]WCHAR, /* product name (NULL terminated string) */ + dwFormats: DWORD, /* formats supported */ + wChannels: WORD, /* number of sources supported */ + wReserved1: WORD, /* packing */ + dwSupport: DWORD, /* functionality supported by driver */ +} +LPWAVEOUTCAPSW :: ^WAVEOUTCAPSW + +// flag values for PlaySound +SND_SYNC :: 0x0000 /* play synchronously (default) */ +SND_ASYNC :: 0x0001 /* play asynchronously */ +SND_NODEFAULT :: 0x0002 /* silence (!default) if sound not found */ +SND_MEMORY :: 0x0004 /* pszSound points to a memory file */ +SND_LOOP :: 0x0008 /* loop the sound until next sndPlaySound */ +SND_NOSTOP :: 0x0010 /* don't stop any currently playing sound */ + +SND_NOWAIT :: 0x00002000 /* don't wait if the driver is busy */ +SND_ALIAS :: 0x00010000 /* name is a registry alias */ +SND_ALIAS_ID :: 0x00110000 /* alias is a predefined ID */ +SND_FILENAME :: 0x00020000 /* name is file name */ +SND_RESOURCE :: 0x00040004 /* name is resource name or atom */ + +SND_PURGE :: 0x0040 /* purge non-static events for task */ +SND_APPLICATION :: 0x0080 /* look for application specific association */ + +SND_SENTRY :: 0x00080000 /* Generate a SoundSentry event with this sound */ +SND_RING :: 0x00100000 /* Treat this as a "ring" from a communications app - don't duck me */ +SND_SYSTEM :: 0x00200000 /* Treat this as a system sound */ + + +CALLBACK_TYPEMASK :: 0x00070000 /* callback type mask */ +CALLBACK_NULL :: 0x00000000 /* no callback */ +CALLBACK_WINDOW :: 0x00010000 /* dwCallback is a HWND */ +CALLBACK_TASK :: 0x00020000 /* dwCallback is a HTASK */ +CALLBACK_FUNCTION :: 0x00030000 /* dwCallback is a FARPROC */ +CALLBACK_THREAD :: CALLBACK_TASK /* thread ID replaces 16 bit task */ +CALLBACK_EVENT :: 0x00050000 /* dwCallback is an EVENT Handle */ diff --git a/core/sys/windows/winnls.odin b/core/sys/windows/winnls.odin new file mode 100644 index 000000000..ffb2638d5 --- /dev/null +++ b/core/sys/windows/winnls.odin @@ -0,0 +1,31 @@ +#+build windows +package sys_windows + +LCTYPE :: distinct DWORD + +LOCALE_NAME_MAX_LENGTH :: 85 +LOCALE_NAME_USER_DEFAULT :: 0 +LOCALE_NAME_INVARIANT : wstring = L("") +LOCALE_NAME_SYSTEM_DEFAULT : wstring = L("!x-sys-default-locale") + +// String Length Maximums. +// 5 ranges, 2 bytes ea., 0 term. +MAX_LEADBYTES :: 12 +// single or double byte +MAX_DEFAULTCHAR :: 2 + +CPINFOEXW :: struct{ + // Maximum length, in bytes, of a character in the code page. + MaxCharSize: UINT, + // The default is usually the "?" character for the code page. + DefaultChar: [MAX_DEFAULTCHAR]BYTE, + // A fixed-length array of lead byte ranges, for which the number of lead byte ranges is variable. + LeadByte: [MAX_LEADBYTES]BYTE, + // The default is usually the "?" character or the katakana middle dot character. + UnicodeDefaultChar: WCHAR, + // Code page value. This value reflects the code page passed to the GetCPInfoEx function. + CodePage: CODEPAGE, + // Full name of the code page. + CodePageName: [MAX_PATH]WCHAR, +} +LPCPINFOEXW :: ^CPINFOEXW diff --git a/core/sys/windows/winver.odin b/core/sys/windows/winver.odin new file mode 100644 index 000000000..47751dab7 --- /dev/null +++ b/core/sys/windows/winver.odin @@ -0,0 +1,92 @@ +#+build windows +package sys_windows + +foreign import version "system:version.lib" + +@(default_calling_convention = "system") +foreign version { + GetFileVersionInfoSizeW :: proc(lpwstrFilename: LPCWSTR, lpdwHandle: LPDWORD) -> DWORD --- + GetFileVersionInfoW :: proc(lptstrFilename: LPCWSTR, dwHandle: DWORD, dwLen: DWORD, lpData: LPVOID) -> BOOL --- + + GetFileVersionInfoSizeExW :: proc(dwFlags: FILE_VER_GET_FLAGS, lpwstrFilename: LPCWSTR, lpdwHandle: LPDWORD) -> DWORD --- + GetFileVersionInfoExW :: proc(dwFlags: FILE_VER_GET_FLAGS, lpwstrFilename: LPCWSTR, dwHandle, dwLen: DWORD, lpData: LPVOID) -> DWORD --- + + VerLanguageNameW :: proc(wLang: DWORD, szLang: LPWSTR, cchLang: DWORD) -> DWORD --- + VerQueryValueW :: proc(pBlock: LPCVOID, lpSubBlock: LPCWSTR, lplpBuffer: ^LPVOID, puLen: PUINT) -> BOOL --- +} + +FILE_VER_GET :: enum DWORD {LOCALISED, NEUTRAL, PREFETCHED} +FILE_VER_GET_FLAGS :: bit_set[FILE_VER_GET; DWORD] + +/* ----- Symbols ----- */ +VS_FILE_INFO :: RT_VERSION +VS_VERSION_INFO :: 1 +VS_USER_DEFINED :: 100 + +VS_FFI_SIGNATURE : DWORD : 0xFEEF04BD + +VS_FFI_STRUCVERSION :: 0x00010000 +VS_FFI_FILEFLAGSMASK :: 0x0000003F + +/* ----- VS_VERSION.dwFileFlags ----- */ +VS_FILEFLAG :: enum DWORD { + DEBUG, + PRERELEASE, + PATCHED, + PRIVATEBUILD, + INFOINFERRED, + SPECIALBUILD, +} +VS_FILEFLAGS :: bit_set[VS_FILEFLAG;DWORD] + +/* ----- VS_VERSION.dwFileOS ----- */ +VOS :: enum WORD { + UNKNOWN = 0x0000, + DOS = 0x0001, + OS216 = 0x0002, + OS232 = 0x0003, + NT = 0x0004, + WINCE = 0x0005, +} +VOS2 :: enum WORD { + BASE = 0x0000, + WINDOWS16 = 0x0001, + PM16 = 0x0002, + PM32 = 0x0003, + WINDOWS32 = 0x0004, +} + +/* ----- VS_VERSION.dwFileType ----- */ +VFT :: enum DWORD { + UNKNOWN = 0x00000000, + APP = 0x00000001, + DLL = 0x00000002, + DRV = 0x00000003, + FONT = 0x00000004, + VXD = 0x00000005, + STATIC_LIB = 0x00000007, +} + +/* ----- VS_VERSION.dwFileSubtype for VFT_WINDOWS_DRV ----- */ +VFT2_WINDOWS_DRV :: enum DWORD { + UNKNOWN = 0x00000000, + DRV_PRINTER = 0x00000001, + DRV_KEYBOARD = 0x00000002, + DRV_LANGUAGE = 0x00000003, + DRV_DISPLAY = 0x00000004, + DRV_MOUSE = 0x00000005, + DRV_NETWORK = 0x00000006, + DRV_SYSTEM = 0x00000007, + DRV_INSTALLABLE = 0x00000008, + DRV_SOUND = 0x00000009, + DRV_COMM = 0x0000000A, + DRV_INPUTMETHOD = 0x0000000B, + DRV_VERSIONED_PRINTER = 0x0000000C, +} + +/* ----- VS_VERSION.dwFileSubtype for VFT_WINDOWS_FONT ----- */ +VFT2_WINDOWS_FONT :: enum DWORD { + FONT_RASTER = 0x00000001, + FONT_VECTOR = 0x00000002, + FONT_TRUETYPE = 0x00000003, +} diff --git a/core/sys/windows/wow64_apiset.odin b/core/sys/windows/wow64_apiset.odin new file mode 100644 index 000000000..3d29b786e --- /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/sys/windows/ws2_32.odin b/core/sys/windows/ws2_32.odin index 1b79be584..5b2952495 100644 --- a/core/sys/windows/ws2_32.odin +++ b/core/sys/windows/ws2_32.odin @@ -1,13 +1,13 @@ -// +build windows +#+build windows package sys_windows // Define flags to be used with the WSAAsyncSelect() call. -FD_READ :: 0x01 -FD_WRITE :: 0x02 -FD_OOB :: 0x04 -FD_ACCEPT :: 0x08 -FD_CONNECT :: 0x10 -FD_CLOSE :: 0x20 +FD_READ :: 0x01 +FD_WRITE :: 0x02 +FD_OOB :: 0x04 +FD_ACCEPT :: 0x08 +FD_CONNECT :: 0x10 +FD_CLOSE :: 0x20 FD_MAX_EVENTS :: 10 INADDR_LOOPBACK :: 0x7f000001 @@ -24,10 +24,10 @@ POLLERR :: 0x0001 POLLHUP :: 0x0002 POLLNVAL :: 0x0004 -WSA_POLLFD::struct{ - fd:SOCKET, - events:c_short, - revents:c_short, +WSA_POLLFD :: struct{ + fd: SOCKET, + events: c_short, + revents: c_short, } WSANETWORKEVENTS :: struct { @@ -37,16 +37,43 @@ WSANETWORKEVENTS :: struct { WSAEVENT :: HANDLE -WSAID_ACCEPTEX :: GUID{0xb5367df1, 0xcbac, 0x11cf, {0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92}} +WSAID_ACCEPTEX :: GUID{0xb5367df1, 0xcbac, 0x11cf, {0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92}} WSAID_GETACCEPTEXSOCKADDRS :: GUID{0xb5367df2, 0xcbac, 0x11cf, {0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92}} +WSAID_CONNECTX :: GUID{0x25a207b9, 0xddf3, 0x4660, {0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e}} + SIO_GET_EXTENSION_FUNCTION_POINTER :: IOC_INOUT | IOC_WS2 | 6 -IOC_OUT :: 0x40000000 -IOC_IN :: 0x80000000 + +IOC_OUT :: 0x40000000 +IOC_IN :: 0x80000000 IOC_INOUT :: (IOC_IN | IOC_OUT) -IOC_WS2 :: 0x08000000 +IOC_WS2 :: 0x08000000 + +SO_UPDATE_ACCEPT_CONTEXT :: 28683 + +LPFN_CONNECTEX :: #type proc "system" ( + s: SOCKET, + sockaddr: ^SOCKADDR_STORAGE_LH, + namelen: c_int, + lpSendBuffer: PVOID, + dwSendDataLength: DWORD, + lpdwBytesSent: LPDWORD, + lpOverlapped: LPOVERLAPPED, +) -> BOOL + +LPFN_ACCEPTEX :: #type proc "system" ( + sListenSocket: SOCKET, + sAcceptSocket: SOCKET, + lpOutputBuffer: PVOID, + dwReceiveDataLength: DWORD, + dwLocalAddressLength: DWORD, + dwRemoteAddressLength: DWORD, + lpdwBytesReceived: LPDWORD, + lpOverlapped: LPOVERLAPPED, +) -> BOOL + /* Example Load: - load_accept_ex :: proc(listener: SOCKET, fn_acceptex: rawptr) { + load_accept_ex :: proc(listener: SOCKET, fn_acceptex: ^LPFN_ACCEPTEX) { bytes: u32 guid_accept_ex := WSAID_ACCEPTEX rc := WSAIoctl(listener, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid_accept_ex, size_of(guid_accept_ex), @@ -56,7 +83,7 @@ Example Load: */ foreign import ws2_32 "system:Ws2_32.lib" -@(default_calling_convention="stdcall") +@(default_calling_convention="system") foreign ws2_32 { // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsastartup) WSAStartup :: proc(wVersionRequested: WORD, lpWSAData: LPWSADATA) -> c_int --- diff --git a/core/testing/events.odin b/core/testing/events.odin new file mode 100644 index 000000000..1a47e2d68 --- /dev/null +++ b/core/testing/events.odin @@ -0,0 +1,56 @@ +#+private +package testing + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Total rewrite. +*/ + +import "base:runtime" +import "core:sync/chan" +import "core:time" + +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..041489dab --- /dev/null +++ b/core/testing/logging.odin @@ -0,0 +1,89 @@ +#+private +package testing + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Ginger Bill: Initial implementation. + Feoramund: Total rewrite. +*/ + +import "base:runtime" +import "core:fmt" +import "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[:]) + + log.do_level_header(options, &buf, level) + log.do_time_header(options, &buf, at_time) + log.do_location_header(options, &buf, location) + + return fmt.aprintf("%s%s", strings.to_string(buf), text, allocator = allocator) +} diff --git a/core/testing/reporting.odin b/core/testing/reporting.odin new file mode 100644 index 000000000..6752cd79b --- /dev/null +++ b/core/testing/reporting.odin @@ -0,0 +1,337 @@ +#+private +package testing + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Total rewrite. +*/ + +import "base:runtime" +import "core:encoding/ansi" +import "core:fmt" +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..6b9d610ed 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -1,73 +1,952 @@ -//+private +#+private package testing +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Ginger Bill: Initial implementation. + Feoramund: Total rewrite. +*/ + +import "base:intrinsics" +import "base:runtime" +import "core:bytes" +import "core:encoding/ansi" +@require import "core:encoding/base64" +@require import "core:encoding/json" +import "core:fmt" import "core:io" +@require import "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) +// Treat memory leaks and bad frees as errors. +FAIL_ON_BAD_MEMORY : bool : #config(ODIN_TEST_FAIL_ON_BAD_MEMORY, false) +// Specify how much memory each thread allocator starts with. +PER_THREAD_MEMORY : int : #config(ODIN_TEST_THREAD_MEMORY, mem.ROLLBACK_STACK_DEFAULT_BLOCK_SIZE) +// Select a specific set of tests to run by name. +// 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_DEFAULT : string : "debug" when ODIN_DEBUG else "info" +LOG_LEVEL : string : #config(ODIN_TEST_LOG_LEVEL, LOG_LEVEL_DEFAULT) +// Show only the most necessary logging information. +USING_SHORT_LOGS : bool : #config(ODIN_TEST_SHORT_LOGS, false) +// Output a report of the tests to the given path. +JSON_REPORT : string : #config(ODIN_TEST_JSON_REPORT, "") + +get_log_level :: #force_inline proc() -> runtime.Logger_Level { + when 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\".") + } } + +JSON :: struct { + total: int, + success: int, + duration: time.Duration, + packages: map[string][dynamic]JSON_Test, +} + +JSON_Test :: struct { + success: bool, + name: string, +} + end_t :: proc(t: ^T) { for i := len(t.cleanups)-1; i >= 0; i -= 1 { - c := t.cleanups[i] + #no_bounds_check c := t.cleanups[i] + context = c.ctx c.procedure(c.user_data) } + + delete(t.cleanups) + t.cleanups = {} +} + +when TRACKING_MEMORY && FAIL_ON_BAD_MEMORY { + Task_Data :: struct { + it: Internal_Test, + t: T, + allocator_index: int, + tracking_allocator: ^mem.Tracking_Allocator, + } +} else { + Task_Data :: struct { + it: Internal_Test, + t: T, + allocator_index: int, + } +} + +Task_Timeout :: struct { + 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) + + when TRACKING_MEMORY && FAIL_ON_BAD_MEMORY { + // NOTE(Feoramund): The simplest way to handle treating memory failures + // as errors is to allow the test task runner to access the tracking + // allocator itself. + // + // This way, it's still able to send up a log message, which will be + // used in the end summary, and it can set the test state to `Failed` + // under the usual conditions. + // + // No outside intervention needed. + memory_leaks := len(data.tracking_allocator.allocation_map) + bad_frees := len(data.tracking_allocator.bad_free_array) + + memory_is_in_bad_state := memory_leaks + bad_frees > 0 + + data.t.error_count += memory_leaks + bad_frees + + if memory_is_in_bad_state { + log.errorf("Memory failure in `%s.%s` with %i leak%s and %i bad free%s.", + data.it.pkg, data.it.name, + memory_leaks, "" if memory_leaks == 1 else "s", + bad_frees, "" if bad_frees == 1 else "s") + } + } + + new_state : Test_State = .Failed if failed(&data.t) else .Successful + + chan.send(data.t.channel, Event_State_Change { + 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 := "" + when ODIN_OS == .Windows { + console_ansi_init() + } + + 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]) + when FAIL_ON_BAD_MEMORY { + data.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 { + 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 { + log.infof("Starting test runner with %i thread%s.", + thread_count, + "" if thread_count == 1 else "s") + } + + when SHARED_RANDOM_SEED == 0 { + log.infof("The random seed sent to every test is: %v. Set with -define:ODIN_TEST_RANDOM_SEED=n.", shared_random_seed) + } else { + log.infof("The random seed sent to every test is: %v.", shared_random_seed) + } + + when TRACKING_MEMORY { + when ALWAYS_REPORT_MEMORY { + log.info("Memory tracking is enabled. Tests will log their memory usage when complete.") + } else { + log.info("Memory tracking is enabled. Tests will log their memory usage if there's an issue.") + } + log.info("< Final Mem/ Total Mem> < Peak Mem> (#Free/Alloc) :: [package.test_name]") + } else { + when ALWAYS_REPORT_MEMORY { + log.warn("ODIN_TEST_ALWAYS_REPORT_MEMORY is true, but ODIN_TEST_TRACK_MEMORY is false.") + } + when FAIL_ON_BAD_MEMORY { + log.warn("ODIN_TEST_FAIL_ON_BAD_MEMORY is true, but ODIN_TEST_TRACK_MEMORY is false.") + } + } + + 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) + + when FAIL_ON_BAD_MEMORY { + log.log(.Error if memory_is_in_bad_state else .Info, bytes.buffer_to_string(&batch_buffer)) + } else { + log.log(.Warning if memory_is_in_bad_state else .Info, bytes.buffer_to_string(&batch_buffer)) + } + bytes.buffer_reset(&batch_buffer) + } + + 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 { + 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) + 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)) + + when JSON_REPORT != "" { + json_report: JSON + + mode: int + when ODIN_OS != .Windows { + mode = os.S_IRUSR|os.S_IWUSR|os.S_IRGRP|os.S_IROTH + } + json_fd, err := os.open(JSON_REPORT, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode) + fmt.assertf(err == nil, "unable to open file %q for writing of JSON report, error: %v", JSON_REPORT, err) + defer os.close(json_fd) + + for test, i in report.all_tests { + #no_bounds_check state := report.all_test_states[i] + + if test.pkg not_in json_report.packages { + json_report.packages[test.pkg] = {} + } + + tests := &json_report.packages[test.pkg] + append(tests, JSON_Test{name = test.name, success = state == .Successful}) + } + + json_report.total = len(internal_tests) + json_report.success = total_success_count + json_report.duration = finished_in + + err := json.marshal_to_writer(os.stream_from_handle(json_fd), json_report, &{ pretty = true }) + fmt.assertf(err == nil, "Error writing JSON report: %v", err) + } + return total_success_count == total_test_count } diff --git a/core/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 index 17bcfce26..401804c71 100644 --- a/core/testing/runner_windows.odin +++ b/core/testing/runner_windows.odin @@ -1,235 +1,22 @@ -//+private -//+build windows +#+private package testing import win32 "core:sys/windows" -import "core:runtime" -import "core: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 +console_ansi_init :: proc() { + stdout := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE) + if stdout != win32.INVALID_HANDLE && stdout != nil { + old_console_mode: u32 + if win32.GetConsoleMode(stdout, &old_console_mode) { + win32.SetConsoleMode(stdout, old_console_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING) } - if original_count == intrinsics.atomic_compare_exchange_strong(&s.count, original_count-1, original_count) { - return + } + + stderr := win32.GetStdHandle(win32.STD_ERROR_HANDLE) + if stderr != win32.INVALID_HANDLE && stderr != nil { + old_console_mode: u32 + if win32.GetConsoleMode(stderr, &old_console_mode) { + win32.SetConsoleMode(stderr, old_console_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING) } } } -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 "stdcall" (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 "stdcall" (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..2f1f7c89a --- /dev/null +++ b/core/testing/signal_handler.odin @@ -0,0 +1,42 @@ +#+private +package testing + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Total rewrite. +*/ + +import "base:runtime" +import "core: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) -> ! { + 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..281fbde40 --- /dev/null +++ b/core/testing/signal_handler_libc.odin @@ -0,0 +1,165 @@ +#+private +#+build windows, linux, darwin, freebsd, openbsd, netbsd, haiku +package testing + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Total rewrite. +*/ + +import "base:intrinsics" +import "core:c/libc" +import "core:encoding/ansi" +import "core:sync" +import "core:os" + +@(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 +@(private="file", thread_local) +local_test_index_set: bool + +// 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) +stop_test_callback :: proc "c" (sig: libc.int) { + if !local_test_index_set { + // We're a thread created by a test thread. + // + // There's nothing we can do to inform the test runner about who + // signalled, so hopefully the test will handle their own sub-threads. + return + } + 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() + + _test_thread_cancel() + } + } +} + +_setup_signal_handler :: proc() { + local_test_index = -1 + local_test_index_set = true + + // 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) + // Catch arithmetic errors. + libc.signal(libc.SIGFPE, stop_test_callback) + // Catch segmentation faults (illegal memory access). + libc.signal(libc.SIGSEGV, stop_test_callback) + + __setup_signal_handler() +} + +_setup_task_signal_handler :: proc(test_index: int) { + local_test_index = cast(libc.sig_atomic_t)test_index + local_test_index_set = true +} + +_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..81f575495 --- /dev/null +++ b/core/testing/signal_handler_other.odin @@ -0,0 +1,33 @@ +#+private +#+build !windows +#+build !linux +#+build !darwin +#+build !freebsd +#+build !openbsd +#+build !netbsd +#+build !haiku +package testing + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Total rewrite. +*/ + +_setup_signal_handler :: proc() { + // Do nothing. +} + +_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/signal_handler_posix.odin b/core/testing/signal_handler_posix.odin new file mode 100644 index 000000000..1bfcc875b --- /dev/null +++ b/core/testing/signal_handler_posix.odin @@ -0,0 +1,22 @@ +#+build linux, darwin, netbsd, openbsd, freebsd +#+private +package testing + +import "core:c/libc" +import "core:sys/posix" + +__setup_signal_handler :: proc() { + libc.signal(posix.SIGTRAP, stop_test_callback) +} + +_test_thread_cancel :: proc "contextless" () { + // 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`. + + posix.pthread_testcancel() +} diff --git a/core/testing/signal_handler_windows.odin b/core/testing/signal_handler_windows.odin new file mode 100644 index 000000000..74ebe2998 --- /dev/null +++ b/core/testing/signal_handler_windows.odin @@ -0,0 +1,6 @@ +#+private +package testing + +__setup_signal_handler :: proc() {} + +_test_thread_cancel :: proc "contextless" () {} diff --git a/core/testing/testing.odin b/core/testing/testing.odin index 1ba05315c..09bf6dc0e 100644 --- a/core/testing/testing.odin +++ b/core/testing/testing.odin @@ -1,12 +1,25 @@ package testing -import "core:fmt" -import "core:io" -import "core:time" -import "core:intrinsics" +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Ginger Bill: Initial implementation. + Feoramund: Total rewrite. +*/ + +import "base:intrinsics" +import "base:runtime" +import "core:log" import "core:reflect" +import "core:sync" +import "core:sync/chan" +import "core:time" +import "core:mem" _ :: reflect // alias reflect to nothing to force visibility for -vet +_ :: mem // in case TRACKING_MEMORY is not enabled // IMPORTANT NOTE: Compiler requires this layout Test_Signature :: proc(^T) @@ -22,85 +35,123 @@ 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, } -error :: proc(t: ^T, args: ..any, loc := #caller_location) { - fmt.wprintf(t.w, "%v: ", loc) - fmt.wprintln(t.w, ..args) - t.error_count += 1 -} - -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 -} - fail :: proc(t: ^T, loc := #caller_location) { - error(t, "FAIL", loc=loc) - t.error_count += 1 + 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) + log.error("FAIL:", msg, location=loc) } else { - error(t, "FAIL", loc=loc) - } - t.error_count += 1 - if t._fail_now != nil { - t._fail_now() + log.error("FAIL", location=loc) } + runtime.trap() } failed :: proc(t: ^T) -> bool { return t.error_count != 0 } -log :: proc(t: ^T, args: ..any, loc := #caller_location) { - fmt.wprintln(t.w, ..args) -} - -logf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) { - fmt.wprintf(t.w, format, ..args) - fmt.wprintln(t.w) -} - - -// cleanup registers a procedure and user_data, which will be called when the test, and all its subtests, complete -// cleanup proceduers 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 { +expect :: proc(t: ^T, ok: bool, msg := "", expr := #caller_expression(ok), loc := #caller_location) -> bool { if !ok { - error(t, msg, loc=loc) + if msg == "" { + log.errorf("expected %v to be true", expr, location=loc) + } else { + log.error(msg, location=loc) + } } return ok } -expect_value :: proc(t: ^T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) { + +expectf :: proc(t: ^T, ok: bool, format: string, args: ..any, loc := #caller_location) -> bool { + if !ok { + log.errorf(format, ..args, location=loc) + } + return ok +} + +expect_value :: proc(t: ^T, value, expected: $T, loc := #caller_location, value_expr := #caller_expression(value)) -> 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) + log.errorf("expected %v to be %v, got %v", value_expr, expected, value, location=loc) } return ok } +Memory_Verifier_Proc :: #type proc(t: ^T, ta: ^mem.Tracking_Allocator) +expect_leaks :: proc(t: ^T, client_test: proc(t: ^T), verifier: Memory_Verifier_Proc) { + when TRACKING_MEMORY { + client_test(t) + ta := (^mem.Tracking_Allocator)(context.allocator.data) + + sync.mutex_lock(&ta.mutex) + // The verifier can inspect this local tracking allocator. + // And then call `testing.expect_*` as makes sense for the client test. + verifier(t, ta) + sync.mutex_unlock(&ta.mutex) + + clear(&ta.bad_free_array) + free_all(context.allocator) + } +} set_fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location) { - _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 8520ba674..49adad4d9 100644 --- a/core/text/edit/text_edit.odin +++ b/core/text/edit/text_edit.odin @@ -1,12 +1,11 @@ +/* +Based off the articles by rxi: +- [[ https://rxi.github.io/textbox_behaviour.html ]] +- [[ https://rxi.github.io/a_simple_undo_system.html ]] +*/ package text_edit -/* - Based off the articles by rxi: - * https://rxi.github.io/textbox_behaviour.html - * https://rxi.github.io/a_simple_undo_system.html -*/ - -import "core:runtime" +import "base:runtime" import "core:time" import "core:mem" import "core:strings" @@ -63,7 +62,9 @@ Translation :: enum u32 { Soft_Line_End, } - +// init the state to some timeout and set the respective allocators +// - undo_state_allocator dictates the dynamic undo|redo arrays allocators +// - undo_text_allocator is the allocator which allocates strings only init :: proc(s: ^State, undo_text_allocator, undo_state_allocator: runtime.Allocator, undo_timeout := DEFAULT_UNDO_TIMEOUT) { s.undo_timeout = undo_timeout @@ -74,6 +75,7 @@ init :: proc(s: ^State, undo_text_allocator, undo_state_allocator: runtime.Alloc s.redo.allocator = undo_state_allocator } +// clear undo|redo strings and delete their stacks destroy :: proc(s: ^State) { undo_clear(s, &s.undo) undo_clear(s, &s.redo) @@ -82,7 +84,6 @@ destroy :: proc(s: ^State) { s.builder = nil } - // Call at the beginning of each frame begin :: proc(s: ^State, id: u64, builder: ^strings.Builder) { assert(builder != nil) @@ -92,11 +93,7 @@ begin :: proc(s: ^State, id: u64, builder: ^strings.Builder) { s.id = id s.selection = {len(builder.buf), 0} s.builder = builder - s.current_time = time.tick_now() - if s.undo_timeout <= 0 { - s.undo_timeout = DEFAULT_UNDO_TIMEOUT - } - set_text(s, string(s.builder.buf[:])) + update_time(s) undo_clear(s, &s.undo) undo_clear(s, &s.redo) } @@ -107,13 +104,41 @@ end :: proc(s: ^State) { s.builder = nil } -set_text :: proc(s: ^State, text: string) { - strings.builder_reset(s.builder) - strings.write_string(s.builder, text) +// update current time so "insert" can check for timeouts +update_time :: proc(s: ^State) { + s.current_time = time.tick_now() + if s.undo_timeout <= 0 { + s.undo_timeout = DEFAULT_UNDO_TIMEOUT + } } +// setup the builder, selection and undo|redo state once allowing to retain selection +setup_once :: proc(s: ^State, builder: ^strings.Builder) { + s.builder = builder + s.selection = { len(builder.buf), 0 } + undo_clear(s, &s.undo) + undo_clear(s, &s.redo) +} +// returns true when the builder had content to be cleared +// clear builder&selection and the undo|redo stacks +clear_all :: proc(s: ^State) -> (cleared: bool) { + if s.builder != nil && len(s.builder.buf) > 0 { + clear(&s.builder.buf) + s.selection = {} + cleared = true + } + + undo_clear(s, &s.undo) + undo_clear(s, &s.redo) + return +} + +// push current text state to the wanted undo|redo stack undo_state_push :: proc(s: ^State, undo: ^[dynamic]^Undo_State) -> mem.Allocator_Error { + if s.builder == nil { + return nil + } text := string(s.builder.buf[:]) item := (^Undo_State)(mem.alloc(size_of(Undo_State) + len(text), align_of(Undo_State), s.undo_text_allocator) or_return) item.selection = s.selection @@ -125,18 +150,21 @@ undo_state_push :: proc(s: ^State, undo: ^[dynamic]^Undo_State) -> mem.Allocator return nil } +// pop undo|redo state - push to redo|undo - set selection & text undo :: proc(s: ^State, undo, redo: ^[dynamic]^Undo_State) { if len(undo) > 0 { undo_state_push(s, redo) item := pop(undo) s.selection = item.selection - #no_bounds_check { - set_text(s, string(item.text[:item.len])) + #no_bounds_check if s.builder != nil { + strings.builder_reset(s.builder) + strings.write_string(s.builder, string(item.text[:item.len])) } free(item, s.undo_text_allocator) } } +// iteratively clearn the undo|redo stack and free each allocated text state undo_clear :: proc(s: ^State, undo: ^[dynamic]^Undo_State) { for len(undo) > 0 { item := pop(undo) @@ -144,6 +172,7 @@ undo_clear :: proc(s: ^State, undo: ^[dynamic]^Undo_State) { } } +// clear redo stack and check if the undo timeout gets hit undo_check :: proc(s: ^State) { undo_clear(s, &s.redo) if time.tick_diff(s.last_edit_time, s.current_time) > s.undo_timeout { @@ -152,20 +181,21 @@ undo_check :: proc(s: ^State) { s.last_edit_time = s.current_time } - - -input_text :: proc(s: ^State, text: string) { +// insert text into the edit state - deletes the current selection +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 input_runes :: proc(s: ^State, text: []rune) { if len(text) == 0 { return @@ -176,60 +206,93 @@ 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} } - -insert :: proc(s: ^State, at: int, text: string) { - undo_check(s) - inject_at(&s.builder.buf, at, text) +// insert a single rune into the edit state - deletes the current selection +input_rune :: proc(s: ^State, r: rune) { + if has_selection(s) { + selection_delete(s) + } + offset := s.selection[0] + b, w := utf8.encode_rune(r) + 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) -> int { + undo_check(s) + if s.builder != nil { + 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 remove :: proc(s: ^State, lo, hi: int) { undo_check(s) - remove_range(&s.builder.buf, lo, hi) + if s.builder != nil { + remove_range(&s.builder.buf, lo, hi) + } } - - +// true if selection head and tail dont match and form a selection of multiple characters has_selection :: proc(s: ^State) -> bool { return s.selection[0] != s.selection[1] } +// return the clamped lo/hi of the current selection +// since the selection[0] moves around and could be ahead of selection[1] +// useful when rendering and needing left->right sorted_selection :: proc(s: ^State) -> (lo, hi: int) { lo = min(s.selection[0], s.selection[1]) hi = max(s.selection[0], s.selection[1]) - lo = clamp(lo, 0, len(s.builder.buf)) - hi = clamp(hi, 0, len(s.builder.buf)) - s.selection[0] = lo - s.selection[1] = hi + lo = clamp(lo, 0, len(s.builder.buf) if s.builder != nil else 0) + hi = clamp(hi, 0, len(s.builder.buf) if s.builder != nil else 0) return } - +// delete the current selection range and set the proper selection afterwards selection_delete :: proc(s: ^State) { lo, hi := sorted_selection(s) remove(s, lo, hi) s.selection = {lo, lo} } +is_continuation_byte :: proc(b: byte) -> bool { + return b >= 0x80 && b < 0xc0 +} - -translate_position :: proc(s: ^State, pos: int, t: Translation) -> int { - 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_space :: proc(b: byte) -> bool { return b == ' ' || b == '\t' || b == '\n' } - buf := s.builder.buf[:] - - pos := pos - pos = clamp(pos, 0, len(buf)) + buf: []byte + if s.builder != nil { + buf = s.builder.buf[:] + } + pos := clamp(s.selection[0], 0, len(buf)) switch t { case .Start: @@ -280,6 +343,7 @@ translate_position :: proc(s: ^State, pos: int, t: Translation) -> int { return clamp(pos, 0, len(buf)) } +// Moves the position of the caret (both sides of the selection) move_to :: proc(s: ^State, t: Translation) { if t == .Left && has_selection(s) { lo, _ := sorted_selection(s) @@ -288,32 +352,39 @@ move_to :: proc(s: ^State, t: Translation) { _, hi := sorted_selection(s) s.selection = {hi, hi} } else { - pos := translate_position(s, s.selection[0], t) + pos := translate_position(s, t) s.selection = {pos, pos} } } + +// Moves only the head of the selection and leaves the tail uneffected select_to :: proc(s: ^State, t: Translation) { - s.selection[0] = translate_position(s, s.selection[0], t) + s.selection[0] = translate_position(s, t) } + +// Deletes everything between the caret and resultant position delete_to :: proc(s: ^State, t: Translation) { if has_selection(s) { selection_delete(s) } else { lo := s.selection[0] - hi := translate_position(s, lo, t) + hi := translate_position(s, t) lo, hi = min(lo, hi), max(lo, hi) remove(s, lo, hi) s.selection = {lo, lo} } } - +// return the currently selected text current_selected_text :: proc(s: ^State) -> string { lo, hi := sorted_selection(s) - return string(s.builder.buf[lo:hi]) + if s.builder != nil { + return string(s.builder.buf[lo:hi]) + } + return "" } - +// copy & delete the current selection when copy() succeeds cut :: proc(s: ^State) -> bool { if copy(s) { selection_delete(s) @@ -322,6 +393,8 @@ cut :: proc(s: ^State) -> bool { return false } +// try and copy the currently selected text to the clipboard +// State.set_clipboard needs to be assigned copy :: proc(s: ^State) -> bool { if s.set_clipboard != nil { return s.set_clipboard(s.clipboard_user_data, current_selected_text(s)) @@ -329,6 +402,8 @@ copy :: proc(s: ^State) -> bool { return s.set_clipboard != nil } +// reinsert whatever the get_clipboard would return +// State.get_clipboard needs to be assigned paste :: proc(s: ^State) -> bool { if s.get_clipboard != nil { input_text(s, s.get_clipboard(s.clipboard_user_data) or_return) @@ -385,7 +460,7 @@ perform_command :: proc(s: ^State, cmd: Command) { case .Cut: cut(s) case .Copy: copy(s) case .Paste: paste(s) - case .Select_All: s.selection = {len(s.builder.buf), 0} + case .Select_All: s.selection = {len(s.builder.buf) if s.builder != nil else 0, 0} case .Backspace: delete_to(s, .Left) case .Delete: delete_to(s, .Right) case .Delete_Word_Left: delete_to(s, .Word_Left) diff --git a/core/text/i18n/doc.odin b/core/text/i18n/doc.odin index ef619451e..d590fd123 100644 --- a/core/text/i18n/doc.odin +++ b/core/text/i18n/doc.odin @@ -1,111 +1,108 @@ -//+build ignore -package i18n /* - 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 ommitting it in the above calls looks up in section "". +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. +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 quantity and returning which plural slot should be used. - You can also assign it to a loaded catalog after parsing, of course. +You can also assign it to a loaded catalog after parsing, of course. - Some code examples follow. -*/ +Example: -/* -```cpp -import "core:fmt" -import "core:text/i18n" + import "core:fmt" + import "core:text/i18n" -T :: i18n.get + T :: i18n.get + Tn :: i18n.get_n -mo :: proc() { - using fmt + mo :: proc() { + using fmt - err: i18n.Error + err: i18n.Error - /* - 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() + // 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 } + if err != .None { return } - /* - These are in the .MO catalog. - */ - println("-----") - println(T("")) - println("-----") - println(T("There are 69,105 leaves here.")) - println("-----") - 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) - // 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) + // These are in the .MO catalog. + println("-----") + println(T("")) + println("-----") + println(T("There are 69,105 leaves here.")) + println("-----") + println(T("Hellope, World!")) + println("-----") + // We pass 1 into `T` to get the singular format string, then 1 again into printf. + 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(Tn("There is %d leaf.\n", 42), 42) - /* - This isn't in the translation catalog, so the key is passed back untranslated. - */ - println("-----") - println(T("Come visit us on Discord!")) -} - -qt :: proc() { - using fmt - - err: i18n.Error - - /* - 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 + // This isn't in the translation catalog, so the key is passed back untranslated. + println("-----") + println(T("Come visit us on Discord!")) } - /* - 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("-----") - println("Page:Also text to translate =", T("Page", "Also text to translate")) - println("-----") - println("--- installscript section ---") - println("installscript:99 bottles of beer on the wall =", T("installscript", "99 bottles of beer on the wall")) - 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)) -} -``` -*/ \ No newline at end of file + qt :: proc() { + using fmt + + err: i18n.Error + + // 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 } + + // 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("-----") + println("Page:Also text to translate =", T("Page", "Also text to translate")) + println("-----") + println("--- installscript section ---") + println("installscript:99 bottles of beer on the wall =", T("installscript", "99 bottles of beer on the wall")) + println("-----") + println("--- apple_count section ---") + println("apple_count:%d apple(s) =") + 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 151f9e129..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 := 0, 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 := 0, 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 fa05a6dc1..bdd3f5fd7 100644 --- a/core/text/i18n/qt_linguist.odin +++ b/core/text/i18n/qt_linguist.odin @@ -128,7 +128,7 @@ parse_qt_linguist_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTI num_plurals: int for { - numerus_id := xml.find_child_by_ident(ts, translation_id, "numerusform", num_plurals) or_break + xml.find_child_by_ident(ts, translation_id, "numerusform", num_plurals) or_break num_plurals += 1 } @@ -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/match/strlib.odin b/core/text/match/strlib.odin index 189ed7ec0..bfa696dcd 100644 --- a/core/text/match/strlib.odin +++ b/core/text/match/strlib.odin @@ -1,6 +1,6 @@ package text_match -import "core:runtime" +import "base:runtime" import "core:unicode" import "core:unicode/utf8" import "core:strings" diff --git a/core/text/regex/common/common.odin b/core/text/regex/common/common.odin new file mode 100644 index 000000000..4a303e0a3 --- /dev/null +++ b/core/text/regex/common/common.odin @@ -0,0 +1,46 @@ +// This package helps break dependency cycles. +package regex_common + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +// VM limitations +MAX_CAPTURE_GROUPS :: max(#config(ODIN_REGEX_MAX_CAPTURE_GROUPS, 10), 10) +MAX_PROGRAM_SIZE :: int(max(i16)) +MAX_CLASSES :: int(max(u8)) + +Flag :: enum u8 { + // Global: try to match the pattern anywhere in the string. + Global, + // Multiline: treat `^` and `$` as if they also match newlines. + Multiline, + // Case Insensitive: treat `a-z` as if it was also `A-Z`. + Case_Insensitive, + // Ignore Whitespace: bypass unescaped whitespace outside of classes. + Ignore_Whitespace, + // Unicode: let the compiler and virtual machine know to expect Unicode strings. + Unicode, + + // No Capture: avoid saving capture group data entirely. + No_Capture, + // No Optimization: do not pass the pattern through the optimizer; for debugging. + No_Optimization, +} + +Flags :: bit_set[Flag; u8] + +@(rodata) +Flag_To_Letter := #sparse[Flag]u8 { + .Global = 'g', + .Multiline = 'm', + .Case_Insensitive = 'i', + .Ignore_Whitespace = 'x', + .Unicode = 'u', + .No_Capture = 'n', + .No_Optimization = '-', +} diff --git a/core/text/regex/common/debugging.odin b/core/text/regex/common/debugging.odin new file mode 100644 index 000000000..0e4161a92 --- /dev/null +++ b/core/text/regex/common/debugging.odin @@ -0,0 +1,33 @@ +package regex_common + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +@require import "core:os" +import "core:io" +import "core:strings" + +ODIN_DEBUG_REGEX :: #config(ODIN_DEBUG_REGEX, false) + +when ODIN_DEBUG_REGEX { + debug_stream := os.stream_from_handle(os.stderr) +} + +write_padded_hex :: proc(w: io.Writer, #any_int n, zeroes: int) { + sb := strings.builder_make() + defer strings.builder_destroy(&sb) + + sbw := strings.to_writer(&sb) + io.write_int(sbw, n, 0x10) + + io.write_string(w, "0x") + for _ in 0... + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +import "base:intrinsics" +import "core:text/regex/common" +import "core:text/regex/parser" +import "core:text/regex/tokenizer" +import "core:text/regex/virtual_machine" +import "core:unicode" + +Token :: tokenizer.Token +Token_Kind :: tokenizer.Token_Kind +Tokenizer :: tokenizer.Tokenizer + +Rune_Class_Range :: parser.Rune_Class_Range +Rune_Class_Data :: parser.Rune_Class_Data + +Node :: parser.Node +Node_Rune :: parser.Node_Rune +Node_Rune_Class :: parser.Node_Rune_Class +Node_Wildcard :: parser.Node_Wildcard +Node_Concatenation :: parser.Node_Concatenation +Node_Alternation :: parser.Node_Alternation +Node_Repeat_Zero :: parser.Node_Repeat_Zero +Node_Repeat_Zero_Non_Greedy :: parser.Node_Repeat_Zero_Non_Greedy +Node_Repeat_One :: parser.Node_Repeat_One +Node_Repeat_One_Non_Greedy :: parser.Node_Repeat_One_Non_Greedy +Node_Repeat_N :: parser.Node_Repeat_N +Node_Optional :: parser.Node_Optional +Node_Optional_Non_Greedy :: parser.Node_Optional_Non_Greedy +Node_Group :: parser.Node_Group +Node_Anchor :: parser.Node_Anchor +Node_Word_Boundary :: parser.Node_Word_Boundary +Node_Match_All_And_Escape :: parser.Node_Match_All_And_Escape + +Opcode :: virtual_machine.Opcode +Program :: [dynamic]Opcode + +JUMP_SIZE :: size_of(Opcode) + 1 * size_of(u16) +SPLIT_SIZE :: size_of(Opcode) + 2 * size_of(u16) + + +Compiler :: struct { + flags: common.Flags, + class_data: [dynamic]Rune_Class_Data, +} + + +Error :: enum { + None, + Program_Too_Big, + Too_Many_Classes, +} + +classes_are_exact :: proc(q, w: ^Rune_Class_Data) -> bool #no_bounds_check { + assert(q != nil) + assert(w != nil) + + if q == w { + return true + } + + if len(q.runes) != len(w.runes) || len(q.ranges) != len(w.ranges) { + return false + } + + for r, i in q.runes { + if r != w.runes[i] { + return false + } + } + + for r, i in q.ranges { + if r.lower != w.ranges[i].lower || r.upper != w.ranges[i].upper { + return false + } + } + + return true +} + +map_all_classes :: proc(tree: Node, collection: ^[dynamic]Rune_Class_Data) { + if tree == nil { + return + } + + switch specific in tree { + case ^Node_Rune: break + case ^Node_Wildcard: break + case ^Node_Anchor: break + case ^Node_Word_Boundary: break + case ^Node_Match_All_And_Escape: break + + case ^Node_Concatenation: + for subnode in specific.nodes { + map_all_classes(subnode, collection) + } + + case ^Node_Repeat_Zero: + map_all_classes(specific.inner, collection) + case ^Node_Repeat_Zero_Non_Greedy: + map_all_classes(specific.inner, collection) + case ^Node_Repeat_One: + map_all_classes(specific.inner, collection) + case ^Node_Repeat_One_Non_Greedy: + map_all_classes(specific.inner, collection) + case ^Node_Repeat_N: + map_all_classes(specific.inner, collection) + case ^Node_Optional: + map_all_classes(specific.inner, collection) + case ^Node_Optional_Non_Greedy: + map_all_classes(specific.inner, collection) + case ^Node_Group: + map_all_classes(specific.inner, collection) + + case ^Node_Alternation: + map_all_classes(specific.left, collection) + map_all_classes(specific.right, collection) + + case ^Node_Rune_Class: + unseen := true + for &value in collection { + if classes_are_exact(&specific.data, &value) { + unseen = false + break + } + } + + if unseen { + append(collection, specific.data) + } + } +} + +append_raw :: #force_inline proc(code: ^Program, data: $T) { + // NOTE: This is system-dependent endian. + for b in transmute([size_of(T)]byte)data { + append(code, cast(Opcode)b) + } +} +inject_raw :: #force_inline proc(code: ^Program, start: int, data: $T) { + // NOTE: This is system-dependent endian. + for b, i in transmute([size_of(T)]byte)data { + inject_at(code, start + i, cast(Opcode)b) + } +} + +@require_results +generate_code :: proc(c: ^Compiler, node: Node) -> (code: Program) { + if node == nil { + return + } + + // NOTE: For Jump/Split arguments, we write as i16 and will reinterpret + // this later when relative jumps are turned into absolute jumps. + + switch specific in node { + // Atomic Nodes: + case ^Node_Rune: + if .Unicode not_in c.flags || specific.data < unicode.MAX_LATIN1 { + append(&code, Opcode.Byte) + append(&code, cast(Opcode)specific.data) + } else { + append(&code, Opcode.Rune) + append_raw(&code, specific.data) + } + + case ^Node_Rune_Class: + if specific.negating { + append(&code, Opcode.Rune_Class_Negated) + } else { + append(&code, Opcode.Rune_Class) + } + + index := -1 + for &data, i in c.class_data { + if classes_are_exact(&data, &specific.data) { + index = i + break + } + } + assert(index != -1, "Unable to find collected Rune_Class_Data index.") + + append(&code, Opcode(index)) + + case ^Node_Wildcard: + append(&code, Opcode.Wildcard) + + case ^Node_Anchor: + if .Multiline in c.flags { + append(&code, Opcode.Multiline_Open) + append(&code, Opcode.Multiline_Close) + } else { + if specific.start { + append(&code, Opcode.Assert_Start) + } else { + append(&code, Opcode.Assert_End) + } + } + case ^Node_Word_Boundary: + if specific.non_word { + append(&code, Opcode.Assert_Non_Word_Boundary) + } else { + append(&code, Opcode.Assert_Word_Boundary) + } + + // Compound Nodes: + case ^Node_Group: + code = generate_code(c, specific.inner) + + if specific.capture && .No_Capture not_in c.flags { + inject_at(&code, 0, Opcode.Save) + inject_at(&code, 1, Opcode(2 * specific.capture_id)) + + append(&code, Opcode.Save) + append(&code, Opcode(2 * specific.capture_id + 1)) + } + + case ^Node_Alternation: + left := generate_code(c, specific.left) + right := generate_code(c, specific.right) + + left_len := len(left) + + // Avoiding duplicate allocation by reusing `left`. + code = left + + inject_at(&code, 0, Opcode.Split) + inject_raw(&code, size_of(byte) , i16(SPLIT_SIZE)) + inject_raw(&code, size_of(byte) + size_of(i16), i16(SPLIT_SIZE + left_len + JUMP_SIZE)) + + append(&code, Opcode.Jump) + append_raw(&code, i16(len(right) + JUMP_SIZE)) + + for opcode in right { + append(&code, opcode) + } + + case ^Node_Concatenation: + for subnode in specific.nodes { + subnode_code := generate_code(c, subnode) + for opcode in subnode_code { + append(&code, opcode) + } + } + + case ^Node_Repeat_Zero: + code = generate_code(c, specific.inner) + original_len := len(code) + + inject_at(&code, 0, Opcode.Split) + inject_raw(&code, size_of(byte) , i16(SPLIT_SIZE)) + inject_raw(&code, size_of(byte) + size_of(i16), i16(SPLIT_SIZE + original_len + JUMP_SIZE)) + + append(&code, Opcode.Jump) + append_raw(&code, i16(-original_len - SPLIT_SIZE)) + + case ^Node_Repeat_Zero_Non_Greedy: + code = generate_code(c, specific.inner) + original_len := len(code) + + inject_at(&code, 0, Opcode.Split) + inject_raw(&code, size_of(byte) , i16(SPLIT_SIZE + original_len + JUMP_SIZE)) + inject_raw(&code, size_of(byte) + size_of(i16), i16(SPLIT_SIZE)) + + append(&code, Opcode.Jump) + append_raw(&code, i16(-original_len - SPLIT_SIZE)) + + case ^Node_Repeat_One: + code = generate_code(c, specific.inner) + original_len := len(code) + + append(&code, Opcode.Split) + append_raw(&code, i16(-original_len)) + append_raw(&code, i16(SPLIT_SIZE)) + + case ^Node_Repeat_One_Non_Greedy: + code = generate_code(c, specific.inner) + original_len := len(code) + + append(&code, Opcode.Split) + append_raw(&code, i16(SPLIT_SIZE)) + append_raw(&code, i16(-original_len)) + + case ^Node_Repeat_N: + inside := generate_code(c, specific.inner) + original_len := len(inside) + + if specific.lower == specific.upper { // {N} + // e{N} ... evaluates to ... e^N + for i := 0; i < specific.upper; i += 1 { + for opcode in inside { + append(&code, opcode) + } + } + + } else if specific.lower == -1 && specific.upper > 0 { // {,M} + // e{,M} ... evaluates to ... e?^M + for i := 0; i < specific.upper; i += 1 { + append(&code, Opcode.Split) + append_raw(&code, i16(SPLIT_SIZE)) + append_raw(&code, i16(SPLIT_SIZE + original_len)) + for opcode in inside { + append(&code, opcode) + } + } + + } else if specific.lower >= 0 && specific.upper == -1 { // {N,} + // e{N,} ... evaluates to ... e^N e* + for i := 0; i < specific.lower; i += 1 { + for opcode in inside { + append(&code, opcode) + } + } + + append(&code, Opcode.Split) + append_raw(&code, i16(SPLIT_SIZE)) + append_raw(&code, i16(SPLIT_SIZE + original_len + JUMP_SIZE)) + + for opcode in inside { + append(&code, opcode) + } + + append(&code, Opcode.Jump) + append_raw(&code, i16(-original_len - SPLIT_SIZE)) + + } else if specific.lower >= 0 && specific.upper > 0 { + // e{N,M} evaluates to ... e^N e?^(M-N) + for i := 0; i < specific.lower; i += 1 { + for opcode in inside { + append(&code, opcode) + } + } + for i := 0; i < specific.upper - specific.lower; i += 1 { + append(&code, Opcode.Split) + append_raw(&code, i16(SPLIT_SIZE + original_len)) + append_raw(&code, i16(SPLIT_SIZE)) + for opcode in inside { + append(&code, opcode) + } + } + + } else { + panic("RegEx compiler received invalid repetition group.") + } + + case ^Node_Optional: + code = generate_code(c, specific.inner) + original_len := len(code) + + inject_at(&code, 0, Opcode.Split) + inject_raw(&code, size_of(byte) , i16(SPLIT_SIZE)) + inject_raw(&code, size_of(byte) + size_of(i16), i16(SPLIT_SIZE + original_len)) + + case ^Node_Optional_Non_Greedy: + code = generate_code(c, specific.inner) + original_len := len(code) + + inject_at(&code, 0, Opcode.Split) + inject_raw(&code, size_of(byte) , i16(SPLIT_SIZE + original_len)) + inject_raw(&code, size_of(byte) + size_of(i16), i16(SPLIT_SIZE)) + + case ^Node_Match_All_And_Escape: + append(&code, Opcode.Match_All_And_Escape) + } + + return +} + +@require_results +compile :: proc(tree: Node, flags: common.Flags) -> (code: Program, class_data: [dynamic]Rune_Class_Data, err: Error) { + if tree == nil { + if .No_Capture not_in flags { + append(&code, Opcode.Save); append(&code, Opcode(0x00)) + append(&code, Opcode.Save); append(&code, Opcode(0x01)) + append(&code, Opcode.Match) + } else { + append(&code, Opcode.Match_And_Exit) + } + return + } + + c: Compiler + c.flags = flags + + map_all_classes(tree, &class_data) + if len(class_data) >= common.MAX_CLASSES { + err = .Too_Many_Classes + return + } + c.class_data = class_data + + code = generate_code(&c, tree) + + pc_open := 0 + + add_global: if .Global in flags { + // Check if the opening to the pattern is predictable. + // If so, use one of the optimized Wait opcodes. + iter := virtual_machine.Opcode_Iterator{ code[:], 0 } + seek_loop: for opcode, pc in virtual_machine.iterate_opcodes(&iter) { + #partial switch opcode { + case .Byte: + inject_at(&code, pc_open, Opcode.Wait_For_Byte) + pc_open += size_of(Opcode) + inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open])) + pc_open += size_of(u8) + break add_global + + case .Rune: + operand := intrinsics.unaligned_load(cast(^rune)&code[pc+1]) + inject_at(&code, pc_open, Opcode.Wait_For_Rune) + pc_open += size_of(Opcode) + inject_raw(&code, pc_open, operand) + pc_open += size_of(rune) + break add_global + + case .Rune_Class: + inject_at(&code, pc_open, Opcode.Wait_For_Rune_Class) + pc_open += size_of(Opcode) + inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open])) + pc_open += size_of(u8) + break add_global + + case .Rune_Class_Negated: + inject_at(&code, pc_open, Opcode.Wait_For_Rune_Class_Negated) + pc_open += size_of(Opcode) + inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open])) + pc_open += size_of(u8) + break add_global + + case .Save: + continue + case: + break seek_loop + } + } + + // `.*?` + inject_at(&code, pc_open, Opcode.Split) + pc_open += size_of(byte) + inject_raw(&code, pc_open, i16(SPLIT_SIZE + size_of(byte) + JUMP_SIZE)) + pc_open += size_of(i16) + inject_raw(&code, pc_open, i16(SPLIT_SIZE)) + pc_open += size_of(i16) + + inject_at(&code, pc_open, Opcode.Wildcard) + pc_open += size_of(byte) + + inject_at(&code, pc_open, Opcode.Jump) + pc_open += size_of(byte) + inject_raw(&code, pc_open, i16(-size_of(byte) - SPLIT_SIZE)) + pc_open += size_of(i16) + + } + + if .No_Capture not_in flags { + // `(` + inject_at(&code, pc_open, Opcode.Save) + inject_at(&code, pc_open + size_of(byte), Opcode(0x00)) + + // `)` + append(&code, Opcode.Save); append(&code, Opcode(0x01)) + + append(&code, Opcode.Match) + } else { + append(&code, Opcode.Match_And_Exit) + } + + if len(code) >= common.MAX_PROGRAM_SIZE { + err = .Program_Too_Big + return + } + + // NOTE: No further opcode addition beyond this point, as we've already + // checked the program size. Removal or transformation is fine. + + // Post-Compile Optimizations: + + // * Jump Extension + // + // A:RelJmp(1) -> B:RelJmp(2) => A:RelJmp(2) + if .No_Optimization not_in flags { + for passes_left := 1; passes_left > 0; passes_left -= 1 { + do_another_pass := false + + iter := virtual_machine.Opcode_Iterator{ code[:], 0 } + for opcode, pc in virtual_machine.iterate_opcodes(&iter) { + #partial switch opcode { + case .Jump: + jmp := cast(^i16)&code[pc+size_of(Opcode)] + jmp_value := intrinsics.unaligned_load(jmp) + if code[cast(i16)pc+jmp_value] == .Jump { + next_jmp := intrinsics.unaligned_load(cast(^i16)&code[cast(i16)pc+jmp_value+size_of(Opcode)]) + intrinsics.unaligned_store(jmp, jmp_value + next_jmp) + do_another_pass = true + } + case .Split: + jmp_x := cast(^i16)&code[pc+size_of(Opcode)] + jmp_x_value := intrinsics.unaligned_load(jmp_x) + if code[cast(i16)pc+jmp_x_value] == .Jump { + next_jmp := intrinsics.unaligned_load(cast(^i16)&code[cast(i16)pc+jmp_x_value+size_of(Opcode)]) + intrinsics.unaligned_store(jmp_x, jmp_x_value + next_jmp) + do_another_pass = true + } + jmp_y := cast(^i16)&code[pc+size_of(Opcode)+size_of(i16)] + jmp_y_value := intrinsics.unaligned_load(jmp_y) + if code[cast(i16)pc+jmp_y_value] == .Jump { + next_jmp := intrinsics.unaligned_load(cast(^i16)&code[cast(i16)pc+jmp_y_value+size_of(Opcode)]) + intrinsics.unaligned_store(jmp_y, jmp_y_value + next_jmp) + do_another_pass = true + } + } + } + + if do_another_pass { + passes_left += 1 + } + } + } + + // * Relative Jump to Absolute Jump + // + // RelJmp{PC +/- N} => AbsJmp{M} + iter := virtual_machine.Opcode_Iterator{ code[:], 0 } + for opcode, pc in virtual_machine.iterate_opcodes(&iter) { + // NOTE: The virtual machine implementation depends on this. + #partial switch opcode { + case .Jump: + jmp := cast(^u16)&code[pc+size_of(Opcode)] + intrinsics.unaligned_store(jmp, intrinsics.unaligned_load(jmp) + cast(u16)pc) + case .Split: + jmp_x := cast(^u16)&code[pc+size_of(Opcode)] + intrinsics.unaligned_store(jmp_x, intrinsics.unaligned_load(jmp_x) + cast(u16)pc) + jmp_y := cast(^u16)&code[pc+size_of(Opcode)+size_of(i16)] + intrinsics.unaligned_store(jmp_y, intrinsics.unaligned_load(jmp_y) + cast(u16)pc) + } + } + + return +} diff --git a/core/text/regex/compiler/debugging.odin b/core/text/regex/compiler/debugging.odin new file mode 100644 index 000000000..142cb8839 --- /dev/null +++ b/core/text/regex/compiler/debugging.odin @@ -0,0 +1,93 @@ +package regex_compiler + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +import "base:intrinsics" +import "core:io" +import "core:text/regex/common" +import "core:text/regex/virtual_machine" + +get_jump_targets :: proc(code: []Opcode) -> (jump_targets: map[int]int) { + iter := virtual_machine.Opcode_Iterator{ code, 0 } + for opcode, pc in virtual_machine.iterate_opcodes(&iter) { + #partial switch opcode { + case .Jump: + jmp := cast(int)intrinsics.unaligned_load(cast(^u16)&code[pc+1]) + jump_targets[jmp] = pc + case .Split: + jmp_x := cast(int)intrinsics.unaligned_load(cast(^u16)&code[pc+1]) + jmp_y := cast(int)intrinsics.unaligned_load(cast(^u16)&code[pc+3]) + jump_targets[jmp_x] = pc + jump_targets[jmp_y] = pc + } + } + return +} + +trace :: proc(w: io.Writer, code: []Opcode) { + jump_targets := get_jump_targets(code) + defer delete(jump_targets) + + iter := virtual_machine.Opcode_Iterator{ code, 0 } + for opcode, pc in virtual_machine.iterate_opcodes(&iter) { + if src, ok := jump_targets[pc]; ok { + io.write_string(w, "--") + common.write_padded_hex(w, src, 4) + io.write_string(w, "--> ") + } else { + io.write_string(w, " ") + } + + io.write_string(w, "[PC: ") + common.write_padded_hex(w, pc, 4) + io.write_string(w, "] ") + io.write_string(w, virtual_machine.opcode_to_name(opcode)) + io.write_byte(w, ' ') + + #partial switch opcode { + case .Byte: + operand := cast(rune)code[pc+1] + io.write_encoded_rune(w, operand) + case .Rune: + operand := intrinsics.unaligned_load(cast(^rune)&code[pc+1]) + io.write_encoded_rune(w, operand) + case .Rune_Class, .Rune_Class_Negated: + operand := cast(u8)code[pc+1] + common.write_padded_hex(w, operand, 2) + case .Jump: + jmp := intrinsics.unaligned_load(cast(^u16)&code[pc+1]) + io.write_string(w, "-> $") + common.write_padded_hex(w, jmp, 4) + case .Split: + jmp_x := intrinsics.unaligned_load(cast(^u16)&code[pc+1]) + jmp_y := intrinsics.unaligned_load(cast(^u16)&code[pc+3]) + io.write_string(w, "=> $") + common.write_padded_hex(w, jmp_x, 4) + io.write_string(w, ", $") + common.write_padded_hex(w, jmp_y, 4) + case .Save: + operand := cast(u8)code[pc+1] + common.write_padded_hex(w, operand, 2) + case .Wait_For_Byte: + operand := cast(rune)code[pc+1] + io.write_encoded_rune(w, operand) + case .Wait_For_Rune: + operand := (cast(^rune)&code[pc+1])^ + io.write_encoded_rune(w, operand) + case .Wait_For_Rune_Class: + operand := cast(u8)code[pc+1] + common.write_padded_hex(w, operand, 2) + case .Wait_For_Rune_Class_Negated: + operand := cast(u8)code[pc+1] + common.write_padded_hex(w, operand, 2) + } + + io.write_byte(w, '\n') + } +} diff --git a/core/text/regex/compiler/doc.odin b/core/text/regex/compiler/doc.odin new file mode 100644 index 000000000..8c876d837 --- /dev/null +++ b/core/text/regex/compiler/doc.odin @@ -0,0 +1,9 @@ +/* +package regex_compiler implements a bytecode compiler for the virtual machine +included alongside it. + +Operands larger than u8 are written in system endian order. + +More details can be found in the documentation for the virtual machine. +*/ +package regex_compiler diff --git a/core/text/regex/doc.odin b/core/text/regex/doc.odin new file mode 100644 index 000000000..61ab8b80e --- /dev/null +++ b/core/text/regex/doc.odin @@ -0,0 +1,97 @@ +/* +package regex implements a complete suite for using Regular Expressions to +match and capture text. + +Regular expressions are used to describe how a piece of text can match to +another, using a pattern language. + +Odin's regex library implements the following features: + + Alternation: `apple|cherry` + Classes: `[0-9_]` + Classes, negated: `[^0-9_]` + Shorthands: `\d\s\w` + Shorthands, negated: `\D\S\W` + Wildcards: `.` + Repeat, optional: `a*` + Repeat, at least once: `a+` + Repetition: `a{1,2}` + Optional: `a?` + Group, capture: `([0-9])` + Group, non-capture: `(?:[0-9])` + Start & End Anchors: `^hello$` + Word Boundaries: `\bhello\b` + Non-Word Boundaries: `hello\B` + +These specifiers can be composed together, such as an optional group: +`(?:hello)?` + +This package also supports the non-greedy variants of the repeating and +optional specifiers by appending a `?` to them. + +Of the shorthand classes that are supported, they are all ASCII-based, even +when compiling in Unicode mode. This is for the sake of general performance and +simplicity, as there are thousands of Unicode codepoints which would qualify as +either a digit, space, or word character which could be irrelevant depending on +what is being matched. + +Here are the shorthand class equivalencies: + \d: [0-9] + \s: [\t\n\f\r ] + \w: [0-9A-Z_a-z] + +If you need your own shorthands, you can compose strings together like so: + MY_HEX :: "[0-9A-Fa-f]" + PATTERN :: MY_HEX + "-" + MY_HEX + +The compiler will handle turning multiple identical classes into references to +the same set of matching runes, so there's no penalty for doing it like this. + + + + ``Some people, when confronted with a problem, think + "I know, I'll use regular expressions." Now they have two problems.'' + + - Jamie Zawinski + + +Regular expressions have gathered a reputation over the decades for often being +chosen as the wrong tool for the job. Here, we will clarify a few cases in +which RegEx might be good or bad. + + +**When is it a good time to use RegEx?** + +- You don't know at compile-time what patterns of text the program will need to + match when it's running. +- As an example, you are making a client which can be configured by the user to + trigger on certain text patterns received from a server. +- For another example, you need a way for users of a text editor to compose + matching strings that are more intricate than a simple substring lookup. +- The text you're matching against is small (< 64 KiB) and your patterns aren't + overly complicated with branches (alternations, repeats, and optionals). +- If none of the above general impressions apply but your project doesn't + warrant long-term maintenance. + +**When is it a bad time to use RegEx?** + +- You know at compile-time the grammar you're parsing; a hand-made parser has + the potential to be more maintainable and readable. +- The grammar you're parsing has certain validation steps that lend itself to + forming complicated expressions, such as e-mail addresses, URIs, dates, + postal codes, credit cards, et cetera. Using RegEx to validate these + structures is almost always a bad sign. +- The text you're matching against is big (> 1 MiB); you would be better served + by first dividing the text into manageable chunks and using some heuristic to + locate the most likely location of a match before applying RegEx against it. +- You value high performance and low memory usage; RegEx will always have a + certain overhead which increases with the complexity of the pattern. + + +The implementation of this package has been optimized, but it will never be as +thoroughly performant as a hand-made parser. In comparison, there are just too +many intermediate steps, assumptions, and generalizations in what it takes to +handle a regular expression. + +*/ +package regex diff --git a/core/text/regex/optimizer/doc.odin b/core/text/regex/optimizer/doc.odin new file mode 100644 index 000000000..7f2c84c8d --- /dev/null +++ b/core/text/regex/optimizer/doc.odin @@ -0,0 +1,58 @@ +/* +package regex_optimizer implements an optimizer which acts upon the AST of a +parsed regular expression pattern, transforming it in-place without moving to a +compilation step. + +Where possible, it aims to reduce branching as much as possible in the +expression by reducing usage of `|`. + + +Here is a summary of the optimizations that it will do: + +* Class Simplification : `[aab]` => `[ab]` + `[aa]` => `[a]` + +* Class Reduction : `[a]` => `a` +* Range Construction : `[abc]` => `[a-c]` +* Rune Merging into Range : `[aa-c]` => `[a-c]` + +* Range Merging : `[a-cc-e]` => `[a-e]` + `[a-cd-e]` => `[a-e]` + `[a-cb-e]` => `[a-e]` + +* Alternation to Optional : `a|` => `a?` +* Alternation to Optional Non-Greedy : `|a` => `a??` +* Alternation Reduction : `a|a` => `a` +* Alternation to Class : `a|b` => `[ab]` +* Class Union : `[a0]|[b1]` => `[a0b1]` + `[a-b]|c` => `[a-bc]` + `a|[b-c]` => `[b-ca]` + +* Wildcard Reduction : `a|.` => `.` + `.|a` => `.` + `[ab]|.` => `.` + `.|[ab]` => `.` + +* Common Suffix Elimination : `blueberry|strawberry` => `(?:blue|straw)berry` +* Common Prefix Elimination : `abi|abe` => `ab(?:i|e)` + +* Composition: Consume All to Anchored End + `.*$` => + `.+$` => `.` + + +Possible future improvements: + +- Change the AST of alternations to be a list instead of a tree, so that + constructions such as `(ab|bb|cb)` can be considered in whole by the affix + elimination optimizations. + +- Introduce specialized opcodes for certain classes of repetition. + +- Add Common Infix Elimination. + +- Measure the precise finite minimum and maximum of a pattern, if available, + and check against that on any strings before running the virtual machine. + +*/ +package regex_optimizer diff --git a/core/text/regex/optimizer/optimizer.odin b/core/text/regex/optimizer/optimizer.odin new file mode 100644 index 000000000..e23cc1bc5 --- /dev/null +++ b/core/text/regex/optimizer/optimizer.odin @@ -0,0 +1,530 @@ +package regex_optimizer + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +import "base:intrinsics" +@require import "core:io" +import "core:slice" +import "core:text/regex/common" +import "core:text/regex/parser" + +Rune_Class_Range :: parser.Rune_Class_Range + +Node :: parser.Node +Node_Rune :: parser.Node_Rune +Node_Rune_Class :: parser.Node_Rune_Class +Node_Wildcard :: parser.Node_Wildcard +Node_Concatenation :: parser.Node_Concatenation +Node_Alternation :: parser.Node_Alternation +Node_Repeat_Zero :: parser.Node_Repeat_Zero +Node_Repeat_Zero_Non_Greedy :: parser.Node_Repeat_Zero_Non_Greedy +Node_Repeat_One :: parser.Node_Repeat_One +Node_Repeat_One_Non_Greedy :: parser.Node_Repeat_One_Non_Greedy +Node_Repeat_N :: parser.Node_Repeat_N +Node_Optional :: parser.Node_Optional +Node_Optional_Non_Greedy :: parser.Node_Optional_Non_Greedy +Node_Group :: parser.Node_Group +Node_Anchor :: parser.Node_Anchor +Node_Word_Boundary :: parser.Node_Word_Boundary +Node_Match_All_And_Escape :: parser.Node_Match_All_And_Escape + + +class_range_sorter :: proc(i, j: Rune_Class_Range) -> bool { + return i.lower < j.lower +} + +optimize_subtree :: proc(tree: Node, flags: common.Flags) -> (result: Node, changes: int) { + if tree == nil { + return nil, 0 + } + + result = tree + + switch specific in tree { + // No direct optimization possible on these nodes: + case ^Node_Rune: break + case ^Node_Wildcard: break + case ^Node_Anchor: break + case ^Node_Word_Boundary: break + case ^Node_Match_All_And_Escape: break + + case ^Node_Concatenation: + // * Composition: Consume All to Anchored End + // + // DO: `.*$` => + // DO: `.+$` => `.` + if .Multiline not_in flags && len(specific.nodes) >= 2 { + i := len(specific.nodes) - 2 + wrza: { + subnode := specific.nodes[i].(^Node_Repeat_Zero) or_break wrza + _ = subnode.inner.(^Node_Wildcard) or_break wrza + next_node := specific.nodes[i+1].(^Node_Anchor) or_break wrza + if next_node.start == false { + specific.nodes[i] = new(Node_Match_All_And_Escape) + ordered_remove(&specific.nodes, i + 1) + changes += 1 + break + } + } + wroa: { + subnode := specific.nodes[i].(^Node_Repeat_One) or_break wroa + subsubnode := subnode.inner.(^Node_Wildcard) or_break wroa + next_node := specific.nodes[i+1].(^Node_Anchor) or_break wroa + if next_node.start == false { + specific.nodes[i] = subsubnode + specific.nodes[i+1] = new(Node_Match_All_And_Escape) + changes += 1 + break + } + } + } + + // Only recursive optimizations: + #no_bounds_check for i := 0; i < len(specific.nodes); i += 1 { + subnode, subnode_changes := optimize_subtree(specific.nodes[i], flags) + changes += subnode_changes + if subnode == nil { + ordered_remove(&specific.nodes, i) + i -= 1 + changes += 1 + } else { + specific.nodes[i] = subnode + } + } + + if len(specific.nodes) == 1 { + result = specific.nodes[0] + changes += 1 + } else if len(specific.nodes) == 0 { + return nil, changes + 1 + } + + case ^Node_Repeat_Zero: + specific.inner, changes = optimize_subtree(specific.inner, flags) + if specific.inner == nil { + return nil, changes + 1 + } + case ^Node_Repeat_Zero_Non_Greedy: + specific.inner, changes = optimize_subtree(specific.inner, flags) + if specific.inner == nil { + return nil, changes + 1 + } + case ^Node_Repeat_One: + specific.inner, changes = optimize_subtree(specific.inner, flags) + if specific.inner == nil { + return nil, changes + 1 + } + case ^Node_Repeat_One_Non_Greedy: + specific.inner, changes = optimize_subtree(specific.inner, flags) + if specific.inner == nil { + return nil, changes + 1 + } + case ^Node_Repeat_N: + specific.inner, changes = optimize_subtree(specific.inner, flags) + if specific.inner == nil { + return nil, changes + 1 + } + case ^Node_Optional: + specific.inner, changes = optimize_subtree(specific.inner, flags) + if specific.inner == nil { + return nil, changes + 1 + } + case ^Node_Optional_Non_Greedy: + specific.inner, changes = optimize_subtree(specific.inner, flags) + if specific.inner == nil { + return nil, changes + 1 + } + + case ^Node_Group: + specific.inner, changes = optimize_subtree(specific.inner, flags) + + if specific.inner == nil { + return nil, changes + 1 + } + + if !specific.capture { + result = specific.inner + changes += 1 + } + + // Full optimization: + case ^Node_Rune_Class: + // * Class Simplification + // + // DO: `[aab]` => `[ab]` + // DO: `[aa]` => `[a]` + runes_seen: map[rune]bool + + for r in specific.runes { + runes_seen[r] = true + } + + if len(runes_seen) != len(specific.runes) { + clear(&specific.runes) + for key in runes_seen { + append(&specific.runes, key) + } + changes += 1 + } + + // * Class Reduction + // + // DO: `[a]` => `a` + if !specific.negating && len(specific.runes) == 1 && len(specific.ranges) == 0 { + only_rune := specific.runes[0] + + node := new(Node_Rune) + node.data = only_rune + + return node, changes + 1 + } + + // * Range Construction + // + // DO: `[abc]` => `[a-c]` + slice.sort(specific.runes[:]) + if len(specific.runes) > 1 { + new_range: Rune_Class_Range + new_range.lower = specific.runes[0] + new_range.upper = specific.runes[0] + + #no_bounds_check for i := 1; i < len(specific.runes); i += 1 { + r := specific.runes[i] + if new_range.lower == -1 { + new_range = { r, r } + continue + } + + if r == new_range.lower - 1 { + new_range.lower -= 1 + ordered_remove(&specific.runes, i) + i -= 1 + changes += 1 + } else if r == new_range.upper + 1 { + new_range.upper += 1 + ordered_remove(&specific.runes, i) + i -= 1 + changes += 1 + } else if new_range.lower != new_range.upper { + append(&specific.ranges, new_range) + new_range = { -1, -1 } + changes += 1 + } + } + + if new_range.lower != new_range.upper { + append(&specific.ranges, new_range) + changes += 1 + } + } + + // * Rune Merging into Range + // + // DO: `[aa-c]` => `[a-c]` + for range in specific.ranges { + #no_bounds_check for i := 0; i < len(specific.runes); i += 1 { + r := specific.runes[i] + if range.lower <= r && r <= range.upper { + ordered_remove(&specific.runes, i) + i -= 1 + changes += 1 + } + } + } + + // * Range Merging + // + // DO: `[a-cc-e]` => `[a-e]` + // DO: `[a-cd-e]` => `[a-e]` + // DO: `[a-cb-e]` => `[a-e]` + slice.sort_by(specific.ranges[:], class_range_sorter) + #no_bounds_check for i := 0; i < len(specific.ranges) - 1; i += 1 { + for j := i + 1; j < len(specific.ranges); j += 1 { + left_range := &specific.ranges[i] + right_range := specific.ranges[j] + + if left_range.upper == right_range.lower || + left_range.upper == right_range.lower - 1 || + left_range.lower <= right_range.lower && right_range.lower <= left_range.upper { + left_range.upper = max(left_range.upper, right_range.upper) + ordered_remove(&specific.ranges, j) + j -= 1 + changes += 1 + } else { + break + } + } + } + + if len(specific.ranges) == 0 { + specific.ranges = {} + } + if len(specific.runes) == 0 { + specific.runes = {} + } + + // * NOP + // + // DO: `[]` => + if len(specific.ranges) + len(specific.runes) == 0 { + return nil, 1 + } + + slice.sort(specific.runes[:]) + slice.sort_by(specific.ranges[:], class_range_sorter) + + case ^Node_Alternation: + // Perform recursive optimization first. + left_changes, right_changes: int + specific.left, left_changes = optimize_subtree(specific.left, flags) + specific.right, right_changes = optimize_subtree(specific.right, flags) + changes += left_changes + right_changes + + // * Alternation to Optional + // + // DO: `a|` => `a?` + if specific.left != nil && specific.right == nil { + node := new(Node_Optional) + node.inner = specific.left + return node, 1 + } + + // * Alternation to Optional Non-Greedy + // + // DO: `|a` => `a??` + if specific.right != nil && specific.left == nil { + node := new(Node_Optional_Non_Greedy) + node.inner = specific.right + return node, 1 + } + + // * NOP + // + // DO: `|` => + if specific.left == nil && specific.right == nil { + return nil, 1 + } + + left_rune, left_is_rune := specific.left.(^Node_Rune) + right_rune, right_is_rune := specific.right.(^Node_Rune) + + if left_is_rune && right_is_rune { + if left_rune.data == right_rune.data { + // * Alternation Reduction + // + // DO: `a|a` => `a` + return left_rune, 1 + } else { + // * Alternation to Class + // + // DO: `a|b` => `[ab]` + node := new(Node_Rune_Class) + append(&node.runes, left_rune.data) + append(&node.runes, right_rune.data) + return node, 1 + } + } + + left_wildcard, left_is_wildcard := specific.left.(^Node_Wildcard) + right_wildcard, right_is_wildcard := specific.right.(^Node_Wildcard) + + // * Class Union + // + // DO: `[a0]|[b1]` => `[a0b1]` + left_class, left_is_class := specific.left.(^Node_Rune_Class) + right_class, right_is_class := specific.right.(^Node_Rune_Class) + if left_is_class && right_is_class { + for r in right_class.runes { + append(&left_class.runes, r) + } + for range in right_class.ranges { + append(&left_class.ranges, range) + } + return left_class, 1 + } + + // * Class Union + // + // DO: `[a-b]|c` => `[a-bc]` + if left_is_class && right_is_rune { + append(&left_class.runes, right_rune.data) + return left_class, 1 + } + + // * Class Union + // + // DO: `a|[b-c]` => `[b-ca]` + if left_is_rune && right_is_class { + append(&right_class.runes, left_rune.data) + return right_class, 1 + } + + // * Wildcard Reduction + // + // DO: `a|.` => `.` + if left_is_rune && right_is_wildcard { + return right_wildcard, 1 + } + + // * Wildcard Reduction + // + // DO: `.|a` => `.` + if left_is_wildcard && right_is_rune { + return left_wildcard, 1 + } + + // * Wildcard Reduction + // + // DO: `[ab]|.` => `.` + if left_is_class && right_is_wildcard { + return right_wildcard, 1 + } + + // * Wildcard Reduction + // + // DO: `.|[ab]` => `.` + if left_is_wildcard && right_is_class { + return left_wildcard, 1 + } + + left_concatenation, left_is_concatenation := specific.left.(^Node_Concatenation) + right_concatenation, right_is_concatenation := specific.right.(^Node_Concatenation) + + // * Common Suffix Elimination + // + // DO: `blueberry|strawberry` => `(?:blue|straw)berry` + if left_is_concatenation && right_is_concatenation { + // Remember that a concatenation could contain any node, not just runes. + left_len := len(left_concatenation.nodes) + right_len := len(right_concatenation.nodes) + least_len := min(left_len, right_len) + same_len := 0 + for i := 1; i <= least_len; i += 1 { + left_subrune, left_is_subrune := left_concatenation.nodes[left_len - i].(^Node_Rune) + right_subrune, right_is_subrune := right_concatenation.nodes[right_len - i].(^Node_Rune) + + if !left_is_subrune || !right_is_subrune { + // One of the nodes isn't a rune; there's nothing more we can do. + break + } + + if left_subrune.data == right_subrune.data { + same_len += 1 + } else { + // No more similarities. + break + } + } + + if same_len > 0 { + // Dissolve this alternation into a concatenation. + cat_node := new(Node_Concatenation) + group_node := new(Node_Group) + append(&cat_node.nodes, group_node) + + // Turn the concatenation into the common suffix. + for i := left_len - same_len; i < left_len; i += 1 { + append(&cat_node.nodes, left_concatenation.nodes[i]) + } + + // Construct the group of alternating prefixes. + for i := same_len; i > 0; i -= 1 { + pop(&left_concatenation.nodes) + pop(&right_concatenation.nodes) + } + + // (Re-using this alternation node.) + alter_node := specific + alter_node.left = left_concatenation + alter_node.right = right_concatenation + group_node.inner = alter_node + + return cat_node, 1 + } + } + + // * Common Prefix Elimination + // + // DO: `abi|abe` => `ab(?:i|e)` + if left_is_concatenation && right_is_concatenation { + // Try to identify a common prefix. + // Remember that a concatenation could contain any node, not just runes. + least_len := min(len(left_concatenation.nodes), len(right_concatenation.nodes)) + same_len := 0 + for i := 0; i < least_len; i += 1 { + left_subrune, left_is_subrune := left_concatenation.nodes[i].(^Node_Rune) + right_subrune, right_is_subrune := right_concatenation.nodes[i].(^Node_Rune) + + if !left_is_subrune || !right_is_subrune { + // One of the nodes isn't a rune; there's nothing more we can do. + break + } + + if left_subrune.data == right_subrune.data { + same_len = i + 1 + } else { + // No more similarities. + break + } + } + + if same_len > 0 { + cat_node := new(Node_Concatenation) + for i := 0; i < same_len; i += 1 { + append(&cat_node.nodes, left_concatenation.nodes[i]) + } + for i := same_len; i > 0; i -= 1 { + ordered_remove(&left_concatenation.nodes, 0) + ordered_remove(&right_concatenation.nodes, 0) + } + + group_node := new(Node_Group) + // (Re-using this alternation node.) + alter_node := specific + alter_node.left = left_concatenation + alter_node.right = right_concatenation + group_node.inner = alter_node + + append(&cat_node.nodes, group_node) + return cat_node, 1 + } + } + } + + return +} + +optimize :: proc(tree: Node, flags: common.Flags) -> (result: Node, changes: int) { + result = tree + new_changes := 0 + + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "AST before Optimizer: ") + parser.write_node(common.debug_stream, tree) + io.write_byte(common.debug_stream, '\n') + } + + // Keep optimizing until no more changes are seen. + for { + result, new_changes = optimize_subtree(result, flags) + changes += new_changes + if new_changes == 0 { + break + } + } + + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "AST after Optimizer: ") + parser.write_node(common.debug_stream, result) + io.write_byte(common.debug_stream, '\n') + } + + + return +} diff --git a/core/text/regex/parser/debugging.odin b/core/text/regex/parser/debugging.odin new file mode 100644 index 000000000..e060f58c2 --- /dev/null +++ b/core/text/regex/parser/debugging.odin @@ -0,0 +1,111 @@ +package regex_parser + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +import "core:io" + +write_node :: proc(w: io.Writer, node: Node) { + switch specific in node { + case ^Node_Rune: + io.write_rune(w, specific.data) + + case ^Node_Rune_Class: + io.write_byte(w, '[') + if specific.negating { + io.write_byte(w, '^') + } + for r in specific.data.runes { + io.write_rune(w, r) + } + for range in specific.data.ranges { + io.write_rune(w, range.lower) + io.write_byte(w, '-') + io.write_rune(w, range.upper) + } + io.write_byte(w, ']') + + case ^Node_Wildcard: + io.write_byte(w, '.') + + case ^Node_Concatenation: + io.write_rune(w, '「') + for subnode, i in specific.nodes { + if i != 0 { + io.write_rune(w, '⋅') + } + write_node(w, subnode) + } + io.write_rune(w, '」') + + case ^Node_Repeat_Zero: + write_node(w, specific.inner) + io.write_byte(w, '*') + case ^Node_Repeat_Zero_Non_Greedy: + write_node(w, specific.inner) + io.write_string(w, "*?") + case ^Node_Repeat_One: + write_node(w, specific.inner) + io.write_byte(w, '+') + case ^Node_Repeat_One_Non_Greedy: + write_node(w, specific.inner) + io.write_string(w, "+?") + + case ^Node_Repeat_N: + write_node(w, specific.inner) + if specific.lower == 0 && specific.upper == -1 { + io.write_byte(w, '*') + } else if specific.lower == 1 && specific.upper == -1 { + io.write_byte(w, '+') + } else { + io.write_byte(w, '{') + io.write_int(w, specific.lower) + io.write_byte(w, ',') + io.write_int(w, specific.upper) + io.write_byte(w, '}') + } + + case ^Node_Alternation: + io.write_rune(w, '《') + write_node(w, specific.left) + io.write_byte(w, '|') + write_node(w, specific.right) + io.write_rune(w, '》') + + case ^Node_Optional: + io.write_rune(w, '〈') + write_node(w, specific.inner) + io.write_byte(w, '?') + io.write_rune(w, '〉') + case ^Node_Optional_Non_Greedy: + io.write_rune(w, '〈') + write_node(w, specific.inner) + io.write_string(w, "??") + io.write_rune(w, '〉') + + case ^Node_Group: + io.write_byte(w, '(') + if !specific.capture { + io.write_string(w, "?:") + } + write_node(w, specific.inner) + io.write_byte(w, ')') + + case ^Node_Anchor: + io.write_byte(w, '^' if specific.start else '$') + + case ^Node_Word_Boundary: + io.write_string(w, `\B` if specific.non_word else `\b`) + + case ^Node_Match_All_And_Escape: + io.write_string(w, "《.*$》") + + case nil: + io.write_string(w, "") + } +} diff --git a/core/text/regex/parser/doc.odin b/core/text/regex/parser/doc.odin new file mode 100644 index 000000000..f518e518d --- /dev/null +++ b/core/text/regex/parser/doc.odin @@ -0,0 +1,10 @@ +/* +package regex_parser implements a Pratt parser, also known as a Top-Down +Operator Precedence parser, for parsing tokenized regular expression patterns. + +References: +- https://dl.acm.org/doi/10.1145/512927.512931 +- https://tdop.github.io/ +- http://crockford.com/javascript/tdop/tdop.html +*/ +package regex_parser diff --git a/core/text/regex/parser/parser.odin b/core/text/regex/parser/parser.odin new file mode 100644 index 000000000..038d4cb85 --- /dev/null +++ b/core/text/regex/parser/parser.odin @@ -0,0 +1,590 @@ +package regex_parser + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +import "base:intrinsics" +import "core:strconv" +import "core:strings" +import "core:text/regex/common" +import "core:text/regex/tokenizer" +import "core:unicode" +import "core:unicode/utf8" + +Token :: tokenizer.Token +Token_Kind :: tokenizer.Token_Kind +Tokenizer :: tokenizer.Tokenizer + +Rune_Class_Range :: struct { + lower, upper: rune, +} +Rune_Class_Data :: struct { + runes: [dynamic]rune, + ranges: [dynamic]Rune_Class_Range, +} + + +Node_Rune :: struct { + data: rune, +} + +Node_Rune_Class :: struct { + negating: bool, + using data: Rune_Class_Data, +} + +Node_Wildcard :: struct {} + +Node_Alternation :: struct { + left, right: Node, +} + +Node_Concatenation :: struct { + nodes: [dynamic]Node, +} + +Node_Repeat_Zero :: struct { + inner: Node, +} +Node_Repeat_Zero_Non_Greedy :: struct { + inner: Node, +} +Node_Repeat_One :: struct { + inner: Node, +} +Node_Repeat_One_Non_Greedy :: struct { + inner: Node, +} + +Node_Repeat_N :: struct { + inner: Node, + lower, upper: int, +} + +Node_Optional :: struct { + inner: Node, +} +Node_Optional_Non_Greedy :: struct { + inner: Node, +} + +Node_Group :: struct { + inner: Node, + capture_id: int, + capture: bool, +} + +Node_Anchor :: struct { + start: bool, +} +Node_Word_Boundary :: struct { + non_word: bool, +} + +Node_Match_All_And_Escape :: struct {} + +Node :: union { + ^Node_Rune, + ^Node_Rune_Class, + ^Node_Wildcard, + ^Node_Concatenation, + ^Node_Alternation, + ^Node_Repeat_Zero, + ^Node_Repeat_Zero_Non_Greedy, + ^Node_Repeat_One, + ^Node_Repeat_One_Non_Greedy, + ^Node_Repeat_N, + ^Node_Optional, + ^Node_Optional_Non_Greedy, + ^Node_Group, + ^Node_Anchor, + ^Node_Word_Boundary, + + // Optimized nodes (not created by the Parser): + ^Node_Match_All_And_Escape, +} + + +left_binding_power :: proc(kind: Token_Kind) -> int { + #partial switch kind { + case .Alternate: return 1 + case .Concatenate: return 2 + case .Repeat_Zero, .Repeat_One, + .Repeat_Zero_Non_Greedy, .Repeat_One_Non_Greedy, + .Repeat_N: return 3 + case .Optional, + .Optional_Non_Greedy: return 4 + case .Open_Paren, + .Open_Paren_Non_Capture: return 9 + } + return 0 +} + + +Expected_Token :: struct { + pos: int, + kind: Token_Kind, +} + +Invalid_Repetition :: struct { + pos: int, +} + +Invalid_Token :: struct { + pos: int, + kind: Token_Kind, +} + +Invalid_Unicode :: struct { + pos: int, +} + +Too_Many_Capture_Groups :: struct { + pos: int, +} + +Unexpected_EOF :: struct { + pos: int, +} + +Error :: union { + Expected_Token, + Invalid_Repetition, + Invalid_Token, + Invalid_Unicode, + Too_Many_Capture_Groups, + Unexpected_EOF, +} + + +Parser :: struct { + flags: common.Flags, + t: Tokenizer, + + cur_token: Token, + + groups: int, +} + + +@require_results +advance :: proc(p: ^Parser) -> Error { + p.cur_token = tokenizer.scan(&p.t) + if p.cur_token.kind == .Invalid { + return Invalid_Unicode { pos = 0 } + } + return nil +} + +expect :: proc(p: ^Parser, kind: Token_Kind) -> (err: Error) { + if p.cur_token.kind == kind { + advance(p) or_return + return + } + + return Expected_Token{ + pos = p.t.offset, + kind = kind, + } +} + +null_denotation :: proc(p: ^Parser, token: Token) -> (result: Node, err: Error) { + #partial switch token.kind { + case .Rune: + r: rune + for ru in token.text { + r = ru + break + } + assert(r != 0, "Parsed an empty Rune token.") + + if .Case_Insensitive in p.flags { + lower := unicode.to_lower(r) + upper := unicode.to_upper(r) + if lower != upper { + node := new(Node_Rune_Class) + append(&node.runes, lower) + append(&node.runes, upper) + return node, nil + } + } + + node := new(Node_Rune) + node ^= { r } + return node, nil + + case .Rune_Class: + if len(token.text) == 0 { + return nil, nil + } + + node := new(Node_Rune_Class) + + #no_bounds_check for i := 0; i < len(token.text); /**/ { + r, size := utf8.decode_rune(token.text[i:]) + if i == 0 && r == '^' { + node.negating = true + i += size + continue + } + i += size + + assert(size > 0, "RegEx tokenizer passed an incomplete Rune_Class to the parser.") + + if r == '\\' { + next_r, next_size := utf8.decode_rune(token.text[i:]) + i += next_size + assert(next_size > 0, "RegEx tokenizer passed an incomplete Rune_Class to the parser.") + + // @MetaCharacter + // NOTE: These must be kept in sync with the tokenizer. + switch next_r { + case 'f': append(&node.runes, '\f') + case 'n': append(&node.runes, '\n') + case 'r': append(&node.runes, '\r') + case 't': append(&node.runes, '\t') + + case 'd': + append(&node.ranges, Rune_Class_Range{ '0', '9' }) + case 's': + append(&node.runes, '\t') + append(&node.runes, '\n') + append(&node.runes, '\f') + append(&node.runes, '\r') + append(&node.runes, ' ') + case 'w': + append(&node.ranges, Rune_Class_Range{ '0', '9' }) + append(&node.ranges, Rune_Class_Range{ 'A', 'Z' }) + append(&node.runes, '_') + append(&node.ranges, Rune_Class_Range{ 'a', 'z' }) + case 'D': + append(&node.ranges, Rune_Class_Range{ 0, '0' - 1 }) + append(&node.ranges, Rune_Class_Range{ '9' + 1, max(rune) }) + case 'S': + append(&node.ranges, Rune_Class_Range{ 0, '\t' - 1 }) + // \t and \n are adjacent. + append(&node.runes, '\x0b') // Vertical Tab + append(&node.ranges, Rune_Class_Range{ '\r' + 1, ' ' - 1 }) + append(&node.ranges, Rune_Class_Range{ ' ' + 1, max(rune) }) + case 'W': + append(&node.ranges, Rune_Class_Range{ 0, '0' - 1 }) + append(&node.ranges, Rune_Class_Range{ '9' + 1, 'A' - 1 }) + append(&node.ranges, Rune_Class_Range{ 'Z' + 1, '_' - 1 }) + append(&node.ranges, Rune_Class_Range{ '_' + 1, 'a' - 1 }) + append(&node.ranges, Rune_Class_Range{ 'z' + 1, max(rune) }) + case: + append(&node.runes, next_r) + } + continue + } + + if r == '-' && len(node.runes) > 0 { + next_r, next_size := utf8.decode_rune(token.text[i:]) + if next_size > 0 { + last := pop(&node.runes) + i += next_size + + append(&node.ranges, Rune_Class_Range{ last, next_r }) + continue + } + } + + append(&node.runes, r) + } + + if .Case_Insensitive in p.flags { + // These two loops cannot be in the form of `for x in y` because + // they append to the data that they iterate over. + length := len(node.runes) + #no_bounds_check for i := 0; i < length; i += 1 { + r := node.runes[i] + lower := unicode.to_lower(r) + upper := unicode.to_upper(r) + + if lower != upper { + if lower != r { + append(&node.runes, lower) + } else { + append(&node.runes, upper) + } + } + } + + length = len(node.ranges) + #no_bounds_check for i := 0; i < length; i += 1 { + range := &node.ranges[i] + + min_lower := unicode.to_lower(range.lower) + max_lower := unicode.to_lower(range.upper) + + min_upper := unicode.to_upper(range.lower) + max_upper := unicode.to_upper(range.upper) + + if min_lower != min_upper && max_lower != max_upper { + range.lower = min_lower + range.upper = max_lower + append(&node.ranges, Rune_Class_Range{ min_upper, max_upper }) + } + } + } + + result = node + + case .Wildcard: + node := new(Node_Wildcard) + result = node + + case .Open_Paren: + // Because of the recursive nature of the token parser, we take the + // group number first instead of afterwards, in order to construct + // group matches from the outside in. + p.groups += 1 + if p.groups == common.MAX_CAPTURE_GROUPS { + return nil, Too_Many_Capture_Groups{ pos = token.pos } + } + this_group := p.groups + + node := new(Node_Group) + node.capture = true + node.capture_id = this_group + + node.inner = parse_expression(p, 0) or_return + expect(p, .Close_Paren) or_return + result = node + case .Open_Paren_Non_Capture: + node := new(Node_Group) + node.inner = parse_expression(p, 0) or_return + expect(p, .Close_Paren) or_return + result = node + case .Close_Paren: + node := new(Node_Rune) + node ^= { ')' } + return node, nil + + case .Anchor_Start: + node := new(Node_Anchor) + node.start = true + result = node + case .Anchor_End: + node := new(Node_Anchor) + result = node + case .Word_Boundary: + node := new(Node_Word_Boundary) + result = node + case .Non_Word_Boundary: + node := new(Node_Word_Boundary) + node.non_word = true + result = node + + case .Alternate: + // A unary alternation with a left-side empty path, i.e. `|a`. + right, right_err := parse_expression(p, left_binding_power(.Alternate)) + #partial switch specific in right_err { + case Unexpected_EOF: + // This token is a NOP, i.e. `|`. + break + case nil: + break + case: + return nil, right_err + } + + node := new(Node_Alternation) + node.right = right + result = node + + case .EOF: + return nil, Unexpected_EOF{ pos = token.pos } + + case: + return nil, Invalid_Token{ pos = token.pos, kind = token.kind } + } + + return +} + +left_denotation :: proc(p: ^Parser, token: Token, left: Node) -> (result: Node, err: Error) { + #partial switch token.kind { + case .Alternate: + if p.cur_token.kind == .Close_Paren { + // `(a|)` + // parse_expression will fail, so intervene here. + node := new(Node_Alternation) + node.left = left + return node, nil + } + + right, right_err := parse_expression(p, left_binding_power(.Alternate)) + + #partial switch specific in right_err { + case nil: + break + case Unexpected_EOF: + // EOF is okay in an alternation; it's an edge case in the way of + // expressing an optional such as `a|`. + break + case: + return nil, right_err + } + + node := new(Node_Alternation) + node.left = left + node.right = right + result = node + + case .Concatenate: + right := parse_expression(p, left_binding_power(.Concatenate)) or_return + + // There should be no need to check if right is Node_Concatenation, due + // to how the parsing direction works. + #partial switch specific in left { + case ^Node_Concatenation: + append(&specific.nodes, right) + result = specific + case: + node := new(Node_Concatenation) + append(&node.nodes, left) + append(&node.nodes, right) + result = node + } + + case .Repeat_Zero: + node := new(Node_Repeat_Zero) + node.inner = left + result = node + case .Repeat_Zero_Non_Greedy: + node := new(Node_Repeat_Zero_Non_Greedy) + node.inner = left + result = node + case .Repeat_One: + node := new(Node_Repeat_One) + node.inner = left + result = node + case .Repeat_One_Non_Greedy: + node := new(Node_Repeat_One_Non_Greedy) + node.inner = left + result = node + + case .Repeat_N: + node := new(Node_Repeat_N) + node.inner = left + + comma := strings.index_byte(token.text, ',') + + switch comma { + case -1: // {N} + exact, ok := strconv.parse_u64_of_base(token.text, base = 10) + if !ok { + return nil, Invalid_Repetition{ pos = token.pos } + } + if exact == 0 { + return nil, Invalid_Repetition{ pos = token.pos } + } + + node.lower = cast(int)exact + node.upper = cast(int)exact + + case 0: // {,M} + upper, ok := strconv.parse_u64_of_base(token.text[1:], base = 10) + if !ok { + return nil, Invalid_Repetition{ pos = token.pos } + } + if upper == 0 { + return nil, Invalid_Repetition{ pos = token.pos } + } + + node.lower = -1 + node.upper = cast(int)upper + + case len(token.text) - 1: // {N,} + lower, ok := strconv.parse_u64_of_base(token.text[:comma], base = 10) + if !ok { + return nil, Invalid_Repetition{ pos = token.pos } + } + + node.lower = cast(int)lower + node.upper = -1 + + case: // {N,M} + lower, lower_ok := strconv.parse_u64_of_base(token.text[:comma], base = 10) + if !lower_ok { + return nil, Invalid_Repetition{ pos = token.pos } + } + upper, upper_ok := strconv.parse_u64_of_base(token.text[comma+1:], base = 10) + if !upper_ok { + return nil, Invalid_Repetition{ pos = token.pos } + } + if lower > upper { + return nil, Invalid_Repetition{ pos = token.pos } + } + if upper == 0 { + return nil, Invalid_Repetition{ pos = token.pos } + } + + node.lower = cast(int)lower + node.upper = cast(int)upper + } + + result = node + + case .Optional: + node := new(Node_Optional) + node.inner = left + result = node + case .Optional_Non_Greedy: + node := new(Node_Optional_Non_Greedy) + node.inner = left + result = node + + case .EOF: + return nil, Unexpected_EOF{ pos = token.pos } + + case: + return nil, Invalid_Token{ pos = token.pos, kind = token.kind } + } + + return +} + +parse_expression :: proc(p: ^Parser, rbp: int) -> (result: Node, err: Error) { + token := p.cur_token + + advance(p) or_return + left := null_denotation(p, token) or_return + + token = p.cur_token + for rbp < left_binding_power(token.kind) { + advance(p) or_return + left = left_denotation(p, token, left) or_return + token = p.cur_token + } + + return left, nil +} + +parse :: proc(str: string, flags: common.Flags) -> (result: Node, err: Error) { + if len(str) == 0 { + node := new(Node_Group) + return node, nil + } + + p: Parser + p.flags = flags + + tokenizer.init(&p.t, str, flags) + + p.cur_token = tokenizer.scan(&p.t) + if p.cur_token.kind == .Invalid { + return nil, Invalid_Unicode { pos = 0 } + } + + node := parse_expression(&p, 0) or_return + result = node + + return +} diff --git a/core/text/regex/regex.odin b/core/text/regex/regex.odin new file mode 100644 index 000000000..8f8efe252 --- /dev/null +++ b/core/text/regex/regex.odin @@ -0,0 +1,451 @@ +package regex + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +import "core:text/regex/common" +import "core:text/regex/compiler" +import "core:text/regex/optimizer" +import "core:text/regex/parser" +import "core:text/regex/virtual_machine" + +Flag :: common.Flag +Flags :: common.Flags +Parser_Error :: parser.Error +Compiler_Error :: compiler.Error + +Creation_Error :: enum { + None, + // A `\` was supplied as the delimiter to `create_by_user`. + Bad_Delimiter, + // A pair of delimiters for `create_by_user` was not found. + Expected_Delimiter, + // An unknown letter was supplied to `create_by_user` after the last delimiter. + Unknown_Flag, +} + +Error :: union #shared_nil { + // An error that can occur in the pattern parsing phase. + // + // Most of these are regular expression syntax errors and are either + // context-dependent as to what they mean or have self-explanatory names. + Parser_Error, + // An error that can occur in the pattern compiling phase. + // + // Of the two that can be returned, they have to do with exceeding the + // limitations of the Virtual Machine. + Compiler_Error, + // An error that occurs only for `create_by_user`. + Creation_Error, +} + +/* +This struct corresponds to a set of string captures from a RegEx match. + +`pos` will contain the start and end positions for each string in `groups`, +such that `str[pos[0][0]:pos[0][1]] == groups[0]`. +*/ +Capture :: struct { + pos: [][2]int, + groups: []string, +} + +/* +A compiled Regular Expression value, to be used with the `match_*` procedures. +*/ +Regular_Expression :: struct { + flags: Flags `fmt:"-"`, + class_data: []virtual_machine.Rune_Class_Data `fmt:"-"`, + program: []virtual_machine.Opcode `fmt:"-"`, +} + + +/* +Create a regular expression from a string pattern and a set of flags. + +*Allocates Using Provided Allocators* + +Inputs: +- pattern: The pattern to compile. +- flags: A `bit_set` of RegEx flags. +- permanent_allocator: The allocator to use for the final regular expression. (default: context.allocator) +- temporary_allocator: The allocator to use for the intermediate compilation stages. (default: context.temp_allocator) + +Returns: +- result: The regular expression. +- err: An error, if one occurred. +*/ +@require_results +create :: proc( + pattern: string, + flags: Flags = {}, + permanent_allocator := context.allocator, + temporary_allocator := context.temp_allocator, +) -> (result: Regular_Expression, err: Error) { + + // For the sake of speed and simplicity, we first run all the intermediate + // processes such as parsing and compilation through the temporary + // allocator. + program: [dynamic]virtual_machine.Opcode = --- + class_data: [dynamic]parser.Rune_Class_Data = --- + { + context.allocator = temporary_allocator + + ast := parser.parse(pattern, flags) or_return + + if .No_Optimization not_in flags { + ast, _ = optimizer.optimize(ast, flags) + } + + program, class_data = compiler.compile(ast, flags) or_return + } + + // When that's successful, re-allocate all at once with the permanent + // allocator so everything can be tightly packed. + context.allocator = permanent_allocator + + result.flags = flags + + if len(class_data) > 0 { + result.class_data = make([]virtual_machine.Rune_Class_Data, len(class_data)) + } + for data, i in class_data { + if len(data.runes) > 0 { + result.class_data[i].runes = make([]rune, len(data.runes)) + copy(result.class_data[i].runes, data.runes[:]) + } + if len(data.ranges) > 0 { + result.class_data[i].ranges = make([]virtual_machine.Rune_Class_Range, len(data.ranges)) + copy(result.class_data[i].ranges, data.ranges[:]) + } + } + + result.program = make([]virtual_machine.Opcode, len(program)) + copy(result.program, program[:]) + + return +} + +/* +Create a regular expression from a delimited string pattern, such as one +provided by users of a program or those found in a configuration file. + +They are in the form of: + + [DELIMITER] [regular expression] [DELIMITER] [flags] + +For example, the following strings are valid: + + /hellope/i + #hellope#i + •hellope•i + つhellopeつi + +The delimiter is determined by the very first rune in the string. +The only restriction is that the delimiter cannot be `\`, as that rune is used +to escape the delimiter if found in the middle of the string. + +All runes after the closing delimiter will be parsed as flags: + +- 'g': Global +- 'm': Multiline +- 'i': Case_Insensitive +- 'x': Ignore_Whitespace +- 'u': Unicode +- 'n': No_Capture +- '-': No_Optimization + + +*Allocates Using Provided Allocators* + +Inputs: +- pattern: The delimited pattern with optional flags to compile. +- str: The string to match against. +- permanent_allocator: The allocator to use for the final regular expression. (default: context.allocator) +- temporary_allocator: The allocator to use for the intermediate compilation stages. (default: context.temp_allocator) + +Returns: +- result: The regular expression. +- err: An error, if one occurred. +*/ +@require_results +create_by_user :: proc( + pattern: string, + permanent_allocator := context.allocator, + temporary_allocator := context.temp_allocator, +) -> (result: Regular_Expression, err: Error) { + + if len(pattern) == 0 { + err = .Expected_Delimiter + return + } + + delimiter: rune + start := -1 + end := -1 + + flags: Flags + + escaping: bool + parse_loop: for r, i in pattern { + if delimiter == 0 { + if r == '\\' { + err = .Bad_Delimiter + return + } + delimiter = r + continue parse_loop + } + + if start == -1 { + start = i + } + + if escaping { + escaping = false + continue parse_loop + } + + switch r { + case '\\': + escaping = true + case delimiter: + end = i + break parse_loop + } + } + + if end == -1 { + err = .Expected_Delimiter + return + } + + // `start` is also the size of the delimiter, which is why it's being added + // to `end` here. + for r in pattern[start + end:] { + switch r { + case 'g': flags += { .Global } + case 'm': flags += { .Multiline } + case 'i': flags += { .Case_Insensitive } + case 'x': flags += { .Ignore_Whitespace } + case 'u': flags += { .Unicode } + case 'n': flags += { .No_Capture } + case '-': flags += { .No_Optimization } + case: + err = .Unknown_Flag + return + } + } + + return create(pattern[start:end], flags, permanent_allocator, temporary_allocator) +} + +/* +Match a regular expression against a string and allocate the results into the +returned `capture` structure. + +The resulting capture strings will be slices to the string `str`, not wholly +copied strings, so they won't need to be individually deleted. + +*Allocates Using Provided Allocators* + +Inputs: +- regex: The regular expression. +- str: The string to match against. +- permanent_allocator: The allocator to use for the capture results. (default: context.allocator) +- temporary_allocator: The allocator to use for the virtual machine. (default: context.temp_allocator) + +Returns: +- capture: The capture groups found in the string. +- success: True if the regex matched the string. +*/ +@require_results +match_and_allocate_capture :: proc( + regex: Regular_Expression, + str: string, + permanent_allocator := context.allocator, + temporary_allocator := context.temp_allocator, +) -> (capture: Capture, success: bool) { + + saved: ^[2 * common.MAX_CAPTURE_GROUPS]int + + { + context.allocator = temporary_allocator + + vm := virtual_machine.create(regex.program, str) + vm.class_data = regex.class_data + + if .Unicode in regex.flags { + saved, success = virtual_machine.run(&vm, true) + } else { + saved, success = virtual_machine.run(&vm, false) + } + } + + if saved != nil { + context.allocator = permanent_allocator + + num_groups := 0 + #no_bounds_check for i := 0; i < len(saved); i += 2 { + a, b := saved[i], saved[i + 1] + if a == -1 || b == -1 { + continue + } + num_groups += 1 + } + + if num_groups > 0 { + capture.groups = make([]string, num_groups) + capture.pos = make([][2]int, num_groups) + n := 0 + + #no_bounds_check for i := 0; i < len(saved); i += 2 { + a, b := saved[i], saved[i + 1] + if a == -1 || b == -1 { + continue + } + + capture.groups[n] = str[a:b] + capture.pos[n] = {a, b} + n += 1 + } + } + } + + return +} + +/* +Match a regular expression against a string and save the capture results into +the provided `capture` structure. + +The resulting capture strings will be slices to the string `str`, not wholly +copied strings, so they won't need to be individually deleted. + +*Allocates Using Provided Allocator* + +Inputs: +- regex: The regular expression. +- str: The string to match against. +- capture: A pointer to a Capture structure with `groups` and `pos` already allocated. +- temporary_allocator: The allocator to use for the virtual machine. (default: context.temp_allocator) + +Returns: +- num_groups: The number of capture groups set into `capture`. +- success: True if the regex matched the string. +*/ +@require_results +match_with_preallocated_capture :: proc( + regex: Regular_Expression, + str: string, + capture: ^Capture, + temporary_allocator := context.temp_allocator, +) -> (num_groups: int, success: bool) { + + assert(capture != nil, "Pre-allocated RegEx capture must not be nil.") + assert(len(capture.groups) >= common.MAX_CAPTURE_GROUPS, + "Pre-allocated RegEx capture `groups` must be at least 10 elements long.") + assert(len(capture.pos) >= common.MAX_CAPTURE_GROUPS, + "Pre-allocated RegEx capture `pos` must be at least 10 elements long.") + + saved: ^[2 * common.MAX_CAPTURE_GROUPS]int + + { + context.allocator = temporary_allocator + + vm := virtual_machine.create(regex.program, str) + vm.class_data = regex.class_data + + if .Unicode in regex.flags { + saved, success = virtual_machine.run(&vm, true) + } else { + saved, success = virtual_machine.run(&vm, false) + } + } + + if saved != nil { + n := 0 + + #no_bounds_check for i := 0; i < len(saved); i += 2 { + a, b := saved[i], saved[i + 1] + if a == -1 || b == -1 { + continue + } + + capture.groups[n] = str[a:b] + capture.pos[n] = {a, b} + n += 1 + } + num_groups = n + } + + return +} + +match :: proc { + match_and_allocate_capture, + match_with_preallocated_capture, +} + +/* +Allocate a `Capture` in advance for use with `match`. This can save some time +if you plan on performing several matches at once and only need the results +between matches. + +Inputs: +- allocator: (default: context.allocator) + +Returns: +- result: The `Capture` with the maximum number of groups allocated. +*/ +@require_results +preallocate_capture :: proc(allocator := context.allocator) -> (result: Capture) { + context.allocator = allocator + result.pos = make([][2]int, common.MAX_CAPTURE_GROUPS) + result.groups = make([]string, common.MAX_CAPTURE_GROUPS) + return +} + +/* +Free all data allocated by the `create*` procedures. + +*Frees Using Provided Allocator* + +Inputs: +- regex: A regular expression. +- allocator: (default: context.allocator) +*/ +destroy_regex :: proc(regex: Regular_Expression, allocator := context.allocator) { + context.allocator = allocator + delete(regex.program) + for data in regex.class_data { + delete(data.runes) + delete(data.ranges) + } + delete(regex.class_data) +} + +/* +Free all data allocated by the `match_and_allocate_capture` procedure. + +*Frees Using Provided Allocator* + +Inputs: +- capture: A Capture. +- allocator: (default: context.allocator) +*/ +destroy_capture :: proc(capture: Capture, allocator := context.allocator) { + context.allocator = allocator + delete(capture.groups) + delete(capture.pos) +} + +destroy :: proc { + destroy_regex, + destroy_capture, +} diff --git a/core/text/regex/tokenizer/tokenizer.odin b/core/text/regex/tokenizer/tokenizer.odin new file mode 100644 index 000000000..447fe4329 --- /dev/null +++ b/core/text/regex/tokenizer/tokenizer.odin @@ -0,0 +1,357 @@ +package regex_tokenizer + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +import "core:text/regex/common" +import "core:unicode/utf8" + +Token_Kind :: enum { + Invalid, + EOF, + + Rune, + Wildcard, + + Alternate, + + Concatenate, + + Repeat_Zero, + Repeat_Zero_Non_Greedy, + Repeat_One, + Repeat_One_Non_Greedy, + + Repeat_N, + + Optional, + Optional_Non_Greedy, + + Rune_Class, + + Open_Paren, + Open_Paren_Non_Capture, + Close_Paren, + + Anchor_Start, + Anchor_End, + + Word_Boundary, + Non_Word_Boundary, +} + +Token :: struct { + kind: Token_Kind, + text: string, + pos: int, +} + +Tokenizer :: struct { + flags: common.Flags, + src: string, + + ch: rune, + offset: int, + read_offset: int, + + last_token_kind: Token_Kind, + held_token: Token, + error_state: Error, + paren_depth: int, +} + +Error :: enum { + None, + Illegal_Null_Character, + Illegal_Codepoint, + Illegal_Byte_Order_Mark, +} + +init :: proc(t: ^Tokenizer, str: string, flags: common.Flags) { + t.src = str + t.flags = flags + t.error_state = advance_rune(t) +} + +peek_byte :: proc(t: ^Tokenizer, offset := 0) -> byte { + if t.read_offset+offset < len(t.src) { + return t.src[t.read_offset+offset] + } + return 0 +} + +advance_rune :: proc(t: ^Tokenizer) -> (err: Error) { + if t.error_state != nil { + return t.error_state + } + + if t.read_offset < len(t.src) { + t.offset = t.read_offset + r, w := rune(t.src[t.read_offset]), 1 + switch { + case r == 0: + err = .Illegal_Null_Character + case r >= utf8.RUNE_SELF: + r, w = utf8.decode_rune(t.src[t.read_offset:]) + if r == utf8.RUNE_ERROR && w == 1 { + err = .Illegal_Codepoint + } else if r == utf8.RUNE_BOM && t.offset > 0 { + err = .Illegal_Byte_Order_Mark + } + } + t.read_offset += w + t.ch = r + } else { + t.offset = len(t.src) + t.ch = -1 + } + + t.error_state = err + + return +} + +@require_results +scan_class :: proc(t: ^Tokenizer) -> (str: string, ok: bool) { + start := t.read_offset + + for { + advance_rune(t) + if t.ch == -1 || t.error_state != nil { + return "", false + } + + if t.ch == '\\' { + advance_rune(t) + continue + } + + if t.ch == ']' { + return t.src[start:t.offset], true + } + } + + unreachable() +} + +@require_results +scan_repeat :: proc(t: ^Tokenizer) -> (str: string, ok: bool) { + start := t.read_offset + + for { + advance_rune(t) + if t.ch == -1 { + return "", false + } + if t.ch == '}' { + return t.src[start:t.offset], true + } + } + + unreachable() +} + +@require_results +scan_non_greedy :: proc(t: ^Tokenizer) -> bool { + if peek_byte(t) == '?' { + advance_rune(t) + return true + } + + return false +} + +scan_comment :: proc(t: ^Tokenizer) { + for { + advance_rune(t) + switch t.ch { + case -1: + return + case '\n': + // UNIX newline. + advance_rune(t) + return + case '\r': + // Mac newline. + advance_rune(t) + if t.ch == '\n' { + // Windows newline. + advance_rune(t) + } + return + } + } +} + +@require_results +scan_non_capture_group :: proc(t: ^Tokenizer) -> bool { + if peek_byte(t) == '?' && peek_byte(t, 1) == ':' { + advance_rune(t) + advance_rune(t) + return true + } + + return false +} + +@require_results +scan :: proc(t: ^Tokenizer) -> (token: Token) { + kind: Token_Kind + lit: string + pos := t.offset + + defer { + t.last_token_kind = token.kind + } + + if t.error_state != nil { + t.error_state = nil + return { .Invalid, "", pos } + } + + if t.held_token != {} { + popped := t.held_token + t.held_token = {} + + return popped + } + + ch_loop: for { + switch t.ch { + case -1: + return { .EOF, "", pos } + + case '\\': + advance_rune(t) + + if t.ch == -1 { + return { .EOF, "", pos } + } + + pos = t.offset + + // @MetaCharacter + // NOTE: These must be kept in sync with the compiler. + DIGIT_CLASS :: "0-9" + SPACE_CLASS :: "\t\n\f\r " + WORD_CLASS :: "0-9A-Z_a-z" + + switch t.ch { + case 'b': kind = .Word_Boundary + case 'B': kind = .Non_Word_Boundary + + case 'f': kind = .Rune; lit = "\f" + case 'n': kind = .Rune; lit = "\n" + case 'r': kind = .Rune; lit = "\r" + case 't': kind = .Rune; lit = "\t" + + case 'd': kind = .Rune_Class; lit = DIGIT_CLASS + case 's': kind = .Rune_Class; lit = SPACE_CLASS + case 'w': kind = .Rune_Class; lit = WORD_CLASS + case 'D': kind = .Rune_Class; lit = "^" + DIGIT_CLASS + case 'S': kind = .Rune_Class; lit = "^" + SPACE_CLASS + case 'W': kind = .Rune_Class; lit = "^" + WORD_CLASS + case: + kind = .Rune + lit = t.src[t.offset:t.read_offset] + } + + case '.': + kind = .Wildcard + + case '|': kind = .Alternate + + case '*': kind = .Repeat_Zero_Non_Greedy if scan_non_greedy(t) else .Repeat_Zero + case '+': kind = .Repeat_One_Non_Greedy if scan_non_greedy(t) else .Repeat_One + case '?': kind = .Optional_Non_Greedy if scan_non_greedy(t) else .Optional + + case '[': + if text, ok := scan_class(t); ok { + kind = .Rune_Class + lit = text + } else { + kind = .EOF + } + + case '{': + if text, ok := scan_repeat(t); ok { + kind = .Repeat_N + lit = text + } else { + kind = .EOF + } + + case '(': + kind = .Open_Paren_Non_Capture if scan_non_capture_group(t) else .Open_Paren + t.paren_depth += 1 + case ')': + kind = .Close_Paren + t.paren_depth -= 1 + + case '^': kind = .Anchor_Start + case '$': + kind = .Anchor_End + + case: + if .Ignore_Whitespace in t.flags { + switch t.ch { + case ' ', '\r', '\n', '\t', '\f': + advance_rune(t) + continue ch_loop + case: + break + } + } + if t.ch == '#' && t.paren_depth == 0 { + scan_comment(t) + continue ch_loop + } + + kind = .Rune + lit = t.src[t.offset:t.read_offset] + } + + break ch_loop + } + + if t.error_state != nil { + t.error_state = nil + return { .Invalid, "", pos } + } + + advance_rune(t) + + // The following set of rules dictate where Concatenate tokens are + // automatically inserted. + #partial switch kind { + case + .Close_Paren, + .Alternate, + .Optional, .Optional_Non_Greedy, + .Repeat_Zero, .Repeat_Zero_Non_Greedy, + .Repeat_One, .Repeat_One_Non_Greedy, + .Repeat_N: + // Never prepend a Concatenate before these tokens. + break + case: + #partial switch t.last_token_kind { + case + .Invalid, + .Open_Paren, .Open_Paren_Non_Capture, + .Alternate: + // Never prepend a Concatenate token when the _last token_ was one + // of these. + break + case: + t.held_token = { kind, lit, pos } + return { .Concatenate, "", pos } + } + } + + return { kind, lit, pos } +} diff --git a/core/text/regex/virtual_machine/doc.odin b/core/text/regex/virtual_machine/doc.odin new file mode 100644 index 000000000..1b0694565 --- /dev/null +++ b/core/text/regex/virtual_machine/doc.odin @@ -0,0 +1,175 @@ +/* +package regex_vm implements a threaded virtual machine for interpreting +regular expressions, based on the designs described by Russ Cox and attributed +to both Ken Thompson and Rob Pike. + +The virtual machine executes all threads in lock step, i.e. the string pointer +does not advance until all threads have finished processing the current rune. +The algorithm does not look backwards. + +Threads merge when splitting or jumping to positions already visited by another +thread, based on the observation that each thread having visited one PC +(Program Counter) state will execute identically to the previous thread. + +Each thread keeps a save state of its capture groups, and thread priority is +used to allow higher precedence operations to complete first with correct save +states, such as greedy versus non-greedy repetition. + +For more information, see: https://swtch.com/~rsc/regexp/regexp2.html + + +**Implementation Details:** + +- Each opcode is 8 bits in size, and most instructions have no operands. + +- All operands larger than `u8` are read in system endian order. + +- Jump and Split instructions operate on absolute positions in `u16` operands. + +- Classes such as `[0-9]` are stored in a RegEx-specific slice of structs which + are then dereferenced by a `u8` index from the `Rune_Class` instructions. + +- Each Byte and Rune opcode have their operands stored inline after the opcode, + sized `u8` and `i32` respectively. + +- A bitmap is used to determine which PC positions are occupied by a thread to + perform merging. The bitmap is cleared with every new frame. + +- The VM supports two modes: ASCII and Unicode, decided by a compile-time + boolean constant argument provided to `run`. The procedure differs only in + string decoding. This was done for the sake of performance. + +- No allocations are ever freed; the VM expects an arena or temporary allocator + to be used in the context preceding it. + + +**Opcode Reference:** + + (0x00) Match + + The terminal opcode which ends a thread. This always comes at the end of + the program. + + (0x01) Match_And_Exit + + A modified version of Match which stops the virtual machine entirely. It is + only compiled for `No_Capture` expressions, as those expressions do not + need to determine which thread may have saved the most appropriate capture + groups. + + (0x02) Byte + + Consumes one byte from the text using its operand, which is also a byte. + + (0x03) Rune + + Consumes one Unicode codepoint from the text using its operand, which is + four bytes long in a system-dependent endian order. + + (0x04) Rune_Class + + Consumes one character (which may be an ASCII byte or Unicode codepoint, + wholly dependent on which mode the virtual machine is running in) from the + text. + + The actual data storing what runes and ranges of runes apply to the class + are stored alongside the program in the Regular_Expression structure and + the operand for this opcode is a single byte which indexes into a + collection of these data structures. + + (0x05) Rune_Class_Negated + + A modified version of Rune_Class that functions the same, save for how it + returns the opposite of what Rune_Class matches. + + (0x06) Wildcard + + Consumes one byte or one Unicode codepoint, depending on the VM mode. + + (0x07) Jump + + Sets the Program Counter of a VM thread to the operand, which is a u16. + This opcode is used to implement Alternation (coming at the end of the left + choice) and Repeat_Zero (to cause the thread to loop backwards). + + (0x08) Split + + Spawns a new thread for the X operand and causes the current thread to jump + to the Y operand. This opcode is used to implement Alternation, all the + Repeat variations, and the Optional nodes. + + Splitting threads is how the virtual machine is able to execute optional + control flow paths, letting it evaluate different possible ways to match + text. + + (0x09) Save + + Saves the current string index to a slot on the thread dictated by the + operand. These values will be used later to reconstruct capture groups. + + (0x0A) Assert_Start + + Asserts that the thread is at the beginning of a string. + + (0x0B) Assert_End + + Asserts that the thread is at the end of a string. + + (0x0C) Assert_Word_Boundary + + Asserts that the thread is on a word boundary, which can be the start or + end of the text. This examines both the current rune and the next rune. + + (0x0D) Assert_Non_Word_Boundary + + A modified version of Assert_Word_Boundary that returns the opposite value. + + (0x0E) Multiline_Open + + This opcode is compiled in only when the `Multiline` flag is present, and + it replaces both `^` and `$` text anchors. + + It asserts that either the current thread is on one of the string + boundaries, or it consumes a `\n` or `\r` character. + + If a `\r` character is consumed, the PC will be advanced to the sibling + `Multiline_Close` opcode to optionally consume a `\n` character on the next + frame. + + (0x0F) Multiline_Close + + This opcode is always present after `Multiline_Open`. + + It handles consuming the second half of a complete newline, if necessary. + For example, Windows newlines are represented by the characters `\r\n`, + whereas UNIX newlines are `\n` and Macintosh newlines are `\r`. + + (0x10) Wait_For_Byte + (0x11) Wait_For_Rune + (0x12) Wait_For_Rune_Class + (0x13) Wait_For_Rune_Class_Negated + + These opcodes are an optimization around restarting threads on failed + matches when the beginning to a pattern is predictable and the Global flag + is set. + + They will cause the VM to wait for the next rune to match before splitting, + as would happen in the un-optimized version. + + (0x14) Match_All_And_Escape + + This opcode is an optimized version of `.*$` or `.+$` that causes the + active thread to immediately work on escaping the program by following all + Jumps out to the end. + + While running through the rest of the program, the thread will trigger on + every Save instruction it passes to store the length of the string. + + This way, any time a program hits one of these `.*$` constructs, the + virtual machine can exit early, vastly improving processing times. + + Be aware, this opcode is not compiled in if the `Multiline` flag is on, as + the meaning of `$` changes with that flag. + +*/ +package regex_vm diff --git a/core/text/regex/virtual_machine/util.odin b/core/text/regex/virtual_machine/util.odin new file mode 100644 index 000000000..fa94a139f --- /dev/null +++ b/core/text/regex/virtual_machine/util.odin @@ -0,0 +1,81 @@ +package regex_vm + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +Opcode_Iterator :: struct { + code: Program, + pc: int, +} + +iterate_opcodes :: proc(iter: ^Opcode_Iterator) -> (opcode: Opcode, pc: int, ok: bool) { + if iter.pc >= len(iter.code) { + return + } + + opcode = iter.code[iter.pc] + pc = iter.pc + ok = true + + switch opcode { + case .Match: iter.pc += size_of(Opcode) + case .Match_And_Exit: iter.pc += size_of(Opcode) + case .Byte: iter.pc += size_of(Opcode) + size_of(u8) + case .Rune: iter.pc += size_of(Opcode) + size_of(rune) + case .Rune_Class: iter.pc += size_of(Opcode) + size_of(u8) + case .Rune_Class_Negated: iter.pc += size_of(Opcode) + size_of(u8) + case .Wildcard: iter.pc += size_of(Opcode) + case .Jump: iter.pc += size_of(Opcode) + size_of(u16) + case .Split: iter.pc += size_of(Opcode) + 2 * size_of(u16) + case .Save: iter.pc += size_of(Opcode) + size_of(u8) + case .Assert_Start: iter.pc += size_of(Opcode) + case .Assert_End: iter.pc += size_of(Opcode) + case .Assert_Word_Boundary: iter.pc += size_of(Opcode) + case .Assert_Non_Word_Boundary: iter.pc += size_of(Opcode) + case .Multiline_Open: iter.pc += size_of(Opcode) + case .Multiline_Close: iter.pc += size_of(Opcode) + case .Wait_For_Byte: iter.pc += size_of(Opcode) + size_of(u8) + case .Wait_For_Rune: iter.pc += size_of(Opcode) + size_of(rune) + case .Wait_For_Rune_Class: iter.pc += size_of(Opcode) + size_of(u8) + case .Wait_For_Rune_Class_Negated: iter.pc += size_of(Opcode) + size_of(u8) + case .Match_All_And_Escape: iter.pc += size_of(Opcode) + case: + panic("Invalid opcode found in RegEx program.") + } + + return +} + +opcode_to_name :: proc(opcode: Opcode) -> (str: string) { + switch opcode { + case .Match: str = "Match" + case .Match_And_Exit: str = "Match_And_Exit" + case .Byte: str = "Byte" + case .Rune: str = "Rune" + case .Rune_Class: str = "Rune_Class" + case .Rune_Class_Negated: str = "Rune_Class_Negated" + case .Wildcard: str = "Wildcard" + case .Jump: str = "Jump" + case .Split: str = "Split" + case .Save: str = "Save" + case .Assert_Start: str = "Assert_Start" + case .Assert_End: str = "Assert_End" + case .Assert_Word_Boundary: str = "Assert_Word_Boundary" + case .Assert_Non_Word_Boundary: str = "Assert_Non_Word_Boundary" + case .Multiline_Open: str = "Multiline_Open" + case .Multiline_Close: str = "Multiline_Close" + case .Wait_For_Byte: str = "Wait_For_Byte" + case .Wait_For_Rune: str = "Wait_For_Rune" + case .Wait_For_Rune_Class: str = "Wait_For_Rune_Class" + case .Wait_For_Rune_Class_Negated: str = "Wait_For_Rune_Class_Negated" + case .Match_All_And_Escape: str = "Match_All_And_Escape" + case: str = "" + } + + return +} diff --git a/core/text/regex/virtual_machine/virtual_machine.odin b/core/text/regex/virtual_machine/virtual_machine.odin new file mode 100644 index 000000000..a4fca6c4d --- /dev/null +++ b/core/text/regex/virtual_machine/virtual_machine.odin @@ -0,0 +1,646 @@ +package regex_vm + +/* + (c) Copyright 2024 Feoramund . + Made available under Odin's BSD-3 license. + + List of contributors: + Feoramund: Initial implementation. +*/ + +import "base:intrinsics" +@require import "core:io" +import "core:slice" +import "core:text/regex/common" +import "core:text/regex/parser" +import "core:unicode/utf8" + +Rune_Class_Range :: parser.Rune_Class_Range + +// NOTE: This structure differs intentionally from the one in `regex/parser`, +// as this data doesn't need to be a dynamic array once it hits the VM. +Rune_Class_Data :: struct { + runes: []rune, + ranges: []Rune_Class_Range, +} + +Opcode :: enum u8 { + // | [ operands ] + Match = 0x00, // | + Match_And_Exit = 0x01, // | + Byte = 0x02, // | u8 + Rune = 0x03, // | i32 + Rune_Class = 0x04, // | u8 + Rune_Class_Negated = 0x05, // | u8 + Wildcard = 0x06, // | + Jump = 0x07, // | u16 + Split = 0x08, // | u16, u16 + Save = 0x09, // | u8 + Assert_Start = 0x0A, // | + Assert_End = 0x0B, // | + Assert_Word_Boundary = 0x0C, // | + Assert_Non_Word_Boundary = 0x0D, // | + Multiline_Open = 0x0E, // | + Multiline_Close = 0x0F, // | + Wait_For_Byte = 0x10, // | u8 + Wait_For_Rune = 0x11, // | i32 + Wait_For_Rune_Class = 0x12, // | u8 + Wait_For_Rune_Class_Negated = 0x13, // | u8 + Match_All_And_Escape = 0x14, // | +} + +Thread :: struct { + pc: int, + saved: ^[2 * common.MAX_CAPTURE_GROUPS]int, +} + +Program :: []Opcode + +Machine :: struct { + // Program state + memory: string, + class_data: []Rune_Class_Data, + code: Program, + + // Thread state + top_thread: int, + threads: [^]Thread, + next_threads: [^]Thread, + + // The busy map is used to merge threads based on their program counters. + busy_map: []u64, + + // Global state + string_pointer: int, + + current_rune: rune, + current_rune_size: int, + next_rune: rune, + next_rune_size: int, +} + + +// @MetaCharacter +// NOTE: This must be kept in sync with the compiler & tokenizer. +is_word_class :: #force_inline proc "contextless" (r: rune) -> bool { + switch r { + case '0'..='9', 'A'..='Z', '_', 'a'..='z': + return true + case: + return false + } +} + +set_busy_map :: #force_inline proc "contextless" (vm: ^Machine, pc: int) -> bool #no_bounds_check { + slot := cast(u64)pc >> 6 + bit: u64 = 1 << (cast(u64)pc & 0x3F) + if vm.busy_map[slot] & bit > 0 { + return false + } + vm.busy_map[slot] |= bit + return true +} + +check_busy_map :: #force_inline proc "contextless" (vm: ^Machine, pc: int) -> bool #no_bounds_check { + slot := cast(u64)pc >> 6 + bit: u64 = 1 << (cast(u64)pc & 0x3F) + return vm.busy_map[slot] & bit > 0 +} + +add_thread :: proc(vm: ^Machine, saved: ^[2 * common.MAX_CAPTURE_GROUPS]int, pc: int) #no_bounds_check { + if check_busy_map(vm, pc) { + return + } + + saved := saved + pc := pc + + resolution_loop: for { + if !set_busy_map(vm, pc) { + return + } + + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "Thread [PC:") + common.write_padded_hex(common.debug_stream, pc, 4) + io.write_string(common.debug_stream, "] thinking about ") + io.write_string(common.debug_stream, opcode_to_name(vm.code[pc])) + io.write_rune(common.debug_stream, '\n') + } + + #partial switch vm.code[pc] { + case .Jump: + pc = cast(int)intrinsics.unaligned_load(cast(^u16)&vm.code[pc + size_of(Opcode)]) + continue + + case .Split: + jmp_x := cast(int)intrinsics.unaligned_load(cast(^u16)&vm.code[pc + size_of(Opcode)]) + jmp_y := cast(int)intrinsics.unaligned_load(cast(^u16)&vm.code[pc + size_of(Opcode) + size_of(u16)]) + + add_thread(vm, saved, jmp_x) + pc = jmp_y + continue + + case .Save: + new_saved := new([2 * common.MAX_CAPTURE_GROUPS]int) + new_saved ^= saved^ + saved = new_saved + + index := vm.code[pc + size_of(Opcode)] + sp := vm.string_pointer+vm.current_rune_size + saved[index] = sp + + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "Thread [PC:") + common.write_padded_hex(common.debug_stream, pc, 4) + io.write_string(common.debug_stream, "] saving state: (slot ") + io.write_int(common.debug_stream, cast(int)index) + io.write_string(common.debug_stream, " = ") + io.write_int(common.debug_stream, sp) + io.write_string(common.debug_stream, ")\n") + } + + pc += size_of(Opcode) + size_of(u8) + continue + + case .Assert_Start: + sp := vm.string_pointer+vm.current_rune_size + if sp == 0 { + pc += size_of(Opcode) + continue + } + case .Assert_End: + sp := vm.string_pointer+vm.current_rune_size + if sp == len(vm.memory) { + pc += size_of(Opcode) + continue + } + case .Multiline_Open: + sp := vm.string_pointer+vm.current_rune_size + if sp == 0 || sp == len(vm.memory) { + if vm.next_rune == '\r' || vm.next_rune == '\n' { + // The VM is currently on a newline at the string boundary, + // so consume the newline next frame. + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "*** New thread added [PC:") + common.write_padded_hex(common.debug_stream, pc, 4) + io.write_string(common.debug_stream, "]\n") + } + vm.next_threads[vm.top_thread] = Thread{ pc = pc, saved = saved } + vm.top_thread += 1 + } else { + // Skip the `Multiline_Close` opcode. + pc += 2 * size_of(Opcode) + continue + } + } else { + // Not on a string boundary. + // Try to consume a newline next frame in the other opcode loop. + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "*** New thread added [PC:") + common.write_padded_hex(common.debug_stream, pc, 4) + io.write_string(common.debug_stream, "]\n") + } + vm.next_threads[vm.top_thread] = Thread{ pc = pc, saved = saved } + vm.top_thread += 1 + } + case .Assert_Word_Boundary: + sp := vm.string_pointer+vm.current_rune_size + if sp == 0 || sp == len(vm.memory) { + pc += size_of(Opcode) + continue + } else { + last_rune_is_wc := is_word_class(vm.current_rune) + this_rune_is_wc := is_word_class(vm.next_rune) + + if last_rune_is_wc && !this_rune_is_wc || !last_rune_is_wc && this_rune_is_wc { + pc += size_of(Opcode) + continue + } + } + case .Assert_Non_Word_Boundary: + sp := vm.string_pointer+vm.current_rune_size + if sp != 0 && sp != len(vm.memory) { + last_rune_is_wc := is_word_class(vm.current_rune) + this_rune_is_wc := is_word_class(vm.next_rune) + + if last_rune_is_wc && this_rune_is_wc || !last_rune_is_wc && !this_rune_is_wc { + pc += size_of(Opcode) + continue + } + } + + case .Wait_For_Byte: + operand := cast(rune)vm.code[pc + size_of(Opcode)] + if vm.next_rune == operand { + add_thread(vm, saved, pc + size_of(Opcode) + size_of(u8)) + } + + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "*** New thread added [PC:") + common.write_padded_hex(common.debug_stream, pc, 4) + io.write_string(common.debug_stream, "]\n") + } + vm.next_threads[vm.top_thread] = Thread{ pc = pc, saved = saved } + vm.top_thread += 1 + + case .Wait_For_Rune: + operand := intrinsics.unaligned_load(cast(^rune)&vm.code[pc + size_of(Opcode)]) + if vm.next_rune == operand { + add_thread(vm, saved, pc + size_of(Opcode) + size_of(rune)) + } + + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "*** New thread added [PC:") + common.write_padded_hex(common.debug_stream, pc, 4) + io.write_string(common.debug_stream, "]\n") + } + vm.next_threads[vm.top_thread] = Thread{ pc = pc, saved = saved } + vm.top_thread += 1 + + case .Wait_For_Rune_Class: + operand := cast(u8)vm.code[pc + size_of(Opcode)] + class_data := vm.class_data[operand] + next_rune := vm.next_rune + + check: { + for r in class_data.runes { + if next_rune == r { + add_thread(vm, saved, pc + size_of(Opcode) + size_of(u8)) + break check + } + } + for range in class_data.ranges { + if range.lower <= next_rune && next_rune <= range.upper { + add_thread(vm, saved, pc + size_of(Opcode) + size_of(u8)) + break check + } + } + } + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "*** New thread added [PC:") + common.write_padded_hex(common.debug_stream, pc, 4) + io.write_string(common.debug_stream, "]\n") + } + vm.next_threads[vm.top_thread] = Thread{ pc = pc, saved = saved } + vm.top_thread += 1 + + case .Wait_For_Rune_Class_Negated: + operand := cast(u8)vm.code[pc + size_of(Opcode)] + class_data := vm.class_data[operand] + next_rune := vm.next_rune + + check_negated: { + for r in class_data.runes { + if next_rune == r { + break check_negated + } + } + for range in class_data.ranges { + if range.lower <= next_rune && next_rune <= range.upper { + break check_negated + } + } + add_thread(vm, saved, pc + size_of(Opcode) + size_of(u8)) + } + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "*** New thread added [PC:") + common.write_padded_hex(common.debug_stream, pc, 4) + io.write_string(common.debug_stream, "]\n") + } + vm.next_threads[vm.top_thread] = Thread{ pc = pc, saved = saved } + vm.top_thread += 1 + + case: + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "*** New thread added [PC:") + common.write_padded_hex(common.debug_stream, pc, 4) + io.write_string(common.debug_stream, "]\n") + } + vm.next_threads[vm.top_thread] = Thread{ pc = pc, saved = saved } + vm.top_thread += 1 + } + + break resolution_loop + } + + return +} + +run :: proc(vm: ^Machine, $UNICODE_MODE: bool) -> (saved: ^[2 * common.MAX_CAPTURE_GROUPS]int, ok: bool) #no_bounds_check { + when UNICODE_MODE { + vm.next_rune, vm.next_rune_size = utf8.decode_rune_in_string(vm.memory) + } else { + if len(vm.memory) > 0 { + vm.next_rune = cast(rune)vm.memory[0] + vm.next_rune_size = 1 + } + } + + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "### Adding initial thread.\n") + } + + { + starter_saved := new([2 * common.MAX_CAPTURE_GROUPS]int) + starter_saved ^= -1 + + add_thread(vm, starter_saved, 0) + } + + // `add_thread` adds to `next_threads` by default, but we need to put this + // thread in the current thread buffer. + vm.threads, vm.next_threads = vm.next_threads, vm.threads + + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "### VM starting.\n") + defer io.write_string(common.debug_stream, "### VM finished.\n") + } + + for { + slice.zero(vm.busy_map[:]) + + assert(vm.string_pointer <= len(vm.memory), "VM string pointer went out of bounds.") + + current_rune := vm.next_rune + vm.current_rune = current_rune + vm.current_rune_size = vm.next_rune_size + when UNICODE_MODE { + vm.next_rune, vm.next_rune_size = utf8.decode_rune_in_string(vm.memory[vm.string_pointer+vm.current_rune_size:]) + } else { + if vm.string_pointer+size_of(u8) < len(vm.memory) { + vm.next_rune = cast(rune)vm.memory[vm.string_pointer+size_of(u8)] + vm.next_rune_size = size_of(u8) + } else { + vm.next_rune = 0 + vm.next_rune_size = 0 + } + } + + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, ">>> Dispatching rune: ") + io.write_encoded_rune(common.debug_stream, current_rune) + io.write_byte(common.debug_stream, '\n') + } + + thread_count := vm.top_thread + vm.top_thread = 0 + thread_loop: for i := 0; i < thread_count; i += 1 { + t := vm.threads[i] + + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "Thread [PC:") + common.write_padded_hex(common.debug_stream, t.pc, 4) + io.write_string(common.debug_stream, "] stepping on ") + io.write_string(common.debug_stream, opcode_to_name(vm.code[t.pc])) + io.write_byte(common.debug_stream, '\n') + } + + #partial opcode: switch vm.code[t.pc] { + case .Match: + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "Thread matched!\n") + } + saved = t.saved + ok = true + break thread_loop + + case .Match_And_Exit: + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "Thread matched! (Exiting)\n") + } + return nil, true + + case .Byte: + operand := cast(rune)vm.code[t.pc + size_of(Opcode)] + if current_rune == operand { + add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8)) + } + + case .Rune: + operand := intrinsics.unaligned_load(cast(^rune)&vm.code[t.pc + size_of(Opcode)]) + if current_rune == operand { + add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(rune)) + } + + case .Rune_Class: + operand := cast(u8)vm.code[t.pc + size_of(Opcode)] + class_data := vm.class_data[operand] + + for r in class_data.runes { + if current_rune == r { + add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8)) + break opcode + } + } + for range in class_data.ranges { + if range.lower <= current_rune && current_rune <= range.upper { + add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8)) + break opcode + } + } + + case .Rune_Class_Negated: + operand := cast(u8)vm.code[t.pc + size_of(Opcode)] + class_data := vm.class_data[operand] + for r in class_data.runes { + if current_rune == r { + break opcode + } + } + for range in class_data.ranges { + if range.lower <= current_rune && current_rune <= range.upper { + break opcode + } + } + add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8)) + + case .Wildcard: + add_thread(vm, t.saved, t.pc + size_of(Opcode)) + + case .Multiline_Open: + if current_rune == '\n' { + // UNIX newline. + add_thread(vm, t.saved, t.pc + 2 * size_of(Opcode)) + } else if current_rune == '\r' { + if vm.next_rune == '\n' { + // Windows newline. (1/2) + add_thread(vm, t.saved, t.pc + size_of(Opcode)) + } else { + // Mac newline. + add_thread(vm, t.saved, t.pc + 2 * size_of(Opcode)) + } + } + case .Multiline_Close: + if current_rune == '\n' { + // Windows newline. (2/2) + add_thread(vm, t.saved, t.pc + size_of(Opcode)) + } + + case .Wait_For_Byte: + operand := cast(rune)vm.code[t.pc + size_of(Opcode)] + if vm.next_rune == operand { + add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8)) + } + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "*** New thread added [PC:") + common.write_padded_hex(common.debug_stream, t.pc, 4) + io.write_string(common.debug_stream, "]\n") + } + vm.next_threads[vm.top_thread] = Thread{ pc = t.pc, saved = t.saved } + vm.top_thread += 1 + + case .Wait_For_Rune: + operand := intrinsics.unaligned_load(cast(^rune)&vm.code[t.pc + size_of(Opcode)]) + if vm.next_rune == operand { + add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(rune)) + } + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "*** New thread added [PC:") + common.write_padded_hex(common.debug_stream, t.pc, 4) + io.write_string(common.debug_stream, "]\n") + } + vm.next_threads[vm.top_thread] = Thread{ pc = t.pc, saved = t.saved } + vm.top_thread += 1 + + case .Wait_For_Rune_Class: + operand := cast(u8)vm.code[t.pc + size_of(Opcode)] + class_data := vm.class_data[operand] + next_rune := vm.next_rune + + check: { + for r in class_data.runes { + if next_rune == r { + add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8)) + break check + } + } + for range in class_data.ranges { + if range.lower <= next_rune && next_rune <= range.upper { + add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8)) + break check + } + } + } + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "*** New thread added [PC:") + common.write_padded_hex(common.debug_stream, t.pc, 4) + io.write_string(common.debug_stream, "]\n") + } + vm.next_threads[vm.top_thread] = Thread{ pc = t.pc, saved = t.saved } + vm.top_thread += 1 + + case .Wait_For_Rune_Class_Negated: + operand := cast(u8)vm.code[t.pc + size_of(Opcode)] + class_data := vm.class_data[operand] + next_rune := vm.next_rune + + check_negated: { + for r in class_data.runes { + if next_rune == r { + break check_negated + } + } + for range in class_data.ranges { + if range.lower <= next_rune && next_rune <= range.upper { + break check_negated + } + } + add_thread(vm, t.saved, t.pc + size_of(Opcode) + size_of(u8)) + } + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "*** New thread added [PC:") + common.write_padded_hex(common.debug_stream, t.pc, 4) + io.write_string(common.debug_stream, "]\n") + } + vm.next_threads[vm.top_thread] = Thread{ pc = t.pc, saved = t.saved } + vm.top_thread += 1 + + case .Match_All_And_Escape: + t.pc += size_of(Opcode) + // The point of this loop is to walk out of wherever this + // opcode lives to the end of the program, while saving the + // index to the length of the string at each pass on the way. + escape_loop: for { + #partial switch vm.code[t.pc] { + case .Match, .Match_And_Exit: + break escape_loop + + case .Jump: + t.pc = cast(int)intrinsics.unaligned_load(cast(^u16)&vm.code[t.pc + size_of(Opcode)]) + + case .Save: + index := vm.code[t.pc + size_of(Opcode)] + t.saved[index] = len(vm.memory) + t.pc += size_of(Opcode) + size_of(u8) + + case .Match_All_And_Escape: + // Layering these is fine. + t.pc += size_of(Opcode) + + // If the loop has to process any opcode not listed above, + // it means someone did something odd like `a(.*$)b`, in + // which case, just fail. Technically, the expression makes + // no sense. + case: + break opcode + } + } + + saved = t.saved + ok = true + return + + case: + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "Opcode: ") + io.write_int(common.debug_stream, cast(int)vm.code[t.pc]) + io.write_string(common.debug_stream, "\n") + } + panic("Invalid opcode in RegEx thread loop.") + } + } + + vm.threads, vm.next_threads = vm.next_threads, vm.threads + + when common.ODIN_DEBUG_REGEX { + io.write_string(common.debug_stream, "<<< Frame ended. (Threads: ") + io.write_int(common.debug_stream, vm.top_thread) + io.write_string(common.debug_stream, ")\n") + } + + if vm.string_pointer == len(vm.memory) || vm.top_thread == 0 { + break + } + + vm.string_pointer += vm.current_rune_size + } + + return +} + +opcode_count :: proc(code: Program) -> (opcodes: int) { + iter := Opcode_Iterator{ code, 0 } + for _ in iterate_opcodes(&iter) { + opcodes += 1 + } + return +} + +create :: proc(code: Program, str: string) -> (vm: Machine) { + assert(len(code) > 0, "RegEx VM has no instructions.") + + vm.memory = str + vm.code = code + + sizing := len(code) >> 6 + (1 if len(code) & 0x3F > 0 else 0) + assert(sizing > 0) + vm.busy_map = make([]u64, sizing) + + max_possible_threads := max(1, opcode_count(vm.code) - 1) + + vm.threads = make([^]Thread, max_possible_threads) + vm.next_threads = make([^]Thread, max_possible_threads) + + return +} diff --git a/core/text/scanner/scanner.odin b/core/text/scanner/scanner.odin index 7c17a0ec0..24dbcc8a4 100644 --- a/core/text/scanner/scanner.odin +++ b/core/text/scanner/scanner.odin @@ -2,12 +2,13 @@ // It takes a string providing the source, which then can be tokenized through // repeated calls to the scan procedure. // For compatibility with existing tooling and languages, the NUL character is not allowed. -// If an UTF-8 encoded byte order mark (BOM) is the first character in the first character in the source, it will be discarded. +// If an UTF-8 encoded byte order mark (BOM) is the first character in the source, it will be discarded. // // By default, a Scanner skips white space and Odin comments and recognizes all literals defined by the Odin programming language specification. // 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 9b5c1f932..63275bbc1 100644 --- a/core/text/table/doc.odin +++ b/core/text/table/doc.odin @@ -1,78 +1,86 @@ /* - 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: + package main - Custom rendering example: + import "core:io" + import "core:text/table" - ```odin - 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. + +Example: + 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) + } + +Output: + + +----------------------------------------------------------------------------------------------------------------------------+ + | 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. + +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 8d96cb26f..66a7d442b 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:runtime" +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 { return _create(procedure, priority) } + +/* +Wait for the thread to finish and free all data associated with it. +*/ destroy :: proc(thread: ^Thread) { _destroy(thread) } +/* +Start a suspended thread. +*/ start :: proc(thread: ^Thread) { _start(thread) } +/* +Check if the thread has finished work. +*/ is_done :: proc(thread: ^Thread) -> bool { return _is_done(thread) } - +/* +Wait for the thread to finish work. +*/ join :: proc(thread: ^Thread) { _join(thread) } - +/* +Wait for all threads to finish work. +*/ join_multiple :: proc(threads: ..^Thread) { _join_multiple(..threads) } +/* +Forcibly terminate a running thread. +*/ terminate :: proc(thread: ^Thread, exit_code: int) { _terminate(thread, exit_code) } +/* +Yield the execution of the current thread to another OS thread or process. +*/ yield :: proc() { _yield() } +/* +Run a procedure on a different thread. +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ run :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) { create_and_start(fn, init_context, priority, true) } +/* +Run a procedure with one pointer parameter on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ run_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) { create_and_start_with_data(data, fn, init_context, priority, true) } +/* +Run a procedure with one polymorphic parameter on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ run_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) - where size_of(T) <= size_of(rawptr) { + where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { create_and_start_with_poly_data(data, fn, init_context, priority, true) } +/* +Run a procedure with two polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ run_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) - where size_of(T1) <= size_of(rawptr), - size_of(T2) <= size_of(rawptr) { + where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { create_and_start_with_poly_data2(arg1, arg2, fn, init_context, priority, true) } +/* +Run a procedure with three polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ run_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) - where size_of(T1) <= size_of(rawptr), - size_of(T2) <= size_of(rawptr), - size_of(T3) <= size_of(rawptr) { + where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { create_and_start_with_poly_data3(arg1, arg2, arg3, fn, init_context, priority, true) } + +/* +Run a procedure with four polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ run_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) - where size_of(T1) <= size_of(rawptr), - size_of(T2) <= size_of(rawptr), - size_of(T3) <= size_of(rawptr) { + where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS { create_and_start_with_poly_data4(arg1, arg2, arg3, arg4, fn, init_context, priority, true) } +/* +Run a procedure on a different thread. +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread { thread_proc :: proc(t: ^Thread) { fn := cast(proc())t.data @@ -147,15 +271,30 @@ create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, } t := create(thread_proc, priority) t.data = rawptr(fn) - if self_cleanup do t.flags += {.Self_Cleanup} + if self_cleanup { + intrinsics.atomic_or(&t.flags, {.Self_Cleanup}) + } t.init_context = init_context start(t) return t } +/* +Run a procedure with one pointer parameter on a different thread. +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread { thread_proc :: proc(t: ^Thread) { fn := cast(proc(rawptr))t.data @@ -166,15 +305,33 @@ create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_co t := create(thread_proc, priority) t.data = rawptr(fn) t.user_index = 1 - t.user_args = data - if self_cleanup do t.flags += {.Self_Cleanup} + t.user_args[0] = data + if self_cleanup { + intrinsics.atomic_or(&t.flags, {.Self_Cleanup}) + } t.init_context = init_context start(t) return t } +/* +Run a procedure with one polymorphic parameter on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread - where size_of(T) <= size_of(rawptr) { + where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { fn := cast(proc(T))t.data assert(t.user_index >= 1) @@ -184,87 +341,166 @@ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_contex t := create(thread_proc, priority) t.data = rawptr(fn) t.user_index = 1 + data := data - mem.copy(&t.user_args[0], &data, size_of(data)) - if self_cleanup do t.flags += {.Self_Cleanup} + + mem.copy(&t.user_args[0], &data, size_of(T)) + + if self_cleanup { + intrinsics.atomic_or(&t.flags, {.Self_Cleanup}) + } + t.init_context = init_context start(t) return t } +/* +Run a procedure with two polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread - where size_of(T1) <= size_of(rawptr), - size_of(T2) <= size_of(rawptr) { + where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { fn := cast(proc(T1, T2))t.data assert(t.user_index >= 2) - arg1 := (^T1)(&t.user_args[0])^ - arg2 := (^T2)(&t.user_args[1])^ + + user_args := mem.slice_to_bytes(t.user_args[:]) + arg1 := (^T1)(raw_data(user_args))^ + arg2 := (^T2)(raw_data(user_args[size_of(T1):]))^ + fn(arg1, arg2) } t := create(thread_proc, priority) t.data = rawptr(fn) t.user_index = 2 + arg1, arg2 := arg1, arg2 - mem.copy(&t.user_args[0], &arg1, size_of(arg1)) - mem.copy(&t.user_args[1], &arg2, size_of(arg2)) - if self_cleanup do t.flags += {.Self_Cleanup} + user_args := mem.slice_to_bytes(t.user_args[:]) + + n := copy(user_args, mem.ptr_to_bytes(&arg1)) + _ = copy(user_args[n:], mem.ptr_to_bytes(&arg2)) + + if self_cleanup { + intrinsics.atomic_or(&t.flags, {.Self_Cleanup}) + } + t.init_context = init_context start(t) return t } +/* +Run a procedure with three polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread - where size_of(T1) <= size_of(rawptr), - size_of(T2) <= size_of(rawptr), - size_of(T3) <= size_of(rawptr) { + where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { fn := cast(proc(T1, T2, T3))t.data assert(t.user_index >= 3) - arg1 := (^T1)(&t.user_args[0])^ - arg2 := (^T2)(&t.user_args[1])^ - arg3 := (^T3)(&t.user_args[2])^ + + user_args := mem.slice_to_bytes(t.user_args[:]) + arg1 := (^T1)(raw_data(user_args))^ + arg2 := (^T2)(raw_data(user_args[size_of(T1):]))^ + arg3 := (^T3)(raw_data(user_args[size_of(T1) + size_of(T2):]))^ + fn(arg1, arg2, arg3) } t := create(thread_proc, priority) t.data = rawptr(fn) t.user_index = 3 + arg1, arg2, arg3 := arg1, arg2, arg3 - mem.copy(&t.user_args[0], &arg1, size_of(arg1)) - mem.copy(&t.user_args[1], &arg2, size_of(arg2)) - mem.copy(&t.user_args[2], &arg3, size_of(arg3)) - if self_cleanup do t.flags += {.Self_Cleanup} - t.init_context = init_context - start(t) - return t -} -create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread - where size_of(T1) <= size_of(rawptr), - size_of(T2) <= size_of(rawptr), - size_of(T3) <= size_of(rawptr) { - thread_proc :: proc(t: ^Thread) { - fn := cast(proc(T1, T2, T3, T4))t.data - assert(t.user_index >= 4) - arg1 := (^T1)(&t.user_args[0])^ - arg2 := (^T2)(&t.user_args[1])^ - arg3 := (^T3)(&t.user_args[2])^ - arg4 := (^T4)(&t.user_args[3])^ - fn(arg1, arg2, arg3, arg4) + user_args := mem.slice_to_bytes(t.user_args[:]) + + n := copy(user_args, mem.ptr_to_bytes(&arg1)) + n += copy(user_args[n:], mem.ptr_to_bytes(&arg2)) + _ = copy(user_args[n:], mem.ptr_to_bytes(&arg3)) + + if self_cleanup { + intrinsics.atomic_or(&t.flags, {.Self_Cleanup}) } - t := create(thread_proc, priority) - t.data = rawptr(fn) - t.user_index = 4 - arg1, arg2, arg3, arg4 := arg1, arg2, arg3, arg4 - mem.copy(&t.user_args[0], &arg1, size_of(arg1)) - mem.copy(&t.user_args[1], &arg2, size_of(arg2)) - mem.copy(&t.user_args[2], &arg3, size_of(arg3)) - mem.copy(&t.user_args[3], &arg4, size_of(arg4)) - if self_cleanup do t.flags += {.Self_Cleanup} + t.init_context = init_context start(t) return t } +/* +Run a procedure with four polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ +create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread + where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS { + thread_proc :: proc(t: ^Thread) { + fn := cast(proc(T1, T2, T3, T4))t.data + assert(t.user_index >= 4) + + user_args := mem.slice_to_bytes(t.user_args[:]) + arg1 := (^T1)(raw_data(user_args))^ + arg2 := (^T2)(raw_data(user_args[size_of(T1):]))^ + arg3 := (^T3)(raw_data(user_args[size_of(T1) + size_of(T2):]))^ + arg4 := (^T4)(raw_data(user_args[size_of(T1) + size_of(T2) + size_of(T3):]))^ + + fn(arg1, arg2, arg3, arg4) + } + t := create(thread_proc, priority) + t.data = rawptr(fn) + t.user_index = 4 + + arg1, arg2, arg3, arg4 := arg1, arg2, arg3, arg4 + user_args := mem.slice_to_bytes(t.user_args[:]) + + n := copy(user_args, mem.ptr_to_bytes(&arg1)) + n += copy(user_args[n:], mem.ptr_to_bytes(&arg2)) + n += copy(user_args[n:], mem.ptr_to_bytes(&arg3)) + _ = copy(user_args[n:], mem.ptr_to_bytes(&arg4)) + + if self_cleanup { + intrinsics.atomic_or(&t.flags, {.Self_Cleanup}) + } + + t.init_context = init_context + start(t) + return t +} _select_context_for_thread :: proc(init_context: Maybe(runtime.Context)) -> runtime.Context { ctx, ok := init_context.? diff --git a/core/thread/thread_js.odin b/core/thread/thread_js.odin deleted file mode 100644 index 3c4935495..000000000 --- a/core/thread/thread_js.odin +++ /dev/null @@ -1,47 +0,0 @@ -//+build js -package thread - -import "core:intrinsics" -import "core:sync" -import "core:mem" - -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 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..dde2a8e48 --- /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 1a4119e5f..59bf90620 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -6,9 +6,10 @@ package thread Made available under Odin's BSD-3 license. */ -import "core:intrinsics" +import "base:intrinsics" import "core:sync" import "core:mem" +import "core:container/queue" Task_Proc :: #type proc(task: Task) @@ -40,48 +41,64 @@ Pool :: struct { threads: []^Thread, - tasks: [dynamic]Task, + tasks: queue.Queue(Task), 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) + sync.guard(&pool.mutex) + data.task = {} + } + } + + sync.post(&pool.sem_available, 1) +} + // Once initialized, the pool's memory address is not allowed to change until -// it is destroyed. +// it is destroyed. // // The thread pool requires an allocator which it either owns, or which is thread safe. pool_init :: proc(pool: ^Pool, allocator: mem.Allocator, thread_count: int) { context.allocator = allocator pool.allocator = allocator - pool.tasks = make([dynamic]Task) + queue.init(&pool.tasks) pool.tasks_done = make([dynamic]Task) pool.threads = make([]^Thread, max(thread_count, 1)) 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 } } pool_destroy :: proc(pool: ^Pool) { - delete(pool.tasks) + queue.destroy(&pool.tasks) delete(pool.tasks_done) for &t in pool.threads { + data := cast(^Pool_Thread_Data)t.data + free(data, pool.allocator) destroy(t) } @@ -103,13 +120,14 @@ 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 { - if .Started in t.flags { + flags := intrinsics.atomic_load(&t.flags) + if .Started in flags { started_count += 1 - if .Joined not_in t.flags { + if .Joined not_in flags { join(t) } } @@ -123,11 +141,11 @@ started_count: int // the thread pool. You can even add tasks from inside other tasks. // // Each task also needs an allocator which it either owns, or which is thread -// safe. +// safe. pool_add_task :: proc(pool: ^Pool, allocator: mem.Allocator, procedure: Task_Proc, data: rawptr, user_index: int = 0) { sync.guard(&pool.mutex) - append(&pool.tasks, Task{ + queue.push_back(&pool.tasks, Task{ procedure = procedure, data = data, user_index = user_index, @@ -138,6 +156,98 @@ 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) + + old_thread_user_index := t.user_index + + destroy(t) + + replacement := create(pool_thread_runner) + replacement.user_index = old_thread_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) + + old_thread_user_index := t.user_index + + destroy(t) + + replacement := create(pool_thread_runner) + replacement.user_index = old_thread_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. @@ -179,10 +289,10 @@ pool_is_empty :: #force_inline proc(pool: ^Pool) -> bool { pool_pop_waiting :: proc(pool: ^Pool) -> (task: Task, got_task: bool) { sync.guard(&pool.mutex) - if len(pool.tasks) != 0 { + if queue.len(pool.tasks) != 0 { intrinsics.atomic_sub(&pool.num_waiting, 1) intrinsics.atomic_add(&pool.num_in_processing, 1) - task = pop_front(&pool.tasks) + task = queue.pop_front(&pool.tasks) got_task = true } diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index c73085ef6..ff79cfcbc 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -1,47 +1,52 @@ -// +build linux, darwin, freebsd, openbsd -// +private +#+build linux, darwin, freebsd, openbsd, netbsd, haiku +#+private package thread -import "core:intrinsics" +import "base:runtime" import "core:sync" -import "core:sys/unix" +import "core:sys/posix" -CAS :: intrinsics.atomic_compare_exchange_strong +_IS_SUPPORTED :: true // NOTE(tetra): Aligned here because of core/unix/pthread_linux.odin/pthread_t. // Also see core/sys/darwin/mach_darwin.odin/semaphore_t. Thread_Os_Specific :: struct #align(16) { - unix_thread: unix.pthread_t, // NOTE: very large on Darwin, small on Linux. - cond: sync.Cond, - mutex: sync.Mutex, + unix_thread: posix.pthread_t, // NOTE: very large on Darwin, small on Linux. + start_ok: sync.Sema, } // // Creates a thread which will run the given procedure. // 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 - } - - sync.lock(&t.mutex) + // We need to give the thread a moment to start up before we enable cancellation. + // NOTE(laytan): setting to .DISABLE on darwin, with .ENABLE pthread_cancel would deadlock + // most of the time, don't ask me why. + can_set_thread_cancel_state := posix.pthread_setcancelstate(.DISABLE when ODIN_OS == .Darwin else .ENABLE, nil) == nil t.id = sync.current_thread_id() - for (.Started not_in t.flags) { - sync.wait(&t.cond, &t.mutex) + if .Started not_in sync.atomic_load(&t.flags) { + sync.wait(&t.start_ok) } - 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. + // NOTE(laytan): Darwin does not correctly/fully support all of this, not doing this does + // actually make pthread_cancel work in the capacity of my tests, while executing this would + // basically always make it deadlock. + if ODIN_OS != .Darwin && can_set_thread_cancel_state { + err := posix.pthread_setcancelstate(.ENABLE, nil) + assert_contextless(err == nil) + + err = posix.pthread_setcanceltype(.ASYNCHRONOUS, nil) + assert_contextless(err == nil) } { @@ -51,16 +56,20 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { // Here on Unix, we start the OS thread in a running state, and so we manually have it wait on a condition // variable above. We must perform that waiting BEFORE we select the context! context = _select_context_for_thread(init_context) - defer _maybe_destroy_default_temp_allocator(init_context) + defer { + _maybe_destroy_default_temp_allocator(init_context) + runtime.run_thread_local_cleaners() + } t.procedure(t) } - intrinsics.atomic_store(&t.flags, t.flags + { .Done }) + sync.atomic_or(&t.flags, { .Done }) - sync.unlock(&t.mutex) + if .Self_Cleanup in sync.atomic_load(&t.flags) { + res := posix.pthread_detach(t.unix_thread) + assert_contextless(res == nil) - if .Self_Cleanup in t.flags { t.unix_thread = {} // NOTE(ftphikari): It doesn't matter which context 'free' received, right? context = {} @@ -70,15 +79,20 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { return nil } - attrs: unix.pthread_attr_t - if unix.pthread_attr_init(&attrs) != 0 { + attrs: posix.pthread_attr_t + if posix.pthread_attr_init(&attrs) != nil { return nil // NOTE(tetra, 2019-11-01): POSIX OOM. } - defer unix.pthread_attr_destroy(&attrs) + defer posix.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) - assert(unix.pthread_attr_setinheritsched(&attrs, unix.PTHREAD_EXPLICIT_SCHED) == 0) + res: posix.Errno + res = posix.pthread_attr_setdetachstate(&attrs, .CREATE_JOINABLE) + assert(res == nil) + when ODIN_OS != .Haiku && ODIN_OS != .NetBSD { + res = posix.pthread_attr_setinheritsched(&attrs, .EXPLICIT_SCHED) + assert(res == nil) + } thread := new(Thread) if thread == nil { @@ -87,24 +101,26 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { thread.creation_allocator = context.allocator // Set thread priority. - policy: i32 - res := unix.pthread_attr_getschedpolicy(&attrs, &policy) - assert(res == 0) - params: unix.sched_param - res = unix.pthread_attr_getschedparam(&attrs, ¶ms) - assert(res == 0) - low := unix.sched_get_priority_min(policy) - high := unix.sched_get_priority_max(policy) + policy: posix.Sched_Policy + when ODIN_OS != .Haiku && ODIN_OS != .NetBSD { + res = posix.pthread_attr_getschedpolicy(&attrs, &policy) + assert(res == nil) + } + params: posix.sched_param + res = posix.pthread_attr_getschedparam(&attrs, ¶ms) + assert(res == nil) + low := posix.sched_get_priority_min(policy) + high := posix.sched_get_priority_max(policy) switch priority { case .Normal: // Okay case .Low: params.sched_priority = low + 1 case .High: params.sched_priority = high } - res = unix.pthread_attr_setschedparam(&attrs, ¶ms) - assert(res == 0) + res = posix.pthread_attr_setschedparam(&attrs, ¶ms) + assert(res == nil) thread.procedure = procedure - if unix.pthread_create(&thread.unix_thread, &attrs, __linux_thread_entry_proc, thread) != 0 { + if posix.pthread_create(&thread.unix_thread, &attrs, __unix_thread_entry_proc, thread) != nil { free(thread, thread.creation_allocator) return nil } @@ -113,32 +129,30 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { } _start :: proc(t: ^Thread) { - // sync.guard(&t.mutex) - t.flags += { .Started } - sync.signal(&t.cond) + sync.atomic_or(&t.flags, { .Started }) + sync.post(&t.start_ok) } _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) { - // sync.guard(&t.mutex) - - if unix.pthread_equal(unix.pthread_self(), t.unix_thread) { + if posix.pthread_equal(posix.pthread_self(), t.unix_thread) { return } - // Preserve other flags besides `.Joined`, like `.Started`. - unjoined := intrinsics.atomic_load(&t.flags) - {.Joined} - joined := unjoined + {.Joined} - - // Try to set `t.flags` from unjoined to joined. If it returns joined, - // it means the previous value had that flag set and we can return. - if res, ok := CAS(&t.flags, unjoined, joined); res == joined && !ok { + // If the previous value was already `Joined`, then we can return. + if .Joined in sync.atomic_or(&t.flags, {.Joined}) { return } - unix.pthread_join(t.unix_thread, nil) + + // Prevent non-started threads from blocking main thread with initial wait + // condition. + if .Started not_in sync.atomic_load(&t.flags) { + _start(t) + } + posix.pthread_join(t.unix_thread, nil) } _join_multiple :: proc(threads: ..^Thread) { @@ -154,12 +168,19 @@ _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. + posix.pthread_cancel(t.unix_thread) } _yield :: proc() { - unix.sched_yield() + posix.sched_yield() } diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index 2d6cad1ad..cc73a2d6a 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -1,11 +1,14 @@ -//+build windows -//+private +#+build windows +#+private package thread -import "core:intrinsics" +import "base:intrinsics" +import "base:runtime" import "core:sync" import win32 "core:sys/windows" +_IS_SUPPORTED :: true + Thread_Os_Specific :: struct { win32_thread: win32.HANDLE, win32_thread_id: win32.DWORD, @@ -21,9 +24,13 @@ _thread_priority_map := [Thread_Priority]i32{ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { win32_thread_id: win32.DWORD - __windows_thread_entry_proc :: proc "stdcall" (t_: rawptr) -> win32.DWORD { + __windows_thread_entry_proc :: proc "system" (t_: rawptr) -> win32.DWORD { t := (^Thread)(t_) + if .Joined in sync.atomic_load(&t.flags) { + return 0 + } + t.id = sync.current_thread_id() { @@ -33,14 +40,17 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { // Here on Windows, the thread is created in a suspended state, and so we can select the context anywhere before the call // to t.procedure(). context = _select_context_for_thread(init_context) - defer _maybe_destroy_default_temp_allocator(init_context) + defer { + _maybe_destroy_default_temp_allocator(init_context) + runtime.run_thread_local_cleaners() + } t.procedure(t) } - intrinsics.atomic_store(&t.flags, t.flags + {.Done}) + intrinsics.atomic_or(&t.flags, {.Done}) - if .Self_Cleanup in t.flags { + if .Self_Cleanup in sync.atomic_load(&t.flags) { win32.CloseHandle(t.win32_thread) t.win32_thread = win32.INVALID_HANDLE // NOTE(ftphikari): It doesn't matter which context 'free' received, right? @@ -93,11 +103,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/constants.odin b/core/time/datetime/constants.odin new file mode 100644 index 000000000..e24709e49 --- /dev/null +++ b/core/time/datetime/constants.odin @@ -0,0 +1,176 @@ +package datetime + +/* +Type representing a mononotic day number corresponding to a date. + + Ordinal 1 = Midnight Monday, January 1, 1 A.D. (Gregorian) + | Midnight Monday, January 3, 1 A.D. (Julian) +*/ +Ordinal :: i64 + +/* +*/ +EPOCH :: Ordinal(1) + +/* +Minimum valid value for date. + +The value is chosen such that a conversion `date -> ordinal -> date` is always +safe. +*/ +MIN_DATE :: Date{year = -25_252_734_927_766_552, month = 1, day = 1} + +/* +Maximum valid value for date + +The value is chosen such that a conversion `date -> ordinal -> date` is always +safe. +*/ +MAX_DATE :: Date{year = 25_252_734_927_766_552, month = 12, day = 31} + +/* +Minimum value for an ordinal +*/ +MIN_ORD :: Ordinal(-9_223_372_036_854_775_234) + +/* +Maximum value for an ordinal +*/ +MAX_ORD :: Ordinal( 9_223_372_036_854_774_869) + +/* +Possible errors returned by datetime functions. +*/ +Error :: enum { + None, + Invalid_Year, + Invalid_Month, + Invalid_Day, + Invalid_Hour, + Invalid_Minute, + Invalid_Second, + Invalid_Nano, + Invalid_Ordinal, + Invalid_Delta, +} + +/* +A type representing a date. + +The minimum and maximum values for a year can be found in `MIN_DATE` and +`MAX_DATE` constants. The `month` field can range from 1 to 12, and the day +ranges from 1 to however many days there are in the specified month. +*/ +Date :: struct { + year: i64, + month: i8, + day: i8, +} + +/* +A type representing a time within a single day within a nanosecond precision. +*/ +Time :: struct { + hour: i8, + minute: i8, + second: i8, + nano: i32, +} + +TZ_Record :: struct { + time: i64, + utc_offset: i64, + shortname: string, + dst: bool, +} + +TZ_Date_Kind :: enum { + No_Leap, + Leap, + Month_Week_Day, +} + +TZ_Transition_Date :: struct { + type: TZ_Date_Kind, + + month: u8, + week: u8, + day: u16, + + time: i64, +} + +TZ_RRule :: struct { + has_dst: bool, + + std_name: string, + std_offset: i64, + std_date: TZ_Transition_Date, + + dst_name: string, + dst_offset: i64, + dst_date: TZ_Transition_Date, +} + +TZ_Region :: struct { + name: string, + records: []TZ_Record, + shortnames: []string, + rrule: TZ_RRule, +} + +/* +A type representing datetime. +*/ +DateTime :: struct { + using date: Date, + using time: Time, + tz: ^TZ_Region, +} + +/* +A type representing a difference between two instances of datetime. + +**Note**: All fields are i64 because we can also use it to add a number of +seconds or nanos to a moment, that are then normalized within their respective +ranges. +*/ +Delta :: struct { + days: i64, + seconds: i64, + nanos: i64, +} + +/* +Type representing one of the months. +*/ +Month :: enum i8 { + January = 1, + February, + March, + April, + May, + June, + July, + August, + September, + October, + November, + December, +} + +/* +Type representing one of the weekdays. +*/ +Weekday :: enum i8 { + Sunday = 0, + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, +} + +@(private) +MONTH_DAYS :: [?]i8{-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} diff --git a/core/time/datetime/datetime.odin b/core/time/datetime/datetime.odin new file mode 100644 index 000000000..2cd90b0e7 --- /dev/null +++ b/core/time/datetime/datetime.odin @@ -0,0 +1,436 @@ +/* +Calendrical conversions using a proleptic Gregorian calendar. + +Implemented using formulas from: Calendrical Calculations Ultimate Edition, +Reingold & Dershowitz +*/ +package datetime + +import "base:intrinsics" + +/* +Obtain an ordinal from a date. + +This procedure converts the specified date into an ordinal. If the specified +date is not a valid date, an error is returned. +*/ +date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal, err: Error) { + validate(date) or_return + return unsafe_date_to_ordinal(date), .None +} + +/* +Obtain an ordinal from date components. + +This procedure converts the specified date, provided by its individual +components, into an ordinal. If the specified date is not a valid date, an error +is returned. +*/ +components_to_ordinal :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (ordinal: Ordinal, err: Error) { + validate(year, month, day) or_return + return unsafe_date_to_ordinal({year, i8(month), i8(day)}), .None +} + +/* +Obtain date using an Ordinal. + +This provedure converts the specified ordinal into a date. If the ordinal is not +a valid ordinal, an error is returned. +*/ +ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date, err: Error) { + validate(ordinal) or_return + return unsafe_ordinal_to_date(ordinal), .None +} + +/* +Obtain a date from date components. + +This procedure converts date components, specified by a year, a month and a day, +into a date object. If the provided date components don't represent a valid +date, an error is returned. +*/ +components_to_date :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (date: Date, err: Error) { + validate(year, month, day) or_return + return Date{i64(year), i8(month), i8(day)}, .None +} + +/* +Obtain time from time components. + +This procedure converts time components, specified by an hour, a minute, a second +and nanoseconds, into a time object. If the provided time components don't +represent a valid time, an error is returned. +*/ +components_to_time :: proc "contextless" (#any_int hour, #any_int minute, #any_int second: i64, #any_int nanos := i64(0)) -> (time: Time, err: Error) { + validate(hour, minute, second, nanos) or_return + return Time{i8(hour), i8(minute), i8(second), i32(nanos)}, .None +} + +/* +Obtain datetime from components. + +This procedure converts date components and time components into a datetime object. +If the provided date components or time components don't represent a valid +datetime, an error is returned. +*/ +components_to_datetime :: proc "contextless" (#any_int year, #any_int month, #any_int day, #any_int hour, #any_int minute, #any_int second: i64, #any_int nanos := i64(0)) -> (datetime: DateTime, err: Error) { + date := components_to_date(year, month, day) or_return + time := components_to_time(hour, minute, second, nanos) or_return + return {date, time, nil}, .None +} + +/* +Obtain an datetime from an ordinal. + +This procedure converts the value of an ordinal into a datetime. Since the +ordinal only has the amount of days, the resulting time in the datetime +object will always have the time equal to `00:00:00.000`. +*/ +ordinal_to_datetime :: proc "contextless" (ordinal: Ordinal) -> (datetime: DateTime, err: Error) { + d := ordinal_to_date(ordinal) or_return + return {Date(d), {}, nil}, .None +} + +/* +Calculate the weekday from an ordinal. + +This procedure takes the value of an ordinal and returns the day of week for +that ordinal. +*/ +day_of_week :: proc "contextless" (ordinal: Ordinal) -> (day: Weekday) { + return Weekday((ordinal - EPOCH + 1) %% 7) +} + +/* +Calculate the difference between two dates. + +This procedure calculates the difference between two dates `a - b`, and returns +a delta between the two dates in `days`. If either `a` or `b` is not a valid +date, an error is returned. +*/ +subtract_dates :: proc "contextless" (a, b: Date) -> (delta: Delta, err: Error) { + ord_a := date_to_ordinal(a) or_return + ord_b := date_to_ordinal(b) or_return + + delta = Delta{days=ord_a - ord_b} + return +} + +/* +Calculate the difference between two datetimes. + +This procedure calculates the difference between two datetimes, `a - b`, and +returns a delta between the two dates. The difference is returned in all three +fields of the `Delta` struct: the difference in days, the difference in seconds +and the difference in nanoseconds. + +If either `a` or `b` is not a valid datetime, an error is returned. +*/ +subtract_datetimes :: proc "contextless" (a, b: DateTime) -> (delta: Delta, err: Error) { + ord_a := date_to_ordinal(a) or_return + ord_b := date_to_ordinal(b) or_return + + validate(a.time) or_return + validate(b.time) or_return + + seconds_a := i64(a.hour) * 3600 + i64(a.minute) * 60 + i64(a.second) + seconds_b := i64(b.hour) * 3600 + i64(b.minute) * 60 + i64(b.second) + + delta = Delta{ord_a - ord_b, seconds_a - seconds_b, i64(a.nano) - i64(b.nano)} + return +} + +/* +Calculate a difference between two deltas. +*/ +subtract_deltas :: proc "contextless" (a, b: Delta) -> (delta: Delta, err: Error) { + delta = Delta{a.days - b.days, a.seconds - b.seconds, a.nanos - b.nanos} + delta = normalize_delta(delta) or_return + return +} + +/* +Calculate a difference between two datetimes, dates or deltas. +*/ +sub :: proc{subtract_datetimes, subtract_dates, subtract_deltas} + +/* +Add certain amount of days to a date. + +This procedure adds the specified amount of days to a date and returns a new +date. The new date would have happened the specified amount of days after the +specified date. +*/ +add_days_to_date :: proc "contextless" (a: Date, days: i64) -> (date: Date, err: Error) { + ord := date_to_ordinal(a) or_return + ord += days + return ordinal_to_date(ord) +} + +/* +Add delta to a date. + +This procedure adds a delta to a date, and returns a new date. The new date +would have happened the time specified by `delta` after the specified date. + +**Note**: The delta is assumed to be normalized. That is, if it contains seconds +or milliseconds, regardless of the amount only the days will be added. +*/ +add_delta_to_date :: proc "contextless" (a: Date, delta: Delta) -> (date: Date, err: Error) { + ord := date_to_ordinal(a) or_return + // Because the input is a Date, we add only the days from the Delta. + ord += delta.days + return ordinal_to_date(ord) +} + +/* +Add delta to datetime. + +This procedure adds a delta to a datetime, and returns a new datetime. The new +datetime would have happened the time specified by `delta` after the specified +datetime. +*/ +add_delta_to_datetime :: proc "contextless" (a: DateTime, delta: Delta) -> (datetime: DateTime, err: Error) { + days := date_to_ordinal(a) or_return + + a_seconds := i64(a.hour) * 3600 + i64(a.minute) * 60 + i64(a.second) + a_delta := Delta{days=days, seconds=a_seconds, nanos=i64(a.nano)} + + sum_delta := Delta{days=a_delta.days + delta.days, seconds=a_delta.seconds + delta.seconds, nanos=a_delta.nanos + delta.nanos} + sum_delta = normalize_delta(sum_delta) or_return + + datetime.date = ordinal_to_date(sum_delta.days) or_return + + hour, rem := divmod(sum_delta.seconds, 3600) + minute, second := divmod(rem, 60) + + datetime.time = components_to_time(hour, minute, second, sum_delta.nanos) or_return + return +} + +/* +Add days to a date, delta to a date or delta to datetime. +*/ +add :: proc{add_days_to_date, add_delta_to_date, add_delta_to_datetime} + +/* +Obtain the day number in a year + +This procedure returns the number of the day in a year, starting from 1. If +the date is not a valid date, an error is returned. +*/ +day_number :: proc "contextless" (date: Date) -> (day_number: i64, err: Error) { + validate(date) or_return + + ord := unsafe_date_to_ordinal(date) + _, day_number = unsafe_ordinal_to_year(ord) + return +} + +/* +Obtain the remaining number of days in a year. + +This procedure returns the number of days between the specified date and +December 31 of the same year. If the date is not a valid date, an error is +returned. +*/ +days_remaining :: proc "contextless" (date: Date) -> (days_remaining: i64, err: Error) { + // Alternative formulation `day_number` subtracted from 365 or 366 depending on leap year + validate(date) or_return + delta := sub(date, Date{date.year, 12, 31}) or_return + return delta.days, .None +} + +/* +Obtain the last day of a given month on a given year. + +This procedure returns the amount of days in a specified month on a specified +date. If the specified year or month is not valid, an error is returned. +*/ +last_day_of_month :: proc "contextless" (#any_int year: i64, #any_int month: i8) -> (day: i8, err: Error) { + // Not using formula 2.27 from the book. This is far simpler and gives the same answer. + + validate(Date{year, month, 1}) or_return + month_days := MONTH_DAYS + + day = month_days[month] + if month == 2 && is_leap_year(year) { + day += 1 + } + return +} + +/* +Obtain the new year date of a given year. + +This procedure returns the January 1st date of the specified year. If the year +is not valid, an error is returned. +*/ +new_year :: proc "contextless" (#any_int year: i64) -> (new_year: Date, err: Error) { + validate(year, 1, 1) or_return + return {year, 1, 1}, .None +} + +/* +Obtain the end year of a given date. + +This procedure returns the December 31st date of the specified year. If the year +is not valid, an error is returned. +*/ +year_end :: proc "contextless" (#any_int year: i64) -> (year_end: Date, err: Error) { + validate(year, 12, 31) or_return + return {year, 12, 31}, .None +} + +/* +Obtain the range of dates for a given year. + +This procedure returns dates, for every day of a given year in a slice. +*/ +year_range :: proc (#any_int year: i64, allocator := context.allocator) -> (range: []Date) { + is_leap := is_leap_year(year) + + days := 366 if is_leap else 365 + range = make([]Date, days, allocator) + + month_days := MONTH_DAYS + if is_leap { + month_days[2] = 29 + } + + i := 0 + for month in 1..=len(month_days) { + for day in 1..=month_days[month] { + range[i], _ = components_to_date(year, month, day) + i += 1 + } + } + return +} + +/* +Normalize the delta. + +This procedure normalizes the delta in such a way that the number of seconds +is between 0 and the number of seconds in the day and nanoseconds is between +0 and 10^9. + +If the value for `days` overflows during this operation, an error is returned. +*/ +normalize_delta :: proc "contextless" (delta: Delta) -> (normalized: Delta, err: Error) { + // Distribute nanos into seconds and remainder + seconds, nanos := divmod(delta.nanos, 1e9) + + // Add original seconds to rolled over seconds. + seconds += delta.seconds + days: i64 + + // Distribute seconds into number of days and remaining seconds. + days, seconds = divmod(seconds, 24 * 3600) + + // Add original days + days += delta.days + + if days <= MIN_ORD || days >= MAX_ORD { + return {}, .Invalid_Delta + } + return Delta{days, seconds, nanos}, .None +} + +// The following procedures don't check whether their inputs are in a valid range. +// They're still exported for those who know their inputs have been validated. + +/* +Obtain an ordinal from a date. + +This procedure converts a date into an ordinal. If the date is not a valid date, +the result is unspecified. +*/ +unsafe_date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal) { + year_minus_one := date.year - 1 + + // Day before epoch + ordinal = EPOCH - 1 + + // Add non-leap days + ordinal += 365 * year_minus_one + + // Add leap days + ordinal += floor_div(year_minus_one, 4) // Julian-rule leap days + ordinal -= floor_div(year_minus_one, 100) // Prior century years + ordinal += floor_div(year_minus_one, 400) // Prior 400-multiple years + ordinal += floor_div(367 * i64(date.month) - 362, 12) // Prior days this year + + // Apply correction + if date.month <= 2 { + ordinal += 0 + } else if is_leap_year(date.year) { + ordinal -= 1 + } else { + ordinal -= 2 + } + + // Add days + ordinal += i64(date.day) + return +} + +/* +Obtain a year and a day of the year from an ordinal. + +This procedure returns the year and the day of the year of a given ordinal. +Of the ordinal is outside of its valid range, the result is unspecified. +*/ +unsafe_ordinal_to_year :: proc "contextless" (ordinal: Ordinal) -> (year: i64, day_ordinal: i64) { + // Days after epoch + d0 := ordinal - EPOCH + + // Number of 400-year cycles and remainder + n400, d1 := divmod(d0, 146097) + + // Number of 100-year cycles and remainder + n100, d2 := divmod(d1, 36524) + + // Number of 4-year cycles and remainder + n4, d3 := divmod(d2, 1461) + + // Number of remaining days + n1, d4 := divmod(d3, 365) + + year = 400 * n400 + 100 * n100 + 4 * n4 + n1 + + if n1 != 4 && n100 != 4 { + day_ordinal = d4 + 1 + } else { + day_ordinal = 366 + } + + if n100 == 4 || n1 == 4 { + return year, day_ordinal + } + return year + 1, day_ordinal +} + +/* +Obtain a date from an ordinal. + +This procedure converts an ordinal into a date. If the ordinal is outside of +its valid range, the result is unspecified. +*/ +unsafe_ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date) { + year, _ := unsafe_ordinal_to_year(ordinal) + + prior_days := ordinal - unsafe_date_to_ordinal(Date{year, 1, 1}) + correction := Ordinal(2) + + if ordinal < unsafe_date_to_ordinal(Date{year, 3, 1}) { + correction = 0 + } else if is_leap_year(year) { + correction = 1 + } + + month := i8(floor_div((12 * (prior_days + correction) + 373), 367)) + day := i8(ordinal - unsafe_date_to_ordinal(Date{year, month, 1}) + 1) + + return {year, month, day} +} diff --git a/core/time/datetime/internal.odin b/core/time/datetime/internal.odin new file mode 100644 index 000000000..3477a47f3 --- /dev/null +++ b/core/time/datetime/internal.odin @@ -0,0 +1,96 @@ +#+private +package datetime + +// Internal helper functions for calendrical conversions + +import "base:intrinsics" + +sign :: proc "contextless" (v: i64) -> (res: i64) { + if v == 0 { + return 0 + } else if v > 0 { + return 1 + } + return -1 +} + +// Caller has to ensure y != 0 +divmod :: proc "contextless" (x, y: $T, loc := #caller_location) -> (a: T, r: T) + where intrinsics.type_is_integer(T) { + a = x / y + r = x % y + if (r > 0 && y < 0) || (r < 0 && y > 0) { + a -= 1 + r += y + } + return a, r +} + +// Divides and floors +floor_div :: proc "contextless" (x, y: $T) -> (res: T) + where intrinsics.type_is_integer(T) { + res = x / y + r := x % y + if (r > 0 && y < 0) || (r < 0 && y > 0) { + res -= 1 + } + return res +} + +// Half open: x mod [1..b] +interval_mod :: proc "contextless" (x, a, b: i64) -> (res: i64) { + if a == b { + return x + } + return a + ((x - a) %% (b - a)) +} + +// x mod [1..b] +adjusted_remainder :: proc "contextless" (x, b: i64) -> (res: i64) { + m := x %% b + return b if m == 0 else m +} + +gcd :: proc "contextless" (x, y: i64) -> (res: i64) { + if y == 0 { + return x + } + + m := x %% y + return gcd(y, m) +} + +lcm :: proc "contextless" (x, y: i64) -> (res: i64) { + return x * y / gcd(x, y) +} + +sum :: proc "contextless" (i: i64, f: proc "contextless" (n: i64) -> i64, cond: proc "contextless" (n: i64) -> bool) -> (res: i64) { + for idx := i; cond(idx); idx += 1 { + res += f(idx) + } + return +} + +product :: proc "contextless" (i: i64, f: proc "contextless" (n: i64) -> i64, cond: proc "contextless" (n: i64) -> bool) -> (res: i64) { + res = 1 + for idx := i; cond(idx); idx += 1 { + res *= f(idx) + } + return +} + +smallest :: proc "contextless" (k: i64, cond: proc "contextless" (n: i64) -> bool) -> (d: i64) { + k := k + for !cond(k) { + k += 1 + } + return k +} + +biggest :: proc "contextless" (k: i64, cond: proc "contextless" (n: i64) -> bool) -> (d: i64) { + k := k + for !cond(k) { + k -= 1 + } + return k +} \ No newline at end of file diff --git a/core/time/datetime/validation.odin b/core/time/datetime/validation.odin new file mode 100644 index 000000000..0a66833b0 --- /dev/null +++ b/core/time/datetime/validation.odin @@ -0,0 +1,114 @@ +package datetime +// Validation helpers + +/* +Check if a year is a leap year. +*/ +is_leap_year :: proc "contextless" (#any_int year: i64) -> (leap: bool) { + return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) +} + +/* +Check for errors in date formation. + +This procedure validates all fields of a date, and if any of the fields is +outside of allowed range, an error is returned. +*/ +validate_date :: proc "contextless" (date: Date) -> (err: Error) { + return validate(date.year, date.month, date.day) +} + +/* +Check for errors in date formation given date components. + +This procedure checks whether a date formed by the specified year month and a +day is a valid date. If not, an error is returned. +*/ +validate_year_month_day :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (err: Error) { + if year < MIN_DATE.year || year > MAX_DATE.year { + return .Invalid_Year + } + if month < 1 || month > 12 { + return .Invalid_Month + } + + month_days := MONTH_DAYS + days_this_month := month_days[month] + if month == 2 && is_leap_year(year) { + days_this_month = 29 + } + + if day < 1 || day > i64(days_this_month) { + return .Invalid_Day + } + return .None +} + +/* +Check for errors in Ordinal + +This procedure checks if the ordinal is in a valid range for roundtrip +conversions with the dates. If not, an error is returned. +*/ +validate_ordinal :: proc "contextless" (ordinal: Ordinal) -> (err: Error) { + if ordinal < MIN_ORD || ordinal > MAX_ORD { + return .Invalid_Ordinal + } + return +} + +/* +Check for errors in time formation + +This procedure checks whether time has all fields in valid ranges, and if not +an error is returned. +*/ +validate_time :: proc "contextless" (time: Time) -> (err: Error) { + return validate(time.hour, time.minute, time.second, time.nano) +} + +/* +Check for errors in time formed by its components. + +This procedure checks whether the time formed by its components is valid, and +if not an error is returned. +*/ +validate_hour_minute_second :: proc "contextless" (#any_int hour, #any_int minute, #any_int second, #any_int nano: i64) -> (err: Error) { + if hour < 0 || hour > 23 { + return .Invalid_Hour + } + if minute < 0 || minute > 59 { + return .Invalid_Minute + } + if second < 0 || second > 59 { + return .Invalid_Second + } + if nano < 0 || nano > 1e9 { + return .Invalid_Nano + } + return .None +} + +/* +Check for errors in datetime formation. + +This procedure checks whether all fields of date and time in the specified +datetime are valid, and if not, an error is returned. +*/ +validate_datetime :: proc "contextless" (datetime: DateTime) -> (err: Error) { + validate(datetime.date) or_return + validate(datetime.time) or_return + return .None +} + +/* +Check for errors in date, time or datetime. +*/ +validate :: proc{ + validate_date, + validate_year_month_day, + validate_ordinal, + validate_hour_minute_second, + validate_time, + validate_datetime, +} diff --git a/core/time/iso8601.odin b/core/time/iso8601.odin new file mode 100644 index 000000000..f00107226 --- /dev/null +++ b/core/time/iso8601.odin @@ -0,0 +1,175 @@ +package time +// Parsing ISO 8601 date/time strings into time.Time. + +import dt "core:time/datetime" + +/* +Parse an ISO 8601 string into a time with UTC offset applied to it. + +This procedure parses an ISO 8601 string of roughly the following format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns time, in UTC represented by that string. In case the timezone offset +is specified in the string, that timezone is applied to time. + +**Inputs**: +- `iso_datetime`: The string to be parsed. +- `is_leap`: Optional output parameter, specifying if the moment was a leap second. + +**Returns**: +- `res`: The time represented by `iso_datetime`, with UTC offset applied. +- `consumed`: Number of bytes consumed by parsing the string. + +**Notes**: +- Only 4-digit years are accepted. +- Leap seconds are smeared into 23:59:59. +*/ +iso8601_to_time_utc :: proc(iso_datetime: string, is_leap: ^bool = nil) -> (res: Time, consumed: int) { + offset: int + res, offset, consumed = iso8601_to_time_and_offset(iso_datetime, is_leap) + res._nsec += (i64(-offset) * i64(Minute)) + return res, consumed +} + +/* +Parse an ISO 8601 string into a time and a UTC offset in minutes. + +This procedure parses an ISO 8601 string of roughly the following format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns time, in UTC represented by that string, and the UTC offset, in +minutes. + +**Inputs**: +- `iso_datetime`: The string to be parsed. +- `is_leap`: Optional output parameter, specifying if the moment was a leap second. + +**Returns**: +- `res`: The time in UTC. +- `utc_offset`: The UTC offset of the time, in minutes. +- `consumed`: Number of bytes consumed by parsing the string. + +**Notes**: +- Only 4-digit years are accepted. +- Leap seconds are smeared into 23:59:59. +*/ +iso8601_to_time_and_offset :: proc(iso_datetime: string, is_leap: ^bool = nil) -> (res: Time, utc_offset: int, consumed: int) { + moment, offset, leap_second, count := iso8601_to_components(iso_datetime) + if count == 0 { + 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 + } +} + +/* +Parse an ISO 8601 string into a datetime and a UTC offset in minutes. + +This procedure parses an ISO 8601 string of roughly the following format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns datetime, in UTC represented by that string, and the UTC offset, in +minutes. + +**Inputs**: +- `iso_datetime`: The string to be parsed + +**Returns**: +- `res`: The parsed datetime, in UTC. +- `utc_offset`: The UTC offset, in minutes. +- `is_leap`: Specifies whether the moment was a leap second. +- `consumed`: The number of bytes consumed by parsing the string. + +**Notes**: +- This procedure performs no validation on whether components are valid, + e.g. it'll return hour = 25 if that's what it's given in the specified + string. +*/ +iso8601_to_components :: proc(iso_datetime: string) -> (res: dt.DateTime, utc_offset: int, is_leap: bool, consumed: int) { + moment, offset, count, leap_second, ok := _iso8601_to_components(iso_datetime) + if !ok { + 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/perf.odin b/core/time/perf.odin index 87192093a..784d7acd6 100644 --- a/core/time/perf.odin +++ b/core/time/perf.odin @@ -1,20 +1,41 @@ package time -import "core:runtime" -import "core:intrinsics" +import "base:runtime" +import "base:intrinsics" +/* +Type representing monotonic time, useful for measuring durations. +*/ Tick :: struct { _nsec: i64, // relative amount } + +/* +Obtain the current tick. +*/ tick_now :: proc "contextless" () -> Tick { return _tick_now() } +/* +Obtain the difference between ticks. +*/ tick_diff :: proc "contextless" (start, end: Tick) -> Duration { d := end._nsec - start._nsec return Duration(d) } +/* +Incrementally obtain durations since last tick. + +This procedure returns the duration between the current tick and the tick +stored in `prev` pointer, and then stores the current tick in location, +specified by `prev`. If the prev pointer contains an zero-initialized tick, +then the returned duration is 0. + +This procedure is meant to be used in a loop, or in other scenarios, where one +might want to obtain time between multiple ticks at specific points. +*/ tick_lap_time :: proc "contextless" (prev: ^Tick) -> Duration { d: Duration t := tick_now() @@ -25,17 +46,21 @@ tick_lap_time :: proc "contextless" (prev: ^Tick) -> Duration { return d } +/* +Obtain the duration since last tick. +*/ tick_since :: proc "contextless" (start: Tick) -> Duration { return tick_diff(start, tick_now()) } - +/* +Capture the duration the code in the current scope takes to execute. +*/ @(deferred_in_out=_tick_duration_end) SCOPED_TICK_DURATION :: proc "contextless" (d: ^Duration) -> Tick { return tick_now() } - _tick_duration_end :: proc "contextless" (d: ^Duration, t: Tick) { d^ = tick_since(t) } @@ -62,6 +87,13 @@ when ODIN_OS != .Darwin && ODIN_OS != .Linux && ODIN_OS != .FreeBSD { } } +/* +Check if the CPU has invariant TSC. + +This procedure checks if the CPU contains an invariant TSC (Time stamp counter). +Invariant TSC is a feature of modern processors that allows them to run their +TSC at a fixed frequency, independent of ACPI state, and CPU frequency. +*/ has_invariant_tsc :: proc "contextless" () -> bool { when ODIN_ARCH == .amd64 { return x86_has_invariant_tsc() @@ -70,6 +102,17 @@ has_invariant_tsc :: proc "contextless" () -> bool { return false } +/* +Obtain the CPU's TSC frequency, in hertz. + +This procedure tries to obtain the CPU's TSC frequency in hertz. If the CPU +doesn't have an invariant TSC, this procedure returns with an error. Otherwise +an attempt is made to fetch the TSC frequency from the OS. If this fails, +the frequency is obtained by sleeping for the specified amount of time and +dividing the readings from TSC by the duration of the sleep. + +The duration of sleep can be controlled by `fallback_sleep` parameter. +*/ tsc_frequency :: proc "contextless" (fallback_sleep := 2 * Second) -> (u64, bool) { if !has_invariant_tsc() { return 0, false @@ -93,37 +136,64 @@ tsc_frequency :: proc "contextless" (fallback_sleep := 2 * Second) -> (u64, bool return hz, true } -/* - Benchmark helpers -*/ +// Benchmark helpers +/* +Errors returned by the `benchmark()` procedure. +*/ Benchmark_Error :: enum { Okay = 0, Allocation_Error, } +/* +Options for benchmarking. +*/ Benchmark_Options :: struct { + // The initialization procedure. `benchmark()` will call this before taking measurements. setup: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error), + // The procedure to benchmark. bench: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error), + // The deinitialization procedure. teardown: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error), - + // Field to be used by `bench()` procedure for any purpose. rounds: int, + // Field to be used by `bench()` procedure for any purpose. bytes: int, + // Field to be used by `bench()` procedure for any purpose. input: []u8, - + // `bench()` writes to specify the count of elements processed. count: int, + // `bench()` writes to specify the number of bytes processed. processed: int, + // `bench()` can write the output slice here. output: []u8, // Unused for hash benchmarks + // `bench()` can write the output hash here. hash: u128, - - /* - Performance - */ + // `benchmark()` procedure will output the duration of benchmark duration: Duration, + // `benchmark()` procedure will output the average count of elements + // processed per second, using the `count` field of this struct. rounds_per_second: f64, + // `benchmark()` procedure will output the average number of megabytes + // processed per second, using the `processed` field of this struct. megabytes_per_second: f64, } +/* +Benchmark a procedure. + +This procedure produces a benchmark. The procedure specified in the `bench` +field of the `options` parameter will be benchmarked. The following metrics +can be obtained: + +- Run time of the procedure +- Number of elements per second processed on average +- Number of bytes per second this processed on average + +In order to obtain these metrics, the `bench()` procedure writes to `options` +struct the number of elements or bytes it has processed. +*/ benchmark :: proc(options: ^Benchmark_Options, allocator := context.allocator) -> (err: Benchmark_Error) { assert(options != nil) assert(options.bench != nil) diff --git a/core/time/rfc3339.odin b/core/time/rfc3339.odin new file mode 100644 index 000000000..20e8ea0bb --- /dev/null +++ b/core/time/rfc3339.odin @@ -0,0 +1,296 @@ +package time +// Parsing RFC 3339 date/time strings into time.Time. +// See https://www.rfc-editor.org/rfc/rfc3339 for the definition + +import dt "core:time/datetime" + +/* +Parse an RFC 3339 string into time with a UTC offset applied to it. + +This procedure parses the specified RFC 3339 strings of roughly the following +format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns the time that was represented by the RFC 3339 string, with the UTC +offset applied to it. + +**Inputs**: +- `rfc_datetime`: An RFC 3339 string to parse. +- `is_leap`: Optional output parameter specifying whether the moment was a leap + second. + +**Returns**: +- `res`: The time, with UTC offset applied, that was parsed from the RFC 3339 + string. +- `consumed`: The number of bytes consumed by parsing the RFC 3339 string. + +**Notes**: +- Only 4-digit years are accepted. +- Leap seconds are smeared into 23:59:59. +*/ +rfc3339_to_time_utc :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: Time, consumed: int) { + offset: int + + res, offset, consumed = rfc3339_to_time_and_offset(rfc_datetime, is_leap) + res._nsec += (i64(-offset) * i64(Minute)) + return res, consumed +} + +/* +Parse an RFC 3339 string into a time and a UTC offset in minutes. + +This procedure parses the specified RFC 3339 strings of roughly the following +format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns the time, in UTC and a UTC offset, in minutes, that were represented +by the RFC 3339 string. + +**Inputs**: +- `rfc_datetime`: The RFC 3339 string to be parsed. +- `is_leap`: Optional output parameter specifying whether the moment was a + leap second. + +**Returns**: +- `res`: The time, in UTC, that was parsed from the RFC 3339 string. +- `utc_offset`: The UTC offset, in minutes, that was parsed from the RFC 3339 + string. +- `consumed`: The number of bytes consumed by parsing the string. + +**Notes**: +- Only 4-digit years are accepted. +- Leap seconds are smeared into 23:59:59. +*/ +rfc3339_to_time_and_offset :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: Time, utc_offset: int, consumed: int) { + moment, offset, leap_second, count := rfc3339_to_components(rfc_datetime) + if count == 0 { + 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 + } +} + +/* +Parse an RFC 3339 string into a datetime and a UTC offset in minutes. + +This procedure parses the specified RFC 3339 strings of roughly the following +format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns the datetime, in UTC and the UTC offset, in minutes, that were +represented by the RFC 3339 string. + +**Inputs**: +- `rfc_datetime`: The RFC 3339 string to parse. + +**Returns**: +- `res`: The datetime, in UTC, that was parsed from the RFC 3339 string. +- `utc_offset`: The UTC offset, in minutes, that was parsed from the RFC 3339 + string. +- `is_leap`: Specifies whether the moment was a leap second. +- `consumed`: Number of bytes consumed by parsing the string. + +Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given +*/ +rfc3339_to_components :: proc(rfc_datetime: string) -> (res: dt.DateTime, utc_offset: int, is_leap: bool, consumed: int) { + moment, offset, count, leap_second, ok := _rfc3339_to_components(rfc_datetime) + if !ok { + return + } + return moment, offset, leap_second, count +} + +// Parses an RFC 3339 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) +_rfc3339_to_components :: proc(rfc_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(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 + nanos := 0 + count := 19 + + if rfc_datetime[count] == '.' { + // Scan hundredths. The string must be at least 4 bytes long (.hhZ) + (len(rfc_datetime[count:]) >= 4) or_return + hundredths := scan_digits(rfc_datetime[count+1:], "", 2) or_return + count += 3 + nanos = 10_000_000 * hundredths + } + + // 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 + } + + // Scan UTC offset + switch rfc_datetime[count] { + case 'Z', 'z': + utc_offset = 0 + count += 1 + case '+', '-': + (len(rfc_datetime[count:]) >= 6) or_return + offset_hour := scan_digits(rfc_datetime[count+1:], ":", 2) or_return + offset_minute := scan_digits(rfc_datetime[count+4:], "", 2) or_return + + utc_offset = 60 * offset_hour + offset_minute + utc_offset *= -1 if rfc_datetime[count] == '-' else 1 + count += 6 + } + return res, utc_offset, count, is_leap, true +} + +@(private) +scan_digits :: proc(s: string, sep: string, count: int) -> (res: int, ok: bool) { + needed := count + min(1, len(sep)) + (len(s) >= needed) or_return + + #no_bounds_check for i in 0..= '0' && v <= '9' { + res = res * 10 + int(v - '0') + } else { + return 0, false + } + } + found_sep := len(sep) == 0 + #no_bounds_check for v in sep { + found_sep |= rune(s[count]) == v + } + return res, found_sep +} + +/* +Serialize the timestamp as a RFC 3339 string. + +The boolean `ok` is false if the `time` is not a valid datetime, or if allocating the result string fails. + +**Inputs**: +- `utc_offset`: offset in minutes wrt UTC (ie. the timezone) +- `include_nanos`: whether to include nanoseconds in the result. +*/ +time_to_rfc3339 :: proc(time: Time, utc_offset : int = 0, include_nanos := true, allocator := context.allocator) -> (res: string, ok: bool) { + utc_offset := utc_offset + + // convert to datetime + datetime := time_to_datetime(time) or_return + + if datetime.year < 0 || datetime.year >= 10_000 { return "", false } + + temp_string := [36]u8{} + offset : uint = 0 + + print_as_fixed_int :: proc(dst: []u8, offset: ^uint, width: i8, i: i64) { + i := i + width := width + for digit_idx in 0.. (res: i64, n_digits: i8) { + res = n + n_digits = 9 + for res % 10 == 0 { + res = res / 10 + n_digits -= 1 + } + return + } + + // pre-epoch times: turn, say, -400ms to +600ms for display + nanos := time._nsec % 1_000_000_000 + if nanos < 0 { + nanos += 1_000_000_000 + } + + if nanos != 0 && include_nanos { + temp_string[offset] = '.' + offset += 1 + + // remove trailing zeroes + nanos_nonzero, n_digits := strip_trailing_zeroes_nanos(nanos) + assert(nanos_nonzero != 0) + + // write digits, right-to-left + for digit_idx : i8 = n_digits-1; digit_idx >= 0; digit_idx -= 1 { + digit := u8(nanos_nonzero % 10) + temp_string[offset + uint(digit_idx)] = '0' + u8(digit) + nanos_nonzero /= 10 + } + offset += uint(n_digits) + } + + if utc_offset == 0 { + temp_string[offset] = 'Z' + offset += 1 + } else { + temp_string[offset] = utc_offset > 0 ? '+' : '-' + offset += 1 + utc_offset = abs(utc_offset) + print_as_fixed_int(temp_string[:], &offset, 2, i64(utc_offset / 60)) + temp_string[offset] = ':' + offset += 1 + print_as_fixed_int(temp_string[:], &offset, 2, i64(utc_offset % 60)) + } + + res_as_slice, res_alloc := make_slice([]u8, len=offset, allocator = allocator) + if res_alloc != nil { + return "", false + } + + copy(res_as_slice, temp_string[:offset]) + + return string(res_as_slice), true +} diff --git a/core/time/time.odin b/core/time/time.odin index 90d051a31..b488f951c 100644 --- a/core/time/time.odin +++ b/core/time/time.odin @@ -1,25 +1,75 @@ package time -import "core:intrinsics" +import "base:intrinsics" +import dt "core:time/datetime" +/* +Type representing duration, with nanosecond precision. +This is the regular Unix timestamp, scaled to nanosecond precision. +*/ Duration :: distinct i64 +/* +The duration equal to one nanosecond (1e-9 seconds). +*/ Nanosecond :: Duration(1) + +/* +The duration equal to one microsecond (1e-6 seconds). +*/ Microsecond :: 1000 * Nanosecond + +/* +The duration equal to one millisecond (1e-3 seconds). +*/ Millisecond :: 1000 * Microsecond + +/* +The duration equal to one second. +*/ Second :: 1000 * Millisecond + +/* +The duration equal to one minute (60 seconds). +*/ Minute :: 60 * Second + +/* +The duration equal to one hour (3600 seconds). +*/ Hour :: 60 * Minute +/* +Minimum representable duration. +*/ MIN_DURATION :: Duration(-1 << 63) + +/* +Maximum representable duration. +*/ MAX_DURATION :: Duration(1<<63 - 1) +/* +Value specifying whether the time procedures are supported by the current +platform. +*/ IS_SUPPORTED :: _IS_SUPPORTED +/* +Specifies time since the UNIX epoch, with nanosecond precision. + +Capable of representing any time within the following range: + +- `min: 1677-09-21 00:12:44.145224192 +0000 UTC` +- `max: 2262-04-11 23:47:16.854775807 +0000 UTC` +*/ Time :: struct { _nsec: i64, // Measured in UNIX nanonseconds } +/* +Type representing a month. +*/ Month :: enum int { January = 1, February, @@ -35,6 +85,9 @@ Month :: enum int { December, } +/* +Type representing a weekday. +*/ Weekday :: enum int { Sunday = 0, Monday, @@ -45,20 +98,37 @@ Weekday :: enum int { Saturday, } +/* +Type representing a stopwatch. + +The stopwatch is used for measuring the total time in multiple "runs". When the +stopwatch is started, it starts counting time. When the stopwatch is stopped, +the difference in time between the last start and the stop is added to the +total. When the stopwatch resets, the total is reset. +*/ Stopwatch :: struct { running: bool, _start_time: Tick, _accumulation: Duration, } +/* +Obtain the current time. +*/ now :: proc "contextless" () -> Time { return _now() } +/* +Sleep for the specified duration. +*/ sleep :: proc "contextless" (d: Duration) { _sleep(d) } +/* +Start the stopwatch. +*/ stopwatch_start :: proc "contextless" (stopwatch: ^Stopwatch) { if !stopwatch.running { stopwatch._start_time = tick_now() @@ -66,6 +136,9 @@ stopwatch_start :: proc "contextless" (stopwatch: ^Stopwatch) { } } +/* +Stop the stopwatch. +*/ stopwatch_stop :: proc "contextless" (stopwatch: ^Stopwatch) { if stopwatch.running { stopwatch._accumulation += tick_diff(stopwatch._start_time, tick_now()) @@ -73,11 +146,21 @@ stopwatch_stop :: proc "contextless" (stopwatch: ^Stopwatch) { } } +/* +Reset the stopwatch. +*/ stopwatch_reset :: proc "contextless" (stopwatch: ^Stopwatch) { stopwatch._accumulation = {} stopwatch.running = false } +/* +Obtain the total time, counted by the stopwatch. + +This procedure obtains the total time, counted by the stopwatch. If the stopwatch +isn't stopped at the time of calling this procedure, the time between the last +start and the current time is also accounted for. +*/ stopwatch_duration :: proc "contextless" (stopwatch: Stopwatch) -> Duration { if !stopwatch.running { return stopwatch._accumulation @@ -85,40 +168,86 @@ stopwatch_duration :: proc "contextless" (stopwatch: Stopwatch) -> Duration { return stopwatch._accumulation + tick_diff(stopwatch._start_time, tick_now()) } +/* +Calculate the duration elapsed between two times. +*/ diff :: proc "contextless" (start, end: Time) -> Duration { d := end._nsec - start._nsec return Duration(d) } +/* +Calculate the duration elapsed since a specific time. +*/ since :: proc "contextless" (start: Time) -> Duration { return diff(start, now()) } +/* +Obtain the number of nanoseconds in a duration. +*/ duration_nanoseconds :: proc "contextless" (d: Duration) -> i64 { return i64(d) } + +/* +Obtain the number of microseconds in a duration. +*/ duration_microseconds :: proc "contextless" (d: Duration) -> f64 { return duration_seconds(d) * 1e6 } + +/* +Obtain the number of milliseconds in a duration. +*/ duration_milliseconds :: proc "contextless" (d: Duration) -> f64 { return duration_seconds(d) * 1e3 } + +/* +Obtain the number of seconds in a duration. +*/ duration_seconds :: proc "contextless" (d: Duration) -> f64 { sec := d / Second nsec := d % Second return f64(sec) + f64(nsec)/1e9 } + +/* +Obtain the number of minutes in a duration. +*/ duration_minutes :: proc "contextless" (d: Duration) -> f64 { min := d / Minute nsec := d % Minute return f64(min) + f64(nsec)/(60*1e9) } + +/* +Obtain the number of hours in a duration. +*/ duration_hours :: proc "contextless" (d: Duration) -> f64 { hour := d / Hour nsec := d % Hour return f64(hour) + f64(nsec)/(60*60*1e9) } +/* +Round a duration to a specific unit + +This procedure rounds the duration to a specific unit + +**Note**: Any duration can be supplied as a unit. + +Inputs: +- d: The duration to round +- m: The unit to round to + +Returns: +- The duration `d`, rounded to the unit specified by `m` + +Example: + time.duration_round(my_duration, time.Second) +*/ duration_round :: proc "contextless" (d, m: Duration) -> Duration { _less_than_half :: #force_inline proc "contextless" (x, y: Duration) -> bool { return u64(x)+u64(x) < u64(y) @@ -148,52 +277,135 @@ duration_round :: proc "contextless" (d, m: Duration) -> Duration { return MAX_DURATION } +/* +Truncate the duration to the specified unit. + +This procedure truncates the duration `d` to the unit specified by `m`. + +**Note**: Any duration can be supplied as a unit. + +Inputs: +- d: The duration to truncate. +- m: The unit to truncate to. + +Returns: +- The duration `d`, truncated to the unit specified by `m`. + +Example: + time.duration_round(my_duration, time.Second) +*/ duration_truncate :: proc "contextless" (d, m: Duration) -> Duration { return d if m <= 0 else d - d%m } +/* +Parse time into date components. +*/ date :: proc "contextless" (t: Time) -> (year: int, month: Month, day: int) { year, month, day, _ = _abs_date(_time_abs(t), true) return } +/* +Obtain the year of the date specified by time. +*/ year :: proc "contextless" (t: Time) -> (year: int) { year, _, _, _ = _date(t, true) return } +/* +Obtain the month of the date specified by time. +*/ month :: proc "contextless" (t: Time) -> (month: Month) { _, month, _, _ = _date(t, true) return } +/* +Obtain the day of the date specified by time. +*/ day :: proc "contextless" (t: Time) -> (day: int) { _, _, day, _ = _date(t, true) return } +/* +Obtain the week day of the date specified by time. +*/ weekday :: proc "contextless" (t: Time) -> (weekday: Weekday) { abs := _time_abs(t) sec := (abs + u64(Weekday.Monday) * SECONDS_PER_DAY) % SECONDS_PER_WEEK return Weekday(int(sec) / SECONDS_PER_DAY) } +/* +Obtain the time components from a time, a duration or a stopwatch's total. +*/ clock :: proc { clock_from_time, clock_from_duration, clock_from_stopwatch } +/* +Obtain the time components from a time, a duration or a stopwatch's total, including nanoseconds. +*/ +precise_clock :: proc { precise_clock_from_time, precise_clock_from_duration, precise_clock_from_stopwatch } + +/* +Obtain the time components from a time. +*/ clock_from_time :: proc "contextless" (t: Time) -> (hour, min, sec: int) { - return clock_from_seconds(_time_abs(t)) + hour, min, sec, _ = precise_clock_from_time(t) + return } +/* +Obtain the time components from a time, including nanoseconds. +*/ +precise_clock_from_time :: proc "contextless" (t: Time) -> (hour, min, sec, nanos: int) { + // Time in nanoseconds since 1-1-1970 00:00 + _sec, _nanos := t._nsec / 1e9, t._nsec % 1e9 + _sec += INTERNAL_TO_ABSOLUTE + nanos = int(_nanos) + sec = int(_sec % SECONDS_PER_DAY) + hour = sec / SECONDS_PER_HOUR + sec -= hour * SECONDS_PER_HOUR + min = sec / SECONDS_PER_MINUTE + sec -= min * SECONDS_PER_MINUTE + return +} + +/* +Obtain the time components from a duration. +*/ clock_from_duration :: proc "contextless" (d: Duration) -> (hour, min, sec: int) { return clock_from_seconds(u64(d/1e9)) } +/* +Obtain the time components from a duration, including nanoseconds. +*/ +precise_clock_from_duration :: proc "contextless" (d: Duration) -> (hour, min, sec, nanos: int) { + return precise_clock_from_time({_nsec=i64(d)}) +} + +/* +Obtain the time components from a stopwatch's total. +*/ clock_from_stopwatch :: proc "contextless" (s: Stopwatch) -> (hour, min, sec: int) { return clock_from_duration(stopwatch_duration(s)) } -clock_from_seconds :: proc "contextless" (nsec: u64) -> (hour, min, sec: int) { - sec = int(nsec % SECONDS_PER_DAY) +/* +Obtain the time components from a stopwatch's total, including nanoseconds +*/ +precise_clock_from_stopwatch :: proc "contextless" (s: Stopwatch) -> (hour, min, sec, nanos: int) { + return precise_clock_from_duration(stopwatch_duration(s)) +} + +/* +Obtain the time components from the number of seconds. +*/ +clock_from_seconds :: proc "contextless" (in_sec: u64) -> (hour, min, sec: int) { + sec = int(in_sec % SECONDS_PER_DAY) hour = sec / SECONDS_PER_HOUR sec -= hour * SECONDS_PER_HOUR min = sec / SECONDS_PER_MINUTE @@ -201,10 +413,317 @@ clock_from_seconds :: proc "contextless" (nsec: u64) -> (hour, min, sec: int) { return } +MIN_HMS_LEN :: 8 +MIN_HMS_12_LEN :: 11 +MIN_YYYY_DATE_LEN :: 10 +MIN_YY_DATE_LEN :: 8 + +/* +Formats a `Time` as a 24-hour `hh:mm:ss` string. + +**Does not allocate** + +Inputs: +- t: The Time to format. +- buf: The backing buffer to use. + +Returns: +- res: The formatted string, backed by buf + +Example: + buf: [MIN_HMS_LEN]u8 + now := time.now() + fmt.println(time.to_string_hms(now, buf[:])) +*/ +time_to_string_hms :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check { + assert(len(buf) >= MIN_HMS_LEN) + h, m, s := clock(t) + + buf[7] = '0' + u8(s % 10); s /= 10 + buf[6] = '0' + u8(s) + buf[5] = ':' + buf[4] = '0' + u8(m % 10); m /= 10 + buf[3] = '0' + u8(m) + buf[2] = ':' + buf[1] = '0' + u8(h % 10); h /= 10 + buf[0] = '0' + u8(h) + + return string(buf[:MIN_HMS_LEN]) +} + +/* +Formats a `Duration` as a 24-hour `hh:mm:ss` string. + +**Does not allocate** + +Inputs: +- d: The Duration to format. +- buf: The backing buffer to use. + +Returns: +- res: The formatted string, backed by buf + +Example: + buf: [MIN_HMS_LEN]u8 + d := time.since(earlier) + fmt.println(time.to_string_hms(now, buf[:])) +*/ +duration_to_string_hms :: proc(d: Duration, buf: []u8) -> (res: string) #no_bounds_check { + return time_to_string_hms(Time{_nsec=i64(d)}, buf) +} + +to_string_hms :: proc{time_to_string_hms, duration_to_string_hms} + +/* +Formats a `Time` as a 12-hour `hh:mm:ss pm` string + +**Does not allocate** + +Inputs: +- t: The Time to format +- buf: The backing buffer to use +- ampm: An optional pair of am/pm strings to use in place of the default + +Returns: +- res: The formatted string, backed by buf + +Example: + buf: [64]u8 + now := time.now() + fmt.println(time.to_string_hms_12(now, buf[:])) + fmt.println(time.to_string_hms_12(now, buf[:], {"㏂", "㏘"})) +*/ +to_string_hms_12 :: proc(t: Time, buf: []u8, ampm: [2]string = {" am", " pm"}) -> (res: string) #no_bounds_check { + assert(len(buf) >= MIN_HMS_LEN + max(len(ampm[0]), len(ampm[1]))) + h, m, s := clock(t) + + _h := h % 12 + buf[7] = '0' + u8(s % 10); s /= 10 + buf[6] = '0' + u8(s) + buf[5] = ':' + buf[4] = '0' + u8(m % 10); m /= 10 + buf[3] = '0' + u8(m) + buf[2] = ':' + buf[1] = '0' + u8(_h% 10); _h /= 10 + buf[0] = '0' + u8(_h) + + if h < 13 { + copy(buf[8:], ampm[0]) + return string(buf[:MIN_HMS_LEN+len(ampm[0])]) + } else { + copy(buf[8:], ampm[1]) + return string(buf[:MIN_HMS_LEN+len(ampm[1])]) + } +} + +/* +Formats a Time as a yyyy-mm-dd date string. + +Inputs: +- t: The Time to format. +- buf: The backing buffer to use. + +Returns: +- res: The formatted string, backed by `buf`. + +Example: + buf: [MIN_YYYY_DATE_LEN]u8 + now := time.now() + fmt.println(time.to_string_yyyy_mm_dd(now, buf[:])) +*/ +to_string_yyyy_mm_dd :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check { + assert(len(buf) >= MIN_YYYY_DATE_LEN) + y, _m, d := date(t) + m := u8(_m) + + buf[9] = '0' + u8(d % 10); d /= 10 + buf[8] = '0' + u8(d % 10) + buf[7] = '-' + buf[6] = '0' + u8(m % 10); m /= 10 + buf[5] = '0' + u8(m % 10) + buf[4] = '-' + buf[3] = '0' + u8(y % 10); y /= 10 + buf[2] = '0' + u8(y % 10); y /= 10 + buf[1] = '0' + u8(y % 10); y /= 10 + buf[0] = '0' + u8(y) + + return string(buf[:MIN_YYYY_DATE_LEN]) +} + +/* +Formats a Time as a yy-mm-dd date string. + +Inputs: +- t: The Time to format. +- buf: The backing buffer to use. + +Returns: +- res: The formatted string, backed by `buf`. + +Example: + buf: [MIN_YY_DATE_LEN]u8 + now := time.now() + fmt.println(time.to_string_yy_mm_dd(now, buf[:])) +*/ +to_string_yy_mm_dd :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check { + assert(len(buf) >= MIN_YY_DATE_LEN) + y, _m, d := date(t) + y %= 100; m := u8(_m) + + buf[7] = '0' + u8(d % 10); d /= 10 + buf[6] = '0' + u8(d % 10) + buf[5] = '-' + buf[4] = '0' + u8(m % 10); m /= 10 + buf[3] = '0' + u8(m % 10) + buf[2] = '-' + buf[1] = '0' + u8(y % 10); y /= 10 + buf[0] = '0' + u8(y) + + return string(buf[:MIN_YY_DATE_LEN]) +} + +/* +Formats a Time as a dd-mm-yyyy date string. + +Inputs: +- t: The Time to format. +- buf: The backing buffer to use. + +Returns: +- res: The formatted string, backed by `buf`. + +Example: + buf: [MIN_YYYY_DATE_LEN]u8 + now := time.now() + fmt.println(time.to_string_dd_mm_yyyy(now, buf[:])) +*/ +to_string_dd_mm_yyyy :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check { + assert(len(buf) >= MIN_YYYY_DATE_LEN) + y, _m, d := date(t) + m := u8(_m) + + buf[9] = '0' + u8(y % 10); y /= 10 + buf[8] = '0' + u8(y % 10); y /= 10 + buf[7] = '0' + u8(y % 10); y /= 10 + buf[6] = '0' + u8(y) + buf[5] = '-' + buf[4] = '0' + u8(m % 10); m /= 10 + buf[3] = '0' + u8(m % 10) + buf[2] = '-' + buf[1] = '0' + u8(d % 10); d /= 10 + buf[0] = '0' + u8(d % 10) + + return string(buf[:MIN_YYYY_DATE_LEN]) +} + +/* +Formats a Time as a dd-mm-yy date string. + +Inputs: +- t: The Time to format. +- buf: The backing buffer to use. + +Returns: +- res: The formatted string, backed by `buf`. + +Example: + buf: [MIN_YY_DATE_LEN]u8 + now := time.now() + fmt.println(time.to_string_dd_mm_yy(now, buf[:])) +*/ +to_string_dd_mm_yy :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check { + assert(len(buf) >= MIN_YY_DATE_LEN) + y, _m, d := date(t) + y %= 100; m := u8(_m) + + buf[7] = '0' + u8(y % 10); y /= 10 + buf[6] = '0' + u8(y) + buf[5] = '-' + buf[4] = '0' + u8(m % 10); m /= 10 + buf[3] = '0' + u8(m % 10) + buf[2] = '-' + buf[1] = '0' + u8(d % 10); d /= 10 + buf[0] = '0' + u8(d % 10) + + return string(buf[:MIN_YY_DATE_LEN]) +} + +/* +Formats a Time as a mm-dd-yyyy date string. + +Inputs: +- t: The Time to format. +- buf: The backing buffer to use. + +Returns: +- res: The formatted string, backed by `buf`. + +Example: + buf: [MIN_YYYY_DATE_LEN]u8 + now := time.now() + fmt.println(time.to_string_mm_dd_yyyy(now, buf[:])) +*/ +to_string_mm_dd_yyyy :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check { + assert(len(buf) >= MIN_YYYY_DATE_LEN) + y, _m, d := date(t) + m := u8(_m) + + buf[9] = '0' + u8(y % 10); y /= 10 + buf[8] = '0' + u8(y % 10); y /= 10 + buf[7] = '0' + u8(y % 10); y /= 10 + buf[6] = '0' + u8(y) + buf[5] = '-' + buf[4] = '0' + u8(d % 10); d /= 10 + buf[3] = '0' + u8(d % 10) + buf[2] = '-' + buf[1] = '0' + u8(m % 10); m /= 10 + buf[0] = '0' + u8(m % 10) + + return string(buf[:MIN_YYYY_DATE_LEN]) +} + +/* +Formats a Time as a mm-dd-yy date string. + +Inputs: +- t: The Time to format. +- buf: The backing buffer to use. + +Returns: +- res: The formatted string, backed by `buf`. + +Example: + buf: [MIN_YY_DATE_LEN]u8 + now := time.now() + fmt.println(time.to_string_mm_dd_yy(now, buf[:])) +*/ +to_string_mm_dd_yy :: proc(t: Time, buf: []u8) -> (res: string) #no_bounds_check { + assert(len(buf) >= MIN_YY_DATE_LEN) + y, _m, d := date(t) + y %= 100; m := u8(_m) + + buf[7] = '0' + u8(y % 10); y /= 10 + buf[6] = '0' + u8(y) + buf[5] = '-' + buf[4] = '0' + u8(d % 10); d /= 10 + buf[3] = '0' + u8(d % 10) + buf[2] = '-' + buf[1] = '0' + u8(m % 10); m /= 10 + buf[0] = '0' + u8(m % 10) + + return string(buf[:MIN_YY_DATE_LEN]) +} + +/* +Read the timestamp counter of the CPU. +*/ read_cycle_counter :: proc "contextless" () -> u64 { return u64(intrinsics.read_cycle_counter()) } +/* +Obtain time from unix seconds and unix nanoseconds. +*/ unix :: proc "contextless" (sec: i64, nsec: i64) -> Time { sec, nsec := sec, nsec if nsec < 0 || nsec >= 1e9 { @@ -219,26 +738,59 @@ unix :: proc "contextless" (sec: i64, nsec: i64) -> Time { return Time{(sec*1e9 + nsec)} } +/* +Obtain time from unix nanoseconds. +*/ +from_nanoseconds :: #force_inline proc "contextless" (nsec: i64) -> Time { + return Time{nsec} +} + +/* +Alias for `time_to_unix`. +*/ to_unix_seconds :: time_to_unix + +/* +Obtain the Unix timestamp in seconds from a Time. +*/ time_to_unix :: proc "contextless" (t: Time) -> i64 { return t._nsec/1e9 } +/* +Alias for `time_to_unix_nano`. +*/ to_unix_nanoseconds :: time_to_unix_nano + +/* +Obtain the Unix timestamp in nanoseconds from a Time. +*/ time_to_unix_nano :: proc "contextless" (t: Time) -> i64 { return t._nsec } +/* +Add duration to a time. +*/ time_add :: proc "contextless" (t: Time, d: Duration) -> Time { return Time{t._nsec + i64(d)} } -// Accurate sleep borrowed from: https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/ -// -// Accuracy seems to be pretty good out of the box on Linux, to within around 4µs worst case. -// On Windows it depends but is comparable with regular sleep in the worst case. -// To get the same kind of accuracy as on Linux, have your program call `win32.time_begin_period(1)` to -// tell Windows to use a more accurate timer for your process. +/* +Accurate sleep + +This procedure sleeps for the duration specified by `d`, very accurately. + +**Note**: Implementation borrowed from: [this source](https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/) + +**Note(linux)**: The accuracy is within around 4µs (microseconds), in the worst case. + +**Note(windows)**: The accuracy depends but is comparable with regular sleep in +the worst case. To get the same kind of accuracy as on Linux, have your program +call `windows.timeBeginPeriod(1)` to tell Windows to use a more accurate timer +for your process. Additionally your program should call `windows.timeEndPeriod(1)` +once you're done with `accurate_sleep`. +*/ accurate_sleep :: proc "contextless" (d: Duration) { to_sleep, estimate, mean, m2, count: Duration @@ -299,10 +851,6 @@ _time_abs :: proc "contextless" (t: Time) -> u64 { @(private) _abs_date :: proc "contextless" (abs: u64, full: bool) -> (year: int, month: Month, day: int, yday: int) { - _is_leap_year :: proc "contextless" (year: int) -> bool { - return year%4 == 0 && (year%100 != 0 || year%400 == 0) - } - d := abs / SECONDS_PER_DAY // 400 year cycles @@ -335,7 +883,7 @@ _abs_date :: proc "contextless" (abs: u64, full: bool) -> (year: int, month: Mon day = yday - if _is_leap_year(year) { + if is_leap_year(year) { switch { case day > 31+29-1: day -= 1 @@ -360,51 +908,81 @@ _abs_date :: proc "contextless" (abs: u64, full: bool) -> (year: int, month: Mon return } -datetime_to_time :: proc "contextless" (year, month, day, hour, minute, second: int, nsec := int(0)) -> (t: Time, ok: bool) { - divmod :: proc "contextless" (year: int, divisor: int) -> (div: int, mod: int) { - if divisor <= 0 { - intrinsics.debug_trap() - } - div = int(year / divisor) - mod = year % divisor +/* +Convert datetime components into time. + +This procedure calculates the time from datetime components supplied in the +arguments to this procedure. If the datetime components don't represent a valid +datetime, the function returns `false` in the second argument. +*/ +components_to_time :: proc "contextless" (#any_int year, #any_int month, #any_int day, #any_int hour, #any_int minute, #any_int second: i64, #any_int nsec := i64(0)) -> (t: Time, ok: bool) { + this_date, err := dt.components_to_datetime(year, month, day, hour, minute, second, nsec) + if err != .None { + return + } + return compound_to_time(this_date) +} + +/* +Convert datetime into time. + +If the datetime represents a time outside of a valid range, `false` is returned +as the second return value. See `Time` for the representable range. +*/ +compound_to_time :: proc "contextless" (datetime: dt.DateTime) -> (t: Time, ok: bool) { + unix_epoch := dt.DateTime{{1970, 1, 1}, {0, 0, 0, 0}, nil} + delta, err := dt.sub(datetime, unix_epoch) + if err != .None { return } - ok = true - - _y := year - 1970 - _m := month - 1 - _d := day - 1 - - if month < 1 || month > 12 { - _m %= 12; ok = false + seconds := delta.days * 86_400 + delta.seconds + // Can this moment be represented in i64 worth of nanoseconds? + // min(Time): 1677-09-21 00:12:44.145224192 +0000 UTC + // max(Time): 2262-04-11 23:47:16.854775807 +0000 UTC + if seconds < -9223372036 || (seconds == -9223372036 && delta.nanos < -854775808) { + return {}, false } - if day < 1 || day > 31 { - _d %= 31; ok = false + if seconds > 9223372036 || (seconds == 9223372036 && delta.nanos > 854775807) { + return {}, false } - - s := i64(0) - div, mod := divmod(_y, 400) - days := div * DAYS_PER_400_YEARS - - div, mod = divmod(mod, 100) - days += div * DAYS_PER_100_YEARS - - div, mod = divmod(mod, 4) - days += (div * DAYS_PER_4_YEARS) + (mod * 365) - - days += int(days_before[_m]) + _d - - s += i64(days) * SECONDS_PER_DAY - s += i64(hour) * SECONDS_PER_HOUR - s += i64(minute) * SECONDS_PER_MINUTE - s += i64(second) - - t._nsec = (s * 1e9) + i64(nsec) - - return + return Time{_nsec=seconds * 1e9 + delta.nanos}, true } +/* +Convert datetime components into time. +*/ +datetime_to_time :: proc{components_to_time, compound_to_time} + +/* +Convert time into datetime. +*/ +time_to_datetime :: proc "contextless" (t: Time) -> (dt.DateTime, bool) { + unix_epoch := dt.DateTime{{1970, 1, 1}, {0, 0, 0, 0}, nil} + + datetime, err := dt.add(unix_epoch, dt.Delta{ nanos = t._nsec }) + if err != .None { + return {}, false + } + return datetime, true +} + +/* +Alias for `time_to_datetime`. +*/ +time_to_compound :: time_to_datetime + +/* +Check if a year is a leap year. +*/ +is_leap_year :: proc "contextless" (year: int) -> (leap: bool) { + return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) +} + +/* +Days before each month in a year, not counting the leap day on february 29th. +*/ +@(rodata) days_before := [?]i32{ 0, 31, @@ -421,11 +999,37 @@ days_before := [?]i32{ 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, } - +/* +Number of seconds in a minute (without leap seconds). +*/ SECONDS_PER_MINUTE :: 60 + +/* +Number of seconds in an hour (without leap seconds). +*/ SECONDS_PER_HOUR :: 60 * SECONDS_PER_MINUTE + +/* +Number of seconds in a day (without leap seconds). +*/ SECONDS_PER_DAY :: 24 * SECONDS_PER_HOUR + +/* +Number of seconds in a week (without leap seconds). +*/ SECONDS_PER_WEEK :: 7 * SECONDS_PER_DAY + +/* +Days in 400 years, with leap days. +*/ DAYS_PER_400_YEARS :: 365*400 + 97 + +/* +Days in 100 years, with leap days. +*/ DAYS_PER_100_YEARS :: 365*100 + 24 + +/* +Days in 4 years, with leap days. +*/ DAYS_PER_4_YEARS :: 365*4 + 1 diff --git a/core/time/time_essence.odin b/core/time/time_essence.odin index b7bc616d8..89883f0b9 100644 --- a/core/time/time_essence.odin +++ b/core/time/time_essence.odin @@ -1,4 +1,4 @@ -//+private +#+private package time import "core:sys/es" diff --git a/core/time/time_freestanding.odin b/core/time/time_freestanding.odin deleted file mode 100644 index 7c67cc5e8..000000000 --- a/core/time/time_freestanding.odin +++ /dev/null @@ -1,19 +0,0 @@ -//+private -//+build freestanding -package time - -_IS_SUPPORTED :: false - -_now :: proc "contextless" () -> Time { - return {} -} - -_sleep :: proc "contextless" (d: Duration) { -} - -_tick_now :: proc "contextless" () -> Tick { - return {} -} - -_yield :: proc "contextless" () { -} diff --git a/core/time/time_js.odin b/core/time/time_js.odin index 932fc2b8e..9175fbfe9 100644 --- a/core/time/time_js.odin +++ b/core/time/time_js.odin @@ -1,5 +1,5 @@ -//+private -//+build js +#+private +#+build js package time foreign import "odin_env" @@ -24,9 +24,9 @@ _sleep :: proc "contextless" (d: Duration) { _tick_now :: proc "contextless" () -> Tick { foreign odin_env { - tick_now :: proc "contextless" () -> i64 --- + tick_now :: proc "contextless" () -> f32 --- } - return Tick{tick_now()*1e6} + return Tick{i64(tick_now()*1e6)} } _yield :: proc "contextless" () { diff --git a/core/time/time_linux.odin b/core/time/time_linux.odin new file mode 100644 index 000000000..4e557766e --- /dev/null +++ b/core/time/time_linux.odin @@ -0,0 +1,38 @@ +package time + +import "core:sys/linux" + +_IS_SUPPORTED :: true + +_now :: proc "contextless" () -> Time { + time_spec_now, _ := linux.clock_gettime(.REALTIME) + ns := i64(time_spec_now.time_sec) * 1e9 + i64(time_spec_now.time_nsec) + return Time{_nsec=ns} +} + +_sleep :: proc "contextless" (d: Duration) { + ds := duration_seconds(d) + seconds := uint(ds) + nanoseconds := uint((ds - f64(seconds)) * 1e9) + + ts := linux.Time_Spec{ + time_sec = seconds, + time_nsec = nanoseconds, + } + + for { + if linux.nanosleep(&ts, &ts) != .EINTR { + break + } + } +} + +_tick_now :: proc "contextless" () -> Tick { + t, _ := linux.clock_gettime(.MONOTONIC_RAW) + return Tick{_nsec = i64(t.time_sec)*1e9 + i64(t.time_nsec)} +} + +_yield :: proc "contextless" () { + linux.sched_yield() +} + diff --git a/core/time/time_orca.odin b/core/time/time_orca.odin new file mode 100644 index 000000000..f529790a5 --- /dev/null +++ b/core/time/time_orca.odin @@ -0,0 +1,29 @@ +#+private +#+build orca +package time + +import "base:intrinsics" + +import "core:sys/orca" + +_IS_SUPPORTED :: true + +_now :: proc "contextless" () -> Time { + CLK_JAN_1970 :: 2208988800 + secs := orca.clock_time(.DATE) + return Time{i64((secs - CLK_JAN_1970) * 1e9)} +} + +_sleep :: proc "contextless" (d: Duration) { + // NOTE: no way to sleep afaict. + if d > 0 { + orca.log_warning("core:time 'sleep' is unimplemented for orca") + } +} + +_tick_now :: proc "contextless" () -> Tick { + secs := orca.clock_time(.MONOTONIC) + return Tick{i64(secs * 1e9)} +} + +_yield :: proc "contextless" () {} diff --git a/core/time/time_other.odin b/core/time/time_other.odin new file mode 100644 index 000000000..d89bcbd42 --- /dev/null +++ b/core/time/time_other.odin @@ -0,0 +1,33 @@ +#+private +#+build !essence +#+build !js +#+build !linux +#+build !openbsd +#+build !freebsd +#+build !netbsd +#+build !darwin +#+build !wasi +#+build !windows +#+build !orca +package time + +_IS_SUPPORTED :: false + +_now :: proc "contextless" () -> Time { + return {} +} + +_sleep :: proc "contextless" (d: Duration) { +} + +_tick_now :: proc "contextless" () -> Tick { + // mul_div_u64 :: proc "contextless" (val, num, den: i64) -> i64 { + // q := val / den + // r := val % den + // return q * num + r * num / den + // } + return {} +} + +_yield :: proc "contextless" () { +} diff --git a/core/time/time_unix.odin b/core/time/time_unix.odin index ba0d91527..61c4e91d3 100644 --- a/core/time/time_unix.odin +++ b/core/time/time_unix.odin @@ -1,34 +1,50 @@ -//+private -//+build linux, darwin, freebsd, openbsd +#+private +#+build darwin, freebsd, openbsd, netbsd package time -import "core:sys/unix" +import "core:sys/posix" -_IS_SUPPORTED :: true // NOTE: Times on Darwin are UTC. +_IS_SUPPORTED :: true _now :: proc "contextless" () -> Time { - time_spec_now: unix.timespec - unix.clock_gettime(unix.CLOCK_REALTIME, &time_spec_now) - ns := time_spec_now.tv_sec * 1e9 + time_spec_now.tv_nsec + time_spec_now: posix.timespec + posix.clock_gettime(.REALTIME, &time_spec_now) + ns := i64(time_spec_now.tv_sec) * 1e9 + time_spec_now.tv_nsec return Time{_nsec=ns} } _sleep :: proc "contextless" (d: Duration) { ds := duration_seconds(d) - seconds := u32(ds) + seconds := posix.time_t(ds) nanoseconds := i64((ds - f64(seconds)) * 1e9) - if seconds > 0 { unix.sleep(seconds) } - if nanoseconds > 0 { unix.inline_nanosleep(nanoseconds) } + ts := posix.timespec{ + tv_sec = seconds, + tv_nsec = nanoseconds, + } + + for { + res := posix.nanosleep(&ts, &ts) + if res == .OK || posix.errno() != .EINTR { + break + } + } +} + +when ODIN_OS == .Darwin { + TICK_CLOCK :: posix.Clock(4) // CLOCK_MONOTONIC_RAW +} else { + // It looks like the BSDs don't have a CLOCK_MONOTONIC_RAW equivalent. + TICK_CLOCK :: posix.Clock.MONOTONIC } _tick_now :: proc "contextless" () -> Tick { - t: unix.timespec - unix.clock_gettime(unix.CLOCK_MONOTONIC_RAW, &t) - return Tick{_nsec = t.tv_sec*1e9 + t.tv_nsec} + t: posix.timespec + posix.clock_gettime(TICK_CLOCK, &t) + return Tick{_nsec = i64(t.tv_sec)*1e9 + t.tv_nsec} } _yield :: proc "contextless" () { - unix.sched_yield() + posix.sched_yield() } diff --git a/core/time/time_wasi.odin b/core/time/time_wasi.odin index dacf911fc..c16c40cce 100644 --- a/core/time/time_wasi.odin +++ b/core/time/time_wasi.odin @@ -1,26 +1,41 @@ -//+private -//+build wasi +#+private +#+build wasi package time -import wasi "core:sys/wasm/wasi" +import "base:intrinsics" -_IS_SUPPORTED :: false +import "core:sys/wasm/wasi" + +_IS_SUPPORTED :: true _now :: proc "contextless" () -> Time { - return {} + ts, err := wasi.clock_time_get(wasi.CLOCK_REALTIME, 0) + assert_contextless(err == nil) + return Time{_nsec=i64(ts)} } _sleep :: proc "contextless" (d: Duration) { + ev: wasi.event_t + n, err := wasi.poll_oneoff( + &{ + tag = .CLOCK, + clock = { + id = wasi.CLOCK_MONOTONIC, + timeout = wasi.timestamp_t(d), + }, + }, + &ev, + 1, + ) + assert_contextless(err == nil && n == 1 && ev.error == nil && ev.type == .CLOCK) } _tick_now :: proc "contextless" () -> Tick { - // mul_div_u64 :: proc "contextless" (val, num, den: i64) -> i64 { - // q := val / den - // r := val % den - // return q * num + r * num / den - // } - return {} + ts, err := wasi.clock_time_get(wasi.CLOCK_MONOTONIC, 0) + assert_contextless(err == nil) + return Tick{_nsec=i64(ts)} } _yield :: proc "contextless" () { + wasi.sched_yield() } diff --git a/core/time/time_windows.odin b/core/time/time_windows.odin index 378b914b0..553ea6d4e 100644 --- a/core/time/time_windows.odin +++ b/core/time/time_windows.odin @@ -1,4 +1,4 @@ -//+private +#+private package time import win32 "core:sys/windows" diff --git a/core/time/timezone/tz_unix.odin b/core/time/timezone/tz_unix.odin new file mode 100644 index 000000000..990e78d41 --- /dev/null +++ b/core/time/timezone/tz_unix.odin @@ -0,0 +1,89 @@ +#+build darwin, linux, freebsd, openbsd, netbsd +#+private +package timezone + +import "core:os" +import "core:strings" +import "core:path/filepath" +import "core:time/datetime" + +local_tz_name :: proc(allocator := context.allocator) -> (name: string, success: bool) { + local_str, ok := os.lookup_env("TZ", allocator) + if !ok { + orig_localtime_path := "/etc/localtime" + path, err := os.absolute_path_from_relative(orig_localtime_path, allocator) + if err != nil { + // If we can't find /etc/localtime, fallback to UTC + if err == .ENOENT { + str, err2 := strings.clone("UTC", allocator) + if err2 != nil { return } + return str, true + } + + return + } + defer delete(path, allocator) + + // FreeBSD makes me sad. + // This is a hackaround, because FreeBSD copies rather than softlinks their local timezone file, + // *sometimes* and then stores the original name of the timezone in /var/db/zoneinfo instead + if path == orig_localtime_path { + data := os.read_entire_file("/var/db/zoneinfo", allocator) or_return + return strings.trim_right_space(string(data)), true + } + + // Looking for tz path (ex fmt: "UTC", "Etc/UTC" or "America/Los_Angeles") + path_dir, path_file := filepath.split(path) + if path_dir == "" { + return + } + upper_path_dir, upper_path_chunk := filepath.split(path_dir[:len(path_dir)-1]) + if upper_path_dir == "" { + return + } + + if strings.contains(upper_path_chunk, "zoneinfo") { + region_str, err := strings.clone(path_file, allocator) + if err != nil { return } + return region_str, true + } else { + region_str, err := filepath.join({upper_path_chunk, path_file}, allocator = allocator) + if err != nil { return } + return region_str, true + } + } + + if local_str == "" { + delete(local_str, allocator) + + str, err := strings.clone("UTC", allocator) + if err != nil { return } + return str, true + } + + return local_str, true +} + +_region_load :: proc(_reg_str: string, allocator := context.allocator) -> (out_reg: ^datetime.TZ_Region, success: bool) { + reg_str := _reg_str + if reg_str == "UTC" { + return nil, true + } + + if reg_str == "local" { + local_name := local_tz_name(allocator) or_return + if local_name == "UTC" { + delete(local_name, allocator) + return nil, true + } + + reg_str = local_name + } + defer if _reg_str == "local" { delete(reg_str, allocator) } + + db_path := "/usr/share/zoneinfo" + region_path := filepath.join({db_path, reg_str}, allocator) + defer delete(region_path, allocator) + + return load_tzif_file(region_path, reg_str, allocator) +} diff --git a/core/time/timezone/tz_windows.odin b/core/time/timezone/tz_windows.odin new file mode 100644 index 000000000..8dc5f533c --- /dev/null +++ b/core/time/timezone/tz_windows.odin @@ -0,0 +1,323 @@ +#+build windows +#+private +package timezone + +import "core:strings" +import "core:sys/windows" +import "core:time/datetime" + +TZ_Abbrev :: struct { + std: string, + dst: string, +} + +@(rodata) +tz_abbrevs := [?]struct{key: string, value: TZ_Abbrev}{ + {"Egypt Standard Time", {"EET", "EEST"}}, // Africa/Cairo + {"Morocco Standard Time", {"+00", "+01"}}, // Africa/Casablanca + {"South Africa Standard Time", {"SAST", "SAST"}}, // Africa/Johannesburg + {"South Sudan Standard Time", {"CAT", "CAT"}}, // Africa/Juba + {"Sudan Standard Time", {"CAT", "CAT"}}, // Africa/Khartoum + {"W. Central Africa Standard Time", {"WAT", "WAT"}}, // Africa/Lagos + {"E. Africa Standard Time", {"EAT", "EAT"}}, // Africa/Nairobi + {"Sao Tome Standard Time", {"GMT", "GMT"}}, // Africa/Sao_Tome + {"Libya Standard Time", {"EET", "EET"}}, // Africa/Tripoli + {"Namibia Standard Time", {"CAT", "CAT"}}, // Africa/Windhoek + {"Aleutian Standard Time", {"HST", "HDT"}}, // America/Adak + {"Alaskan Standard Time", {"AKST", "AKDT"}}, // America/Anchorage + {"Tocantins Standard Time", {"-03", "-03"}}, // America/Araguaina + {"Paraguay Standard Time", {"-04", "-03"}}, // America/Asuncion + {"Bahia Standard Time", {"-03", "-03"}}, // America/Bahia + {"SA Pacific Standard Time", {"-05", "-05"}}, // America/Bogota + {"Argentina Standard Time", {"-03", "-03"}}, // America/Buenos_Aires + {"Eastern Standard Time (Mexico)", {"EST", "EST"}}, // America/Cancun + {"Venezuela Standard Time", {"-04", "-04"}}, // America/Caracas + {"SA Eastern Standard Time", {"-03", "-03"}}, // America/Cayenne + {"Central Standard Time", {"CST", "CDT"}}, // America/Chicago + {"Central Brazilian Standard Time", {"-04", "-04"}}, // America/Cuiaba + {"Mountain Standard Time", {"MST", "MDT"}}, // America/Denver + {"Greenland Standard Time", {"-03", "-02"}}, // America/Godthab + {"Turks And Caicos Standard Time", {"EST", "EDT"}}, // America/Grand_Turk + {"Central America Standard Time", {"CST", "CST"}}, // America/Guatemala + {"Atlantic Standard Time", {"AST", "ADT"}}, // America/Halifax + {"Cuba Standard Time", {"CST", "CDT"}}, // America/Havana + {"US Eastern Standard Time", {"EST", "EDT"}}, // America/Indianapolis + {"SA Western Standard Time", {"-04", "-04"}}, // America/La_Paz + {"Pacific Standard Time", {"PST", "PDT"}}, // America/Los_Angeles + {"Mountain Standard Time (Mexico)", {"MST", "MST"}}, // America/Mazatlan + {"Central Standard Time (Mexico)", {"CST", "CST"}}, // America/Mexico_City + {"Saint Pierre Standard Time", {"-03", "-02"}}, // America/Miquelon + {"Montevideo Standard Time", {"-03", "-03"}}, // America/Montevideo + {"Eastern Standard Time", {"EST", "EDT"}}, // America/New_York + {"US Mountain Standard Time", {"MST", "MST"}}, // America/Phoenix + {"Haiti Standard Time", {"EST", "EDT"}}, // America/Port-au-Prince + {"Magallanes Standard Time", {"-03", "-03"}}, // America/Punta_Arenas + {"Canada Central Standard Time", {"CST", "CST"}}, // America/Regina + {"Pacific SA Standard Time", {"-04", "-03"}}, // America/Santiago + {"E. South America Standard Time", {"-03", "-03"}}, // America/Sao_Paulo + {"Newfoundland Standard Time", {"NST", "NDT"}}, // America/St_Johns + {"Pacific Standard Time (Mexico)", {"PST", "PDT"}}, // America/Tijuana + {"Yukon Standard Time", {"MST", "MST"}}, // America/Whitehorse + {"Central Asia Standard Time", {"+06", "+06"}}, // Asia/Almaty + {"Jordan Standard Time", {"+03", "+03"}}, // Asia/Amman + {"Arabic Standard Time", {"+03", "+03"}}, // Asia/Baghdad + {"Azerbaijan Standard Time", {"+04", "+04"}}, // Asia/Baku + {"SE Asia Standard Time", {"+07", "+07"}}, // Asia/Bangkok + {"Altai Standard Time", {"+07", "+07"}}, // Asia/Barnaul + {"Middle East Standard Time", {"EET", "EEST"}}, // Asia/Beirut + {"India Standard Time", {"IST", "IST"}}, // Asia/Calcutta + {"Transbaikal Standard Time", {"+09", "+09"}}, // Asia/Chita + {"Sri Lanka Standard Time", {"+0530", "+0530"}}, // Asia/Colombo + {"Syria Standard Time", {"+03", "+03"}}, // Asia/Damascus + {"Bangladesh Standard Time", {"+06", "+06"}}, // Asia/Dhaka + {"Arabian Standard Time", {"+04", "+04"}}, // Asia/Dubai + {"West Bank Standard Time", {"EET", "EEST"}}, // Asia/Hebron + {"W. Mongolia Standard Time", {"+07", "+07"}}, // Asia/Hovd + {"North Asia East Standard Time", {"+08", "+08"}}, // Asia/Irkutsk + {"Israel Standard Time", {"IST", "IDT"}}, // Asia/Jerusalem + {"Afghanistan Standard Time", {"+0430", "+0430"}}, // Asia/Kabul + {"Russia Time Zone 11", {"+12", "+12"}}, // Asia/Kamchatka + {"Pakistan Standard Time", {"PKT", "PKT"}}, // Asia/Karachi + {"Nepal Standard Time", {"+0545", "+0545"}}, // Asia/Katmandu + {"North Asia Standard Time", {"+07", "+07"}}, // Asia/Krasnoyarsk + {"Magadan Standard Time", {"+11", "+11"}}, // Asia/Magadan + {"N. Central Asia Standard Time", {"+07", "+07"}}, // Asia/Novosibirsk + {"Omsk Standard Time", {"+06", "+06"}}, // Asia/Omsk + {"North Korea Standard Time", {"KST", "KST"}}, // Asia/Pyongyang + {"Qyzylorda Standard Time", {"+05", "+05"}}, // Asia/Qyzylorda + {"Myanmar Standard Time", {"+0630", "+0630"}}, // Asia/Rangoon + {"Arab Standard Time", {"+03", "+03"}}, // Asia/Riyadh + {"Sakhalin Standard Time", {"+11", "+11"}}, // Asia/Sakhalin + {"Korea Standard Time", {"KST", "KST"}}, // Asia/Seoul + {"China Standard Time", {"CST", "CST"}}, // Asia/Shanghai + {"Singapore Standard Time", {"+08", "+08"}}, // Asia/Singapore + {"Russia Time Zone 10", {"+11", "+11"}}, // Asia/Srednekolymsk + {"Taipei Standard Time", {"CST", "CST"}}, // Asia/Taipei + {"West Asia Standard Time", {"+05", "+05"}}, // Asia/Tashkent + {"Georgian Standard Time", {"+04", "+04"}}, // Asia/Tbilisi + {"Iran Standard Time", {"+0330", "+0330"}}, // Asia/Tehran + {"Tokyo Standard Time", {"JST", "JST"}}, // Asia/Tokyo + {"Tomsk Standard Time", {"+07", "+07"}}, // Asia/Tomsk + {"Ulaanbaatar Standard Time", {"+08", "+08"}}, // Asia/Ulaanbaatar + {"Vladivostok Standard Time", {"+10", "+10"}}, // Asia/Vladivostok + {"Yakutsk Standard Time", {"+09", "+09"}}, // Asia/Yakutsk + {"Ekaterinburg Standard Time", {"+05", "+05"}}, // Asia/Yekaterinburg + {"Caucasus Standard Time", {"+04", "+04"}}, // Asia/Yerevan + {"Azores Standard Time", {"-01", "+00"}}, // Atlantic/Azores + {"Cape Verde Standard Time", {"-01", "-01"}}, // Atlantic/Cape_Verde + {"Greenwich Standard Time", {"GMT", "GMT"}}, // Atlantic/Reykjavik + {"Cen. Australia Standard Time", {"ACST", "ACDT"}}, // Australia/Adelaide + {"E. Australia Standard Time", {"AEST", "AEST"}}, // Australia/Brisbane + {"AUS Central Standard Time", {"ACST", "ACST"}}, // Australia/Darwin + {"Aus Central W. Standard Time", {"+0845", "+0845"}}, // Australia/Eucla + {"Tasmania Standard Time", {"AEST", "AEDT"}}, // Australia/Hobart + {"Lord Howe Standard Time", {"+1030", "+11"}}, // Australia/Lord_Howe + {"W. Australia Standard Time", {"AWST", "AWST"}}, // Australia/Perth + {"AUS Eastern Standard Time", {"AEST", "AEDT"}}, // Australia/Sydney + {"UTC-11", {"-11", "-11"}}, // Etc/GMT+11 + {"Dateline Standard Time", {"-12", "-12"}}, // Etc/GMT+12 + {"UTC-02", {"-02", "-02"}}, // Etc/GMT+2 + {"UTC-08", {"-08", "-08"}}, // Etc/GMT+8 + {"UTC-09", {"-09", "-09"}}, // Etc/GMT+9 + {"UTC+12", {"+12", "+12"}}, // Etc/GMT-12 + {"UTC+13", {"+13", "+13"}}, // Etc/GMT-13 + {"UTC", {"UTC", "UTC"}}, // Etc/UTC + {"Astrakhan Standard Time", {"+04", "+04"}}, // Europe/Astrakhan + {"W. Europe Standard Time", {"CET", "CEST"}}, // Europe/Berlin + {"GTB Standard Time", {"EET", "EEST"}}, // Europe/Bucharest + {"Central Europe Standard Time", {"CET", "CEST"}}, // Europe/Budapest + {"E. Europe Standard Time", {"EET", "EEST"}}, // Europe/Chisinau + {"Turkey Standard Time", {"+03", "+03"}}, // Europe/Istanbul + {"Kaliningrad Standard Time", {"EET", "EET"}}, // Europe/Kaliningrad + {"FLE Standard Time", {"EET", "EEST"}}, // Europe/Kiev + {"GMT Standard Time", {"GMT", "BST"}}, // Europe/London + {"Belarus Standard Time", {"+03", "+03"}}, // Europe/Minsk + {"Russian Standard Time", {"MSK", "MSK"}}, // Europe/Moscow + {"Romance Standard Time", {"CET", "CEST"}}, // Europe/Paris + {"Russia Time Zone 3", {"+04", "+04"}}, // Europe/Samara + {"Saratov Standard Time", {"+04", "+04"}}, // Europe/Saratov + {"Volgograd Standard Time", {"MSK", "MSK"}}, // Europe/Volgograd + {"Central European Standard Time", {"CET", "CEST"}}, // Europe/Warsaw + {"Mauritius Standard Time", {"+04", "+04"}}, // Indian/Mauritius + {"Samoa Standard Time", {"+13", "+13"}}, // Pacific/Apia + {"New Zealand Standard Time", {"NZST", "NZDT"}}, // Pacific/Auckland + {"Bougainville Standard Time", {"+11", "+11"}}, // Pacific/Bougainville + {"Chatham Islands Standard Time", {"+1245", "+1345"}}, // Pacific/Chatham + {"Easter Island Standard Time", {"-06", "-05"}}, // Pacific/Easter + {"Fiji Standard Time", {"+12", "+12"}}, // Pacific/Fiji + {"Central Pacific Standard Time", {"+11", "+11"}}, // Pacific/Guadalcanal + {"Hawaiian Standard Time", {"HST", "HST"}}, // Pacific/Honolulu + {"Line Islands Standard Time", {"+14", "+14"}}, // Pacific/Kiritimati + {"Marquesas Standard Time", {"-0930", "-0930"}}, // Pacific/Marquesas + {"Norfolk Standard Time", {"+11", "+12"}}, // Pacific/Norfolk + {"West Pacific Standard Time", {"+10", "+10"}}, // Pacific/Port_Moresby + {"Tonga Standard Time", {"+13", "+13"}}, // Pacific/Tongatapu +} + +iana_to_windows_tz :: proc(iana_name: string, allocator := context.allocator) -> (name: string, success: bool) { + wintz_name_buffer: [128]u16 + status: windows.UError + + iana_name_wstr := windows.utf8_to_wstring(iana_name, allocator) + defer free(iana_name_wstr, allocator) + + wintz_name_len := windows.ucal_getWindowsTimeZoneID(iana_name_wstr, -1, raw_data(wintz_name_buffer[:]), len(wintz_name_buffer), &status) + if status != .U_ZERO_ERROR { + return + } + + wintz_name, err := windows.utf16_to_utf8(wintz_name_buffer[:wintz_name_len], allocator) + if err != nil { + return + } + + return wintz_name, true +} + +local_tz_name :: proc(allocator := context.allocator) -> (name: string, success: bool) { + iana_name_buffer: [128]u16 + status: windows.UError + + zone_str_len := windows.ucal_getDefaultTimeZone(raw_data(iana_name_buffer[:]), len(iana_name_buffer), &status) + if status != .U_ZERO_ERROR { + return + } + + iana_name, err := windows.utf16_to_utf8(iana_name_buffer[:zone_str_len], allocator) + if err != nil { + return + } + + return iana_name, true +} + +REG_TZI_FORMAT :: struct #packed { + bias: windows.LONG, + std_bias: windows.LONG, + dst_bias: windows.LONG, + std_date: windows.SYSTEMTIME, + dst_date: windows.SYSTEMTIME, +} + +generate_rrule_from_tzi :: proc(tzi: ^REG_TZI_FORMAT, abbrevs: TZ_Abbrev, allocator := context.allocator) -> (rrule: datetime.TZ_RRule, ok: bool) { + std_name, err := strings.clone(abbrevs.std, allocator) + if err != nil { return } + defer if err != nil { delete(std_name, allocator) } + + if (tzi.std_date.month == 0) { + return datetime.TZ_RRule{ + has_dst = false, + + std_name = std_name, + std_offset = -(i64(tzi.bias) + i64(tzi.std_bias)) * 60, + dst_date = datetime.TZ_Transition_Date{ + type = .Month_Week_Day, + month = u8(tzi.std_date.month), + week = u8(tzi.std_date.day), + day = tzi.std_date.day_of_week, + time = (i64(tzi.std_date.hour) * 60 * 60) + (i64(tzi.std_date.minute) * 60) + i64(tzi.std_date.second), + }, + }, true + } + + dst_name: string + dst_name, err = strings.clone(abbrevs.dst, allocator) + if err != nil { return } + defer if err != nil { delete(dst_name, allocator) } + + return datetime.TZ_RRule{ + has_dst = true, + + std_name = std_name, + std_offset = -(i64(tzi.bias) + i64(tzi.std_bias)) * 60, + dst_date = datetime.TZ_Transition_Date{ + type = .Month_Week_Day, + month = u8(tzi.std_date.month), + week = u8(tzi.std_date.day), + day = tzi.std_date.day_of_week, + time = (i64(tzi.std_date.hour) * 60 * 60) + (i64(tzi.std_date.minute) * 60) + i64(tzi.std_date.second), + }, + + dst_name = dst_name, + dst_offset = -(i64(tzi.bias) + i64(tzi.dst_bias)) * 60, + std_date = datetime.TZ_Transition_Date{ + type = .Month_Week_Day, + month = u8(tzi.dst_date.month), + week = u8(tzi.dst_date.day), + day = tzi.dst_date.day_of_week, + time = (i64(tzi.dst_date.hour) * 60 * 60) + (i64(tzi.dst_date.minute) * 60) + i64(tzi.dst_date.second), + }, + }, true +} + +_region_load :: proc(reg_str: string, allocator := context.allocator) -> (out_reg: ^datetime.TZ_Region, success: bool) { + wintz_name: string + iana_name: string + + if reg_str == "local" { + ok := false + + iana_name = local_tz_name(allocator) or_return + wintz_name, ok = iana_to_windows_tz(iana_name, allocator) + if !ok { + delete(iana_name, allocator) + return + } + } else { + wintz_name = iana_to_windows_tz(reg_str, allocator) or_return + iana_name = strings.clone(reg_str, allocator) + } + defer delete(wintz_name, allocator) + defer delete(iana_name, allocator) + + abbrevs: TZ_Abbrev + abbrevs_ok: bool + for pair in tz_abbrevs { + if pair.key == wintz_name { + abbrevs = pair.value + abbrevs_ok = true + break + } + } + if !abbrevs_ok { + return + } + if abbrevs.std == "UTC" && abbrevs.dst == abbrevs.std { + return nil, true + } + + key_base := `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones` + tz_key := strings.join({key_base, wintz_name}, "\\", allocator = allocator) + defer delete(tz_key, allocator) + + tz_key_wstr := windows.utf8_to_wstring(tz_key, allocator) + defer free(tz_key_wstr, allocator) + + key: windows.HKEY + res := windows.RegOpenKeyExW(windows.HKEY_LOCAL_MACHINE, tz_key_wstr, 0, windows.KEY_READ, &key) + if res != 0 { return } + defer windows.RegCloseKey(key) + + tzi: REG_TZI_FORMAT + size := u32(size_of(REG_TZI_FORMAT)) + + res = windows.RegGetValueW(key, nil, windows.L("TZI"), windows.RRF_RT_ANY, nil, &tzi, &size) + if res != 0 { + return + } + + rrule := generate_rrule_from_tzi(&tzi, abbrevs, allocator) or_return + + region_name, err := strings.clone(iana_name, allocator) + if err != nil { return } + defer if err != nil { delete(region_name, allocator) } + + region: ^datetime.TZ_Region + region, err = new_clone(datetime.TZ_Region{ + name = region_name, + rrule = rrule, + }, allocator) + if err != nil { return } + + return region, true +} diff --git a/core/time/timezone/tzdate.odin b/core/time/timezone/tzdate.odin new file mode 100644 index 000000000..96df44299 --- /dev/null +++ b/core/time/timezone/tzdate.odin @@ -0,0 +1,341 @@ +package timezone + +import "core:fmt" +import "core:slice" +import "core:time" +import "core:time/datetime" + +region_load :: proc(reg: string, allocator := context.allocator) -> (out_reg: ^datetime.TZ_Region, ok: bool) { + return _region_load(reg, allocator) +} + +region_load_from_file :: proc(file_path, reg: string, allocator := context.allocator) -> (out_reg: ^datetime.TZ_Region, ok: bool) { + return load_tzif_file(file_path, reg, allocator) +} + +region_load_from_buffer :: proc(buffer: []u8, reg: string, allocator := context.allocator) -> (out_reg: ^datetime.TZ_Region, ok: bool) { + return parse_tzif(buffer, reg, allocator) +} + +rrule_destroy :: proc(rrule: datetime.TZ_RRule, allocator := context.allocator) { + delete(rrule.std_name, allocator) + delete(rrule.dst_name, allocator) +} + +region_destroy :: proc(region: ^datetime.TZ_Region, allocator := context.allocator) { + if region == nil { + return + } + + for name in region.shortnames { + delete(name, allocator) + } + delete(region.shortnames, allocator) + delete(region.records, allocator) + delete(region.name, allocator) + rrule_destroy(region.rrule, allocator) + free(region, allocator) +} + + +@private +region_get_nearest :: proc(region: ^datetime.TZ_Region, tm: time.Time) -> (out: datetime.TZ_Record, success: bool) { + if len(region.records) == 0 { + return process_rrule(region.rrule, tm) + } + + n := len(region.records) + left, right := 0, n + + tm_sec := time.to_unix_seconds(tm) + last_time := region.records[len(region.records)-1].time + if tm_sec > last_time { + return process_rrule(region.rrule, tm) + } + + for left < right { + mid := int(uint(left+right) >> 1) + if region.records[mid].time < tm_sec { + left = mid + 1 + } else { + right = mid + } + } + + idx := max(0, left-1) + return region.records[idx], true +} + +@private +month_to_seconds :: proc(month: int, is_leap: bool) -> i64 { + month_seconds := []i64{ + 0, 31 * 86_400, 59 * 86_400, 90 * 86_400, + 120 * 86_400, 151 * 86_400, 181 * 86_400, 212 * 86_400, + 243 * 86_400, 273 * 86_400, 304 * 86_400, 334 * 86_400, + } + + t := month_seconds[month] + if is_leap && month >= 2 { + t += 86_400 + } + return t +} + +@private +trans_date_to_seconds :: proc(year: i64, td: datetime.TZ_Transition_Date) -> (secs: i64, ok: bool) { + is_leap := datetime.is_leap_year(year) + DAY_SEC :: 86_400 + + year_start := datetime.DateTime{{year, 1, 1}, {0, 0, 0, 0}, nil} + year_start_time := time.datetime_to_time(year_start) or_return + + t := i64(time.to_unix_seconds(year_start_time)) + + switch td.type { + case .Month_Week_Day: + if td.month < 1 { return } + + t += month_to_seconds(int(td.month) - 1, is_leap) + + weekday := ((t + (4 * DAY_SEC)) %% (7 * DAY_SEC)) / DAY_SEC + days := i64(td.day) - weekday + if days < 0 { days += 7 } + + month_daycount, err := datetime.last_day_of_month(year, td.month) + if err != nil { return } + + week := td.week + if week == 5 && days + 28 >= i64(month_daycount) { + week = 4 + } + + t += DAY_SEC * (days + (7 * i64(week - 1))) + t += td.time + + return t, true + + // Both of these should result in 0 -> 365 days (in seconds) + case .No_Leap: + day := i64(td.day) + + // if before Feb 29th || not a leap year + if day < 60 || !is_leap { + day -= 1 + } + t += DAY_SEC * day + + return t, true + + case .Leap: + t += DAY_SEC * i64(td.day) + + return t, true + + case: + return + } + + return +} + +@private +process_rrule :: proc(rrule: datetime.TZ_RRule, tm: time.Time) -> (out: datetime.TZ_Record, success: bool) { + if !rrule.has_dst { + return datetime.TZ_Record{ + time = time.to_unix_seconds(tm), + utc_offset = rrule.std_offset, + shortname = rrule.std_name, + dst = false, + }, true + } + + y, _, _ := time.date(tm) + std_secs := trans_date_to_seconds(i64(y), rrule.std_date) or_return + dst_secs := trans_date_to_seconds(i64(y), rrule.dst_date) or_return + + records := []datetime.TZ_Record{ + { + time = std_secs, + utc_offset = rrule.std_offset, + shortname = rrule.std_name, + dst = false, + }, + { + time = dst_secs, + utc_offset = rrule.dst_offset, + shortname = rrule.dst_name, + dst = true, + }, + } + record_sort_proc :: proc(i, j: datetime.TZ_Record) -> bool { + return i.time > j.time + } + slice.sort_by(records, record_sort_proc) + + tm_sec := time.to_unix_seconds(tm) + for record in records { + if tm_sec < record.time { + return record, true + } + } + + return records[len(records)-1], true +} + +datetime_to_utc :: proc(dt: datetime.DateTime) -> (out: datetime.DateTime, success: bool) #optional_ok { + if dt.tz == nil { + return dt, true + } + + tm := time.datetime_to_time(dt) or_return + record := region_get_nearest(dt.tz, tm) or_return + + secs := time.time_to_unix(tm) + adj_time := time.unix(secs - record.utc_offset, 0) + adj_dt := time.time_to_datetime(adj_time) or_return + return adj_dt, true +} + +/* +Converts a datetime on one timezone to another timezone + +Inputs: +- dt: The input datetime +- tz: The timezone to convert to + +NOTE: tz will be referenced in the result datetime, so it must stay alive/allocated as long as it is used +Returns: +- out: The converted datetime +- success: `false` if the datetime was invalid +*/ +datetime_to_tz :: proc(dt: datetime.DateTime, tz: ^datetime.TZ_Region) -> (out: datetime.DateTime, success: bool) #optional_ok { + dt := dt + if dt.tz == tz { + return dt, true + } + if dt.tz != nil { + dt = datetime_to_utc(dt) + } + if tz == nil { + return dt, true + } + + tm := time.datetime_to_time(dt) or_return + record := region_get_nearest(tz, tm) or_return + + secs := time.time_to_unix(tm) + adj_time := time.unix(secs + record.utc_offset, 0) + adj_dt := time.time_to_datetime(adj_time) or_return + adj_dt.tz = tz + + return adj_dt, true +} + +/* +Gets the timezone abbreviation/shortname for a given date. +(ex: "PDT") + +Inputs: +- dt: The datetime containing the date, time, and timezone pointer for the lookup + +NOTE: The lifetime of name matches the timezone it was pulled from. +Returns: +- name: The timezone abbreviation +- success: returns `false` if the passed datetime is invalid +*/ +shortname :: proc(dt: datetime.DateTime) -> (name: string, success: bool) #optional_ok { + tm := time.datetime_to_time(dt) or_return + if dt.tz == nil { return "UTC", true } + + record := region_get_nearest(dt.tz, tm) or_return + return record.shortname, true +} + +/* +Gets the timezone abbreviation/shortname for a given date. +(ex: "PDT") + +WARNING: This is unsafe because it doesn't check if your datetime is valid or if your region contains a valid record. + +Inputs: +- dt: The input datetime + +NOTE: The lifetime of name matches the timezone it was pulled from. +Returns: +- name: The timezone abbreviation +*/ +shortname_unsafe :: proc(dt: datetime.DateTime) -> string { + if dt.tz == nil { return "UTC" } + + tm, _ := time.datetime_to_time(dt) + record, _ := region_get_nearest(dt.tz, tm) + return record.shortname +} + +/* +Checks DST for a given date. + +Inputs: +- dt: The input datetime + +Returns: +- is_dst: returns `true` if dt is in daylight savings time, `false` if not +- success: returns `false` if the passed datetime is invalid +*/ +dst :: proc(dt: datetime.DateTime) -> (is_dst: bool, success: bool) #optional_ok { + tm := time.datetime_to_time(dt) or_return + if dt.tz == nil { return false, true } + + record := region_get_nearest(dt.tz, tm) or_return + return record.dst, true +} + +/* +Checks DST for a given date. + +WARNING: This is unsafe because it doesn't check if your datetime is valid or if your region contains a valid record. + +Inputs: +- dt: The input datetime + +Returns: +- is_dst: returns `true` if dt is in daylight savings time, `false` if not +*/ +dst_unsafe :: proc(dt: datetime.DateTime) -> bool { + if dt.tz == nil { return false } + + tm, _ := time.datetime_to_time(dt) + record, _ := region_get_nearest(dt.tz, tm) + return record.dst +} + +datetime_to_str :: proc(dt: datetime.DateTime, allocator := context.allocator) -> string { + if dt.tz == nil { + _, ok := time.datetime_to_time(dt) + if !ok { + return "" + } + + return fmt.aprintf("%02d-%02d-%04d @ %02d:%02d:%02d UTC", dt.month, dt.day, dt.year, dt.hour, dt.minute, dt.second, allocator = allocator) + + } else { + tm, ok := time.datetime_to_time(dt) + if !ok { + return "" + } + + record, ok2 := region_get_nearest(dt.tz, tm) + if !ok2 { + return "" + } + + hour := dt.hour + am_pm_str := "AM" + if hour > 12 { + am_pm_str = "PM" + hour -= 12 + } + + return fmt.aprintf("%02d-%02d-%04d @ %02d:%02d:%02d %s %s", dt.month, dt.day, dt.year, hour, dt.minute, dt.second, am_pm_str, record.shortname, allocator = allocator) + } +} diff --git a/core/time/timezone/tzif.odin b/core/time/timezone/tzif.odin new file mode 100644 index 000000000..3fec7be53 --- /dev/null +++ b/core/time/timezone/tzif.odin @@ -0,0 +1,649 @@ +package timezone + +import "base:intrinsics" + +import "core:slice" +import "core:strings" +import "core:os" +import "core:strconv" +import "core:time/datetime" + +// Implementing RFC8536 [https://datatracker.ietf.org/doc/html/rfc8536] + +TZIF_MAGIC :: u32be(0x545A6966) // 'TZif' +TZif_Version :: enum u8 { + V1 = 0, + V2 = '2', + V3 = '3', + V4 = '4', +} +BIG_BANG_ISH :: -0x800000000000000 + +TZif_Header :: struct #packed { + magic: u32be, + version: TZif_Version, + reserved: [15]u8, + isutcnt: u32be, + isstdcnt: u32be, + leapcnt: u32be, + timecnt: u32be, + typecnt: u32be, + charcnt: u32be, +} + +Sun_Shift :: enum u8 { + Standard = 0, + DST = 1, +} + +Local_Time_Type :: struct #packed { + utoff: i32be, + dst: Sun_Shift, + idx: u8, +} + +Leapsecond_Record :: struct #packed { + occur: i64be, + corr: i32be, +} + +@private +tzif_data_block_size :: proc(hdr: ^TZif_Header, version: TZif_Version) -> (block_size: int, ok: bool) { + time_size : int + + if version == .V1 { + time_size = 4 + } else if version == .V2 || version == .V3 || version == .V4 { + time_size = 8 + } else { + return + } + + return (int(hdr.timecnt) * time_size) + + int(hdr.timecnt) + + int(hdr.typecnt * size_of(Local_Time_Type)) + + int(hdr.charcnt) + + (int(hdr.leapcnt) * (time_size + 4)) + + int(hdr.isstdcnt) + + int(hdr.isutcnt), true +} + + +load_tzif_file :: proc(filename: string, region_name: string, allocator := context.allocator) -> (out: ^datetime.TZ_Region, ok: bool) { + tzif_data := os.read_entire_file_from_filename(filename, allocator) or_return + defer delete(tzif_data, allocator) + return parse_tzif(tzif_data, region_name, allocator) +} + +@private +is_alphabetic :: proc(ch: u8) -> bool { + // ('A' -> 'Z') || ('a' -> 'z') + return (ch > 0x40 && ch < 0x5B) || (ch > 0x60 && ch < 0x7B) +} + +@private +is_numeric :: proc(ch: u8) -> bool { + // ('0' -> '9') + return (ch > 0x2F && ch < 0x3A) +} + +@private +is_alphanumeric :: proc(ch: u8) -> bool { + return is_alphabetic(ch) || is_numeric(ch) +} + +@private +is_valid_quoted_char :: proc(ch: u8) -> bool { + return is_alphabetic(ch) || is_numeric(ch) || ch == '+' || ch == '-' +} + +@private +parse_posix_tz_shortname :: proc(str: string) -> (out: string, idx: int, ok: bool) { + was_quoted := false + quoted := false + i := 0 + + for ; i < len(str); i += 1 { + ch := str[i] + + if !quoted && ch == '<' { + quoted = true + was_quoted = true + continue + } + + if quoted && ch == '>' { + quoted = false + break + } + + if !is_valid_quoted_char(ch) && ch != ',' { + return + } + + if !quoted && !is_alphabetic(ch) { + break + } + } + + // If we didn't see the trailing quote + if was_quoted && quoted { + return + } + + out_str: string + end_idx := i + if was_quoted { + end_idx += 1 + out_str = str[1:i] + } else { + out_str = str[:i] + } + + return out_str, end_idx, true +} + +@private +parse_posix_tz_offset :: proc(str: string) -> (out_sec: i64, idx: int, ok: bool) { + str := str + + sign : i64 = 1 + start_idx := 0 + i := 0 + if str[i] == '+' { + i += 1 + sign = 1 + start_idx = 1 + } else if str[i] == '-' { + i += 1 + sign = -1 + start_idx = 1 + } + + got_more_time := false + for ; i < len(str); i += 1 { + if is_numeric(str[i]) { + continue + } + + if str[i] == ':' { + got_more_time = true + break + } + + break + } + + ret_sec : i64 = 0 + hours := strconv.parse_int(str[start_idx:i], 10) or_return + if hours > 167 || hours < -167 { + return + } + ret_sec += i64(hours) * (60 * 60) + if !got_more_time { + return ret_sec * sign, i, true + } + + i += 1 + start_idx = i + + got_more_time = false + for ; i < len(str); i += 1 { + if is_numeric(str[i]) { + continue + } + + if str[i] == ':' { + got_more_time = true + break + } + + break + } + + mins_str := str[start_idx:i] + if len(mins_str) != 2 { + return + } + + mins := strconv.parse_int(mins_str, 10) or_return + if mins > 59 || mins < 0 { + return + } + ret_sec += i64(mins) * 60 + if !got_more_time { + return ret_sec * sign, i, true + } + + i += 1 + start_idx = i + + for ; i < len(str); i += 1 { + if !is_numeric(str[i]) { + break + } + } + secs_str := str[start_idx:i] + if len(secs_str) != 2 { + return + } + + secs := strconv.parse_int(secs_str, 10) or_return + if secs > 59 || secs < 0 { + return + } + ret_sec += i64(secs) + return ret_sec * sign, i, true +} + +@private +skim_digits :: proc(str: string) -> (out: string, idx: int, ok: bool) { + i := 0 + for ; i < len(str); i += 1 { + ch := str[i] + if ch == '.' || ch == '/' || ch == ',' { + break + } + + if !is_numeric(ch) { + return + } + } + + return str[:i], i, true +} + +TWO_AM :: 2 * 60 * 60 +parse_posix_rrule :: proc(str: string) -> (out: datetime.TZ_Transition_Date, idx: int, ok: bool) { + str := str + if len(str) < 2 { return } + + i := 0 + // No leap + if str[i] == 'J' { + i += 1 + + day_str, off := skim_digits(str[i:]) or_return + i += off + + day := strconv.parse_int(day_str, 10) or_return + if day < 1 || day > 365 { return } + + offset : i64 = TWO_AM + if len(str) != i && str[i] == '/' { + i += 1 + + offset, off = parse_posix_tz_offset(str[i:]) or_return + i += off + } + + if len(str) != i && str[i] == ',' { + i += 1 + } + + return datetime.TZ_Transition_Date{ + type = .No_Leap, + day = u16(day), + time = offset, + }, i, true + + // Leap + } else if is_numeric(str[i]) { + day_str, off := skim_digits(str[i:]) or_return + i += off + + day := strconv.parse_int(day_str, 10) or_return + if day < 0 || day > 365 { return } + + offset : i64 = TWO_AM + if len(str) != i && str[i] == '/' { + i += 1 + + offset, off = parse_posix_tz_offset(str[i:]) or_return + i += off + } + + if len(str) != i && str[i] == ',' { + i += 1 + } + + return datetime.TZ_Transition_Date{ + type = .Leap, + day = u16(day), + time = offset, + }, i, true + + } else if str[i] == 'M' { + i += 1 + + month_str, week_str, day_str: string + off := 0 + + month_str, off = skim_digits(str[i:]) or_return + i += off + 1 + + week_str, off = skim_digits(str[i:]) or_return + i += off + 1 + + day_str, off = skim_digits(str[i:]) or_return + i += off + + month := strconv.parse_int(month_str, 10) or_return + if month < 1 || month > 12 { return } + + week := strconv.parse_int(week_str, 10) or_return + if week < 1 || week > 5 { return } + + day := strconv.parse_int(day_str, 10) or_return + if day < 0 || day > 6 { return } + + offset : i64 = TWO_AM + if len(str) != i && str[i] == '/' { + i += 1 + + offset, off = parse_posix_tz_offset(str[i:]) or_return + i += off + } + + if len(str) != i && str[i] == ',' { + i += 1 + } + + return datetime.TZ_Transition_Date{ + type = .Month_Week_Day, + month = u8(month), + week = u8(week), + day = u16(day), + time = offset, + }, i, true + } + + return +} + +parse_posix_tz :: proc(posix_tz: string, allocator := context.allocator) -> (out: datetime.TZ_RRule, ok: bool) { + // TZ string contain at least 3 characters for the STD name, and 1 for the offset + if len(posix_tz) < 4 { + return + } + + str := posix_tz + + std_name, idx := parse_posix_tz_shortname(str) or_return + str = str[idx:] + + std_offset, idx2 := parse_posix_tz_offset(str) or_return + std_offset *= -1 + str = str[idx2:] + + std_name_str, err := strings.clone(std_name, allocator) + if err != nil { return } + defer if !ok { delete(std_name_str, allocator) } + + if len(str) == 0 { + return datetime.TZ_RRule{ + has_dst = false, + std_name = std_name_str, + std_offset = std_offset, + std_date = datetime.TZ_Transition_Date{ + type = .Leap, + day = 0, + time = TWO_AM, + }, + }, true + } + + dst_name: string + dst_offset := std_offset + (1 * 60 * 60) + if str[0] != ',' { + dst_name, idx = parse_posix_tz_shortname(str) or_return + str = str[idx:] + + if str[0] != ',' { + dst_offset, idx = parse_posix_tz_offset(str) or_return + dst_offset *= -1 + str = str[idx:] + } + } + if str[0] != ',' { return } + str = str[1:] + + std_td, idx3 := parse_posix_rrule(str) or_return + str = str[idx3:] + + dst_td, idx4 := parse_posix_rrule(str) or_return + str = str[idx4:] + + dst_name_str: string + dst_name_str, err = strings.clone(dst_name, allocator) + if err != nil { return } + + return datetime.TZ_RRule{ + has_dst = true, + + std_name = std_name_str, + std_offset = std_offset, + std_date = std_td, + + dst_name = dst_name_str, + dst_offset = dst_offset, + dst_date = dst_td, + }, true +} + +parse_tzif :: proc(_buffer: []u8, region_name: string, allocator := context.allocator) -> (out: ^datetime.TZ_Region, ok: bool) { + context.allocator = allocator + + buffer := _buffer + + // TZif is crufty. Skip the initial header. + + v1_hdr := slice.to_type(buffer, TZif_Header) or_return + if v1_hdr.magic != TZIF_MAGIC { + return + } + if v1_hdr.typecnt == 0 || v1_hdr.charcnt == 0 { + return + } + if v1_hdr.isutcnt != 0 && v1_hdr.isutcnt != v1_hdr.typecnt { + return + } + if v1_hdr.isstdcnt != 0 && v1_hdr.isstdcnt != v1_hdr.typecnt { + return + } + + // We don't bother supporting v1, it uses u32 timestamps + if v1_hdr.version == .V1 { + return + } + // We only support v2 and v3 + if v1_hdr.version != .V2 && v1_hdr.version != .V3 { + return + } + + // Skip the initial v1 block too. + first_block_size, _ := tzif_data_block_size(&v1_hdr, .V1) + if len(buffer) <= size_of(v1_hdr) + first_block_size { + return + } + buffer = buffer[size_of(v1_hdr)+first_block_size:] + + // Ok, time to parse real things + real_hdr := slice.to_type(buffer, TZif_Header) or_return + if real_hdr.magic != TZIF_MAGIC { + return + } + if real_hdr.typecnt == 0 || real_hdr.charcnt == 0 { + return + } + if real_hdr.isutcnt != 0 && real_hdr.isutcnt != real_hdr.typecnt { + return + } + if real_hdr.isstdcnt != 0 && real_hdr.isstdcnt != real_hdr.typecnt { + return + } + + // Grab the real data block + real_block_size, _ := tzif_data_block_size(&real_hdr, v1_hdr.version) + if len(buffer) <= size_of(real_hdr) + real_block_size { + return + } + buffer = buffer[size_of(real_hdr):] + + time_size := 8 + transition_times := slice.reinterpret([]i64be, buffer[:int(real_hdr.timecnt)*size_of(i64be)]) + for time in transition_times { + if time < BIG_BANG_ISH { + return + } + } + buffer = buffer[int(real_hdr.timecnt)*time_size:] + + transition_types := buffer[:int(real_hdr.timecnt)] + for type in transition_types { + if int(type) > int(real_hdr.typecnt - 1) { + return + } + } + buffer = buffer[int(real_hdr.timecnt):] + + local_time_types := slice.reinterpret([]Local_Time_Type, buffer[:int(real_hdr.typecnt)*size_of(Local_Time_Type)]) + for ltt in local_time_types { + // UT offset should be > -25 hours and < 26 hours + if int(ltt.utoff) < -89999 || int(ltt.utoff) > 93599 { + return + } + + if ltt.dst != .DST && ltt.dst != .Standard { + return + } + + if int(ltt.idx) > int(real_hdr.charcnt - 1) { + return + } + } + + buffer = buffer[int(real_hdr.typecnt) * size_of(Local_Time_Type):] + timezone_string_table := buffer[:real_hdr.charcnt] + buffer = buffer[real_hdr.charcnt:] + + leapsecond_records := slice.reinterpret([]Leapsecond_Record, buffer[:int(real_hdr.leapcnt)*size_of(Leapsecond_Record)]) + if len(leapsecond_records) > 0 { + if leapsecond_records[0].occur < 0 { + return + } + } + buffer = buffer[(int(real_hdr.leapcnt) * size_of(Leapsecond_Record)):] + + standard_wall_tags := buffer[:int(real_hdr.isstdcnt)] + for stdwall_tag, _ in standard_wall_tags { + if (stdwall_tag != 0 && stdwall_tag != 1) { + return + } + } + + buffer = buffer[int(real_hdr.isstdcnt):] + + ut_tags := buffer[:int(real_hdr.isutcnt)] + for ut_tag, _ in ut_tags { + if (ut_tag != 0 && ut_tag != 1) { + return + } + } + + buffer = buffer[int(real_hdr.isutcnt):] + + // Start of footer + if buffer[0] != '\n' { + return + } + buffer = buffer[1:] + + if buffer[0] == ':' { + return + } + + end_idx := 0 + for ch in buffer { + if ch == '\n' { + break + } + + if ch == 0 { + return + } + end_idx += 1 + } + footer_str := string(buffer[:end_idx]) + + // UTC is a special case, we don't need to alloc + if len(local_time_types) == 1 { + name := cstring(raw_data(timezone_string_table[local_time_types[0].idx:])) + if name != "UTC" { + return + } + + return nil, true + } + + ltt_names, err := make([dynamic]string, 0, len(local_time_types), allocator) + if err != nil { return } + defer if err != nil { + for name in ltt_names { + delete(name, allocator) + } + delete(ltt_names) + } + + for ltt in local_time_types { + name := cstring(raw_data(timezone_string_table[ltt.idx:])) + ltt_name: string + + ltt_name, err = strings.clone_from_cstring_bounded(name, len(timezone_string_table), allocator) + if err != nil { return } + + append(<t_names, ltt_name) + } + + records: []datetime.TZ_Record + records, err = make([]datetime.TZ_Record, len(transition_times), allocator) + if err != nil { return } + defer if err != nil { delete(records, allocator) } + + for trans_time, idx in transition_times { + trans_idx := transition_types[idx] + ltt := local_time_types[trans_idx] + + records[idx] = datetime.TZ_Record{ + time = i64(trans_time), + utc_offset = i64(ltt.utoff), + shortname = ltt_names[trans_idx], + dst = bool(ltt.dst), + } + } + + rrule, ok2 := parse_posix_tz(footer_str, allocator) + if !ok2 { return } + defer if err != nil { + delete(rrule.std_name, allocator) + delete(rrule.dst_name, allocator) + } + + region_name_out: string + region_name_out, err = strings.clone(region_name, allocator) + if err != nil { return } + defer if err != nil { delete(region_name_out, allocator) } + + region: ^datetime.TZ_Region + region, err = new_clone(datetime.TZ_Region{ + records = records, + shortnames = ltt_names[:], + name = region_name_out, + rrule = rrule, + }, allocator) + if err != nil { + return + } + + return region, true +} diff --git a/core/time/tsc_darwin.odin b/core/time/tsc_darwin.odin index 9e54ee8f7..3726cff49 100644 --- a/core/time/tsc_darwin.odin +++ b/core/time/tsc_darwin.odin @@ -1,21 +1,10 @@ -//+private -//+build darwin +#+private package time -import "core:c" +import "core:sys/unix" -foreign import libc "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/time/tsc_freebsd.odin b/core/time/tsc_freebsd.odin index f4d6ccc3a..dabcb69cb 100644 --- a/core/time/tsc_freebsd.odin +++ b/core/time/tsc_freebsd.odin @@ -1,5 +1,5 @@ -//+private -//+build freebsd +#+private +#+build freebsd package time import "core:c" diff --git a/core/time/tsc_linux.odin b/core/time/tsc_linux.odin index 77a79fe52..a83634414 100644 --- a/core/time/tsc_linux.odin +++ b/core/time/tsc_linux.odin @@ -1,5 +1,5 @@ -//+private -//+build linux +#+private +#+build linux package time import linux "core:sys/linux" 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 e5a4d5513..020ef94e4 100644 --- a/core/unicode/tools/generate_entity_table.odin +++ b/core/unicode/tools/generate_entity_table.odin @@ -2,7 +2,7 @@ package xml_example import "core:encoding/xml" import "core:os" -import "core:path" +import path "core:path/filepath" import "core:mem" import "core:strings" import "core:strconv" @@ -23,38 +23,38 @@ Entity :: struct { } generate_encoding_entity_table :: proc() { - using fmt - - filename := path.join(ODIN_ROOT, "tests", "core", "assets", "XML", "unicode.xml") + filename := path.join({ODIN_ROOT, "tests", "core", "assets", "XML", "unicode.xml"}) defer delete(filename) - generated_filename := path.join(ODIN_ROOT, "core", "encoding", "entity", "generated.odin") + generated_filename := path.join({ODIN_ROOT, "core", "encoding", "entity", "generated.odin"}) defer delete(generated_filename) - doc, err := xml.parse(filename, OPTIONS, Error_Handler) + doc, err := xml.load_from_file(filename, OPTIONS, Error_Handler) defer xml.destroy(doc) if err != .None { - printf("Load/Parse error: %v\n", err) + fmt.printf("Load/Parse error: %v\n", err) if err == .File_Error { - printf("\"%v\" not found. Did you run \"tests\\download_assets.py\"?", filename) + fmt.printf("\"%v\" not found. Did you run \"tests\\download_assets.py\"?", filename) } os.exit(1) } - printf("\"%v\" loaded and parsed.\n", filename) + fmt.printf("\"%v\" loaded and parsed.\n", filename) generated_buf: strings.Builder defer strings.builder_destroy(&generated_buf) w := strings.to_writer(&generated_buf) - charlist, charlist_ok := xml.find_child_by_ident(doc.root, "charlist") + charlist_id, charlist_ok := xml.find_child_by_ident(doc, 0, "charlist") if !charlist_ok { - eprintln("Could not locate top-level `` tag.") + fmt.eprintln("Could not locate top-level `` tag.") os.exit(1) } - printf("Found `` with %v children.\n", len(charlist.children)) + charlist := doc.elements[charlist_id] + + fmt.printf("Found `` with %v children.\n", len(charlist.value)) entity_map: map[string]Entity names: [dynamic]string @@ -65,20 +65,26 @@ generate_encoding_entity_table :: proc() { longest_name: string count := 0 - for char in charlist.children { + for char_id in charlist.value { + id := char_id.(xml.Element_ID) + char := doc.elements[id] + if char.ident != "character" { - eprintf("Expected ``, got `<%v>`\n", char.ident) + fmt.eprintf("Expected ``, got `<%v>`\n", char.ident) os.exit(1) } - if codepoint_string, ok := xml.find_attribute_val_by_key(char, "dec"); !ok { - eprintln("`` attribute not found.") + if codepoint_string, ok := xml.find_attribute_val_by_key(doc, id, "dec"); !ok { + fmt.eprintln("`` attribute not found.") os.exit(1) } else { codepoint := strconv.atoi(codepoint_string) - desc, desc_ok := xml.find_child_by_ident(char, "description") - description := desc.value if desc_ok else "" + desc, desc_ok := xml.find_child_by_ident(doc, id, "description") + description := "" + if len(doc.elements[desc].value) == 1 { + description = doc.elements[desc].value[0].(string) + } /* For us to be interested in this codepoint, it has to have at least one entity. @@ -86,9 +92,9 @@ generate_encoding_entity_table :: proc() { nth := 0 for { - character_entity := xml.find_child_by_ident(char, "entity", nth) or_break + character_entity := xml.find_child_by_ident(doc, id, "entity", nth) or_break nth += 1 - name := xml.find_attribute_val_by_key(character_entity, "id") or_continue + name := xml.find_attribute_val_by_key(doc, character_entity, "id") or_continue if len(name) == 0 { /* Invalid name. Skip. @@ -97,8 +103,8 @@ generate_encoding_entity_table :: proc() { } if name == "\"\"" { - printf("%#v\n", char) - printf("%#v\n", character_entity) + fmt.printf("%#v\n", char) + fmt.printf("%#v\n", character_entity) } if len(name) > max_name_length { longest_name = name } @@ -129,35 +135,33 @@ generate_encoding_entity_table :: proc() { */ slice.sort(names[:]) - printf("Found %v unique `&name;` -> rune mappings.\n", count) - printf("Shortest name: %v (%v)\n", shortest_name, min_name_length) - printf("Longest name: %v (%v)\n", longest_name, max_name_length) - - // println(rune_to_string(1234)) + fmt.printf("Found %v unique `&name;` -> rune mappings.\n", count) + fmt.printf("Shortest name: %v (%v)\n", shortest_name, min_name_length) + fmt.printf("Longest name: %v (%v)\n", longest_name, max_name_length) /* Generate table. */ - wprintln(w, "package unicode_entity") - wprintln(w, "") - wprintln(w, GENERATED) - wprintln(w, "") - wprintf (w, TABLE_FILE_PROLOG) - wprintln(w, "") + fmt.wprintln(w, "package encoding_unicode_entity") + fmt.wprintln(w, "") + fmt.wprintln(w, GENERATED) + fmt.wprintln(w, "") + fmt.wprintf (w, TABLE_FILE_PROLOG) + fmt.wprintln(w, "") - wprintf (w, "// `&%v;`\n", shortest_name) - wprintf (w, "XML_NAME_TO_RUNE_MIN_LENGTH :: %v\n", min_name_length) - wprintf (w, "// `&%v;`\n", longest_name) - wprintf (w, "XML_NAME_TO_RUNE_MAX_LENGTH :: %v\n", max_name_length) - wprintln(w, "") + fmt.wprintf (w, "// `&%v;`\n", shortest_name) + fmt.wprintf (w, "XML_NAME_TO_RUNE_MIN_LENGTH :: %v\n", min_name_length) + fmt.wprintf (w, "// `&%v;`\n", longest_name) + fmt.wprintf (w, "XML_NAME_TO_RUNE_MAX_LENGTH :: %v\n", max_name_length) + fmt.wprintln(w, "") - wprintln(w, + fmt.wprintln(w, ` /* Input: entity_name - a string, like "copy" that describes a user-encoded Unicode entity as used in XML. - Output: + Returns: "decoded" - The decoded rune if found by name, or -1 otherwise. "ok" - true if found, false if not. @@ -181,38 +185,41 @@ named_xml_entity_to_rune :: proc(name: string) -> (decoded: rune, ok: bool) { for v in names { if rune(v[0]) != prefix { if should_close { - wprintln(w, "\t\t}\n") + fmt.wprintln(w, "\t\t}\n") } prefix = rune(v[0]) - wprintf (w, "\tcase '%v':\n", prefix) - wprintln(w, "\t\tswitch name {") + fmt.wprintf (w, "\tcase '%v':\n", prefix) + fmt.wprintln(w, "\t\tswitch name {") } e := entity_map[v] - wprintf(w, "\t\t\tcase \"%v\": \n", e.name) - wprintf(w, "\t\t\t\t// %v\n", e.description) - wprintf(w, "\t\t\t\treturn %v, true\n", rune_to_string(e.codepoint)) + 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\treturn %v, true\n", rune_to_string(e.codepoint)) should_close = true } - wprintln(w, "\t\t}") - wprintln(w, "\t}") - wprintln(w, "\treturn -1, false") - wprintln(w, "}\n") - wprintln(w, GENERATED) + fmt.wprintln(w, "\t\t}") + fmt.wprintln(w, "\t}") + fmt.wprintln(w, "\treturn -1, false") + fmt.wprintln(w, "}\n") + fmt.wprintln(w, GENERATED) - println() - println(strings.to_string(generated_buf)) - println() + fmt.println() + fmt.println(strings.to_string(generated_buf)) + fmt.println() written := os.write_entire_file(generated_filename, transmute([]byte)strings.to_string(generated_buf)) if written { - fmt.printf("Successfully written generated \"%v\".", generated_filename) + fmt.printf("Successfully written generated \"%v\".\n", generated_filename) } else { - fmt.printf("Failed to write generated \"%v\".", generated_filename) + fmt.printf("Failed to write generated \"%v\".\n", generated_filename) } delete(entity_map) @@ -227,7 +234,7 @@ GENERATED :: `/* */` TABLE_FILE_PROLOG :: `/* - This file is generated from "https://www.w3.org/2003/entities/2007xml/unicode.xml". + This file is generated from "https://github.com/w3c/xml-entities/blob/gh-pages/unicode.xml". UPDATE: - Ensure the XML file was downloaded using "tests\core\download_assets.py". @@ -235,15 +242,21 @@ TABLE_FILE_PROLOG :: `/* Odin unicode generated tables: https://github.com/odin-lang/Odin/tree/master/core/encoding/entity - Copyright © 2021 World Wide Web Consortium, (Massachusetts Institute of Technology, - European Research Consortium for Informatics and Mathematics, Keio University, Beihang). + Copyright David Carlisle 1999-2023 - All Rights Reserved. + Use and distribution of this code are permitted under the terms of the + W3C Software Notice and License. + http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231.html - This work is distributed under the W3C® Software License [1] in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - [1] http://www.w3.org/Consortium/Legal/copyright-software + + This file is a collection of information about how to map + Unicode entities to LaTeX, and various SGML/XML entity + sets (ISO and MathML/HTML). A Unicode character may be mapped + to several entities. + + Originally designed by Sebastian Rahtz in conjunction with + Barbara Beeton for the STIX project See also: LICENSE_table.md */ @@ -265,8 +278,6 @@ is_dotted_name :: proc(name: string) -> (dotted: bool) { } main :: proc() { - using fmt - track: mem.Tracking_Allocator mem.tracking_allocator_init(&track, context.allocator) context.allocator = mem.tracking_allocator(&track) @@ -274,10 +285,10 @@ main :: proc() { generate_encoding_entity_table() if len(track.allocation_map) > 0 { - println() + fmt.println() for _, v in track.allocation_map { - printf("%v Leaked %v bytes.\n", v.location, v.size) + fmt.printf("%v Leaked %v bytes.\n", v.location, v.size) } } - println("Done and cleaned up!") + fmt.println("Done and cleaned up!") } \ No newline at end of file diff --git a/core/unicode/utf16/utf16.odin b/core/unicode/utf16/utf16.odin index 6bdd6558a..e2bcf7f68 100644 --- a/core/unicode/utf16/utf16.odin +++ b/core/unicode/utf16/utf16.odin @@ -106,6 +106,18 @@ decode :: proc(d: []rune, s: []u16) -> (n: int) { return } +rune_count :: proc(s: []u16) -> (n: int) { + for i := 0; i < len(s); i += 1 { + c := s[i] + if _surr1 <= c && c < _surr2 && i+1 < len(s) && + _surr2 <= s[i+1] && s[i+1] < _surr3 { + i += 1 + } + n += 1 + } + return +} + decode_to_utf8 :: proc(d: []byte, s: []u16) -> (n: int) { for i := 0; i < len(s); i += 1 { @@ -127,4 +139,4 @@ decode_to_utf8 :: proc(d: []byte, s: []u16) -> (n: int) { n += copy(d[n:], b[:w]) } return -} \ No newline at end of file +} 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/core/unicode/utf8/utf8.odin b/core/unicode/utf8/utf8.odin index 3642b8078..67f8a8be9 100644 --- a/core/unicode/utf8/utf8.odin +++ b/core/unicode/utf8/utf8.odin @@ -45,18 +45,18 @@ accept_ranges := [5]Accept_Range{ {0x80, 0x8f}, } -accept_sizes := [256]u8{ - 0x00..=0x7f = 0xf0, - 0x80..=0xc1 = 0xf1, - 0xc2..=0xdf = 0x02, - 0xe0 = 0x13, - 0xe1..=0xec = 0x03, - 0xed = 0x23, - 0xee..=0xef = 0x03, - 0xf0 = 0x34, - 0xf1..=0xf3 = 0x04, - 0xf4 = 0x44, - 0xf5..=0xff = 0xf1, +accept_sizes := [256]u8{ + 0x00..=0x7f = 0xf0, // ascii, size 1 + 0x80..=0xc1 = 0xf1, // invalid, size 1 + 0xc2..=0xdf = 0x02, // accept 1, size 2 + 0xe0 = 0x13, // accept 1, size 3 + 0xe1..=0xec = 0x03, // accept 0, size 3 + 0xed = 0x23, // accept 2, size 3 + 0xee..=0xef = 0x03, // accept 0, size 3 + 0xf0 = 0x34, // accept 3, size 4 + 0xf1..=0xf3 = 0x04, // accept 0, size 4 + 0xf4 = 0x44, // accept 4, size 4 + 0xf5..=0xff = 0xf1, // ascii, size 1 } encode_rune :: proc "contextless" (c: rune) -> ([4]u8, int) { @@ -385,7 +385,7 @@ full_rune_in_bytes :: proc "contextless" (b: []byte) -> bool { if n == 0 { return false } - x := _first[b[0]] + x := accept_sizes[b[0]] if n >= int(x & 7) { return true } @@ -403,18 +403,3 @@ full_rune_in_bytes :: proc "contextless" (b: []byte) -> bool { full_rune_in_string :: proc "contextless" (s: string) -> bool { return full_rune_in_bytes(transmute([]byte)s) } - - -_first := [256]u8{ - 0x00..=0x7f = 0xf0, // ascii, size 1 - 0x80..=0xc1 = 0xf1, // invalid, size 1 - 0xc2..=0xdf = 0x02, // accept 1, size 2 - 0xe0 = 0x13, // accept 1, size 3 - 0xe1..=0xec = 0x03, // accept 0, size 3 - 0xed = 0x23, // accept 2, size 3 - 0xee..=0xef = 0x03, // accept 0, size 3 - 0xf0 = 0x34, // accept 3, size 4 - 0xf1..=0xf3 = 0x04, // accept 0, size 4 - 0xf4 = 0x44, // accept 4, size 4 - 0xf5..=0xff = 0xf1, // ascii, size 1 -} diff --git a/core/unicode/utf8/utf8string/string.odin b/core/unicode/utf8/utf8string/string.odin index 86267defb..4b0fe7241 100644 --- a/core/unicode/utf8/utf8string/string.odin +++ b/core/unicode/utf8/utf8string/string.odin @@ -1,8 +1,8 @@ package utf8string import "core:unicode/utf8" -import "core:runtime" -import "core:builtin" +import "base:runtime" +import "base:builtin" String :: struct { contents: string, @@ -66,7 +66,7 @@ at :: proc(s: ^String, i: int, loc := #caller_location) -> (r: rune) { return case s.rune_count-1: - r, s.width = utf8.decode_rune_in_string(s.contents) + r, s.width = utf8.decode_last_rune(s.contents) s.rune_pos = i s.byte_pos = _len(s.contents) - s.width return diff --git a/default.nix b/default.nix deleted file mode 100644 index 64d20f674..000000000 --- a/default.nix +++ /dev/null @@ -1,27 +0,0 @@ -{ pkgs ? import { } }: -let - odin-unwrapped = pkgs.llvmPackages_11.stdenv.mkDerivation (rec { - name = "odin-unwrapped"; - src = ./.; - dontConfigure = true; - nativeBuildInputs = [ pkgs.git ]; - buildPhase = '' - make debug SHELL=${pkgs.llvmPackages_11.stdenv.shell} - ''; - installPhase = '' - mkdir -p $out/bin - cp odin $out/bin/odin - cp -r core $out/bin/core - ''; - }); - path = builtins.map (path: path + "/bin") (with pkgs.llvmPackages_11; [ - bintools - llvm - clang - lld - ]); -in -pkgs.writeScriptBin "odin" '' - #!${pkgs.llvmPackages_11.stdenv.shell} - PATH="${(builtins.concatStringsSep ":" path)}" exec ${odin-unwrapped}/bin/odin $@ -'' diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000..90076df23 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,9 @@ +# Examples + +The `example` directory contains two packages: + +A [demo](demo) illustrating the basics of Odin. + +It further contains [all](all), which imports all [core](/core) and [vendor](/vendor) packages so we can conveniently run `odin check` on everything at once. + +For additional example code, see the [examples](https://github.com/odin-lang/examples) repository. diff --git a/examples/all/README.md b/examples/all/README.md new file mode 100644 index 000000000..72398540f --- /dev/null +++ b/examples/all/README.md @@ -0,0 +1,3 @@ +# `examples/all` for Documentation + +**NOTE:** This exists purely for the documentation generator located at \ No newline at end of file diff --git a/examples/all/all_experimental.odin b/examples/all/all_experimental.odin index cd60c269c..f00098abd 100644 --- a/examples/all/all_experimental.odin +++ b/examples/all/all_experimental.odin @@ -1,8 +1,2 @@ -//+build windows +#+build windows package all - -import c_tokenizer "core:c/frontend/tokenizer" -import c_preprocessor "core:c/frontend/preprocessor" - -_ :: c_tokenizer -_ :: c_preprocessor diff --git a/examples/all/all_linux.odin b/examples/all/all_linux.odin index 18bba951c..ca51d6562 100644 --- a/examples/all/all_linux.odin +++ b/examples/all/all_linux.odin @@ -1,4 +1,4 @@ -//+build linux +#+build linux package all import linux "core:sys/linux" diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index c6b727e42..4a8a198d3 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -14,61 +14,67 @@ import shoco "core:compress/shoco" import gzip "core:compress/gzip" import zlib "core:compress/zlib" +import avl "core:container/avl" import bit_array "core:container/bit_array" import priority_queue "core:container/priority_queue" 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 blake "core:crypto/blake" +import aead "core:crypto/aead" +import aes "core:crypto/aes" import blake2b "core:crypto/blake2b" import blake2s "core:crypto/blake2s" import chacha20 "core:crypto/chacha20" import chacha20poly1305 "core:crypto/chacha20poly1305" -import gost "core:crypto/gost" -import groestl "core:crypto/groestl" -import haval "core:crypto/haval" -import jh "core:crypto/jh" -import keccak "core:crypto/keccak" -import md2 "core:crypto/md2" -import md4 "core:crypto/md4" -import md5 "core:crypto/md5" +import crypto_hash "core:crypto/hash" +import ed25519 "core:crypto/ed25519" +import hkdf "core:crypto/hkdf" +import hmac "core:crypto/hmac" +import kmac "core:crypto/kmac" +import keccak "core:crypto/legacy/keccak" +import md5 "core:crypto/legacy/md5" +import sha1 "core:crypto/legacy/sha1" +import pbkdf2 "core:crypto/pbkdf2" import poly1305 "core:crypto/poly1305" -import ripemd "core:crypto/ripemd" -import sha1 "core:crypto/sha1" +import ristretto255 "core:crypto/ristretto255" import sha2 "core:crypto/sha2" import sha3 "core:crypto/sha3" import shake "core:crypto/shake" import sm3 "core:crypto/sm3" -import streebog "core:crypto/streebog" -import tiger "core:crypto/tiger" -import tiger2 "core:crypto/tiger2" -import crypto_util "core:crypto/util" -import whirlpool "core:crypto/whirlpool" +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 ini "core:encoding/ini" import json "core:encoding/json" import varint "core:encoding/varint" import xml "core:encoding/xml" -import endian "core:encoding/endian" +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" @@ -90,13 +96,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" @@ -106,9 +111,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 "core:runtime" +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" @@ -116,14 +124,21 @@ 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 regex "core:text/regex" +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 timezone "core:time/timezone" +import flags "core:flags" +import orca "core:sys/orca" import sysinfo "core:sys/info" import unicode "core:unicode" @@ -142,55 +157,59 @@ _ :: compress _ :: shoco _ :: gzip _ :: zlib +_ :: avl _ :: bit_array _ :: priority_queue _ :: queue _ :: small_array _ :: lru _ :: list +_ :: rbtree _ :: topological_sort _ :: crypto -_ :: blake +_ :: crypto_hash +_ :: aead +_ :: aes _ :: blake2b _ :: blake2s _ :: chacha20 _ :: chacha20poly1305 -_ :: gost -_ :: groestl -_ :: haval -_ :: jh +_ :: ed25519 +_ :: hmac +_ :: hkdf +_ :: kmac _ :: keccak -_ :: md2 -_ :: md4 _ :: md5 +_ :: pbkdf2 _ :: poly1305 -_ :: ripemd +_ :: ristretto255 _ :: sha1 _ :: sha2 _ :: sha3 _ :: shake _ :: sm3 -_ :: streebog -_ :: tiger -_ :: tiger2 -_ :: crypto_util -_ :: whirlpool +_ :: tuplehash _ :: x25519 _ :: pe +_ :: trace _ :: dynlib _ :: net +_ :: ansi _ :: base32 _ :: base64 _ :: csv _ :: hxa +_ :: ini _ :: json _ :: varint _ :: xml _ :: endian +_ :: cbor _ :: fmt _ :: hash _ :: xxhash _ :: image +_ :: bmp _ :: netpbm _ :: png _ :: qoi @@ -209,20 +228,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,12 +253,19 @@ _ :: testing _ :: scanner _ :: i18n _ :: match +_ :: regex _ :: table _ :: edit _ :: thread _ :: time +_ :: datetime +_ :: timezone +_ :: flags +_ :: orca _ :: sysinfo _ :: unicode +_ :: uuid +_ :: uuid_legacy _ :: utf8 _ :: utf8string _ :: utf16 diff --git a/examples/all/all_posix.odin b/examples/all/all_posix.odin new file mode 100644 index 000000000..61b33a5c6 --- /dev/null +++ b/examples/all/all_posix.odin @@ -0,0 +1,8 @@ +#+build darwin, openbsd, freebsd, netbsd +package all + +import posix "core:sys/posix" +import kqueue "core:sys/kqueue" + +_ :: posix +_ :: kqueue diff --git a/examples/all/all_vendor.odin b/examples/all/all_vendor.odin index 6864a7be2..b224a3bbe 100644 --- a/examples/all/all_vendor.odin +++ b/examples/all/all_vendor.odin @@ -1,23 +1,5 @@ package all -import botan_bindings "vendor:botan/bindings" -import botan_blake2b "vendor:botan/blake2b" -import gost "vendor:botan/gost" -import keccak "vendor:botan/keccak" -import md4 "vendor:botan/md4" -import md5 "vendor:botan/md5" -import ripemd "vendor:botan/ripemd" -import sha1 "vendor:botan/sha1" -import sha2 "vendor:botan/sha2" -import sha3 "vendor:botan/sha3" -import shake "vendor:botan/shake" -import siphash "vendor:botan/siphash" -import skein512 "vendor:botan/skein512" -import sm3 "vendor:botan/sm3" -import streebog "vendor:botan/streebog" -import tiger "vendor:botan/tiger" -import whirlpool "vendor:botan/whirlpool" - import cgltf "vendor:cgltf" // import commonmark "vendor:commonmark" import ENet "vendor:ENet" @@ -46,24 +28,7 @@ import nvg "vendor:nanovg" import nvg_gl "vendor:nanovg/gl" import fontstash "vendor:fontstash" -_ :: botan_bindings -_ :: botan_blake2b -_ :: gost -_ :: keccak -_ :: md4 -_ :: md5 -_ :: ripemd -_ :: sha1 -_ :: sha2 -_ :: sha3 -_ :: shake -_ :: siphash -_ :: skein512 -_ :: sm3 -_ :: streebog -_ :: tiger -_ :: whirlpool - +import xlib "vendor:x11/xlib" _ :: cgltf // _ :: commonmark @@ -90,4 +55,52 @@ _ :: lua_5_4 _ :: nvg _ :: nvg_gl -_ :: fontstash \ No newline at end of file +_ :: fontstash + +_ :: xlib + + +// NOTE: needed for doc generator + +import NS "core:sys/darwin/Foundation" +import CF "core:sys/darwin/CoreFoundation" +import SEC "core:sys/darwin/Security" +import MTL "vendor:darwin/Metal" +import MTK "vendor:darwin/MetalKit" +import CA "vendor:darwin/QuartzCore" + +_ :: NS +_ :: CF +_ :: SEC +_ :: MTL +_ :: MTK +_ :: CA + + +import DXC "vendor:directx/dxc" +import D3D11 "vendor:directx/d3d11" +import D3D12 "vendor:directx/d3d12" +import DXGI "vendor:directx/dxgi" + +_ :: DXC +_ :: D3D11 +_ :: D3D12 +_ :: DXGI + + +import cm "vendor:commonmark" +_ :: cm + + +import stb_easy_font "vendor:stb/easy_font" +import stbi "vendor:stb/image" +import stbrp "vendor:stb/rect_pack" +import stbtt "vendor:stb/truetype" +import stb_vorbis "vendor:stb/vorbis" + +_ :: stb_easy_font +_ :: stbi +_ :: stbrp +_ :: stbtt +_ :: stb_vorbis + diff --git a/examples/all/all_vendor_cmark.odin b/examples/all/all_vendor_cmark.odin deleted file mode 100644 index 5faf47efc..000000000 --- a/examples/all/all_vendor_cmark.odin +++ /dev/null @@ -1,5 +0,0 @@ -//+build windows, linux -package all - -import cm "vendor:commonmark" -_ :: cm diff --git a/examples/all/all_vendor_darwin.odin b/examples/all/all_vendor_darwin.odin deleted file mode 100644 index 9aa41396c..000000000 --- a/examples/all/all_vendor_darwin.odin +++ /dev/null @@ -1,12 +0,0 @@ -//+build darwin -package all - -import NS "vendor:darwin/Foundation" -import MTL "vendor:darwin/Metal" -import MTK "vendor:darwin/MetalKit" -import CA "vendor:darwin/QuartzCore" - -_ :: NS -_ :: MTL -_ :: MTK -_ :: CA diff --git a/examples/all/all_vendor_directx.odin b/examples/all/all_vendor_directx.odin deleted file mode 100644 index 2f10d92f8..000000000 --- a/examples/all/all_vendor_directx.odin +++ /dev/null @@ -1,10 +0,0 @@ -//+build windows -package all - -import D3D11 "vendor:directx/d3d11" -import D3D12 "vendor:directx/d3d12" -import DXGI "vendor:directx/dxgi" - -_ :: D3D11 -_ :: D3D12 -_ :: DXGI diff --git a/examples/all/all_vendor_stl.odin b/examples/all/all_vendor_stl.odin deleted file mode 100644 index 9faf53c63..000000000 --- a/examples/all/all_vendor_stl.odin +++ /dev/null @@ -1,15 +0,0 @@ -//+build windows, linux -package all - -import stb_easy_font "vendor:stb/easy_font" -import stbi "vendor:stb/image" -import stbrp "vendor:stb/rect_pack" -import stbtt "vendor:stb/truetype" -import stb_vorbis "vendor:stb/vorbis" - -_ :: stb_easy_font -_ :: stbi -_ :: stbrp -_ :: stbtt -_ :: stb_vorbis - diff --git a/examples/all/all_vendor_windows.odin b/examples/all/all_vendor_windows.odin new file mode 100644 index 000000000..50529afc9 --- /dev/null +++ b/examples/all/all_vendor_windows.odin @@ -0,0 +1,7 @@ +package all + +import wgpu "vendor:wgpu" +import b2 "vendor:box2d" + +_ :: wgpu +_ :: b2 \ No newline at end of file diff --git a/examples/all/all_vendor_zlib.odin b/examples/all/all_vendor_zlib.odin deleted file mode 100644 index 6004bed50..000000000 --- a/examples/all/all_vendor_zlib.odin +++ /dev/null @@ -1,5 +0,0 @@ -//+build windows, linux -package all - -import zlib "vendor:zlib" -_ :: zlib diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin index 00dd8a171..82b047103 100644 --- a/examples/demo/demo.odin +++ b/examples/demo/demo.odin @@ -1,4 +1,5 @@ -//+vet !using-stmt !using-param +#+vet !using-stmt !using-param +#+feature dynamic-literals package main import "core:fmt" @@ -7,8 +8,8 @@ import "core:os" import "core:thread" import "core:time" import "core:reflect" -import "core:runtime" -import "core:intrinsics" +import "base:runtime" +import "base:intrinsics" import "core:math/big" /* @@ -44,7 +45,13 @@ the_basics :: proc() { fmt.println("\n# the basics") { // The Basics - fmt.println("Hellope") + + // 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 if len(os.args) > 2 { + fmt.printf("%v, %v! from %v.\n", os.args[1], os.args[2], os.args[0]) + } // Lexical elements and literals // A comment @@ -353,7 +360,7 @@ control_flow :: proc() { if false { f, err := os.open("my_file.txt") - if err != os.ERROR_NONE { + if err != nil { // handle error } defer os.close(f) @@ -535,7 +542,7 @@ struct_type :: proc() { p.x = 1335 fmt.println(v) - // We could write p^.x, however, it is to nice abstract the ability + // We could write p^.x, however, it is nice to abstract the ability // to not explicitly dereference the pointer. This is very useful when // refactoring code to use a pointer rather than a value, and vice versa. } @@ -847,7 +854,7 @@ implicit_context_system :: proc() { what_a_fool_believes :: proc() { c := context // this `context` is the same as the parent procedure that it was called from // From this example, context.user_index == 123 - // An context.allocator is assigned to the return value of `my_custom_allocator()` + // A context.allocator is assigned to the return value of `my_custom_allocator()` assert(context.user_index == 123) // The memory management procedure use the `context.allocator` by @@ -900,7 +907,7 @@ parametric_polymorphism :: proc() { // This is how `new` is implemented alloc_type :: proc($T: typeid) -> ^T { - t := cast(^T)alloc(size_of(T), align_of(T)) + t := cast(^T)mem.alloc(size_of(T), align_of(T)) t^ = T{} // Use default initialization value return t } @@ -1134,6 +1141,7 @@ prefix_table := [?]string{ print_mutex := b64(false) +@(disabled=!thread.IS_SUPPORTED) threading_example :: proc() { fmt.println("\n# threading_example") @@ -1508,7 +1516,7 @@ quaternions :: proc() { { // Quaternion operations q := 1 + 2i + 3j + 4k - r := quaternion(5, 6, 7, 8) + r := quaternion(real=5, imag=6, jmag=7, kmag=8) t := q * r fmt.printf("(%v) * (%v) = %v\n", q, r, t) v := q / r @@ -1521,8 +1529,10 @@ quaternions :: proc() { { // The quaternion types q128: quaternion128 // 4xf32 q256: quaternion256 // 4xf64 - q128 = quaternion(1, 0, 0, 0) - q256 = 1 // quaternion(1, 0, 0, 0) + q128 = quaternion(w=1, x=0, y=0, z=0) + q256 = 1 // quaternion(x=0, y=0, z=0, w=1) + + // NOTE: The internal memory layout of a quaternion is xyzw } { // Built-in procedures q := 1 + 2i + 3j + 4k @@ -2043,22 +2053,6 @@ explicit_context_definition :: proc "c" () { dummy_procedure() } -relative_data_types :: proc() { - fmt.println("\n#relative data types") - - x: int = 123 - ptr: #relative(i16) ^int - ptr = &x - fmt.println(ptr^) - - arr := [3]int{1, 2, 3} - multi_ptr: #relative(i16) [^]int - multi_ptr = &arr[0] - fmt.println(multi_ptr) - fmt.println(multi_ptr[:3]) - fmt.println(multi_ptr[1]) -} - or_else_operator :: proc() { fmt.println("\n#'or_else'") { @@ -2142,7 +2136,7 @@ or_return_operator :: proc() { return .None } foo_2 :: proc() -> (n: int, err: Error) { - // It is more common that your procedure turns multiple values + // It is more common that your procedure returns multiple values // If 'or_return' is used within a procedure multiple parameters (2+), // then all the parameters must be named so that the remaining parameters // so that a bare 'return' statement can be used @@ -2267,7 +2261,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() @@ -2377,7 +2371,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 @@ -2430,7 +2424,7 @@ matrix_type :: proc() { // component-wise multiplication // since a * b would be a standard matrix multiplication - c6 := hadamard_product(a, b) + c6 := intrinsics.hadamard_product(a, b) fmt.println("a + b", c0) @@ -2472,7 +2466,7 @@ matrix_type :: proc() { 5, 0, 6, 0, 0, 7, 0, 8, } - fmt.println("b4", matrix_flatten(b4)) + fmt.println("b4", intrinsics.matrix_flatten(b4)) } { // Casting non-square matrices @@ -2511,7 +2505,7 @@ matrix_type :: proc() { // This is because matrices are stored as values (not a reference type), and thus operations on them will // be stored on the stack. Restricting the maximum element count minimizing the possibility of stack overflows. - // Built-in Procedures (Compiler Level) + // 'intrinsics' Procedures (Compiler Level) // transpose(m) // transposes a matrix // outer_product(a, b) @@ -2532,16 +2526,60 @@ matrix_type :: proc() { // conj(x) // conjugates the elements of a matrix for complex element types only - // Built-in Procedures (Runtime Level) (all square matrix procedures) + // Procedures in "core:math/linalg" and related (Runtime Level) (all square matrix procedures) // determinant(m) // adjugate(m) // inverse(m) // inverse_transpose(m) // hermitian_adjoint(m) - // matrix_trace(m) + // trace(m) // 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 @@ -2581,11 +2619,11 @@ main :: proc() { constant_literal_expressions() union_maybe() explicit_context_definition() - relative_data_types() or_else_operator() or_return_operator() or_break_and_or_continue_operators() arbitrary_precision_mathematics() matrix_type() + bit_field_type() } -} \ No newline at end of file +} diff --git a/examples/demo/demo.rc b/examples/demo/demo.rc new file mode 100644 index 000000000..ef3ec2fec --- /dev/null +++ b/examples/demo/demo.rc @@ -0,0 +1,75 @@ + +#define Filename "demo.exe" +#define FileDescription "Odin demo project." +#define ProductName "Odin Programming Language Demo" + +#include "winres.h" + +LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT +#pragma code_page(65001) + +#define IDI_ICON1 101 + +#define Q(x) #x +#define QUOTE(x) Q(x) +#define FMTVER(x,y,z,w) QUOTE(x.y.z.w) + +#ifndef V1 +#define V1 1 +#endif +#ifndef V2 +#define V2 0 +#endif +#ifndef V3 +#define V3 0 +#endif +#ifndef V4 +#define V4 0 +#endif +#ifndef ODIN_VERSION +#define ODIN_VERSION FMTVER(V1,V2,V3,V4) +#endif +#ifndef GIT_SHA +#define GIT_SHA _ +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION V1,V2,V3,V4 + PRODUCTVERSION V1,V2,V3,V4 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "0409FDE9" + BEGIN + VALUE "CompanyName", "https://odin-lang.org/" + VALUE "FileDescription", "Odin Demo" + VALUE "FileVersion", FMTVER(V1,V2,V3,V4) + VALUE "InternalName", "demo.exe" + VALUE "LegalCopyright", "Copyright (c) 2016-2024 Ginger Bill. All rights reserved." + VALUE "OriginalFilename", "demo.exe" + VALUE "ProductName", "Odin Programming Language Demo" + VALUE "ProductVersion", QUOTE(ODIN_VERSION) + VALUE "Comments", QUOTE(ODIN_VERSION) + // PrivateBuild + // SpecialBuild + // custom values + VALUE "GitSha", QUOTE(GIT_SHA) + END + END + BLOCK "VarFileInfo" + BEGIN + //0xFDE9=65001=CP_UTF8 + VALUE "Translation", 0x0409, 0xFDE9 + END +END + +IDI_ICON1 ICON "..\\..\\misc\\sourcefile.ico" diff --git a/misc/emblem.ico b/misc/emblem.ico new file mode 100644 index 000000000..f5644b417 Binary files /dev/null and b/misc/emblem.ico differ diff --git a/misc/featuregen/README.md b/misc/featuregen/README.md new file mode 100644 index 000000000..82d95a2b6 --- /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_microarch.cpp`: + +- `target_microarch_list`: an array of strings indexed by the architecture, each string is a comma-seperated list of microarchitectures available on that architecture +- `target_features_list`: an array of strings indexed by the architecture, each string is a comma-seperated list of target features available on that architecture +- `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_featuregen.sh` +1. `python3 featuregen.py` +1. Copy the output into `src/build_settings.cpp` diff --git a/misc/featuregen/build_featuregen.sh b/misc/featuregen/build_featuregen.sh new file mode 100755 index 000000000..d68f29925 --- /dev/null +++ b/misc/featuregen/build_featuregen.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -ex + +$(llvm-config --bindir)/clang++ $(llvm-config --cxxflags --ldflags --libs) featuregen.cpp -o featuregen diff --git a/misc/featuregen/featuregen.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..ecc47f70c --- /dev/null +++ b/misc/featuregen/featuregen.py @@ -0,0 +1,119 @@ +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", [], []), + ("riscv64", "linux_riscv64", "riscv64-linux-gnu", [], []), +]; + +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}:") + cpus_str = ','.join(cpus) + print(f'\tstr_lit("{cpus_str}"),') +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}:") + features_str = ','.join(features) + print(f'\tstr_lit("{features_str}"),') +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/get-date.c b/misc/get-date.c new file mode 100644 index 000000000..bf5b32738 --- /dev/null +++ b/misc/get-date.c @@ -0,0 +1,13 @@ +/* + Prints the current date as YYYYMMDD + + e.g. 2024-12-25 +*/ +#include +#include + +int main(int arg_count, char const **arg_ptr) { + time_t t = time(NULL); + struct tm* now = localtime(&t); + printf("%04d%02d%02d", now->tm_year + 1900, now->tm_mon + 1, now->tm_mday); +} \ No newline at end of file diff --git a/misc/odin.manifest b/misc/odin.manifest new file mode 100644 index 000000000..d42403b22 --- /dev/null +++ b/misc/odin.manifest @@ -0,0 +1,8 @@ + + + + + UTF-8 + + + diff --git a/misc/odin.rc b/misc/odin.rc new file mode 100644 index 000000000..70d395030 --- /dev/null +++ b/misc/odin.rc @@ -0,0 +1,79 @@ + +#include "winres.h" + +// https://learn.microsoft.com/en-us/windows/win32/menurc/stringfileinfo-block + +LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT +#pragma code_page(65001) // CP_UTF8 + +#define IDI_ICON1 101 +#define IDI_ICON2 102 + +#ifndef V1 +#define V1 1 +#endif +#ifndef V2 +#define V2 0 +#endif +#ifndef V3 +#define V3 0 +#endif +#ifndef V4 +#define V4 0 +#endif +#ifndef VF +#define VF "1.0.0.0" +#endif +#ifndef VP +#define VP "1.0.0.0" +#endif +#ifndef GIT_SHA +#define GIT_SHA 0 +#endif +#ifndef NIGHTLY +#define NIGHTLY 0 +#endif + +#define Q(x) #x +#define QUOTE(x) Q(x) + +VS_VERSION_INFO VERSIONINFO + FILEVERSION V1,V2,V3,V4 + PRODUCTVERSION V1,V2,V3,V4 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "0409FDE9" + BEGIN + VALUE "CompanyName", "https://odin-lang.org/" + VALUE "FileDescription", "Odin" // note this is shown in the task manager + VALUE "FileVersion", QUOTE(VF) + VALUE "InternalName", "odin.exe" + VALUE "LegalCopyright", "Copyright (c) 2016-2024 Ginger Bill. All rights reserved." + VALUE "OriginalFilename", "odin.exe" + VALUE "ProductName", "Odin Programming Language" + VALUE "ProductVersion", QUOTE(VP) + VALUE "Comments", QUOTE(git-sha: GIT_SHA) + // custom values + VALUE "GitSha", QUOTE(GIT_SHA) + VALUE "NightlyBuild", QUOTE(NIGHTLY) + END + END + BLOCK "VarFileInfo" + BEGIN + //0xFDE9=65001=CP_UTF8 + VALUE "Translation", 0x0409, 0xFDE9 + END +END + +IDI_ICON1 ICON "emblem.ico" +IDI_ICON2 ICON "sourcefile.ico" 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/misc/sourcefile.ico b/misc/sourcefile.ico new file mode 100644 index 000000000..5f3772633 Binary files /dev/null and b/misc/sourcefile.ico differ diff --git a/odin.rdi b/odin.rdi new file mode 100644 index 000000000..851ee3578 Binary files /dev/null and b/odin.rdi differ diff --git a/shell.nix b/shell.nix new file mode 100644 index 000000000..040c7696e --- /dev/null +++ b/shell.nix @@ -0,0 +1,12 @@ +{ pkgs ? import {} }: +pkgs.mkShell { + name = "odin"; + nativeBuildInputs = with pkgs; [ + git + which + clang_17 + llvmPackages_17.llvm + llvmPackages_17.bintools + ]; + shellHook="CXX=clang++"; +} diff --git a/src/array.cpp b/src/array.cpp index 4583a31a9..ec2c97d0e 100644 --- a/src/array.cpp +++ b/src/array.cpp @@ -51,6 +51,13 @@ template gb_internal void array_copy(Array *array, Array cons template gb_internal T *array_end_ptr(Array *array); +template +gb_internal void array_sort(Array &array, gbCompareProc compare_proc) { + gb_sort_array(array.data, array.count, compare_proc); +} + + + template struct Slice { T *data; diff --git a/src/big_int.cpp b/src/big_int.cpp index e350687b4..8e476f090 100644 --- a/src/big_int.cpp +++ b/src/big_int.cpp @@ -62,6 +62,7 @@ gb_internal void big_int_shl (BigInt *dst, BigInt const *x, BigInt const *y); gb_internal void big_int_shr (BigInt *dst, BigInt const *x, BigInt const *y); gb_internal void big_int_mul (BigInt *dst, BigInt const *x, BigInt const *y); gb_internal void big_int_mul_u64(BigInt *dst, BigInt const *x, u64 y); +gb_internal void big_int_exp_u64(BigInt *dst, BigInt const *x, u64 y, bool *success); gb_internal void big_int_quo_rem(BigInt const *x, BigInt const *y, BigInt *q, BigInt *r); gb_internal void big_int_quo (BigInt *z, BigInt const *x, BigInt const *y); @@ -250,9 +251,7 @@ gb_internal void big_int_from_string(BigInt *dst, String const &s, bool *success exp *= 10; exp += v; } - for (u64 x = 0; x < exp; x++) { - big_int_mul_eq(dst, &b); - } + big_int_exp_u64(dst, &b, exp, success); } if (is_negative) { @@ -328,6 +327,18 @@ gb_internal void big_int_mul_u64(BigInt *dst, BigInt const *x, u64 y) { big_int_dealloc(&d); } +gb_internal void big_int_exp_u64(BigInt *dst, BigInt const *x, u64 y, bool *success) { + if (y > INT_MAX) { + *success = false; + return; + } + + // Note: The cutoff for square-multiply being faster than the naive + // for loop is when exp > 4, but it probably isn't worth adding + // a fast path. + mp_err err = mp_expt_n(x, int(y), dst); + *success = err == MP_OKAY; +} gb_internal void big_int_mul(BigInt *dst, BigInt const *x, BigInt const *y) { mp_mul(x, y, dst); @@ -621,3 +632,7 @@ gb_internal String big_int_to_string(gbAllocator allocator, BigInt const *x, u64 } return make_string(cast(u8 *)buf.data, buf.count); } + +gb_internal int big_int_log2(BigInt const *x) { + return mp_count_bits(x) - 1; +} diff --git a/src/bug_report.cpp b/src/bug_report.cpp index fbf616efb..ca5d0a395 100644 --- a/src/bug_report.cpp +++ b/src/bug_report.cpp @@ -1,1007 +1,692 @@ -/* - Gather and print platform and version info to help with reporting Odin bugs. -*/ - -#if !defined(GB_COMPILER_MSVC) - #if defined(GB_CPU_X86) - #include - #endif -#endif - -#if defined(GB_SYSTEM_LINUX) - #include - #include -#endif - -#if defined(GB_SYSTEM_OSX) - #include -#endif - -#if defined(GB_SYSTEM_OPENBSD) - #include - #include -#endif - -#if defined(GB_SYSTEM_FREEBSD) - #include -#endif - -/* - NOTE(Jeroen): This prints the Windows product edition only, to be called from `print_platform_details`. -*/ -#if defined(GB_SYSTEM_WINDOWS) -gb_internal void report_windows_product_type(DWORD ProductType) { - switch (ProductType) { - case PRODUCT_ULTIMATE: - gb_printf("Ultimate"); - break; - - case PRODUCT_HOME_BASIC: - gb_printf("Home Basic"); - break; - - case PRODUCT_HOME_PREMIUM: - gb_printf("Home Premium"); - break; - - case PRODUCT_ENTERPRISE: - gb_printf("Enterprise"); - break; - - case PRODUCT_CORE: - gb_printf("Home Basic"); - break; - - case PRODUCT_HOME_BASIC_N: - gb_printf("Home Basic N"); - break; - - case PRODUCT_EDUCATION: - gb_printf("Education"); - break; - - case PRODUCT_EDUCATION_N: - gb_printf("Education N"); - break; - - case PRODUCT_BUSINESS: - gb_printf("Business"); - break; - - case PRODUCT_STANDARD_SERVER: - gb_printf("Standard Server"); - break; - - case PRODUCT_DATACENTER_SERVER: - gb_printf("Datacenter"); - break; - - case PRODUCT_SMALLBUSINESS_SERVER: - gb_printf("Windows Small Business Server"); - break; - - case PRODUCT_ENTERPRISE_SERVER: - gb_printf("Enterprise Server"); - break; - - case PRODUCT_STARTER: - gb_printf("Starter"); - break; - - case PRODUCT_DATACENTER_SERVER_CORE: - gb_printf("Datacenter Server Core"); - break; - - case PRODUCT_STANDARD_SERVER_CORE: - gb_printf("Server Standard Core"); - break; - - case PRODUCT_ENTERPRISE_SERVER_CORE: - gb_printf("Enterprise Server Core"); - break; - - case PRODUCT_BUSINESS_N: - gb_printf("Business N"); - break; - - case PRODUCT_HOME_SERVER: - gb_printf("Home Server"); - break; - - case PRODUCT_SERVER_FOR_SMALLBUSINESS: - gb_printf("Windows Server 2008 for Windows Essential Server Solutions"); - break; - - case PRODUCT_SMALLBUSINESS_SERVER_PREMIUM: - gb_printf("Small Business Server Premium"); - break; - - case PRODUCT_HOME_PREMIUM_N: - gb_printf("Home Premium N"); - break; - - case PRODUCT_ENTERPRISE_N: - gb_printf("Enterprise N"); - break; - - case PRODUCT_ULTIMATE_N: - gb_printf("Ultimate N"); - break; - - case PRODUCT_HYPERV: - gb_printf("HyperV"); - break; - - case PRODUCT_STARTER_N: - gb_printf("Starter N"); - break; - - case PRODUCT_PROFESSIONAL: - gb_printf("Professional"); - break; - - case PRODUCT_PROFESSIONAL_N: - gb_printf("Professional N"); - break; - - case PRODUCT_UNLICENSED: - gb_printf("Unlicensed"); - break; - - default: - gb_printf("Unknown Edition (%08x)", cast(unsigned)ProductType); - } -} -#endif - -gb_internal void odin_cpuid(int leaf, int result[]) { - #if defined(GB_CPU_ARM) - return; - - #elif defined(GB_CPU_X86) - - #if defined(GB_COMPILER_MSVC) - __cpuid(result, leaf); - #else - __get_cpuid(leaf, (unsigned int*)&result[0], (unsigned int*)&result[1], (unsigned int*)&result[2], (unsigned int*)&result[3]); - #endif - - #endif -} - -gb_internal void report_cpu_info() { - gb_printf("\tCPU: "); - - #if defined(GB_CPU_X86) - - /* - Get extended leaf info - */ - int cpu[4]; - - odin_cpuid(0x80000000, &cpu[0]); - int number_of_extended_ids = cpu[0]; - - int brand[0x12] = {}; - - /* - Read CPU brand if supported. - */ - if (number_of_extended_ids >= 0x80000004) { - odin_cpuid(0x80000002, &brand[0]); - odin_cpuid(0x80000003, &brand[4]); - odin_cpuid(0x80000004, &brand[8]); - - /* - Some CPUs like ` Intel(R) Xeon(R) CPU E5-1650 v2 @ 3.50GHz` may include leading spaces. Trim them. - */ - char * brand_name = (char *)&brand[0]; - for (; brand_name[0] == ' '; brand_name++) {} - - gb_printf("%s\n", brand_name); - } else { - gb_printf("Unable to retrieve.\n"); - } - - #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"); - #endif - #else - gb_printf("Unknown\n"); - #endif -} - -/* - Report the amount of installed RAM. -*/ -gb_internal void report_ram_info() { - gb_printf("\tRAM: "); - - #if defined(GB_SYSTEM_WINDOWS) - MEMORYSTATUSEX statex; - statex.dwLength = sizeof(statex); - GlobalMemoryStatusEx (&statex); - - gb_printf("%lld MiB\n", statex.ullTotalPhys / gb_megabytes(1)); - - #elif defined(GB_SYSTEM_LINUX) - /* - Retrieve RAM info using `sysinfo()`, - */ - struct sysinfo info; - int result = sysinfo(&info); - - if (result == 0x0) { - gb_printf("%lu MiB\n", info.totalram * info.mem_unit / gb_megabytes(1)); - } else { - gb_printf("Unknown.\n"); - } - #elif defined(GB_SYSTEM_OSX) - uint64_t ram_amount; - size_t val_size = sizeof(ram_amount); - - int mibs[] = { CTL_HW, HW_MEMSIZE }; - 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_OPENBSD) - 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("%lld MiB\n", ram_amount / gb_megabytes(1)); - } - #elif defined(GB_SYSTEM_FREEBSD) - uint64_t ram_amount; - size_t val_size = sizeof(ram_amount); - - int mibs[] = { CTL_HW, HW_PHYSMEM }; - if (sysctl(mibs, 2, &ram_amount, &val_size, NULL, 0) != -1) { - gb_printf("%lu MiB\n", ram_amount / gb_megabytes(1)); - } - #else - gb_printf("Unknown.\n"); - #endif -} - -gb_internal void report_os_info() { - gb_printf("\tOS: "); - - #if defined(GB_SYSTEM_WINDOWS) - /* - NOTE(Jeroen): - `GetVersionEx` will return 6.2 for Windows 10 unless the program is manifested for Windows 10. - `RtlGetVersion` will return the true version. - - Rather than include the WinDDK, we ask the kernel directly. - - `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion` is for the minor build version (Update Build Release) - - */ - OSVERSIONINFOEXW osvi; - ZeroMemory(&osvi, sizeof(OSVERSIONINFOEXW)); - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW); - - typedef NTSTATUS (WINAPI* RtlGetVersionPtr)(OSVERSIONINFOW*); - typedef BOOL (WINAPI* GetProductInfoPtr)(DWORD dwOSMajorVersion, DWORD dwOSMinorVersion, DWORD dwSpMajorVersion, DWORD dwSpMinorVersion, PDWORD pdwReturnedProductType); - - // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-rtlgetversion - RtlGetVersionPtr RtlGetVersion = (RtlGetVersionPtr)GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "RtlGetVersion"); - // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getproductinfo - GetProductInfoPtr GetProductInfo = (GetProductInfoPtr)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetProductInfo"); - - NTSTATUS status = {}; - DWORD ProductType = {}; - if (RtlGetVersion != nullptr) { - status = RtlGetVersion((OSVERSIONINFOW*)&osvi); - } - - if (RtlGetVersion == nullptr || status != 0x0) { - gb_printf("Windows (Unknown Version)"); - } else { - if (GetProductInfo != nullptr) { - GetProductInfo(osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.wServicePackMajor, osvi.wServicePackMinor, &ProductType); - } - - if (false) { - gb_printf("dwMajorVersion: %u\n", cast(unsigned)osvi.dwMajorVersion); - gb_printf("dwMinorVersion: %u\n", cast(unsigned)osvi.dwMinorVersion); - gb_printf("dwBuildNumber: %u\n", cast(unsigned)osvi.dwBuildNumber); - gb_printf("dwPlatformId: %u\n", cast(unsigned)osvi.dwPlatformId); - gb_printf("wServicePackMajor: %u\n", cast(unsigned)osvi.wServicePackMajor); - gb_printf("wServicePackMinor: %u\n", cast(unsigned)osvi.wServicePackMinor); - gb_printf("wSuiteMask: %u\n", cast(unsigned)osvi.wSuiteMask); - gb_printf("wProductType: %u\n", cast(unsigned)osvi.wProductType); - } - - gb_printf("Windows "); - - switch (osvi.dwMajorVersion) { - case 10: - /* - Windows 10 (Pro), Windows 2016 Server, Windows 2019 Server, Windows 2022 Server - */ - switch (osvi.wProductType) { - case VER_NT_WORKSTATION: // Workstation - if (osvi.dwBuildNumber < 22000) { - gb_printf("10 "); - } else { - gb_printf("11 "); - } - - report_windows_product_type(ProductType); - - break; - default: // Server or Domain Controller - switch(osvi.dwBuildNumber) { - case 14393: - gb_printf("2016 Server"); - break; - case 17763: - gb_printf("2019 Server"); - break; - case 20348: - gb_printf("2022 Server"); - break; - default: - gb_printf("Unknown Server"); - break; - } - } - break; - case 6: - switch (osvi.dwMinorVersion) { - case 0: - switch (osvi.wProductType) { - case VER_NT_WORKSTATION: - gb_printf("Windows Vista "); - report_windows_product_type(ProductType); - break; - case 3: - gb_printf("Windows Server 2008"); - break; - } - break; - - case 1: - switch (osvi.wProductType) { - case VER_NT_WORKSTATION: - gb_printf("Windows 7 "); - report_windows_product_type(ProductType); - break; - case 3: - gb_printf("Windows Server 2008 R2"); - break; - } - break; - case 2: - switch (osvi.wProductType) { - case VER_NT_WORKSTATION: - gb_printf("Windows 8 "); - report_windows_product_type(ProductType); - break; - case 3: - gb_printf("Windows Server 2012"); - break; - } - break; - case 3: - switch (osvi.wProductType) { - case VER_NT_WORKSTATION: - gb_printf("Windows 8.1 "); - report_windows_product_type(ProductType); - break; - case 3: - gb_printf("Windows Server 2012 R2"); - break; - } - break; - } - break; - case 5: - switch (osvi.dwMinorVersion) { - case 0: - gb_printf("Windows 2000"); - break; - case 1: - gb_printf("Windows XP"); - break; - case 2: - gb_printf("Windows Server 2003"); - break; - } - break; - default: - break; - } - - /* - Grab Windows DisplayVersion (like 20H02) - */ - LPDWORD ValueType = {}; - DWORD UBR; - char DisplayVersion[256]; - DWORD ValueSize = 256; - - status = RegGetValue( - HKEY_LOCAL_MACHINE, - TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), - TEXT("DisplayVersion"), - RRF_RT_REG_SZ, - ValueType, - DisplayVersion, - &ValueSize - ); - - if (status == 0x0) { - gb_printf(" (version: %s)", DisplayVersion); - } - - /* - Now print build number. - */ - gb_printf(", build %u", cast(unsigned)osvi.dwBuildNumber); - - ValueSize = sizeof(UBR); - status = RegGetValue( - HKEY_LOCAL_MACHINE, - TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), - TEXT("UBR"), - RRF_RT_REG_DWORD, - ValueType, - &UBR, - &ValueSize - ); - - if (status == 0x0) { - gb_printf(".%u", cast(unsigned)UBR); - } - gb_printf("\n"); - } - #elif defined(GB_SYSTEM_LINUX) - /* - Try to parse `/etc/os-release` for `PRETTY_NAME="Ubuntu 20.04.3 LTS` - */ - gbAllocator a = heap_allocator(); - - gbFileContents release = gb_file_read_contents(a, 1, "/etc/os-release"); - defer (gb_file_free_contents(&release)); - - b32 found = 0; - if (release.size) { - char *start = (char *)release.data; - char *end = (char *)release.data + release.size; - const char *needle = "PRETTY_NAME=\""; - isize needle_len = gb_strlen((needle)); - - char *c = start; - for (; c < end; c++) { - if (gb_strncmp(c, needle, needle_len) == 0) { - found = 1; - start = c + needle_len; - break; - } - } - - if (found) { - for (c = start; c < end; c++) { - if (*c == '"') { - // Found the closing quote. Replace it with \0 - *c = 0; - gb_printf("%s", (char *)start); - break; - } else if (*c == '\n') { - found = 0; - } - } - } - } - - if (!found) { - gb_printf("Unknown Linux Distro"); - } - - /* - Print kernel info using `uname()` syscall, https://linux.die.net/man/2/uname - */ - char buffer[1024]; - uname((struct utsname *)&buffer[0]); - - struct utsname *info; - info = (struct utsname *)&buffer[0]; - - gb_printf(", %s %s\n", info->sysname, info->release); - - #elif defined(GB_SYSTEM_OSX) - struct Darwin_To_Release { - const char* build; // 21G83 - int darwin[3]; // Darwin kernel triplet - const char* os_name; // OS X, MacOS - struct { - const char* name; // Monterey, Mojave, etc. - int version[3]; // 12.4, etc. - } release; - }; - - Darwin_To_Release macos_release_map[] = { - {"8A428", { 8, 0, 0}, "macOS", {"Tiger", {10, 4, 0}}}, - {"8A432", { 8, 0, 0}, "macOS", {"Tiger", {10, 4, 0}}}, - {"8B15", { 8, 1, 0}, "macOS", {"Tiger", {10, 4, 1}}}, - {"8B17", { 8, 1, 0}, "macOS", {"Tiger", {10, 4, 1}}}, - {"8C46", { 8, 2, 0}, "macOS", {"Tiger", {10, 4, 2}}}, - {"8C47", { 8, 2, 0}, "macOS", {"Tiger", {10, 4, 2}}}, - {"8E102", { 8, 2, 0}, "macOS", {"Tiger", {10, 4, 2}}}, - {"8E45", { 8, 2, 0}, "macOS", {"Tiger", {10, 4, 2}}}, - {"8E90", { 8, 2, 0}, "macOS", {"Tiger", {10, 4, 2}}}, - {"8F46", { 8, 3, 0}, "macOS", {"Tiger", {10, 4, 3}}}, - {"8G32", { 8, 4, 0}, "macOS", {"Tiger", {10, 4, 4}}}, - {"8G1165", { 8, 4, 0}, "macOS", {"Tiger", {10, 4, 4}}}, - {"8H14", { 8, 5, 0}, "macOS", {"Tiger", {10, 4, 5}}}, - {"8G1454", { 8, 5, 0}, "macOS", {"Tiger", {10, 4, 5}}}, - {"8I127", { 8, 6, 0}, "macOS", {"Tiger", {10, 4, 6}}}, - {"8I1119", { 8, 6, 0}, "macOS", {"Tiger", {10, 4, 6}}}, - {"8J135", { 8, 7, 0}, "macOS", {"Tiger", {10, 4, 7}}}, - {"8J2135a", { 8, 7, 0}, "macOS", {"Tiger", {10, 4, 7}}}, - {"8K1079", { 8, 7, 0}, "macOS", {"Tiger", {10, 4, 7}}}, - {"8N5107", { 8, 7, 0}, "macOS", {"Tiger", {10, 4, 7}}}, - {"8L127", { 8, 8, 0}, "macOS", {"Tiger", {10, 4, 8}}}, - {"8L2127", { 8, 8, 0}, "macOS", {"Tiger", {10, 4, 8}}}, - {"8P135", { 8, 9, 0}, "macOS", {"Tiger", {10, 4, 9}}}, - {"8P2137", { 8, 9, 0}, "macOS", {"Tiger", {10, 4, 9}}}, - {"8R218", { 8, 10, 0}, "macOS", {"Tiger", {10, 4, 10}}}, - {"8R2218", { 8, 10, 0}, "macOS", {"Tiger", {10, 4, 10}}}, - {"8R2232", { 8, 10, 0}, "macOS", {"Tiger", {10, 4, 10}}}, - {"8S165", { 8, 11, 0}, "macOS", {"Tiger", {10, 4, 11}}}, - {"8S2167", { 8, 11, 0}, "macOS", {"Tiger", {10, 4, 11}}}, - {"9A581", { 9, 0, 0}, "macOS", {"Leopard", {10, 5, 0}}}, - {"9B18", { 9, 1, 0}, "macOS", {"Leopard", {10, 5, 1}}}, - {"9B2117", { 9, 1, 1}, "macOS", {"Leopard", {10, 5, 1}}}, - {"9C31", { 9, 2, 0}, "macOS", {"Leopard", {10, 5, 2}}}, - {"9C7010", { 9, 2, 0}, "macOS", {"Leopard", {10, 5, 2}}}, - {"9D34", { 9, 3, 0}, "macOS", {"Leopard", {10, 5, 3}}}, - {"9E17", { 9, 4, 0}, "macOS", {"Leopard", {10, 5, 4}}}, - {"9F33", { 9, 5, 0}, "macOS", {"Leopard", {10, 5, 5}}}, - {"9G55", { 9, 6, 0}, "macOS", {"Leopard", {10, 5, 6}}}, - {"9G66", { 9, 6, 0}, "macOS", {"Leopard", {10, 5, 6}}}, - {"9G71", { 9, 6, 0}, "macOS", {"Leopard", {10, 5, 6}}}, - {"9J61", { 9, 7, 0}, "macOS", {"Leopard", {10, 5, 7}}}, - {"9L30", { 9, 8, 0}, "macOS", {"Leopard", {10, 5, 8}}}, - {"9L34", { 9, 8, 0}, "macOS", {"Leopard", {10, 5, 8}}}, - {"10A432", {10, 0, 0}, "macOS", {"Snow Leopard", {10, 6, 0}}}, - {"10A433", {10, 0, 0}, "macOS", {"Snow Leopard", {10, 6, 0}}}, - {"10B504", {10, 1, 0}, "macOS", {"Snow Leopard", {10, 6, 1}}}, - {"10C540", {10, 2, 0}, "macOS", {"Snow Leopard", {10, 6, 2}}}, - {"10D573", {10, 3, 0}, "macOS", {"Snow Leopard", {10, 6, 3}}}, - {"10D575", {10, 3, 0}, "macOS", {"Snow Leopard", {10, 6, 3}}}, - {"10D578", {10, 3, 0}, "macOS", {"Snow Leopard", {10, 6, 3}}}, - {"10F569", {10, 4, 0}, "macOS", {"Snow Leopard", {10, 6, 4}}}, - {"10H574", {10, 5, 0}, "macOS", {"Snow Leopard", {10, 6, 5}}}, - {"10J567", {10, 6, 0}, "macOS", {"Snow Leopard", {10, 6, 6}}}, - {"10J869", {10, 7, 0}, "macOS", {"Snow Leopard", {10, 6, 7}}}, - {"10J3250", {10, 7, 0}, "macOS", {"Snow Leopard", {10, 6, 7}}}, - {"10J4138", {10, 7, 0}, "macOS", {"Snow Leopard", {10, 6, 7}}}, - {"10K540", {10, 8, 0}, "macOS", {"Snow Leopard", {10, 6, 8}}}, - {"10K549", {10, 8, 0}, "macOS", {"Snow Leopard", {10, 6, 8}}}, - {"11A511", {11, 0, 0}, "macOS", {"Lion", {10, 7, 0}}}, - {"11A511s", {11, 0, 0}, "macOS", {"Lion", {10, 7, 0}}}, - {"11A2061", {11, 0, 2}, "macOS", {"Lion", {10, 7, 0}}}, - {"11A2063", {11, 0, 2}, "macOS", {"Lion", {10, 7, 0}}}, - {"11B26", {11, 1, 0}, "macOS", {"Lion", {10, 7, 1}}}, - {"11B2118", {11, 1, 0}, "macOS", {"Lion", {10, 7, 1}}}, - {"11C74", {11, 2, 0}, "macOS", {"Lion", {10, 7, 2}}}, - {"11D50", {11, 3, 0}, "macOS", {"Lion", {10, 7, 3}}}, - {"11E53", {11, 4, 0}, "macOS", {"Lion", {10, 7, 4}}}, - {"11G56", {11, 4, 2}, "macOS", {"Lion", {10, 7, 5}}}, - {"11G63", {11, 4, 2}, "macOS", {"Lion", {10, 7, 5}}}, - {"12A269", {12, 0, 0}, "macOS", {"Mountain Lion", {10, 8, 0}}}, - {"12B19", {12, 1, 0}, "macOS", {"Mountain Lion", {10, 8, 1}}}, - {"12C54", {12, 2, 0}, "macOS", {"Mountain Lion", {10, 8, 2}}}, - {"12C60", {12, 2, 0}, "macOS", {"Mountain Lion", {10, 8, 2}}}, - {"12C2034", {12, 2, 0}, "macOS", {"Mountain Lion", {10, 8, 2}}}, - {"12C3104", {12, 2, 0}, "macOS", {"Mountain Lion", {10, 8, 2}}}, - {"12D78", {12, 3, 0}, "macOS", {"Mountain Lion", {10, 8, 3}}}, - {"12E55", {12, 4, 0}, "macOS", {"Mountain Lion", {10, 8, 4}}}, - {"12E3067", {12, 4, 0}, "macOS", {"Mountain Lion", {10, 8, 4}}}, - {"12E4022", {12, 4, 0}, "macOS", {"Mountain Lion", {10, 8, 4}}}, - {"12F37", {12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - {"12F45", {12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - {"12F2501", {12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - {"12F2518", {12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - {"12F2542", {12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - {"12F2560", {12, 5, 0}, "macOS", {"Mountain Lion", {10, 8, 5}}}, - {"13A603", {13, 0, 0}, "macOS", {"Mavericks", {10, 9, 0}}}, - {"13B42", {13, 0, 0}, "macOS", {"Mavericks", {10, 9, 1}}}, - {"13C64", {13, 1, 0}, "macOS", {"Mavericks", {10, 9, 2}}}, - {"13C1021", {13, 1, 0}, "macOS", {"Mavericks", {10, 9, 2}}}, - {"13D65", {13, 2, 0}, "macOS", {"Mavericks", {10, 9, 3}}}, - {"13E28", {13, 3, 0}, "macOS", {"Mavericks", {10, 9, 4}}}, - {"13F34", {13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - {"13F1066", {13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - {"13F1077", {13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - {"13F1096", {13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - {"13F1112", {13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - {"13F1134", {13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - {"13F1507", {13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - {"13F1603", {13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - {"13F1712", {13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - {"13F1808", {13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - {"13F1911", {13, 4, 0}, "macOS", {"Mavericks", {10, 9, 5}}}, - {"14A389", {14, 0, 0}, "macOS", {"Yosemite", {10, 10, 0}}}, - {"14B25", {14, 0, 0}, "macOS", {"Yosemite", {10, 10, 1}}}, - {"14C109", {14, 1, 0}, "macOS", {"Yosemite", {10, 10, 2}}}, - {"14C1510", {14, 1, 0}, "macOS", {"Yosemite", {10, 10, 2}}}, - {"14C2043", {14, 1, 0}, "macOS", {"Yosemite", {10, 10, 2}}}, - {"14C1514", {14, 1, 0}, "macOS", {"Yosemite", {10, 10, 2}}}, - {"14C2513", {14, 1, 0}, "macOS", {"Yosemite", {10, 10, 2}}}, - {"14D131", {14, 3, 0}, "macOS", {"Yosemite", {10, 10, 3}}}, - {"14D136", {14, 3, 0}, "macOS", {"Yosemite", {10, 10, 3}}}, - {"14E46", {14, 4, 0}, "macOS", {"Yosemite", {10, 10, 4}}}, - {"14F27", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F1021", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F1505", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F1509", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F1605", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F1713", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F1808", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F1909", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F1912", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F2009", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F2109", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F2315", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F2411", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"14F2511", {14, 5, 0}, "macOS", {"Yosemite", {10, 10, 5}}}, - {"15A284", {15, 0, 0}, "macOS", {"El Capitan", {10, 11, 0}}}, - {"15B42", {15, 0, 0}, "macOS", {"El Capitan", {10, 11, 1}}}, - {"15C50", {15, 2, 0}, "macOS", {"El Capitan", {10, 11, 2}}}, - {"15D21", {15, 3, 0}, "macOS", {"El Capitan", {10, 11, 3}}}, - {"15E65", {15, 4, 0}, "macOS", {"El Capitan", {10, 11, 4}}}, - {"15F34", {15, 5, 0}, "macOS", {"El Capitan", {10, 11, 5}}}, - {"15G31", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G1004", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G1011", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G1108", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G1212", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G1217", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G1421", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G1510", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G1611", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G17023", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G18013", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G19009", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G20015", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G21013", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"15G22010", {15, 6, 0}, "macOS", {"El Capitan", {10, 11, 6}}}, - {"16A323", {16, 0, 0}, "macOS", {"Sierra", {10, 12, 0}}}, - {"16B2555", {16, 1, 0}, "macOS", {"Sierra", {10, 12, 1}}}, - {"16B2657", {16, 1, 0}, "macOS", {"Sierra", {10, 12, 1}}}, - {"16C67", {16, 3, 0}, "macOS", {"Sierra", {10, 12, 2}}}, - {"16C68", {16, 3, 0}, "macOS", {"Sierra", {10, 12, 2}}}, - {"16D32", {16, 4, 0}, "macOS", {"Sierra", {10, 12, 3}}}, - {"16E195", {16, 5, 0}, "macOS", {"Sierra", {10, 12, 4}}}, - {"16F73", {16, 6, 0}, "macOS", {"Sierra", {10, 12, 5}}}, - {"16F2073", {16, 6, 0}, "macOS", {"Sierra", {10, 12, 5}}}, - {"16G29", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G1036", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G1114", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G1212", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G1314", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G1408", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G1510", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G1618", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G1710", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G1815", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G1917", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G1918", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G2016", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G2127", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G2128", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"16G2136", {16, 7, 0}, "macOS", {"Sierra", {10, 12, 6}}}, - {"17A365", {17, 0, 0}, "macOS", {"High Sierra", {10, 13, 0}}}, - {"17A405", {17, 0, 0}, "macOS", {"High Sierra", {10, 13, 0}}}, - {"17B48", {17, 2, 0}, "macOS", {"High Sierra", {10, 13, 1}}}, - {"17B1002", {17, 2, 0}, "macOS", {"High Sierra", {10, 13, 1}}}, - {"17B1003", {17, 2, 0}, "macOS", {"High Sierra", {10, 13, 1}}}, - {"17C88", {17, 3, 0}, "macOS", {"High Sierra", {10, 13, 2}}}, - {"17C89", {17, 3, 0}, "macOS", {"High Sierra", {10, 13, 2}}}, - {"17C205", {17, 3, 0}, "macOS", {"High Sierra", {10, 13, 2}}}, - {"17C2205", {17, 3, 0}, "macOS", {"High Sierra", {10, 13, 2}}}, - {"17D47", {17, 4, 0}, "macOS", {"High Sierra", {10, 13, 3}}}, - {"17D2047", {17, 4, 0}, "macOS", {"High Sierra", {10, 13, 3}}}, - {"17D102", {17, 4, 0}, "macOS", {"High Sierra", {10, 13, 3}}}, - {"17D2102", {17, 4, 0}, "macOS", {"High Sierra", {10, 13, 3}}}, - {"17E199", {17, 5, 0}, "macOS", {"High Sierra", {10, 13, 4}}}, - {"17E202", {17, 5, 0}, "macOS", {"High Sierra", {10, 13, 4}}}, - {"17F77", {17, 6, 0}, "macOS", {"High Sierra", {10, 13, 5}}}, - {"17G65", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G2208", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G2307", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G3025", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G4015", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G5019", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G6029", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G6030", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G7024", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G8029", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G8030", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G8037", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G9016", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G10021", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G11023", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G12034", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G13033", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G13035", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G14019", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G14033", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"17G14042", {17, 7, 0}, "macOS", {"High Sierra", {10, 13, 6}}}, - {"18A391", {18, 0, 0}, "macOS", {"Mojave", {10, 14, 0}}}, - {"18B75", {18, 2, 0}, "macOS", {"Mojave", {10, 14, 1}}}, - {"18B2107", {18, 2, 0}, "macOS", {"Mojave", {10, 14, 1}}}, - {"18B3094", {18, 2, 0}, "macOS", {"Mojave", {10, 14, 1}}}, - {"18C54", {18, 2, 0}, "macOS", {"Mojave", {10, 14, 2}}}, - {"18D42", {18, 2, 0}, "macOS", {"Mojave", {10, 14, 3}}}, - {"18D43", {18, 2, 0}, "macOS", {"Mojave", {10, 14, 3}}}, - {"18D109", {18, 2, 0}, "macOS", {"Mojave", {10, 14, 3}}}, - {"18E226", {18, 5, 0}, "macOS", {"Mojave", {10, 14, 4}}}, - {"18E227", {18, 5, 0}, "macOS", {"Mojave", {10, 14, 4}}}, - {"18F132", {18, 6, 0}, "macOS", {"Mojave", {10, 14, 5}}}, - {"18G84", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G87", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G95", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G103", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G1012", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G2022", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G3020", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G4032", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G5033", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G6020", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G6032", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G6042", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G7016", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G8012", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G8022", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G9028", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G9216", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"18G9323", {18, 7, 0}, "macOS", {"Mojave", {10, 14, 6}}}, - {"19A583", {19, 0, 0}, "macOS", {"Catalina", {10, 15, 0}}}, - {"19A602", {19, 0, 0}, "macOS", {"Catalina", {10, 15, 0}}}, - {"19A603", {19, 0, 0}, "macOS", {"Catalina", {10, 15, 0}}}, - {"19B88", {19, 0, 0}, "macOS", {"Catalina", {10, 15, 1}}}, - {"19C57", {19, 2, 0}, "macOS", {"Catalina", {10, 15, 2}}}, - {"19C58", {19, 2, 0}, "macOS", {"Catalina", {10, 15, 2}}}, - {"19D76", {19, 3, 0}, "macOS", {"Catalina", {10, 15, 3}}}, - {"19E266", {19, 4, 0}, "macOS", {"Catalina", {10, 15, 4}}}, - {"19E287", {19, 4, 0}, "macOS", {"Catalina", {10, 15, 4}}}, - {"19F96", {19, 5, 0}, "macOS", {"Catalina", {10, 15, 5}}}, - {"19F101", {19, 5, 0}, "macOS", {"Catalina", {10, 15, 5}}}, - {"19G73", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 6}}}, - {"19G2021", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 6}}}, - {"19H2", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H4", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H15", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H114", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H512", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H524", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H1030", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H1217", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H1323", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H1417", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H1419", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H1519", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H1615", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H1713", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H1715", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H1824", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H1922", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"19H2026", {19, 6, 0}, "macOS", {"Catalina", {10, 15, 7}}}, - {"20A2411", {20, 1, 0}, "macOS", {"Big Sur", {11, 0, 0}}}, - {"20B29", {20, 1, 0}, "macOS", {"Big Sur", {11, 0, 1}}}, - {"20B50", {20, 1, 0}, "macOS", {"Big Sur", {11, 0, 1}}}, - {"20C69", {20, 2, 0}, "macOS", {"Big Sur", {11, 1, 0}}}, - {"20D64", {20, 3, 0}, "macOS", {"Big Sur", {11, 2, 0}}}, - {"20D74", {20, 3, 0}, "macOS", {"Big Sur", {11, 2, 1}}}, - {"20D75", {20, 3, 0}, "macOS", {"Big Sur", {11, 2, 1}}}, - {"20D80", {20, 3, 0}, "macOS", {"Big Sur", {11, 2, 2}}}, - {"20D91", {20, 3, 0}, "macOS", {"Big Sur", {11, 2, 3}}}, - {"20E232", {20, 4, 0}, "macOS", {"Big Sur", {11, 3, 0}}}, - {"20E241", {20, 4, 0}, "macOS", {"Big Sur", {11, 3, 1}}}, - {"20F71", {20, 5, 0}, "macOS", {"Big Sur", {11, 4, 0}}}, - {"20G71", {20, 6, 0}, "macOS", {"Big Sur", {11, 5, 0}}}, - {"20G80", {20, 6, 0}, "macOS", {"Big Sur", {11, 5, 1}}}, - {"20G95", {20, 6, 0}, "macOS", {"Big Sur", {11, 5, 2}}}, - {"20G165", {20, 6, 0}, "macOS", {"Big Sur", {11, 6, 0}}}, - {"20G224", {20, 6, 0}, "macOS", {"Big Sur", {11, 6, 1}}}, - {"20G314", {20, 6, 0}, "macOS", {"Big Sur", {11, 6, 2}}}, - {"20G415", {20, 6, 0}, "macOS", {"Big Sur", {11, 6, 3}}}, - {"20G417", {20, 6, 0}, "macOS", {"Big Sur", {11, 6, 4}}}, - {"20G527", {20, 6, 0}, "macOS", {"Big Sur", {11, 6, 5}}}, - {"20G624", {20, 6, 0}, "macOS", {"Big Sur", {11, 6, 6}}}, - {"20G630", {20, 6, 0}, "macOS", {"Big Sur", {11, 6, 7}}}, - {"20G730", {20, 6, 0}, "macOS", {"Big Sur", {11, 6, 8}}}, - {"21A344", {21, 0, 1}, "macOS", {"Monterey", {12, 0, 0}}}, - {"21A559", {21, 1, 0}, "macOS", {"Monterey", {12, 0, 1}}}, - {"21C52", {21, 2, 0}, "macOS", {"Monterey", {12, 1, 0}}}, - {"21D49", {21, 3, 0}, "macOS", {"Monterey", {12, 2, 0}}}, - {"21D62", {21, 3, 0}, "macOS", {"Monterey", {12, 2, 1}}}, - {"21E230", {21, 4, 0}, "macOS", {"Monterey", {12, 3, 0}}}, - {"21E258", {21, 4, 0}, "macOS", {"Monterey", {12, 3, 1}}}, - {"21F79", {21, 5, 0}, "macOS", {"Monterey", {12, 4, 0}}}, - {"21F2081", {21, 5, 0}, "macOS", {"Monterey", {12, 4, 0}}}, - {"21F2092", {21, 5, 0}, "macOS", {"Monterey", {12, 4, 0}}}, - {"21G72", {21, 6, 0}, "macOS", {"Monterey", {12, 5, 0}}}, - {"21G83", {21, 6, 0}, "macOS", {"Monterey", {12, 5, 1}}}, - }; - - - b32 build_found = 1; - b32 darwin_found = 1; - uint32_t major, minor, patch; - - #define MACOS_VERSION_BUFFER_SIZE 100 - char build_buffer[MACOS_VERSION_BUFFER_SIZE]; - char darwin_buffer[MACOS_VERSION_BUFFER_SIZE]; - size_t build_buffer_size = MACOS_VERSION_BUFFER_SIZE - 1; - size_t darwin_buffer_size = MACOS_VERSION_BUFFER_SIZE - 1; - #undef MACOS_VERSION_BUFFER_SIZE - - int build_mibs[] = { CTL_KERN, KERN_OSVERSION }; - if (sysctl(build_mibs, 2, build_buffer, &build_buffer_size, NULL, 0) == -1) { - build_found = 0; - } - - int darwin_mibs[] = { CTL_KERN, KERN_OSRELEASE }; - if (sysctl(darwin_mibs, 2, darwin_buffer, &darwin_buffer_size, NULL, 0) == -1) { - gb_printf("macOS Unknown\n"); - return; - } else { - if (sscanf(darwin_buffer, "%u.%u.%u", &major, &minor, &patch) != 3) { - darwin_found = 0; - } - } - - // Scan table for match on BUILD - int macos_release_count = sizeof(macos_release_map) / sizeof(macos_release_map[0]); - Darwin_To_Release match = {}; - - for (int build = 0; build < macos_release_count; build++) { - Darwin_To_Release rel = macos_release_map[build]; - - // Do we have an exact match on the BUILD? - if (gb_strcmp(rel.build, (const char *)build_buffer) == 0) { - match = rel; - break; - } - - // Do we have an exact Darwin match? - if (rel.darwin[0] == major && rel.darwin[1] == minor && rel.darwin[2] == patch) { - match = rel; - break; - } - - // Major kernel version needs to match exactly, - if (rel.darwin[0] == major) { - // No major version match yet. - if (!match.os_name) { - match = rel; - } - if (minor >= rel.darwin[1]) { - match = rel; - if (patch >= rel.darwin[2]) { - match = rel; - } - } - } - } - - if (match.os_name) { - gb_printf("%s %s %d", match.os_name, match.release.name, match.release.version[0]); - if (match.release.version[1] > 0 || match.release.version[2] > 0) { - gb_printf(".%d", match.release.version[1]); - } - if (match.release.version[2] > 0) { - gb_printf(".%d", match.release.version[2]); - } - if (build_found) { - gb_printf(" (build: %s, kernel: %d.%d.%d)\n", build_buffer, match.darwin[0], match.darwin[1], match.darwin[2]); - } else { - gb_printf(" (build: %s?, kernel: %d.%d.%d)\n", match.build, match.darwin[0], match.darwin[1], match.darwin[2]); - } - return; - } - - if (build_found && darwin_found) { - gb_printf("macOS Unknown (build: %s, kernel: %d.%d.%d)\n", build_buffer, major, minor, patch); - return; - } else if (build_found) { - gb_printf("macOS Unknown (build: %s)\n", build_buffer); - return; - } else if (darwin_found) { - gb_printf("macOS Unknown (kernel: %d.%d.%d)\n", major, minor, patch); - return; - } - #elif defined(GB_SYSTEM_OPENBSD) - 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"); - } - #elif defined(GB_SYSTEM_FREEBSD) - #define freebsd_version_buffer 129 - char buffer[freebsd_version_buffer]; - size_t buffer_size = freebsd_version_buffer - 1; - #undef freebsd_version_buffer - - int mibs[] = { CTL_KERN, KERN_VERSION }; - if (sysctl(mibs, 2, buffer, &buffer_size, NULL, 0) == -1) { - gb_printf("FreeBSD: Unknown\n"); - } else { - // KERN_VERSION can end in a \n, replace it with a \0 - for (int i = 0; i < buffer_size; i += 1) { - if (buffer[i] == '\n') buffer[i] = 0; - } - gb_printf("%s", &buffer[0]); - - // Retrieve kernel revision using `sysctl`, e.g. 199506 - mibs[1] = KERN_OSREV; - uint64_t revision; - size_t revision_size = sizeof(revision); - - if (sysctl(mibs, 2, &revision, &revision_size, NULL, 0) == -1) { - gb_printf("\n"); - } else { - gb_printf(", revision %ld\n", revision); - } - } - #else - gb_printf("Unknown"); - #endif -} - -// NOTE(Jeroen): `odin report` prints some system information for easier bug reporting. -gb_internal void print_bug_report_help() { - gb_printf("Where to find more information and get into contact when you encounter a bug:\n\n"); - gb_printf("\tWebsite: https://odin-lang.org\n"); - gb_printf("\tGitHub: https://github.com/odin-lang/Odin/issues\n"); - /* - Uncomment and update URL once we have a Discord vanity URL. For now people can get here from the site. - gb_printf("\tDiscord: https://discord.com/invite/sVBPHEv\n"); - */ - gb_printf("\n\n"); - - gb_printf("Useful information to add to a bug report:\n\n"); - - gb_printf("\tOdin: %.*s", LIT(ODIN_VERSION)); - - #ifdef NIGHTLY - gb_printf("-nightly"); - #endif - - #ifdef GIT_SHA - gb_printf(":%s", GIT_SHA); - #endif - - gb_printf("\n"); - - /* - Print OS information. - */ - report_os_info(); - - /* - Now print CPU info. - */ - report_cpu_info(); - - /* - And RAM info. - */ - report_ram_info(); -} \ No newline at end of file +/* + Gather and print platform and version info to help with reporting Odin bugs. +*/ + +#if defined(GB_SYSTEM_LINUX) + #include + #include +#endif + +#if defined(GB_SYSTEM_OSX) + #include +#endif + +#if defined(GB_SYSTEM_OPENBSD) || defined(GB_SYSTEM_NETBSD) + #include + #include +#endif + +#if defined(GB_SYSTEM_FREEBSD) + #include +#endif + +/* + NOTE(Jeroen): This prints the Windows product edition only, to be called from `print_platform_details`. +*/ +#if defined(GB_SYSTEM_WINDOWS) +gb_internal void report_windows_product_type(DWORD ProductType) { + switch (ProductType) { + case PRODUCT_ULTIMATE: + gb_printf("Ultimate"); + break; + + case PRODUCT_HOME_BASIC: + gb_printf("Home Basic"); + break; + + case PRODUCT_HOME_PREMIUM: + gb_printf("Home Premium"); + break; + + case PRODUCT_ENTERPRISE: + gb_printf("Enterprise"); + break; + + case PRODUCT_CORE: + gb_printf("Home Basic"); + break; + + case PRODUCT_HOME_BASIC_N: + gb_printf("Home Basic N"); + break; + + case PRODUCT_EDUCATION: + gb_printf("Education"); + break; + + case PRODUCT_EDUCATION_N: + gb_printf("Education N"); + break; + + case PRODUCT_BUSINESS: + gb_printf("Business"); + break; + + case PRODUCT_STANDARD_SERVER: + gb_printf("Standard Server"); + break; + + case PRODUCT_DATACENTER_SERVER: + gb_printf("Datacenter"); + break; + + case PRODUCT_SMALLBUSINESS_SERVER: + gb_printf("Windows Small Business Server"); + break; + + case PRODUCT_ENTERPRISE_SERVER: + gb_printf("Enterprise Server"); + break; + + case PRODUCT_STARTER: + gb_printf("Starter"); + break; + + case PRODUCT_DATACENTER_SERVER_CORE: + gb_printf("Datacenter Server Core"); + break; + + case PRODUCT_STANDARD_SERVER_CORE: + gb_printf("Server Standard Core"); + break; + + case PRODUCT_ENTERPRISE_SERVER_CORE: + gb_printf("Enterprise Server Core"); + break; + + case PRODUCT_BUSINESS_N: + gb_printf("Business N"); + break; + + case PRODUCT_HOME_SERVER: + gb_printf("Home Server"); + break; + + case PRODUCT_SERVER_FOR_SMALLBUSINESS: + gb_printf("Windows Server 2008 for Windows Essential Server Solutions"); + break; + + case PRODUCT_SMALLBUSINESS_SERVER_PREMIUM: + gb_printf("Small Business Server Premium"); + break; + + case PRODUCT_HOME_PREMIUM_N: + gb_printf("Home Premium N"); + break; + + case PRODUCT_ENTERPRISE_N: + gb_printf("Enterprise N"); + break; + + case PRODUCT_ULTIMATE_N: + gb_printf("Ultimate N"); + break; + + case PRODUCT_HYPERV: + gb_printf("HyperV"); + break; + + case PRODUCT_STARTER_N: + gb_printf("Starter N"); + break; + + case PRODUCT_PROFESSIONAL: + gb_printf("Professional"); + break; + + case PRODUCT_PROFESSIONAL_N: + gb_printf("Professional N"); + break; + + case PRODUCT_UNLICENSED: + gb_printf("Unlicensed"); + break; + + default: + gb_printf("Unknown Edition (%08x)", cast(unsigned)ProductType); + } +} +#endif + +gb_internal void report_cpu_info() { + gb_printf("\tCPU: "); + + #if defined(GB_CPU_X86) + + /* + Get extended leaf info + */ + int cpu[4]; + + odin_cpuid(0x80000000, &cpu[0]); + int number_of_extended_ids = cpu[0]; + + int brand[0x12] = {}; + + /* + Read CPU brand if supported. + */ + if (number_of_extended_ids >= 0x80000004) { + odin_cpuid(0x80000002, &brand[0]); + odin_cpuid(0x80000003, &brand[4]); + odin_cpuid(0x80000004, &brand[8]); + + /* + Some CPUs like ` Intel(R) Xeon(R) CPU E5-1650 v2 @ 3.50GHz` may include leading spaces. Trim them. + */ + char * brand_name = (char *)&brand[0]; + for (; brand_name[0] == ' '; brand_name++) {} + + gb_printf("%s\n", brand_name); + } else { + gb_printf("Unable to retrieve.\n"); + } + + #elif defined(GB_CPU_ARM) + 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 + } + #elif defined(GB_CPU_RISCV) + #if defined(GB_ARCH_64_BIT) + gb_printf("RISCV64\n"); + #else + gb_printf("RISCV32\n"); + #endif + #else + gb_printf("Unknown\n"); + #endif +} + +/* + Report the amount of installed RAM. +*/ +gb_internal void report_ram_info() { + gb_printf("\tRAM: "); + + #if defined(GB_SYSTEM_WINDOWS) + MEMORYSTATUSEX statex; + statex.dwLength = sizeof(statex); + GlobalMemoryStatusEx (&statex); + + gb_printf("%lld MiB\n", statex.ullTotalPhys / gb_megabytes(1)); + + #elif defined(GB_SYSTEM_LINUX) + /* + Retrieve RAM info using `sysinfo()`, + */ + struct sysinfo info; + int result = sysinfo(&info); + + if (result == 0x0) { + gb_printf("%lu MiB\n", (unsigned long)(info.totalram * info.mem_unit / gb_megabytes(1))); + } else { + gb_printf("Unknown.\n"); + } + #elif defined(GB_SYSTEM_OSX) + uint64_t ram_amount; + size_t val_size = sizeof(ram_amount); + + int mibs[] = { CTL_HW, HW_MEMSIZE }; + 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); + + int mibs[] = { CTL_HW, HW_PHYSMEM64 }; + 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_FREEBSD) + uint64_t ram_amount; + size_t val_size = sizeof(ram_amount); + + int mibs[] = { CTL_HW, HW_PHYSMEM }; + if (sysctl(mibs, 2, &ram_amount, &val_size, NULL, 0) != -1) { + gb_printf("%lu MiB\n", ram_amount / gb_megabytes(1)); + } + #else + gb_printf("Unknown.\n"); + #endif +} + +gb_internal void report_os_info() { + gb_printf("\tOS: "); + + #if defined(GB_SYSTEM_WINDOWS) + /* + NOTE(Jeroen): + `GetVersionEx` will return 6.2 for Windows 10 unless the program is manifested for Windows 10. + `RtlGetVersion` will return the true version. + + Rather than include the WinDDK, we ask the kernel directly. + + `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion` is for the minor build version (Update Build Release) + + */ + OSVERSIONINFOEXW osvi; + ZeroMemory(&osvi, sizeof(OSVERSIONINFOEXW)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW); + + typedef NTSTATUS (WINAPI* RtlGetVersionPtr)(OSVERSIONINFOW*); + typedef BOOL (WINAPI* GetProductInfoPtr)(DWORD dwOSMajorVersion, DWORD dwOSMinorVersion, DWORD dwSpMajorVersion, DWORD dwSpMinorVersion, PDWORD pdwReturnedProductType); + + // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-rtlgetversion + RtlGetVersionPtr RtlGetVersion = (RtlGetVersionPtr)GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "RtlGetVersion"); + // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getproductinfo + GetProductInfoPtr GetProductInfo = (GetProductInfoPtr)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetProductInfo"); + + NTSTATUS status = {}; + DWORD ProductType = {}; + if (RtlGetVersion != nullptr) { + status = RtlGetVersion((OSVERSIONINFOW*)&osvi); + } + + if (RtlGetVersion == nullptr || status != 0x0) { + gb_printf("Windows (Unknown Version)"); + } else { + if (GetProductInfo != nullptr) { + GetProductInfo(osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.wServicePackMajor, osvi.wServicePackMinor, &ProductType); + } + + if (false) { + gb_printf("dwMajorVersion: %u\n", cast(unsigned)osvi.dwMajorVersion); + gb_printf("dwMinorVersion: %u\n", cast(unsigned)osvi.dwMinorVersion); + gb_printf("dwBuildNumber: %u\n", cast(unsigned)osvi.dwBuildNumber); + gb_printf("dwPlatformId: %u\n", cast(unsigned)osvi.dwPlatformId); + gb_printf("wServicePackMajor: %u\n", cast(unsigned)osvi.wServicePackMajor); + gb_printf("wServicePackMinor: %u\n", cast(unsigned)osvi.wServicePackMinor); + gb_printf("wSuiteMask: %u\n", cast(unsigned)osvi.wSuiteMask); + gb_printf("wProductType: %u\n", cast(unsigned)osvi.wProductType); + } + + gb_printf("Windows "); + + switch (osvi.dwMajorVersion) { + case 10: + /* + Windows 10 (Pro), Windows 2016 Server, Windows 2019 Server, Windows 2022 Server + */ + switch (osvi.wProductType) { + case VER_NT_WORKSTATION: // Workstation + if (osvi.dwBuildNumber < 22000) { + gb_printf("10 "); + } else { + gb_printf("11 "); + } + + report_windows_product_type(ProductType); + + break; + default: // Server or Domain Controller + switch(osvi.dwBuildNumber) { + case 14393: + gb_printf("2016 Server"); + break; + case 17763: + gb_printf("2019 Server"); + break; + case 20348: + gb_printf("2022 Server"); + break; + default: + gb_printf("Unknown Server"); + break; + } + } + break; + case 6: + switch (osvi.dwMinorVersion) { + case 0: + switch (osvi.wProductType) { + case VER_NT_WORKSTATION: + gb_printf("Windows Vista "); + report_windows_product_type(ProductType); + break; + case 3: + gb_printf("Windows Server 2008"); + break; + } + break; + + case 1: + switch (osvi.wProductType) { + case VER_NT_WORKSTATION: + gb_printf("Windows 7 "); + report_windows_product_type(ProductType); + break; + case 3: + gb_printf("Windows Server 2008 R2"); + break; + } + break; + case 2: + switch (osvi.wProductType) { + case VER_NT_WORKSTATION: + gb_printf("Windows 8 "); + report_windows_product_type(ProductType); + break; + case 3: + gb_printf("Windows Server 2012"); + break; + } + break; + case 3: + switch (osvi.wProductType) { + case VER_NT_WORKSTATION: + gb_printf("Windows 8.1 "); + report_windows_product_type(ProductType); + break; + case 3: + gb_printf("Windows Server 2012 R2"); + break; + } + break; + } + break; + case 5: + switch (osvi.dwMinorVersion) { + case 0: + gb_printf("Windows 2000"); + break; + case 1: + gb_printf("Windows XP"); + break; + case 2: + gb_printf("Windows Server 2003"); + break; + } + break; + default: + break; + } + + /* + Grab Windows DisplayVersion (like 20H02) + */ + LPDWORD ValueType = {}; + DWORD UBR; + char DisplayVersion[256]; + DWORD ValueSize = 256; + + status = RegGetValue( + HKEY_LOCAL_MACHINE, + TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), + TEXT("DisplayVersion"), + RRF_RT_REG_SZ, + ValueType, + DisplayVersion, + &ValueSize + ); + + if (status == 0x0) { + gb_printf(" (version: %s)", DisplayVersion); + } + + /* + Now print build number. + */ + gb_printf(", build %u", cast(unsigned)osvi.dwBuildNumber); + + ValueSize = sizeof(UBR); + status = RegGetValue( + HKEY_LOCAL_MACHINE, + TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), + TEXT("UBR"), + RRF_RT_REG_DWORD, + ValueType, + &UBR, + &ValueSize + ); + + if (status == 0x0) { + gb_printf(".%u", cast(unsigned)UBR); + } + gb_printf("\n"); + } + #elif defined(GB_SYSTEM_LINUX) + /* + Try to parse `/etc/os-release` for `PRETTY_NAME="Ubuntu 20.04.3 LTS` + */ + gbAllocator a = heap_allocator(); + + gbFileContents release = gb_file_read_contents(a, 1, "/etc/os-release"); + defer (gb_file_free_contents(&release)); + + b32 found = 0; + if (release.size) { + char *start = (char *)release.data; + char *end = (char *)release.data + release.size; + const char *needle = "PRETTY_NAME=\""; + isize needle_len = gb_strlen((needle)); + + char *c = start; + for (; c < end; c++) { + if (gb_strncmp(c, needle, needle_len) == 0) { + found = 1; + start = c + needle_len; + break; + } + } + + if (found) { + for (c = start; c < end; c++) { + if (*c == '"') { + // Found the closing quote. Replace it with \0 + *c = 0; + gb_printf("%s", (char *)start); + break; + } else if (*c == '\n') { + found = 0; + } + } + } + } + + if (!found) { + gb_printf("Unknown Linux Distro"); + } + + /* + Print kernel info using `uname()` syscall, https://linux.die.net/man/2/uname + */ + char buffer[1024]; + uname((struct utsname *)&buffer[0]); + + struct utsname *info; + info = (struct utsname *)&buffer[0]; + + gb_printf(", %s %s\n", info->sysname, info->release); + + #elif defined(GB_SYSTEM_OSX) + gbString sw_vers = gb_string_make(heap_allocator(), ""); + if (!system_exec_command_line_app_output("sw_vers --productVersion", &sw_vers)) { + gb_printf("macOS Unknown\n"); + return; + } + + uint32_t major, minor, patch; + + if (sscanf(cast(const char *)sw_vers, "%u.%u.%u", &major, &minor, &patch) != 3) { + gb_printf("macOS Unknown\n"); + return; + } + + switch (major) { + case 15: gb_printf("macOS Sequoia"); break; + case 14: gb_printf("macOS Sonoma"); break; + case 13: gb_printf("macOS Ventura"); break; + case 12: gb_printf("macOS Monterey"); break; + case 11: gb_printf("macOS Big Sur"); break; + case 10: + { + switch (minor) { + case 15: gb_printf("macOS Catalina"); break; + case 14: gb_printf("macOS Mojave"); break; + case 13: gb_printf("macOS High Sierra"); break; + case 12: gb_printf("macOS Sierra"); break; + case 11: gb_printf("OS X El Capitan"); break; + case 10: gb_printf("OS X Yosemite"); break; + default: gb_printf("macOS Unknown"); + }; + break; + } + default: + gb_printf("macOS Unknown"); + }; + + gb_printf(" %d.%d.%d (build ", major, minor, patch); + + b32 build_found = 1; + b32 darwin_found = 1; + + #define MACOS_VERSION_BUFFER_SIZE 100 + char build_buffer[MACOS_VERSION_BUFFER_SIZE]; + char darwin_buffer[MACOS_VERSION_BUFFER_SIZE]; + size_t build_buffer_size = MACOS_VERSION_BUFFER_SIZE - 1; + size_t darwin_buffer_size = MACOS_VERSION_BUFFER_SIZE - 1; + #undef MACOS_VERSION_BUFFER_SIZE + + int build_mibs[] = { CTL_KERN, KERN_OSVERSION }; + if (sysctl(build_mibs, 2, build_buffer, &build_buffer_size, NULL, 0) == -1) { + build_found = 0; + } + + int darwin_mibs[] = { CTL_KERN, KERN_OSRELEASE }; + if (sysctl(darwin_mibs, 2, darwin_buffer, &darwin_buffer_size, NULL, 0) == -1) { + darwin_found = 0; + } else { + if (sscanf(darwin_buffer, "%u.%u.%u", &major, &minor, &patch) != 3) { + darwin_found = 0; + } + } + + if (build_found) { + gb_printf("%s, kernel ", build_buffer); + } else { + gb_printf("Unknown, kernel "); + } + + if (darwin_found) { + gb_printf("%s)\n", darwin_buffer); + } else { + gb_printf("Unknown)\n"); + } + + #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 { + #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 + char buffer[freebsd_version_buffer]; + size_t buffer_size = freebsd_version_buffer - 1; + #undef freebsd_version_buffer + + int mibs[] = { CTL_KERN, KERN_VERSION }; + if (sysctl(mibs, 2, buffer, &buffer_size, NULL, 0) == -1) { + gb_printf("FreeBSD: Unknown\n"); + } else { + // KERN_VERSION can end in a \n, replace it with a \0 + for (int i = 0; i < buffer_size; i += 1) { + if (buffer[i] == '\n') buffer[i] = 0; + } + gb_printf("%s", &buffer[0]); + + // Retrieve kernel revision using `sysctl`, e.g. 199506 + mibs[1] = KERN_OSREV; + uint64_t revision; + size_t revision_size = sizeof(revision); + + if (sysctl(mibs, 2, &revision, &revision_size, NULL, 0) == -1) { + gb_printf("\n"); + } else { + gb_printf(", revision %ld\n", revision); + } + } + #else + gb_printf("Unknown"); + #endif +} + +gb_internal void report_backend_info() { + gb_printf("\tBackend: LLVM %s\n", LLVM_VERSION_STRING); +} + +// NOTE(Jeroen): `odin report` prints some system information for easier bug reporting. +gb_internal void print_bug_report_help() { + gb_printf("Where to find more information and get into contact when you encounter a bug:\n\n"); + gb_printf("\tWebsite: https://odin-lang.org\n"); + gb_printf("\tGitHub: https://github.com/odin-lang/Odin/issues\n"); + /* + Uncomment and update URL once we have a Discord vanity URL. For now people can get here from the site. + gb_printf("\tDiscord: https://discord.com/invite/sVBPHEv\n"); + */ + gb_printf("\n\n"); + + gb_printf("Useful information to add to a bug report:\n\n"); + + gb_printf("\tOdin: %.*s", LIT(ODIN_VERSION)); + + #ifdef NIGHTLY + gb_printf("-nightly"); + #endif + + #ifdef GIT_SHA + gb_printf(":%s", GIT_SHA); + #endif + + gb_printf("\n"); + + /* + Print OS information. + */ + report_os_info(); + + /* + Now print CPU info. + */ + report_cpu_info(); + + /* + And RAM info. + */ + report_ram_info(); + + report_backend_info(); +} diff --git a/src/build_cpuid.cpp b/src/build_cpuid.cpp new file mode 100644 index 000000000..b7ba5dcdf --- /dev/null +++ b/src/build_cpuid.cpp @@ -0,0 +1,35 @@ +#if !defined(GB_COMPILER_MSVC) + #if defined(GB_CPU_X86) + #include + #endif +#endif + +gb_internal void odin_cpuid(int leaf, int result[]) { + #if defined(GB_CPU_ARM) || defined(GB_CPU_RISCV) + return; + + #elif defined(GB_CPU_X86) + + #if defined(GB_COMPILER_MSVC) + __cpuid(result, leaf); + #else + __get_cpuid(leaf, (unsigned int*)&result[0], (unsigned int*)&result[1], (unsigned int*)&result[2], (unsigned int*)&result[3]); + #endif + + #endif +} + +gb_internal bool should_use_march_native() { + #if !defined(GB_CPU_X86) + return false; + + #else + + int cpu[4]; + odin_cpuid(0x1, &cpu[0]); // Get feature information in ECX + EDX + + bool have_popcnt = cpu[2] & (1 << 23); // bit 23 in ECX = popcnt + return !have_popcnt; + + #endif +} \ No newline at end of file diff --git a/src/build_settings.cpp b/src/build_settings.cpp index ffb276d1e..93168cf77 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -2,6 +2,7 @@ #include #include #endif +#include "build_cpuid.cpp" // #if defined(GB_SYSTEM_WINDOWS) // #define DEFAULT_TO_THREADED_CHECKER @@ -18,9 +19,12 @@ enum TargetOsKind : u16 { TargetOs_essence, TargetOs_freebsd, TargetOs_openbsd, + TargetOs_netbsd, + TargetOs_haiku, TargetOs_wasi, TargetOs_js, + TargetOs_orca, TargetOs_freestanding, @@ -36,6 +40,7 @@ enum TargetArchKind : u16 { TargetArch_arm64, TargetArch_wasm32, TargetArch_wasm64p32, + TargetArch_riscv64, TargetArch_COUNT, }; @@ -56,6 +61,24 @@ enum TargetABIKind : u16 { TargetABI_COUNT, }; +enum Windows_Subsystem : u8 { + Windows_Subsystem_BOOT_APPLICATION, + Windows_Subsystem_CONSOLE, // Default, + Windows_Subsystem_EFI_APPLICATION, + Windows_Subsystem_EFI_BOOT_SERVICE_DRIVER, + Windows_Subsystem_EFI_ROM, + Windows_Subsystem_EFI_RUNTIME_DRIVER, + Windows_Subsystem_NATIVE, + Windows_Subsystem_POSIX, + Windows_Subsystem_WINDOWS, + Windows_Subsystem_WINDOWSCE, + Windows_Subsystem_COUNT, +}; + +struct MicroarchFeatureList { + String microarch; + String features; +}; gb_global String target_os_names[TargetOs_COUNT] = { str_lit(""), @@ -65,9 +88,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"), }; @@ -80,8 +106,11 @@ gb_global String target_arch_names[TargetArch_COUNT] = { str_lit("arm64"), str_lit("wasm32"), str_lit("wasm64p32"), + str_lit("riscv64"), }; +#include "build_settings_microarch.cpp" + gb_global String target_endian_names[TargetEndian_COUNT] = { str_lit("little"), str_lit("big"), @@ -103,14 +132,25 @@ gb_global TargetEndianKind target_endians[TargetArch_COUNT] = { TargetEndian_Little, }; +gb_global String windows_subsystem_names[Windows_Subsystem_COUNT] = { + str_lit("BOOT_APPLICATION"), + str_lit("CONSOLE"), // Default + str_lit("EFI_APPLICATION"), + str_lit("EFI_BOOT_SERVICE_DRIVER"), + str_lit("EFI_ROM"), + str_lit("EFI_RUNTIME_DRIVER"), + str_lit("NATIVE"), + str_lit("POSIX"), + str_lit("WINDOWS"), + str_lit("WINDOWSCE"), +}; + #ifndef ODIN_VERSION_RAW #define ODIN_VERSION_RAW "dev-unknown-unknown" #endif gb_global String const ODIN_VERSION = str_lit(ODIN_VERSION_RAW); - - struct TargetMetrics { TargetOsKind os; TargetArchKind arch; @@ -119,7 +159,6 @@ struct TargetMetrics { isize max_align; isize max_simd_align; String target_triplet; - String target_data_layout; TargetABIKind abi; }; @@ -151,6 +190,7 @@ struct QueryDataSetSettings { enum BuildModeKind { BuildMode_Executable, BuildMode_DynamicLibrary, + BuildMode_StaticLibrary, BuildMode_Object, BuildMode_Assembly, BuildMode_LLVM_IR, @@ -198,6 +238,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 @@ -229,15 +275,22 @@ enum BuildPath : u8 { }; enum VetFlags : u64 { - VetFlag_NONE = 0, - VetFlag_Unused = 1u<<0, // 1 - VetFlag_Shadowing = 1u<<1, // 2 - VetFlag_UsingStmt = 1u<<2, // 4 - VetFlag_UsingParam = 1u<<3, // 8 - VetFlag_Style = 1u<<4, // 16 - VetFlag_Semicolon = 1u<<5, // 32 + VetFlag_NONE = 0, + VetFlag_Shadowing = 1u<<0, + VetFlag_UsingStmt = 1u<<1, + VetFlag_UsingParam = 1u<<2, + VetFlag_Style = 1u<<3, + 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_UnusedProcedures = 1u<<10, - VetFlag_All = VetFlag_Unused|VetFlag_Shadowing|VetFlag_UsingStmt, + VetFlag_Unused = VetFlag_UnusedVariables|VetFlag_UnusedImports, + + VetFlag_All = VetFlag_Unused|VetFlag_Shadowing|VetFlag_UsingStmt|VetFlag_Deprecated|VetFlag_Cast, VetFlag_Using = VetFlag_UsingStmt|VetFlag_UsingParam, }; @@ -245,6 +298,10 @@ enum VetFlags : u64 { u64 get_vet_flag_from_name(String const &name) { if (name == "unused") { return VetFlag_Unused; + } else if (name == "unused-variables") { + return VetFlag_UnusedVariables; + } else if (name == "unused-imports") { + return VetFlag_UnusedImports; } else if (name == "shadowing") { return VetFlag_Shadowing; } else if (name == "using-stmt") { @@ -255,10 +312,30 @@ 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; + } else if (name == "unused-procedures") { + return VetFlag_UnusedProcedures; } return VetFlag_NONE; } +enum OptInFeatureFlags : u64 { + OptInFeatureFlag_NONE = 0, + OptInFeatureFlag_DynamicLiterals = 1u<<0, +}; + +u64 get_feature_flag_from_name(String const &name) { + if (name == "dynamic-literals") { + return OptInFeatureFlag_DynamicLiterals; + } + return OptInFeatureFlag_NONE; +} + enum SanitizerFlags : u32 { SanitizerFlag_NONE = 0, @@ -267,20 +344,48 @@ 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; +}; + + +enum LinkerChoice : i32 { + Linker_Invalid = -1, + Linker_Default = 0, + Linker_lld, + Linker_radlink, + + Linker_COUNT, +}; + +String linker_choices[Linker_COUNT] = { + str_lit("default"), + str_lit("lld"), + str_lit("radlink"), +}; // This stores the information for the specify architecture of this build struct BuildContext { // Constants - String ODIN_OS; // target operating system - String ODIN_ARCH; // target architecture - String ODIN_VENDOR; // compiler vendor - String ODIN_VERSION; // compiler version - String ODIN_ROOT; // Odin ROOT - String ODIN_BUILD_PROJECT_NAME; // Odin main/initial package's directory name - bool ODIN_DEBUG; // Odin in debug mode - bool ODIN_DISABLE_ASSERT; // Whether the default 'assert' et al is disabled in code or not + String ODIN_OS; // Target operating system + String ODIN_ARCH; // Target architecture + String ODIN_VENDOR; // Compiler vendor + String ODIN_VERSION; // Compiler version + String ODIN_ROOT; // Odin ROOT + String ODIN_BUILD_PROJECT_NAME; // Odin main/initial package's directory name + String ODIN_WINDOWS_SUBSYSTEM; // Empty string for non-Windows targets + bool ODIN_DEBUG; // Odin in debug mode + bool ODIN_DISABLE_ASSERT; // Whether the default 'assert' et al is disabled in code or not bool ODIN_DEFAULT_TO_NIL_ALLOCATOR; // Whether the default allocator is a "nil" allocator or not (i.e. it does nothing) + bool ODIN_DEFAULT_TO_PANIC_ALLOCATOR; // Whether the default allocator is a "panic" allocator or not (i.e. panics on any call to it) bool ODIN_FOREIGN_ERROR_PROCEDURES; bool ODIN_VALGRIND_SUPPORT; @@ -310,6 +415,7 @@ struct BuildContext { u64 vet_flags; u32 sanitizer_flags; + StringSet vet_packages; bool has_resource; String link_flags; @@ -323,49 +429,73 @@ 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; bool no_bounds_check; - bool no_dynamic_literals; + bool no_type_assert; bool no_output_files; bool no_crt; + bool no_rpath; bool no_entry_point; bool no_thread_local; - bool use_lld; bool cross_compiling; bool different_os; bool keep_object_files; bool disallow_do; + LinkerChoice linker_choice; + + StringSet custom_attributes; + bool strict_style; bool ignore_warnings; bool warnings_as_errors; bool hide_error_line; + bool terse_errors; + bool json_errors; bool has_ansi_terminal_colours; + bool fast_isel; bool ignore_lazy; bool ignore_llvm_build; + bool ignore_panic; - bool use_subsystem_windows; 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 internal_by_value; + bool no_threaded_checker; bool show_debug_messages; - + bool copy_file_contents; bool no_rtti; bool dynamic_map_calls; + bool obfuscate_source_code_locations; + + bool min_link_libs; + + bool print_linker_flags; + RelocMode reloc_mode; bool disable_red_zone; @@ -377,17 +507,19 @@ struct BuildContext { u32 cmd_doc_flags; Array extra_packages; - StringSet test_names; + bool test_all_packages; gbAffinity affinity; isize thread_count; 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; }; gb_global BuildContext build_context = {0}; @@ -411,95 +543,136 @@ 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 + +#if LLVM_VERSION_MAJOR >= 18 + #define I386_MAX_ALIGNMENT 16 +#else + #define I386_MAX_ALIGNMENT 4 +#endif gb_global TargetMetrics target_windows_i386 = { TargetOs_windows, TargetArch_i386, - 4, 4, 4, 8, + 4, 4, I386_MAX_ALIGNMENT, 16, str_lit("i386-pc-windows-msvc"), }; gb_global TargetMetrics target_windows_amd64 = { TargetOs_windows, TargetArch_amd64, - 8, 8, 8, 16, + 8, 8, AMD64_MAX_ALIGNMENT, 32, 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 = { TargetOs_linux, TargetArch_i386, - 4, 4, 4, 8, + 4, 4, I386_MAX_ALIGNMENT, 16, str_lit("i386-pc-linux-gnu"), - }; gb_global TargetMetrics target_linux_amd64 = { TargetOs_linux, TargetArch_amd64, - 8, 8, 8, 16, + 8, 8, AMD64_MAX_ALIGNMENT, 32, 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, 32, 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"), + 4, 4, 8, 16, + str_lit("arm-unknown-linux-gnueabihf"), +}; +gb_global TargetMetrics target_linux_riscv64 = { + TargetOs_linux, + TargetArch_riscv64, + 8, 8, 16, 32, + str_lit("riscv64-linux-gnu"), }; gb_global TargetMetrics target_darwin_amd64 = { TargetOs_darwin, TargetArch_amd64, - 8, 8, 8, 16, - str_lit("x86_64-apple-darwin"), - str_lit("e-m:o-i64:64-f80:128-n8:16:32:64-S128"), + 8, 8, AMD64_MAX_ALIGNMENT, 32, + str_lit("x86_64-apple-macosx"), // NOTE: Changes during initialization based on build flags. }; gb_global TargetMetrics target_darwin_arm64 = { TargetOs_darwin, TargetArch_arm64, - 8, 8, 8, 16, - str_lit("arm64-apple-macosx11.0.0"), - str_lit("e-m:o-i64:64-i128:128-n32:64-S128"), + 8, 8, 16, 32, + str_lit("arm64-apple-macosx"), // NOTE: Changes during initialization based on build flags. }; gb_global TargetMetrics target_freebsd_i386 = { TargetOs_freebsd, TargetArch_i386, - 4, 4, 4, 8, + 4, 4, I386_MAX_ALIGNMENT, 16, str_lit("i386-unknown-freebsd-elf"), }; gb_global TargetMetrics target_freebsd_amd64 = { TargetOs_freebsd, TargetArch_amd64, - 8, 8, 8, 16, + 8, 8, AMD64_MAX_ALIGNMENT, 32, 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, 32, + 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, 32, 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, 32, + str_lit("x86_64-unknown-netbsd-elf"), +}; + +gb_global TargetMetrics target_netbsd_arm64 = { + TargetOs_netbsd, + TargetArch_arm64, + 8, 8, 16, 32, + str_lit("aarch64-unknown-netbsd-elf"), +}; + +gb_global TargetMetrics target_haiku_amd64 = { + TargetOs_haiku, + TargetArch_amd64, + 8, 8, AMD64_MAX_ALIGNMENT, 32, + 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, 32, str_lit("x86_64-pc-none-elf"), }; @@ -509,7 +682,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 = { @@ -517,7 +689,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 = { @@ -525,7 +696,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"), }; @@ -534,7 +712,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 = { @@ -542,7 +719,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 = { @@ -550,7 +726,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"), }; @@ -558,12 +733,38 @@ 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, 32, 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, AMD64_MAX_ALIGNMENT, 32, + str_lit("x86_64-pc-none-msvc"), + TargetABI_Win64, +}; + +gb_global TargetMetrics target_freestanding_arm64 = { + TargetOs_freestanding, + TargetArch_arm64, + 8, 8, 16, 32, + str_lit("aarch64-none-elf"), +}; + +gb_global TargetMetrics target_freestanding_arm32 = { + TargetOs_freestanding, + TargetArch_arm32, + 4, 4, 8, 16, + str_lit("arm-unknown-unknown-gnueabihf"), +}; +gb_global TargetMetrics target_freestanding_riscv64 = { + TargetOs_freestanding, + TargetArch_riscv64, + 8, 8, 16, 32, + str_lit("riscv64-unknown-gnu"), +}; struct NamedTargetMetrics { @@ -581,24 +782,37 @@ gb_global NamedTargetMetrics named_targets[] = { { str_lit("linux_amd64"), &target_linux_amd64 }, { str_lit("linux_arm64"), &target_linux_arm64 }, { str_lit("linux_arm32"), &target_linux_arm32 }, + { str_lit("linux_riscv64"), &target_linux_riscv64 }, { str_lit("windows_i386"), &target_windows_i386 }, { str_lit("windows_amd64"), &target_windows_amd64 }, { 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 }, { 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 }, { str_lit("wasi_wasm64p32"), &target_wasi_wasm64p32 }, - { str_lit("freestanding_amd64_sysv"), &target_freestanding_amd64_sysv }, + { str_lit("freestanding_amd64_sysv"), &target_freestanding_amd64_sysv }, + { str_lit("freestanding_amd64_win64"), &target_freestanding_amd64_win64 }, + + { str_lit("freestanding_arm64"), &target_freestanding_arm64 }, + { str_lit("freestanding_arm32"), &target_freestanding_arm32 }, + + { str_lit("freestanding_riscv64"), &target_freestanding_riscv64 }, }; gb_global NamedTargetMetrics *selected_target_metrics; @@ -623,7 +837,6 @@ gb_internal TargetArchKind get_target_arch_from_string(String str) { return TargetArch_Invalid; } - gb_internal bool is_excluded_target_filename(String name) { String original_name = name; name = remove_extension_from_path(name); @@ -722,15 +935,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 @@ -754,13 +958,11 @@ gb_internal String odin_root_dir(void) { char const *found = gb_get_env("ODIN_ROOT", a); if (found) { String path = path_to_full_path(a, make_string_c(found)); - if (path[path.len-1] != '/' && path[path.len-1] != '\\') { #if defined(GB_SYSTEM_WINDOWS) - path = concatenate_strings(a, path, WIN32_SEPARATOR_STRING); + path = normalize_path(a, path, WIN32_SEPARATOR_STRING); #else - path = concatenate_strings(a, path, NIX_SEPARATOR_STRING); + path = normalize_path(a, path, NIX_SEPARATOR_STRING); #endif - } global_module_path = path; global_module_path_set = true; @@ -820,11 +1022,11 @@ gb_internal String internal_odin_root_dir(void) { return path; } -#elif defined(GB_SYSTEM_OSX) +#elif defined(GB_SYSTEM_HAIKU) -#include +#include -gb_internal String path_to_fullpath(gbAllocator a, String s); +gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_); gb_internal String internal_odin_root_dir(void) { String path = global_module_path; @@ -836,6 +1038,59 @@ gb_internal String internal_odin_root_dir(void) { } auto path_buf = array_make(heap_allocator(), 300); + defer (array_free(&path_buf)); + + len = 0; + for (;;) { + u32 sz = path_buf.count; + int res = find_path(B_APP_IMAGE_SYMBOL, B_FIND_PATH_IMAGE_PATH, nullptr, &path_buf[0], sz); + if(res == B_OK) { + len = sz; + break; + } else { + array_resize(&path_buf, sz + 1); + } + } + + mutex_lock(&string_buffer_mutex); + defer (mutex_unlock(&string_buffer_mutex)); + + text = gb_alloc_array(permanent_allocator(), u8, len + 1); + gb_memmove(text, &path_buf[0], len); + + path = path_to_fullpath(heap_allocator(), make_string(text, len), nullptr); + + for (i = path.len-1; i >= 0; i--) { + u8 c = path[i]; + if (c == '/' || c == '\\') { + break; + } + path.len--; + } + + global_module_path = path; + global_module_path_set = true; + + return path; +} + +#elif defined(GB_SYSTEM_OSX) + +#include + +gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_); + +gb_internal String internal_odin_root_dir(void) { + String path = global_module_path; + isize len, i; + u8 *text; + + if (global_module_path_set) { + return global_module_path; + } + + auto path_buf = array_make(heap_allocator(), 300); + defer (array_free(&path_buf)); len = 0; for (;;) { @@ -855,7 +1110,7 @@ gb_internal String internal_odin_root_dir(void) { text = gb_alloc_array(permanent_allocator(), u8, len + 1); gb_memmove(text, &path_buf[0], len); - path = path_to_fullpath(heap_allocator(), make_string(text, len)); + path = path_to_fullpath(heap_allocator(), make_string(text, len), nullptr); for (i = path.len-1; i >= 0; i--) { u8 c = path[i]; @@ -868,9 +1123,6 @@ gb_internal String internal_odin_root_dir(void) { global_module_path = path; global_module_path_set = true; - - // array_free(&path_buf); - return path; } #else @@ -878,7 +1130,7 @@ gb_internal String internal_odin_root_dir(void) { // NOTE: Linux / Unix is unfinished and not tested very well. #include -gb_internal String path_to_fullpath(gbAllocator a, String s); +gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_); gb_internal String internal_odin_root_dir(void) { String path = global_module_path; @@ -1020,7 +1272,7 @@ gb_internal String internal_odin_root_dir(void) { gb_memmove(text, &path_buf[0], len); - path = path_to_fullpath(heap_allocator(), make_string(text, len)); + path = path_to_fullpath(heap_allocator(), make_string(text, len), nullptr); for (i = path.len-1; i >= 0; i--) { u8 c = path[i]; if (c == '/' || c == '\\') { @@ -1039,7 +1291,7 @@ gb_internal String internal_odin_root_dir(void) { gb_global BlockingMutex fullpath_mutex; #if defined(GB_SYSTEM_WINDOWS) -gb_internal String path_to_fullpath(gbAllocator a, String s) { +gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_) { String result = {}; String16 string16 = string_to_string16(heap_allocator(), s); @@ -1065,27 +1317,70 @@ gb_internal String path_to_fullpath(gbAllocator a, String s) { result.text[i] = '/'; } } + if (ok_) *ok_ = true; } else { + if (ok_) *ok_ = false; mutex_unlock(&fullpath_mutex); } return result; } #elif defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_UNIX) -gb_internal String path_to_fullpath(gbAllocator a, String s) { + +struct PathToFullpathResult { + String result; + bool ok; +}; + +gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_) { + static gb_thread_local StringMap cache; + + PathToFullpathResult *cached = string_map_get(&cache, s); + if (cached != nullptr) { + if (ok_) *ok_ = cached->ok; + return copy_string(a, cached->result); + } + char *p; - mutex_lock(&fullpath_mutex); p = realpath(cast(char *)s.text, 0); - mutex_unlock(&fullpath_mutex); - if(p == nullptr) return String{}; - return make_string_c(p); + defer (free(p)); + if(p == nullptr) { + if (ok_) *ok_ = false; + + // Path doesn't exist or is malformed, Windows's `GetFullPathNameW` does not check for + // existence of the file where `realpath` does, which causes different behaviour between platforms. + // Two things could be done here: + // 1. clean the path and resolve it manually, just like the Windows function does, + // probably requires porting `filepath.clean` from Odin and doing some more processing. + // 2. just return a copy of the original path. + // + // I have opted for 2 because it is much simpler + we already return `ok = false` + further + // checks and processes will use the path and cause errors (which we want). + String result = copy_string(a, s); + + PathToFullpathResult cached_result = {}; + cached_result.result = copy_string(permanent_allocator(), result); + cached_result.ok = false; + string_map_set(&cache, copy_string(permanent_allocator(), s), cached_result); + + return result; + } + if (ok_) *ok_ = true; + String result = copy_string(a, make_string_c(p)); + + PathToFullpathResult cached_result = {}; + cached_result.result = copy_string(permanent_allocator(), result); + cached_result.ok = true; + string_map_set(&cache, copy_string(permanent_allocator(), s), cached_result); + + return result; } #else #error Implement system #endif -gb_internal String get_fullpath_relative(gbAllocator a, String base_dir, String path) { +gb_internal String get_fullpath_relative(gbAllocator a, String base_dir, String path, bool *ok_) { u8 *str = gb_alloc_array(heap_allocator(), u8, base_dir.len+1+path.len+1); defer (gb_free(heap_allocator(), str)); @@ -1107,11 +1402,31 @@ gb_internal String get_fullpath_relative(gbAllocator a, String base_dir, String String res = make_string(str, i); res = string_trim_whitespace(res); - return path_to_fullpath(a, res); + return path_to_fullpath(a, res, ok_); } -gb_internal String get_fullpath_core(gbAllocator a, String path) { +gb_internal String get_fullpath_base_collection(gbAllocator a, String path, bool *ok_) { + String module_dir = odin_root_dir(); + + String base = str_lit("base/"); + + isize str_len = module_dir.len + base.len + path.len; + u8 *str = gb_alloc_array(heap_allocator(), u8, str_len+1); + defer (gb_free(heap_allocator(), str)); + + isize i = 0; + gb_memmove(str+i, module_dir.text, module_dir.len); i += module_dir.len; + gb_memmove(str+i, base.text, base.len); i += base.len; + gb_memmove(str+i, path.text, path.len); i += path.len; + str[i] = 0; + + String res = make_string(str, i); + res = string_trim_whitespace(res); + return path_to_fullpath(a, res, ok_); +} + +gb_internal String get_fullpath_core_collection(gbAllocator a, String path, bool *ok_) { String module_dir = odin_root_dir(); String core = str_lit("core/"); @@ -1128,14 +1443,21 @@ gb_internal String get_fullpath_core(gbAllocator a, String path) { String res = make_string(str, i); res = string_trim_whitespace(res); - return path_to_fullpath(a, res); + return path_to_fullpath(a, res, ok_); } gb_internal bool show_error_line(void) { - return !build_context.hide_error_line; + return !build_context.hide_error_line && !build_context.json_errors; +} + +gb_internal bool terse_errors(void) { + return build_context.terse_errors; +} +gb_internal bool json_errors(void) { + return build_context.json_errors; } gb_internal bool has_ansi_terminal_colours(void) { - return build_context.has_ansi_terminal_colours; + return build_context.has_ansi_terminal_colours && !json_errors(); } gb_internal bool has_asm_extension(String const &path) { @@ -1220,14 +1542,38 @@ 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) metrics = &target_linux_arm64; + #elif defined(GB_CPU_RISCV) + metrics = &target_linux_riscv64; #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; @@ -1258,32 +1604,16 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta GB_ASSERT(metrics->int_size == 2*metrics->ptr_size); } - - bc->metrics = *metrics; - switch (subtarget) { - case Subtarget_Default: - break; - case Subtarget_iOS: - GB_ASSERT(metrics->os == TargetOs_darwin); - if (metrics->arch == TargetArch_arm64) { - bc->metrics.target_triplet = str_lit("arm64-apple-ios"); - } else if (metrics->arch == TargetArch_amd64) { - bc->metrics.target_triplet = str_lit("x86_64-apple-ios"); - } else { - GB_PANIC("Unknown architecture for darwin"); - } - break; - } - bc->ODIN_OS = target_os_names[metrics->os]; - bc->ODIN_ARCH = target_arch_names[metrics->arch]; - bc->endian_kind = target_endians[metrics->arch]; - bc->ptr_size = metrics->ptr_size; - bc->int_size = metrics->int_size; - bc->max_align = metrics->max_align; - bc->max_simd_align = metrics->max_simd_align; - bc->link_flags = str_lit(" "); + bc->ODIN_OS = target_os_names[metrics->os]; + bc->ODIN_ARCH = target_arch_names[metrics->arch]; + bc->endian_kind = target_endians[metrics->arch]; + bc->ptr_size = metrics->ptr_size; + bc->int_size = metrics->int_size; + bc->max_align = metrics->max_align; + bc->max_simd_align = metrics->max_simd_align; + bc->link_flags = str_lit(" "); #if defined(DEFAULT_TO_THREADED_CHECKER) bc->threaded_checker = true; @@ -1305,90 +1635,101 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta } } - // 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 "); + // Default to subsystem:CONSOLE on Windows targets + if (bc->ODIN_WINDOWS_SUBSYSTEM == "" && bc->metrics.os == TargetOs_windows) { + bc->ODIN_WINDOWS_SUBSYSTEM = windows_subsystem_names[Windows_Subsystem_CONSOLE]; + } + + 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: - 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; - } - } 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; + } if(bc->metrics.arch == TargetArch_riscv64 && bc->cross_compiling) { + bc->link_flags = str_lit("-target riscv64 "); } 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; } @@ -1401,6 +1742,10 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta break; } } + + if (bc->metrics.os == TargetOs_freestanding) { + bc->ODIN_DEFAULT_TO_NIL_ALLOCATOR = !bc->ODIN_DEFAULT_TO_PANIC_ALLOCATOR; + } } #if defined(GB_SYSTEM_WINDOWS) @@ -1409,48 +1754,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; } } @@ -1458,52 +1785,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) { - 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; - 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++] = '"'; - 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. @@ -1533,11 +1866,6 @@ gb_internal bool init_build_paths(String init_filename) { produces_output_file = true; } - - if (build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR) { - bc->no_dynamic_literals = true; - } - if (!produces_output_file) { // Command doesn't produce output files. We're done. return true; @@ -1546,10 +1874,12 @@ gb_internal bool init_build_paths(String init_filename) { #if defined(GB_SYSTEM_WINDOWS) if (bc->metrics.os == TargetOs_windows) { if (bc->resource_filepath.len > 0) { - bc->build_paths[BuildPath_RC] = path_from_string(ha, bc->resource_filepath); - bc->build_paths[BuildPath_RES] = path_from_string(ha, bc->resource_filepath); - bc->build_paths[BuildPath_RC].ext = copy_string(ha, STR_LIT("rc")); - bc->build_paths[BuildPath_RES].ext = copy_string(ha, STR_LIT("res")); + bc->build_paths[BuildPath_RES] = path_from_string(ha, bc->resource_filepath); + if (!string_ends_with(bc->resource_filepath, str_lit(".res"))) { + bc->build_paths[BuildPath_RES].ext = copy_string(ha, STR_LIT("res")); + bc->build_paths[BuildPath_RC] = path_from_string(ha, bc->resource_filepath); + bc->build_paths[BuildPath_RC].ext = copy_string(ha, STR_LIT("rc")); + } } if (bc->pdb_filepath.len > 0) { @@ -1565,7 +1895,7 @@ gb_internal bool init_build_paths(String init_filename) { return false; } - if (!build_context.use_lld && find_result.vs_exe_path.len == 0) { + if (build_context.linker_choice == Linker_Default && find_result.vs_exe_path.len == 0) { gb_printf_err("link.exe not found.\n"); return false; } @@ -1637,7 +1967,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"); @@ -1725,25 +2060,33 @@ gb_internal bool init_build_paths(String init_filename) { // Do we have an extension? We might not if the output filename was supplied. if (bc->build_paths[BuildPath_Output].ext.len == 0) { - if (build_context.metrics.os == TargetOs_windows || build_context.build_mode != BuildMode_Executable) { + if (build_context.metrics.os == TargetOs_windows || is_arch_wasm() || build_context.build_mode != BuildMode_Executable) { bc->build_paths[BuildPath_Output].ext = copy_string(ha, output_extension); } } + String output_file = path_to_string(ha, bc->build_paths[BuildPath_Output]); + defer (gb_free(ha, output_file.text)); + // Check if output path is a directory. if (path_is_directory(bc->build_paths[BuildPath_Output])) { - String output_file = path_to_string(ha, bc->build_paths[BuildPath_Output]); - defer (gb_free(ha, output_file.text)); gb_printf_err("Output path %.*s is a directory.\n", LIT(output_file)); return false; } - if (!write_directory(bc->build_paths[BuildPath_Output].basename)) { - String output_file = path_to_string(ha, bc->build_paths[BuildPath_Output]); - defer (gb_free(ha, output_file.text)); - gb_printf_err("No write permissions for output path: %.*s\n", LIT(output_file)); - return false; - } + // gbFile output_file_test; + // const char* output_file_name = (const char*)output_file.text; + // gbFileError output_test_err = gb_file_open_mode(&output_file_test, gbFileMode_Append | gbFileMode_Rw, output_file_name); + + // if (output_test_err == 0) { + // gb_file_close(&output_file_test); + // gb_file_remove(output_file_name); + // } else { + // String output_file = path_to_string(ha, bc->build_paths[BuildPath_Output]); + // defer (gb_free(ha, output_file.text)); + // gb_printf_err("No write permissions for output path: %.*s\n", LIT(output_file)); + // return false; + // } if (build_context.sanitizer_flags & SanitizerFlag_Address) { switch (build_context.metrics.os) { @@ -1781,9 +2124,18 @@ gb_internal bool init_build_paths(String init_filename) { } } - - if (bc->target_features_string.len != 0) { - enable_target_feature({}, bc->target_features_string); + if (build_context.no_crt && !build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR && !build_context.ODIN_DEFAULT_TO_PANIC_ALLOCATOR) { + switch (build_context.metrics.os) { + case TargetOs_linux: + case TargetOs_darwin: + case TargetOs_essence: + case TargetOs_freebsd: + case TargetOs_openbsd: + case TargetOs_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; + } } return true; diff --git a/src/build_settings_microarch.cpp b/src/build_settings_microarch.cpp new file mode 100644 index 000000000..8f64d4026 --- /dev/null +++ b/src/build_settings_microarch.cpp @@ -0,0 +1,516 @@ +// 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,arrowlake,arrowlake-s,arrowlake_s,athlon,athlon-4,athlon-fx,athlon-mp,athlon-tbird,athlon-xp,athlon64,athlon64-sse3,atom,atom_sse4_2,atom_sse4_2_movbe,barcelona,bdver1,bdver2,bdver3,bdver4,bonnell,broadwell,btver1,btver2,c3,c3-2,cannonlake,cascadelake,clearwaterforest,cooperlake,core-avx-i,core-avx2,core2,core_2_duo_sse4_1,core_2_duo_ssse3,core_2nd_gen_avx,core_3rd_gen_avx,core_4th_gen_avx,core_4th_gen_avx_tsx,core_5th_gen_avx,core_5th_gen_avx_tsx,core_aes_pclmulqdq,core_i7_sse4_2,corei7,corei7-avx,emeraldrapids,generic,geode,goldmont,goldmont-plus,goldmont_plus,gracemont,grandridge,graniterapids,graniterapids-d,graniterapids_d,haswell,i386,i486,i586,i686,icelake-client,icelake-server,icelake_client,icelake_server,ivybridge,k6,k6-2,k6-3,k8,k8-sse3,knl,knm,lakemont,lunarlake,meteorlake,mic_avx512,nehalem,nocona,opteron,opteron-sse3,pantherlake,penryn,pentium,pentium-m,pentium-mmx,pentium2,pentium3,pentium3m,pentium4,pentium4m,pentium_4,pentium_4_sse3,pentium_ii,pentium_iii,pentium_iii_no_xmm_regs,pentium_m,pentium_mmx,pentium_pro,pentiumpro,prescott,raptorlake,rocketlake,sandybridge,sapphirerapids,sierraforest,silvermont,skx,skylake,skylake-avx512,skylake_avx512,slm,tigerlake,tremont,westmere,winchip-c6,winchip2,x86-64,x86-64-v2,x86-64-v3,x86-64-v4,yonah,znver1,znver2,znver3,znver4"), + // TargetArch_i386: + str_lit("alderlake,amdfam10,arrowlake,arrowlake-s,arrowlake_s,athlon,athlon-4,athlon-fx,athlon-mp,athlon-tbird,athlon-xp,athlon64,athlon64-sse3,atom,atom_sse4_2,atom_sse4_2_movbe,barcelona,bdver1,bdver2,bdver3,bdver4,bonnell,broadwell,btver1,btver2,c3,c3-2,cannonlake,cascadelake,clearwaterforest,cooperlake,core-avx-i,core-avx2,core2,core_2_duo_sse4_1,core_2_duo_ssse3,core_2nd_gen_avx,core_3rd_gen_avx,core_4th_gen_avx,core_4th_gen_avx_tsx,core_5th_gen_avx,core_5th_gen_avx_tsx,core_aes_pclmulqdq,core_i7_sse4_2,corei7,corei7-avx,emeraldrapids,generic,geode,goldmont,goldmont-plus,goldmont_plus,gracemont,grandridge,graniterapids,graniterapids-d,graniterapids_d,haswell,i386,i486,i586,i686,icelake-client,icelake-server,icelake_client,icelake_server,ivybridge,k6,k6-2,k6-3,k8,k8-sse3,knl,knm,lakemont,lunarlake,meteorlake,mic_avx512,nehalem,nocona,opteron,opteron-sse3,pantherlake,penryn,pentium,pentium-m,pentium-mmx,pentium2,pentium3,pentium3m,pentium4,pentium4m,pentium_4,pentium_4_sse3,pentium_ii,pentium_iii,pentium_iii_no_xmm_regs,pentium_m,pentium_mmx,pentium_pro,pentiumpro,prescott,raptorlake,rocketlake,sandybridge,sapphirerapids,sierraforest,silvermont,skx,skylake,skylake-avx512,skylake_avx512,slm,tigerlake,tremont,westmere,winchip-c6,winchip2,x86-64,x86-64-v2,x86-64-v3,x86-64-v4,yonah,znver1,znver2,znver3,znver4"), + // TargetArch_arm32: + str_lit("arm1020e,arm1020t,arm1022e,arm10e,arm10tdmi,arm1136j-s,arm1136jf-s,arm1156t2-s,arm1156t2f-s,arm1176jz-s,arm1176jzf-s,arm710t,arm720t,arm7tdmi,arm7tdmi-s,arm8,arm810,arm9,arm920,arm920t,arm922t,arm926ej-s,arm940t,arm946e-s,arm966e-s,arm968e-s,arm9e,arm9tdmi,cortex-a12,cortex-a15,cortex-a17,cortex-a32,cortex-a35,cortex-a5,cortex-a53,cortex-a55,cortex-a57,cortex-a7,cortex-a710,cortex-a72,cortex-a73,cortex-a75,cortex-a76,cortex-a76ae,cortex-a77,cortex-a78,cortex-a78c,cortex-a8,cortex-a9,cortex-m0,cortex-m0plus,cortex-m1,cortex-m23,cortex-m3,cortex-m33,cortex-m35p,cortex-m4,cortex-m52,cortex-m55,cortex-m7,cortex-m85,cortex-r4,cortex-r4f,cortex-r5,cortex-r52,cortex-r7,cortex-r8,cortex-x1,cortex-x1c,cyclone,ep9312,exynos-m3,exynos-m4,exynos-m5,generic,iwmmxt,krait,kryo,mpcore,mpcorenovfp,neoverse-n1,neoverse-n2,neoverse-v1,sc000,sc300,strongarm,strongarm110,strongarm1100,strongarm1110,swift,xscale"), + // TargetArch_arm64: + str_lit("a64fx,ampere1,ampere1a,ampere1b,apple-a10,apple-a11,apple-a12,apple-a13,apple-a14,apple-a15,apple-a16,apple-a17,apple-a7,apple-a8,apple-a9,apple-latest,apple-m1,apple-m2,apple-m3,apple-s4,apple-s5,carmel,cortex-a34,cortex-a35,cortex-a510,cortex-a520,cortex-a53,cortex-a55,cortex-a57,cortex-a65,cortex-a65ae,cortex-a710,cortex-a715,cortex-a72,cortex-a720,cortex-a73,cortex-a75,cortex-a76,cortex-a76ae,cortex-a77,cortex-a78,cortex-a78c,cortex-r82,cortex-x1,cortex-x1c,cortex-x2,cortex-x3,cortex-x4,cyclone,exynos-m3,exynos-m4,exynos-m5,falkor,generic,kryo,neoverse-512tvb,neoverse-e1,neoverse-n1,neoverse-n2,neoverse-v1,neoverse-v2,saphira,thunderx,thunderx2t99,thunderx3t110,thunderxt81,thunderxt83,thunderxt88,tsv110"), + // TargetArch_wasm32: + str_lit("bleeding-edge,generic,mvp"), + // TargetArch_wasm64p32: + str_lit("bleeding-edge,generic,mvp"), + // TargetArch_riscv64: + str_lit("generic,generic-rv32,generic-rv64,rocket,rocket-rv32,rocket-rv64,sifive-7-series,sifive-e20,sifive-e21,sifive-e24,sifive-e31,sifive-e34,sifive-e76,sifive-p450,sifive-p670,sifive-s21,sifive-s51,sifive-s54,sifive-s76,sifive-u54,sifive-u74,sifive-x280,syntacore-scr1-base,syntacore-scr1-max,veyron-v1,xiangshan-nanhu"), +}; + +// Generated with the featuregen script in `misc/featuregen` +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,avx10.1-256,avx10.1-512,avx2,avx512bf16,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512er,avx512f,avx512fp16,avx512ifma,avx512pf,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vp2intersect,avx512vpopcntdq,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,branchfusion,ccmp,cf,cldemote,clflushopt,clwb,clzero,cmov,cmpccxadd,crc32,cx16,cx8,egpr,enqcmd,ermsb,evex512,f16c,false-deps-getmant,false-deps-lzcnt-tzcnt,false-deps-mulc,false-deps-mullq,false-deps-perm,false-deps-popcnt,false-deps-range,fast-11bytenop,fast-15bytenop,fast-7bytenop,fast-bextr,fast-gather,fast-hops,fast-lzcnt,fast-movbe,fast-scalar-fsqrt,fast-scalar-shift-masks,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fast-vector-shift-masks,faster-shift-than-shuffle,fma,fma4,fsgsbase,fsrm,fxsr,gfni,harden-sls-ijmp,harden-sls-ret,hreset,idivl-to-divb,idivq-to-divl,invpcid,kl,lea-sp,lea-uses-ag,lvi-cfi,lvi-load-hardening,lwp,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,mwaitx,ndd,no-bypass-delay,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pad-short-functions,pclmul,pconfig,pku,popcnt,ppx,prefer-128-bit,prefer-256-bit,prefer-mask-registers,prefer-movmsk-over-vtest,prefer-no-gather,prefer-no-scatter,prefetchi,prefetchwt1,prfchw,ptwrite,push2pop2,raoint,rdpid,rdpru,rdrnd,rdseed,retpoline,retpoline-external-thunk,retpoline-indirect-branches,retpoline-indirect-calls,rtm,sahf,sbb-dep-breaking,serialize,seses,sgx,sha,sha512,shstk,slow-3ops-lea,slow-incdec,slow-lea,slow-pmaddwd,slow-pmulld,slow-shld,slow-two-mem-ops,slow-unaligned-mem-16,slow-unaligned-mem-32,sm3,sm4,soft-float,sse,sse-unaligned-mem,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,tagged-globals,tbm,tsxldtrk,tuning-fast-imm-vector-shift,uintr,use-glm-div-sqrt-costs,use-slm-arith-costs,usermsr,vaes,vpclmulqdq,vzeroupper,waitpkg,wbnoinvd,widekl,x87,xop,xsave,xsavec,xsaveopt,xsaves"), + // TargetArch_i386: + str_lit("16bit-mode,32bit-mode,3dnow,3dnowa,64bit,64bit-mode,adx,aes,allow-light-256-bit,amx-bf16,amx-complex,amx-fp16,amx-int8,amx-tile,avx,avx10.1-256,avx10.1-512,avx2,avx512bf16,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512er,avx512f,avx512fp16,avx512ifma,avx512pf,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vp2intersect,avx512vpopcntdq,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,branchfusion,ccmp,cf,cldemote,clflushopt,clwb,clzero,cmov,cmpccxadd,crc32,cx16,cx8,egpr,enqcmd,ermsb,evex512,f16c,false-deps-getmant,false-deps-lzcnt-tzcnt,false-deps-mulc,false-deps-mullq,false-deps-perm,false-deps-popcnt,false-deps-range,fast-11bytenop,fast-15bytenop,fast-7bytenop,fast-bextr,fast-gather,fast-hops,fast-lzcnt,fast-movbe,fast-scalar-fsqrt,fast-scalar-shift-masks,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fast-vector-shift-masks,faster-shift-than-shuffle,fma,fma4,fsgsbase,fsrm,fxsr,gfni,harden-sls-ijmp,harden-sls-ret,hreset,idivl-to-divb,idivq-to-divl,invpcid,kl,lea-sp,lea-uses-ag,lvi-cfi,lvi-load-hardening,lwp,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,mwaitx,ndd,no-bypass-delay,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pad-short-functions,pclmul,pconfig,pku,popcnt,ppx,prefer-128-bit,prefer-256-bit,prefer-mask-registers,prefer-movmsk-over-vtest,prefer-no-gather,prefer-no-scatter,prefetchi,prefetchwt1,prfchw,ptwrite,push2pop2,raoint,rdpid,rdpru,rdrnd,rdseed,retpoline,retpoline-external-thunk,retpoline-indirect-branches,retpoline-indirect-calls,rtm,sahf,sbb-dep-breaking,serialize,seses,sgx,sha,sha512,shstk,slow-3ops-lea,slow-incdec,slow-lea,slow-pmaddwd,slow-pmulld,slow-shld,slow-two-mem-ops,slow-unaligned-mem-16,slow-unaligned-mem-32,sm3,sm4,soft-float,sse,sse-unaligned-mem,sse2,sse3,sse4.1,sse4.2,sse4a,ssse3,tagged-globals,tbm,tsxldtrk,tuning-fast-imm-vector-shift,uintr,use-glm-div-sqrt-costs,use-slm-arith-costs,usermsr,vaes,vpclmulqdq,vzeroupper,waitpkg,wbnoinvd,widekl,x87,xop,xsave,xsavec,xsaveopt,xsaves"), + // TargetArch_arm32: + str_lit("32bit,8msecext,a12,a15,a17,a32,a35,a5,a53,a55,a57,a7,a72,a73,a75,a76,a77,a78c,a8,a9,aapcs-frame-chain,aapcs-frame-chain-leaf,aclass,acquire-release,aes,armv4,armv4t,armv5t,armv5te,armv5tej,armv6,armv6-m,armv6j,armv6k,armv6kz,armv6s-m,armv6t2,armv7-a,armv7-m,armv7-r,armv7e-m,armv7k,armv7s,armv7ve,armv8-a,armv8-m.base,armv8-m.main,armv8-r,armv8.1-a,armv8.1-m.main,armv8.2-a,armv8.3-a,armv8.4-a,armv8.5-a,armv8.6-a,armv8.7-a,armv8.8-a,armv8.9-a,armv9-a,armv9.1-a,armv9.2-a,armv9.3-a,armv9.4-a,armv9.5-a,atomics-32,avoid-movs-shop,avoid-partial-cpsr,bf16,big-endian-instructions,cde,cdecp0,cdecp1,cdecp2,cdecp3,cdecp4,cdecp5,cdecp6,cdecp7,cheap-predicable-cpsr,clrbhb,cortex-a710,cortex-a78,cortex-x1,cortex-x1c,crc,crypto,d32,db,dfb,disable-postra-scheduler,dont-widen-vmovs,dotprod,dsp,execute-only,expand-fp-mlx,exynos,fix-cmse-cve-2021-35465,fix-cortex-a57-aes-1742098,fp-armv8,fp-armv8d16,fp-armv8d16sp,fp-armv8sp,fp16,fp16fml,fp64,fpao,fpregs,fpregs16,fpregs64,fullfp16,fuse-aes,fuse-literals,harden-sls-blr,harden-sls-nocomdat,harden-sls-retbr,hwdiv,hwdiv-arm,i8mm,iwmmxt,iwmmxt2,krait,kryo,lob,long-calls,loop-align,m3,m7,mclass,mp,muxed-units,mve,mve.fp,mve1beat,mve2beat,mve4beat,nacl-trap,neon,neon-fpmovs,neonfp,neoverse-v1,no-branch-predictor,no-bti-at-return-twice,no-movt,no-neg-immediates,noarm,nonpipelined-vfp,pacbti,perfmon,prefer-ishst,prefer-vmovsr,prof-unpr,r4,r5,r52,r7,ras,rclass,read-tp-tpidrprw,read-tp-tpidruro,read-tp-tpidrurw,reserve-r9,ret-addr-stack,sb,sha2,slow-fp-brcc,slow-load-D-subreg,slow-odd-reg,slow-vdup32,slow-vgetlni32,slowfpvfmx,slowfpvmlx,soft-float,splat-vfp-neon,strict-align,swift,thumb-mode,thumb2,trustzone,use-mipipeliner,use-misched,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8,v8.1a,v8.1m.main,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8.7a,v8.8a,v8.9a,v8m,v8m.main,v9.1a,v9.2a,v9.3a,v9.4a,v9.5a,v9a,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization,vldn-align,vmlx-forwarding,vmlx-hazards,wide-stride-vfp,xscale,zcz"), + // TargetArch_arm64: + str_lit("CONTEXTIDREL2,a35,a510,a520,a53,a55,a57,a64fx,a65,a710,a715,a72,a720,a73,a75,a76,a77,a78,a78c,addr-lsl-fast,aes,aggressive-fma,all,alternate-sextload-cvt-f32-pattern,altnzcv,alu-lsl-fast,am,ampere1,ampere1a,ampere1b,amvs,apple-a10,apple-a11,apple-a12,apple-a13,apple-a14,apple-a15,apple-a16,apple-a17,apple-a7,apple-a7-sysreg,arith-bcc-fusion,arith-cbz-fusion,ascend-store-address,b16b16,balance-fp-ops,bf16,brbe,bti,call-saved-x10,call-saved-x11,call-saved-x12,call-saved-x13,call-saved-x14,call-saved-x15,call-saved-x18,call-saved-x8,call-saved-x9,carmel,ccdp,ccidx,ccpp,chk,clrbhb,cmp-bcc-fusion,complxnum,cortex-r82,cortex-x1,cortex-x2,cortex-x3,cortex-x4,cpa,crc,crypto,cssc,d128,disable-latency-sched-heuristic,disable-ldp,disable-stp,dit,dotprod,ecv,el2vmsa,el3,enable-select-opt,ete,exynos-cheap-as-move,exynosm3,exynosm4,f32mm,f64mm,falkor,faminmax,fgt,fix-cortex-a53-835769,flagm,fmv,force-32bit-jump-tables,fp-armv8,fp16fml,fp8,fp8dot2,fp8dot4,fp8fma,fpmr,fptoint,fullfp16,fuse-address,fuse-addsub-2reg-const1,fuse-adrp-add,fuse-aes,fuse-arith-logic,fuse-crypto-eor,fuse-csel,fuse-literals,gcs,harden-sls-blr,harden-sls-nocomdat,harden-sls-retbr,hbc,hcx,i8mm,ite,jsconv,kryo,ldp-aligned-only,lor,ls64,lse,lse128,lse2,lut,mec,mops,mpam,mte,neon,neoverse512tvb,neoversee1,neoversen1,neoversen2,neoversev1,neoversev2,nmi,no-bti-at-return-twice,no-neg-immediates,no-sve-fp-ld1r,no-zcz-fp,nv,outline-atomics,pan,pan-rwv,pauth,pauth-lr,perfmon,predictable-select-expensive,predres,prfm-slc-target,rand,ras,rasv2,rcpc,rcpc-immo,rcpc3,rdm,reserve-x1,reserve-x10,reserve-x11,reserve-x12,reserve-x13,reserve-x14,reserve-x15,reserve-x18,reserve-x2,reserve-x20,reserve-x21,reserve-x22,reserve-x23,reserve-x24,reserve-x25,reserve-x26,reserve-x27,reserve-x28,reserve-x3,reserve-x30,reserve-x4,reserve-x5,reserve-x6,reserve-x7,reserve-x9,rme,saphira,sb,sel2,sha2,sha3,slow-misaligned-128store,slow-paired-128,slow-strqro-store,sm4,sme,sme-f16f16,sme-f64f64,sme-f8f16,sme-f8f32,sme-fa64,sme-i16i64,sme-lutv2,sme2,sme2p1,spe,spe-eef,specres2,specrestrict,ssbs,ssve-fp8dot2,ssve-fp8dot4,ssve-fp8fma,store-pair-suppress,stp-aligned-only,strict-align,sve,sve2,sve2-aes,sve2-bitperm,sve2-sha3,sve2-sm4,sve2p1,tagged-globals,the,thunderx,thunderx2t99,thunderx3t110,thunderxt81,thunderxt83,thunderxt88,tlb-rmi,tlbiw,tme,tpidr-el1,tpidr-el2,tpidr-el3,tpidrro-el0,tracev8.4,trbe,tsv110,uaops,use-experimental-zeroing-pseudos,use-postra-scheduler,use-reciprocal-square-root,use-scalar-inc-vl,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8.7a,v8.8a,v8.9a,v8a,v8r,v9.1a,v9.2a,v9.3a,v9.4a,v9.5a,v9a,vh,wfxt,xs,zcm,zcz,zcz-fp-workaround,zcz-gp"), + // TargetArch_wasm32: + str_lit("atomics,bulk-memory,exception-handling,extended-const,multimemory,multivalue,mutable-globals,nontrapping-fptoint,reference-types,relaxed-simd,sign-ext,simd128,tail-call"), + // TargetArch_wasm64p32: + str_lit("atomics,bulk-memory,exception-handling,extended-const,multimemory,multivalue,mutable-globals,nontrapping-fptoint,reference-types,relaxed-simd,sign-ext,simd128,tail-call"), + // TargetArch_riscv64: + str_lit("32bit,64bit,a,auipc-addi-fusion,c,conditional-cmv-fusion,d,dlen-factor-2,e,experimental,experimental-zacas,experimental-zcmop,experimental-zfbfmin,experimental-zicfilp,experimental-zicfiss,experimental-zimop,experimental-ztso,experimental-zvfbfmin,experimental-zvfbfwma,f,fast-unaligned-access,forced-atomics,h,i,ld-add-fusion,lui-addi-fusion,m,no-default-unroll,no-optimized-zero-stride-load,no-rvc-hints,relax,reserve-x1,reserve-x10,reserve-x11,reserve-x12,reserve-x13,reserve-x14,reserve-x15,reserve-x16,reserve-x17,reserve-x18,reserve-x19,reserve-x2,reserve-x20,reserve-x21,reserve-x22,reserve-x23,reserve-x24,reserve-x25,reserve-x26,reserve-x27,reserve-x28,reserve-x29,reserve-x3,reserve-x30,reserve-x31,reserve-x4,reserve-x5,reserve-x6,reserve-x7,reserve-x8,reserve-x9,save-restore,seq-cst-trailing-fence,shifted-zextw-fusion,short-forward-branch-opt,sifive7,smaia,smepmp,ssaia,svinval,svnapot,svpbmt,tagged-globals,unaligned-scalar-mem,use-postra-scheduler,v,ventana-veyron,xcvalu,xcvbi,xcvbitmanip,xcvelw,xcvmac,xcvmem,xcvsimd,xsfvcp,xsfvfnrclipxfqf,xsfvfwmaccqqq,xsfvqmaccdod,xsfvqmaccqoq,xtheadba,xtheadbb,xtheadbs,xtheadcmo,xtheadcondmov,xtheadfmemidx,xtheadmac,xtheadmemidx,xtheadmempair,xtheadsync,xtheadvdot,xventanacondops,za128rs,za64rs,zawrs,zba,zbb,zbc,zbkb,zbkc,zbkx,zbs,zca,zcb,zcd,zce,zcf,zcmp,zcmt,zdinx,zexth-fusion,zextw-fusion,zfa,zfh,zfhmin,zfinx,zhinx,zhinxmin,zic64b,zicbom,zicbop,zicboz,ziccamoa,ziccif,zicclsm,ziccrse,zicntr,zicond,zicsr,zifencei,zihintntl,zihintpause,zihpm,zk,zkn,zknd,zkne,zknh,zkr,zks,zksed,zksh,zkt,zmmul,zvbb,zvbc,zve32f,zve32x,zve64d,zve64f,zve64x,zvfh,zvfhmin,zvkb,zvkg,zvkn,zvknc,zvkned,zvkng,zvknha,zvknhb,zvks,zvksc,zvksed,zvksg,zvksh,zvkt,zvl1024b,zvl128b,zvl16384b,zvl2048b,zvl256b,zvl32768b,zvl32b,zvl4096b,zvl512b,zvl64b,zvl65536b,zvl8192b"), +}; + +// Generated with the featuregen script in `misc/featuregen` +gb_global int target_microarch_counts[TargetArch_COUNT] = { + // TargetArch_Invalid: + 0, + // TargetArch_amd64: + 127, + // TargetArch_i386: + 127, + // TargetArch_arm32: + 91, + // TargetArch_arm64: + 69, + // TargetArch_wasm32: + 3, + // TargetArch_wasm64p32: + 3, + // TargetArch_riscv64: + 26, +}; + +// 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("arrowlake"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("arrowlake-s"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,sha512,shstk,slow-3ops-lea,sm3,sm4,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("arrowlake_s"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,sha512,shstk,slow-3ops-lea,sm3,sm4,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("athlon"), str_lit("3dnow,3dnowa,64bit-mode,cmov,cx8,mmx,nopl,slow-shld,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("athlon-4"), str_lit("3dnow,3dnowa,64bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-shld,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("athlon-fx"), str_lit("3dnow,3dnowa,64bit,64bit-mode,cmov,cx8,fast-scalar-shift-masks,fxsr,mmx,nopl,sbb-dep-breaking,slow-shld,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { 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("clearwaterforest"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prefetchi,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,sha512,shstk,slow-3ops-lea,sm3,sm4,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,usermsr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("cooperlake"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avx512bf16,avx512bw,avx512cd,avx512dq,avx512f,avx512vl,avx512vnni,bmi,bmi2,clflushopt,clwb,cmov,crc32,cx16,cx8,ermsb,evex512,f16c,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,faster-shift-than-shuffle,fma,fsgsbase,fxsr,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pku,popcnt,prefer-256-bit,prfchw,rdrnd,rdseed,sahf,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("core-avx-i"), str_lit("64bit,64bit-mode,avx,cmov,crc32,cx16,cx8,f16c,false-deps-popcnt,fast-15bytenop,fast-scalar-fsqrt,fast-shld-rotate,fsgsbase,fxsr,idivq-to-divl,macrofusion,mmx,no-bypass-delay-mov,nopl,pclmul,popcnt,rdrnd,sahf,slow-3ops-lea,slow-unaligned-mem-32,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave,xsaveopt") }, + { str_lit("core-avx2"), str_lit("64bit,64bit-mode,allow-light-256-bit,avx,avx2,bmi,bmi2,cmov,crc32,cx16,cx8,ermsb,f16c,false-deps-lzcnt-tzcnt,false-deps-popcnt,fast-15bytenop,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fma,fsgsbase,fxsr,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,popcnt,rdrnd,sahf,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave,xsaveopt") }, + { 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("gracemont"), str_lit("64bit,64bit-mode,adx,aes,avx,avx2,avxvnni,bmi,bmi2,cldemote,clflushopt,clwb,cmov,crc32,cx16,cx8,f16c,false-deps-popcnt,fast-15bytenop,fast-scalar-fsqrt,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivl-to-divb,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,nopl,pclmul,pconfig,pku,popcnt,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("grandridge"), str_lit("64bit,64bit-mode,adx,aes,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,fast-movbe,fma,fsgsbase,fxsr,gfni,hreset,invpcid,kl,lzcnt,mmx,movbe,movdir64b,movdiri,no-bypass-delay,nopl,pclmul,pconfig,pku,popcnt,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,slow-incdec,slow-lea,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,uintr,use-glm-div-sqrt-costs,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("graniterapids"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,amx-bf16,amx-fp16,amx-int8,amx-tile,avx,avx2,avx512bf16,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512fp16,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vpopcntdq,avxvnni,bmi,bmi2,cldemote,clflushopt,clwb,cmov,crc32,cx16,cx8,enqcmd,ermsb,evex512,f16c,false-deps-getmant,false-deps-mulc,false-deps-mullq,false-deps-perm,false-deps-range,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fsrm,fxsr,gfni,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-256-bit,prefetchi,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tsxldtrk,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,wbnoinvd,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("graniterapids-d"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,amx-bf16,amx-complex,amx-fp16,amx-int8,amx-tile,avx,avx2,avx512bf16,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512fp16,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vpopcntdq,avxvnni,bmi,bmi2,cldemote,clflushopt,clwb,cmov,crc32,cx16,cx8,enqcmd,ermsb,evex512,f16c,false-deps-getmant,false-deps-mulc,false-deps-mullq,false-deps-perm,false-deps-range,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fsrm,fxsr,gfni,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-256-bit,prefetchi,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tsxldtrk,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,wbnoinvd,x87,xsave,xsavec,xsaveopt,xsaves") }, + { 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("lunarlake"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,sha512,shstk,slow-3ops-lea,sm3,sm4,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("meteorlake"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avxvnni,bmi,bmi2,cldemote,clflushopt,clwb,cmov,crc32,cx16,cx8,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("mic_avx512"), str_lit("64bit,64bit-mode,adx,aes,avx,avx2,avx512cd,avx512er,avx512f,avx512pf,bmi,bmi2,cmov,crc32,cx16,cx8,evex512,f16c,fast-gather,fast-movbe,fma,fsgsbase,fxsr,idivq-to-divl,lzcnt,mmx,movbe,nopl,pclmul,popcnt,prefer-mask-registers,prefetchwt1,prfchw,rdrnd,rdseed,sahf,slow-3ops-lea,slow-incdec,slow-pmaddwd,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,x87,xsave,xsaveopt") }, + { str_lit("nehalem"), str_lit("64bit,64bit-mode,cmov,crc32,cx16,cx8,fxsr,macrofusion,mmx,no-bypass-delay-mov,nopl,popcnt,sahf,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87") }, + { str_lit("nocona"), str_lit("64bit,64bit-mode,cmov,cx16,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,sse3,vzeroupper,x87") }, + { str_lit("opteron"), str_lit("3dnow,3dnowa,64bit,64bit-mode,cmov,cx8,fast-scalar-shift-masks,fxsr,mmx,nopl,sbb-dep-breaking,slow-shld,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("opteron-sse3"), str_lit("3dnow,3dnowa,64bit,64bit-mode,cmov,cx16,cx8,fast-scalar-shift-masks,fxsr,mmx,nopl,sbb-dep-breaking,slow-shld,slow-unaligned-mem-16,sse,sse2,sse3,vzeroupper,x87") }, + { str_lit("pantherlake"), str_lit("64bit,64bit-mode,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prefetchi,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,sha512,shstk,slow-3ops-lea,sm3,sm4,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("penryn"), str_lit("64bit,64bit-mode,cmov,cx16,cx8,fxsr,macrofusion,mmx,nopl,sahf,slow-unaligned-mem-16,sse,sse2,sse3,sse4.1,ssse3,vzeroupper,x87") }, + { str_lit("pentium"), str_lit("64bit-mode,cx8,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("pentium-m"), str_lit("64bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { 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("arrowlake"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("arrowlake-s"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,sha512,shstk,slow-3ops-lea,sm3,sm4,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("arrowlake_s"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,sha512,shstk,slow-3ops-lea,sm3,sm4,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("athlon"), str_lit("32bit-mode,3dnow,3dnowa,cmov,cx8,mmx,nopl,slow-shld,slow-unaligned-mem-16,vzeroupper,x87") }, + { str_lit("athlon-4"), str_lit("32bit-mode,3dnow,3dnowa,cmov,cx8,fxsr,mmx,nopl,slow-shld,slow-unaligned-mem-16,sse,vzeroupper,x87") }, + { str_lit("athlon-fx"), str_lit("32bit-mode,3dnow,3dnowa,64bit,cmov,cx8,fast-scalar-shift-masks,fxsr,mmx,nopl,sbb-dep-breaking,slow-shld,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { 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("clearwaterforest"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prefetchi,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,sha512,shstk,slow-3ops-lea,sm3,sm4,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,usermsr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("cooperlake"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avx512bf16,avx512bw,avx512cd,avx512dq,avx512f,avx512vl,avx512vnni,bmi,bmi2,clflushopt,clwb,cmov,crc32,cx16,cx8,ermsb,evex512,f16c,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,faster-shift-than-shuffle,fma,fsgsbase,fxsr,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pku,popcnt,prefer-256-bit,prfchw,rdrnd,rdseed,sahf,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vzeroupper,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("core-avx-i"), str_lit("32bit-mode,64bit,avx,cmov,crc32,cx16,cx8,f16c,false-deps-popcnt,fast-15bytenop,fast-scalar-fsqrt,fast-shld-rotate,fsgsbase,fxsr,idivq-to-divl,macrofusion,mmx,no-bypass-delay-mov,nopl,pclmul,popcnt,rdrnd,sahf,slow-3ops-lea,slow-unaligned-mem-32,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave,xsaveopt") }, + { str_lit("core-avx2"), str_lit("32bit-mode,64bit,allow-light-256-bit,avx,avx2,bmi,bmi2,cmov,crc32,cx16,cx8,ermsb,f16c,false-deps-lzcnt-tzcnt,false-deps-popcnt,fast-15bytenop,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fma,fsgsbase,fxsr,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,popcnt,rdrnd,sahf,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87,xsave,xsaveopt") }, + { 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("gracemont"), str_lit("32bit-mode,64bit,adx,aes,avx,avx2,avxvnni,bmi,bmi2,cldemote,clflushopt,clwb,cmov,crc32,cx16,cx8,f16c,false-deps-popcnt,fast-15bytenop,fast-scalar-fsqrt,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivl-to-divb,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,nopl,pclmul,pconfig,pku,popcnt,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("grandridge"), str_lit("32bit-mode,64bit,adx,aes,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,fast-movbe,fma,fsgsbase,fxsr,gfni,hreset,invpcid,kl,lzcnt,mmx,movbe,movdir64b,movdiri,no-bypass-delay,nopl,pclmul,pconfig,pku,popcnt,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,slow-incdec,slow-lea,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,uintr,use-glm-div-sqrt-costs,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("graniterapids"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,amx-bf16,amx-fp16,amx-int8,amx-tile,avx,avx2,avx512bf16,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512fp16,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vpopcntdq,avxvnni,bmi,bmi2,cldemote,clflushopt,clwb,cmov,crc32,cx16,cx8,enqcmd,ermsb,evex512,f16c,false-deps-getmant,false-deps-mulc,false-deps-mullq,false-deps-perm,false-deps-range,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fsrm,fxsr,gfni,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-256-bit,prefetchi,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tsxldtrk,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,wbnoinvd,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("graniterapids-d"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,amx-bf16,amx-complex,amx-fp16,amx-int8,amx-tile,avx,avx2,avx512bf16,avx512bitalg,avx512bw,avx512cd,avx512dq,avx512f,avx512fp16,avx512ifma,avx512vbmi,avx512vbmi2,avx512vl,avx512vnni,avx512vpopcntdq,avxvnni,bmi,bmi2,cldemote,clflushopt,clwb,cmov,crc32,cx16,cx8,enqcmd,ermsb,evex512,f16c,false-deps-getmant,false-deps-mulc,false-deps-mullq,false-deps-perm,false-deps-range,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fsrm,fxsr,gfni,idivq-to-divl,invpcid,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-256-bit,prefetchi,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tsxldtrk,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,wbnoinvd,x87,xsave,xsavec,xsaveopt,xsaves") }, + { 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("lunarlake"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,sha512,shstk,slow-3ops-lea,sm3,sm4,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("meteorlake"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avxvnni,bmi,bmi2,cldemote,clflushopt,clwb,cmov,crc32,cx16,cx8,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,shstk,slow-3ops-lea,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("mic_avx512"), str_lit("32bit-mode,64bit,adx,aes,avx,avx2,avx512cd,avx512er,avx512f,avx512pf,bmi,bmi2,cmov,crc32,cx16,cx8,evex512,f16c,fast-gather,fast-movbe,fma,fsgsbase,fxsr,idivq-to-divl,lzcnt,mmx,movbe,nopl,pclmul,popcnt,prefer-mask-registers,prefetchwt1,prfchw,rdrnd,rdseed,sahf,slow-3ops-lea,slow-incdec,slow-pmaddwd,slow-two-mem-ops,sse,sse2,sse3,sse4.1,sse4.2,ssse3,x87,xsave,xsaveopt") }, + { str_lit("nehalem"), str_lit("32bit-mode,64bit,cmov,crc32,cx16,cx8,fxsr,macrofusion,mmx,no-bypass-delay-mov,nopl,popcnt,sahf,sse,sse2,sse3,sse4.1,sse4.2,ssse3,vzeroupper,x87") }, + { str_lit("nocona"), str_lit("32bit-mode,64bit,cmov,cx16,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,sse3,vzeroupper,x87") }, + { str_lit("opteron"), str_lit("32bit-mode,3dnow,3dnowa,64bit,cmov,cx8,fast-scalar-shift-masks,fxsr,mmx,nopl,sbb-dep-breaking,slow-shld,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { str_lit("opteron-sse3"), str_lit("32bit-mode,3dnow,3dnowa,64bit,cmov,cx16,cx8,fast-scalar-shift-masks,fxsr,mmx,nopl,sbb-dep-breaking,slow-shld,slow-unaligned-mem-16,sse,sse2,sse3,vzeroupper,x87") }, + { str_lit("pantherlake"), str_lit("32bit-mode,64bit,adx,aes,allow-light-256-bit,avx,avx2,avxifma,avxneconvert,avxvnni,avxvnniint16,avxvnniint8,bmi,bmi2,cldemote,clflushopt,clwb,cmov,cmpccxadd,crc32,cx16,cx8,enqcmd,f16c,false-deps-perm,false-deps-popcnt,fast-15bytenop,fast-gather,fast-scalar-fsqrt,fast-shld-rotate,fast-variable-crosslane-shuffle,fast-variable-perlane-shuffle,fast-vector-fsqrt,fma,fsgsbase,fxsr,gfni,hreset,idivq-to-divl,invpcid,kl,lzcnt,macrofusion,mmx,movbe,movdir64b,movdiri,no-bypass-delay-blend,no-bypass-delay-mov,no-bypass-delay-shuffle,nopl,pclmul,pconfig,pku,popcnt,prefer-movmsk-over-vtest,prefetchi,prfchw,ptwrite,rdpid,rdrnd,rdseed,sahf,serialize,sha,sha512,shstk,slow-3ops-lea,sm3,sm4,sse,sse2,sse3,sse4.1,sse4.2,ssse3,tuning-fast-imm-vector-shift,uintr,vaes,vpclmulqdq,vzeroupper,waitpkg,widekl,x87,xsave,xsavec,xsaveopt,xsaves") }, + { str_lit("penryn"), str_lit("32bit-mode,64bit,cmov,cx16,cx8,fxsr,macrofusion,mmx,nopl,sahf,slow-unaligned-mem-16,sse,sse2,sse3,sse4.1,ssse3,vzeroupper,x87") }, + { str_lit("pentium"), str_lit("32bit-mode,cx8,slow-unaligned-mem-16,vzeroupper,x87") }, + { str_lit("pentium-m"), str_lit("32bit-mode,cmov,cx8,fxsr,mmx,nopl,slow-unaligned-mem-16,sse,sse2,vzeroupper,x87") }, + { 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,perfmon,ret-addr-stack,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization,vmlx-forwarding") }, + { str_lit("cortex-a15"), str_lit("a15,aclass,armv7-a,avoid-partial-cpsr,d32,db,dont-widen-vmovs,dsp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,muxed-units,perfmon,ret-addr-stack,splat-vfp-neon,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization,vldn-align") }, + { str_lit("cortex-a17"), str_lit("a17,aclass,armv7-a,avoid-partial-cpsr,d32,db,dsp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,perfmon,ret-addr-stack,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization,vmlx-forwarding") }, + { str_lit("cortex-a32"), str_lit("aclass,acquire-release,aes,armv8-a,crc,crypto,d32,db,dsp,fp-armv8,fp-armv8d16,fp-armv8d16sp,fp-armv8sp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,neon,perfmon,sha2,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization") }, + { str_lit("cortex-a35"), str_lit("a35,aclass,acquire-release,aes,armv8-a,crc,crypto,d32,db,dsp,fp-armv8,fp-armv8d16,fp-armv8d16sp,fp-armv8sp,fp16,fp64,fpregs,fpregs64,hwdiv,hwdiv-arm,mp,neon,perfmon,sha2,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,virtualization") }, + { str_lit("cortex-a5"), str_lit("a5,aclass,armv7-a,d32,db,dsp,fp16,fp64,fpregs,fpregs64,mp,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,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,nonpipelined-vfp,perfmon,ret-addr-stack,slow-fp-brcc,slowfpvfmx,slowfpvmlx,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vmlx-forwarding,vmlx-hazards") }, + { str_lit("cortex-a9"), str_lit("a9,aclass,armv7-a,avoid-partial-cpsr,d32,db,dsp,expand-fp-mlx,fp16,fp64,fpregs,fpregs64,mp,muxed-units,neon-fpmovs,perfmon,prefer-vmovsr,ret-addr-stack,thumb2,trustzone,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vldn-align,vmlx-forwarding,vmlx-hazards") }, + { str_lit("cortex-m0"), str_lit("armv6-m,db,mclass,no-branch-predictor,noarm,strict-align,thumb-mode,v4t,v5t,v5te,v6,v6m") }, + { str_lit("cortex-m0plus"), str_lit("armv6-m,db,mclass,no-branch-predictor,noarm,strict-align,thumb-mode,v4t,v5t,v5te,v6,v6m") }, + { str_lit("cortex-m1"), str_lit("armv6-m,db,mclass,no-branch-predictor,noarm,strict-align,thumb-mode,v4t,v5t,v5te,v6,v6m") }, + { 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-m52"), str_lit("8msecext,acquire-release,armv8.1-m.main,db,dsp,fp-armv8d16,fp-armv8d16sp,fp16,fp64,fpregs,fpregs16,fpregs64,fullfp16,hwdiv,lob,loop-align,mclass,mve,mve.fp,mve1beat,no-branch-predictor,noarm,pacbti,ras,slowfpvmlx,thumb-mode,thumb2,use-misched,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8.1m.main,v8m,v8m.main,vfp2,vfp2sp,vfp3d16,vfp3d16sp,vfp4d16,vfp4d16sp") }, + { str_lit("cortex-m55"), str_lit("8msecext,acquire-release,armv8.1-m.main,db,dsp,fix-cmse-cve-2021-35465,fp-armv8d16,fp-armv8d16sp,fp16,fp64,fpregs,fpregs16,fpregs64,fullfp16,hwdiv,lob,loop-align,mclass,mve,mve.fp,no-branch-predictor,noarm,ras,slowfpvmlx,thumb-mode,thumb2,use-misched,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8.1m.main,v8m,v8m.main,vfp2,vfp2sp,vfp3d16,vfp3d16sp,vfp4d16,vfp4d16sp") }, + { str_lit("cortex-m7"), str_lit("armv7e-m,db,dsp,fp-armv8d16,fp-armv8d16sp,fp16,fp64,fpregs,fpregs64,hwdiv,m7,mclass,noarm,thumb-mode,thumb2,use-mipipeliner,use-misched,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3d16,vfp3d16sp,vfp4d16,vfp4d16sp") }, + { str_lit("cortex-m85"), str_lit("8msecext,acquire-release,armv8.1-m.main,db,dsp,fp-armv8d16,fp-armv8d16sp,fp16,fp64,fpregs,fpregs16,fpregs64,fullfp16,hwdiv,lob,mclass,mve,mve.fp,noarm,pacbti,ras,thumb-mode,thumb2,use-misched,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8.1m.main,v8m,v8m.main,vfp2,vfp2sp,vfp3d16,vfp3d16sp,vfp4d16,vfp4d16sp") }, + { 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,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,neonfp,perfmon,prefer-ishst,prof-unpr,ret-addr-stack,slow-load-D-subreg,slow-odd-reg,slow-vdup32,slow-vgetlni32,slowfpvfmx,slowfpvmlx,swift,thumb2,use-misched,v4t,v5t,v5te,v6,v6k,v6m,v6t2,v7,v7clrex,v8m,vfp2,vfp2sp,vfp3,vfp3d16,vfp3d16sp,vfp3sp,vfp4,vfp4d16,vfp4d16sp,vfp4sp,vmlx-hazards,wide-stride-vfp") }, + { str_lit("xscale"), str_lit("armv5te,v4t,v5t,v5te") }, + // TargetArch_arm64: + { str_lit("a64fx"), str_lit("CONTEXTIDREL2,a64fx,aggressive-fma,arith-bcc-fusion,ccpp,complxnum,crc,el2vmsa,el3,fp-armv8,fullfp16,lor,lse,neon,pan,pan-rwv,perfmon,predictable-select-expensive,ras,rdm,sha2,store-pair-suppress,sve,uaops,use-postra-scheduler,v8.1a,v8.2a,v8a,vh") }, + { str_lit("ampere1"), str_lit("CONTEXTIDREL2,addr-lsl-fast,aes,aggressive-fma,altnzcv,alu-lsl-fast,am,ampere1,amvs,arith-bcc-fusion,bf16,bti,ccdp,ccidx,ccpp,cmp-bcc-fusion,complxnum,crc,dit,dotprod,ecv,el2vmsa,el3,fgt,flagm,fp-armv8,fptoint,fuse-address,fuse-aes,fuse-literals,i8mm,jsconv,ldp-aligned-only,lor,lse,lse2,mpam,neon,nv,pan,pan-rwv,pauth,perfmon,predres,rand,ras,rcpc,rcpc-immo,rdm,sb,sel2,sha2,sha3,specrestrict,ssbs,store-pair-suppress,stp-aligned-only,tlb-rmi,tracev8.4,uaops,use-postra-scheduler,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8a,vh") }, + { str_lit("ampere1a"), str_lit("CONTEXTIDREL2,addr-lsl-fast,aes,aggressive-fma,altnzcv,alu-lsl-fast,am,ampere1a,amvs,arith-bcc-fusion,bf16,bti,ccdp,ccidx,ccpp,cmp-bcc-fusion,complxnum,crc,dit,dotprod,ecv,el2vmsa,el3,fgt,flagm,fp-armv8,fptoint,fuse-address,fuse-aes,fuse-literals,i8mm,jsconv,ldp-aligned-only,lor,lse,lse2,mpam,mte,neon,nv,pan,pan-rwv,pauth,perfmon,predres,rand,ras,rcpc,rcpc-immo,rdm,sb,sel2,sha2,sha3,sm4,specrestrict,ssbs,store-pair-suppress,stp-aligned-only,tlb-rmi,tracev8.4,uaops,use-postra-scheduler,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8a,vh") }, + { str_lit("ampere1b"), str_lit("CONTEXTIDREL2,addr-lsl-fast,aes,aggressive-fma,altnzcv,alu-lsl-fast,am,ampere1b,amvs,arith-bcc-fusion,bf16,bti,ccdp,ccidx,ccpp,cmp-bcc-fusion,complxnum,crc,cssc,dit,dotprod,ecv,el2vmsa,el3,enable-select-opt,fgt,flagm,fp-armv8,fptoint,fullfp16,fuse-address,fuse-adrp-add,fuse-aes,fuse-literals,hcx,i8mm,jsconv,ldp-aligned-only,lor,lse,lse2,mpam,mte,neon,nv,pan,pan-rwv,pauth,perfmon,predictable-select-expensive,predres,rand,ras,rcpc,rcpc-immo,rdm,sb,sel2,sha2,sha3,sm4,specrestrict,ssbs,store-pair-suppress,stp-aligned-only,tlb-rmi,tracev8.4,uaops,use-postra-scheduler,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8.7a,v8a,vh,wfxt,xs") }, + { str_lit("apple-a10"), str_lit("CONTEXTIDREL2,aes,alternate-sextload-cvt-f32-pattern,apple-a10,arith-bcc-fusion,arith-cbz-fusion,crc,crypto,disable-latency-sched-heuristic,el2vmsa,el3,fp-armv8,fuse-aes,fuse-crypto-eor,lor,neon,pan,perfmon,rdm,sha2,store-pair-suppress,v8a,vh,zcm,zcz,zcz-gp") }, + { str_lit("apple-a11"), str_lit("CONTEXTIDREL2,aes,alternate-sextload-cvt-f32-pattern,apple-a11,arith-bcc-fusion,arith-cbz-fusion,ccpp,crc,crypto,disable-latency-sched-heuristic,el2vmsa,el3,fp-armv8,fullfp16,fuse-aes,fuse-crypto-eor,lor,lse,neon,pan,pan-rwv,perfmon,ras,rdm,sha2,store-pair-suppress,uaops,v8.1a,v8.2a,v8a,vh,zcm,zcz,zcz-gp") }, + { str_lit("apple-a12"), str_lit("CONTEXTIDREL2,aes,alternate-sextload-cvt-f32-pattern,apple-a12,arith-bcc-fusion,arith-cbz-fusion,ccidx,ccpp,complxnum,crc,crypto,disable-latency-sched-heuristic,el2vmsa,el3,fp-armv8,fullfp16,fuse-aes,fuse-crypto-eor,jsconv,lor,lse,neon,pan,pan-rwv,pauth,perfmon,ras,rcpc,rdm,sha2,store-pair-suppress,uaops,v8.1a,v8.2a,v8.3a,v8a,vh,zcm,zcz,zcz-gp") }, + { 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-a17"), str_lit("CONTEXTIDREL2,aes,alternate-sextload-cvt-f32-pattern,altnzcv,am,amvs,apple-a17,arith-bcc-fusion,arith-cbz-fusion,bf16,bti,ccdp,ccidx,ccpp,complxnum,crc,crypto,disable-latency-sched-heuristic,dit,dotprod,ecv,el2vmsa,el3,fgt,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-address,fuse-aes,fuse-arith-logic,fuse-crypto-eor,fuse-csel,fuse-literals,hcx,i8mm,jsconv,lor,lse,lse2,mpam,neon,nv,pan,pan-rwv,pauth,perfmon,predres,ras,rcpc,rcpc-immo,rdm,sb,sel2,sha2,sha3,specrestrict,ssbs,store-pair-suppress,tlb-rmi,tracev8.4,uaops,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8a,vh,zcm,zcz,zcz-gp") }, + { str_lit("apple-a7"), str_lit("aes,alternate-sextload-cvt-f32-pattern,apple-a7,apple-a7-sysreg,arith-bcc-fusion,arith-cbz-fusion,crypto,disable-latency-sched-heuristic,el2vmsa,el3,fp-armv8,fuse-aes,fuse-crypto-eor,neon,perfmon,sha2,store-pair-suppress,v8a,zcm,zcz,zcz-fp-workaround,zcz-gp") }, + { str_lit("apple-a8"), str_lit("aes,alternate-sextload-cvt-f32-pattern,apple-a7,apple-a7-sysreg,arith-bcc-fusion,arith-cbz-fusion,crypto,disable-latency-sched-heuristic,el2vmsa,el3,fp-armv8,fuse-aes,fuse-crypto-eor,neon,perfmon,sha2,store-pair-suppress,v8a,zcm,zcz,zcz-fp-workaround,zcz-gp") }, + { str_lit("apple-a9"), str_lit("aes,alternate-sextload-cvt-f32-pattern,apple-a7,apple-a7-sysreg,arith-bcc-fusion,arith-cbz-fusion,crypto,disable-latency-sched-heuristic,el2vmsa,el3,fp-armv8,fuse-aes,fuse-crypto-eor,neon,perfmon,sha2,store-pair-suppress,v8a,zcm,zcz,zcz-fp-workaround,zcz-gp") }, + { str_lit("apple-latest"), str_lit("CONTEXTIDREL2,aes,alternate-sextload-cvt-f32-pattern,altnzcv,am,amvs,apple-a16,arith-bcc-fusion,arith-cbz-fusion,bf16,bti,ccdp,ccidx,ccpp,complxnum,crc,crypto,disable-latency-sched-heuristic,dit,dotprod,ecv,el2vmsa,el3,fgt,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-address,fuse-aes,fuse-arith-logic,fuse-crypto-eor,fuse-csel,fuse-literals,hcx,i8mm,jsconv,lor,lse,lse2,mpam,neon,nv,pan,pan-rwv,pauth,perfmon,predres,ras,rcpc,rcpc-immo,rdm,sb,sel2,sha2,sha3,specrestrict,ssbs,store-pair-suppress,tlb-rmi,tracev8.4,uaops,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8a,vh,zcm,zcz,zcz-gp") }, + { str_lit("apple-m1"), str_lit("CONTEXTIDREL2,aes,aggressive-fma,alternate-sextload-cvt-f32-pattern,altnzcv,am,apple-a14,arith-bcc-fusion,arith-cbz-fusion,ccdp,ccidx,ccpp,complxnum,crc,crypto,disable-latency-sched-heuristic,dit,dotprod,el2vmsa,el3,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-address,fuse-adrp-add,fuse-aes,fuse-arith-logic,fuse-crypto-eor,fuse-csel,fuse-literals,jsconv,lor,lse,lse2,mpam,neon,nv,pan,pan-rwv,pauth,perfmon,predres,ras,rcpc,rcpc-immo,rdm,sb,sel2,sha2,sha3,specrestrict,ssbs,store-pair-suppress,tlb-rmi,tracev8.4,uaops,v8.1a,v8.2a,v8.3a,v8.4a,v8a,vh,zcm,zcz,zcz-gp") }, + { str_lit("apple-m2"), str_lit("CONTEXTIDREL2,aes,alternate-sextload-cvt-f32-pattern,altnzcv,am,amvs,apple-a15,arith-bcc-fusion,arith-cbz-fusion,bf16,bti,ccdp,ccidx,ccpp,complxnum,crc,crypto,disable-latency-sched-heuristic,dit,dotprod,ecv,el2vmsa,el3,fgt,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-address,fuse-aes,fuse-arith-logic,fuse-crypto-eor,fuse-csel,fuse-literals,i8mm,jsconv,lor,lse,lse2,mpam,neon,nv,pan,pan-rwv,pauth,perfmon,predres,ras,rcpc,rcpc-immo,rdm,sb,sel2,sha2,sha3,specrestrict,ssbs,store-pair-suppress,tlb-rmi,tracev8.4,uaops,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8a,vh,zcm,zcz,zcz-gp") }, + { str_lit("apple-m3"), str_lit("CONTEXTIDREL2,aes,alternate-sextload-cvt-f32-pattern,altnzcv,am,amvs,apple-a16,arith-bcc-fusion,arith-cbz-fusion,bf16,bti,ccdp,ccidx,ccpp,complxnum,crc,crypto,disable-latency-sched-heuristic,dit,dotprod,ecv,el2vmsa,el3,fgt,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-address,fuse-aes,fuse-arith-logic,fuse-crypto-eor,fuse-csel,fuse-literals,hcx,i8mm,jsconv,lor,lse,lse2,mpam,neon,nv,pan,pan-rwv,pauth,perfmon,predres,ras,rcpc,rcpc-immo,rdm,sb,sel2,sha2,sha3,specrestrict,ssbs,store-pair-suppress,tlb-rmi,tracev8.4,uaops,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8a,vh,zcm,zcz,zcz-gp") }, + { str_lit("apple-s4"), str_lit("CONTEXTIDREL2,aes,alternate-sextload-cvt-f32-pattern,apple-a12,arith-bcc-fusion,arith-cbz-fusion,ccidx,ccpp,complxnum,crc,crypto,disable-latency-sched-heuristic,el2vmsa,el3,fp-armv8,fullfp16,fuse-aes,fuse-crypto-eor,jsconv,lor,lse,neon,pan,pan-rwv,pauth,perfmon,ras,rcpc,rdm,sha2,store-pair-suppress,uaops,v8.1a,v8.2a,v8.3a,v8a,vh,zcm,zcz,zcz-gp") }, + { str_lit("apple-s5"), str_lit("CONTEXTIDREL2,aes,alternate-sextload-cvt-f32-pattern,apple-a12,arith-bcc-fusion,arith-cbz-fusion,ccidx,ccpp,complxnum,crc,crypto,disable-latency-sched-heuristic,el2vmsa,el3,fp-armv8,fullfp16,fuse-aes,fuse-crypto-eor,jsconv,lor,lse,neon,pan,pan-rwv,pauth,perfmon,ras,rcpc,rdm,sha2,store-pair-suppress,uaops,v8.1a,v8.2a,v8.3a,v8a,vh,zcm,zcz,zcz-gp") }, + { str_lit("carmel"), str_lit("CONTEXTIDREL2,aes,carmel,ccpp,crc,crypto,el2vmsa,el3,fp-armv8,fullfp16,lor,lse,neon,pan,pan-rwv,ras,rdm,sha2,uaops,v8.1a,v8.2a,v8a,vh") }, + { str_lit("cortex-a34"), str_lit("a35,aes,crc,crypto,el2vmsa,el3,fp-armv8,neon,perfmon,sha2,v8a") }, + { str_lit("cortex-a35"), str_lit("a35,aes,crc,crypto,el2vmsa,el3,fp-armv8,neon,perfmon,sha2,v8a") }, + { str_lit("cortex-a510"), str_lit("CONTEXTIDREL2,a510,altnzcv,am,bf16,bti,ccdp,ccidx,ccpp,complxnum,crc,dit,dotprod,el2vmsa,el3,ete,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-adrp-add,fuse-aes,i8mm,jsconv,lor,lse,lse2,mec,mpam,mte,neon,nv,pan,pan-rwv,pauth,perfmon,predres,ras,rcpc,rcpc-immo,rdm,rme,sb,sel2,specrestrict,ssbs,sve,sve2,sve2-bitperm,tlb-rmi,tracev8.4,trbe,uaops,use-postra-scheduler,use-scalar-inc-vl,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8a,v9a,vh") }, + { str_lit("cortex-a520"), str_lit("CONTEXTIDREL2,a520,altnzcv,am,amvs,bf16,bti,ccdp,ccidx,ccpp,complxnum,crc,dit,dotprod,ecv,el2vmsa,el3,ete,fgt,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-adrp-add,fuse-aes,hcx,i8mm,jsconv,lor,lse,lse2,mec,mpam,mte,neon,nv,pan,pan-rwv,pauth,perfmon,predres,ras,rcpc,rcpc-immo,rdm,rme,sb,sel2,specrestrict,ssbs,sve,sve2,sve2-bitperm,tlb-rmi,tracev8.4,trbe,uaops,use-postra-scheduler,use-scalar-inc-vl,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8.7a,v8a,v9.1a,v9.2a,v9a,vh,wfxt,xs") }, + { str_lit("cortex-a53"), str_lit("a53,aes,balance-fp-ops,crc,crypto,el2vmsa,el3,fp-armv8,fuse-adrp-add,fuse-aes,neon,perfmon,sha2,use-postra-scheduler,v8a") }, + { str_lit("cortex-a55"), str_lit("CONTEXTIDREL2,a55,aes,ccpp,crc,crypto,dotprod,el2vmsa,el3,fp-armv8,fullfp16,fuse-address,fuse-adrp-add,fuse-aes,lor,lse,neon,pan,pan-rwv,perfmon,ras,rcpc,rdm,sha2,uaops,use-postra-scheduler,v8.1a,v8.2a,v8a,vh") }, + { str_lit("cortex-a57"), str_lit("a57,aes,balance-fp-ops,crc,crypto,el2vmsa,el3,enable-select-opt,fp-armv8,fuse-adrp-add,fuse-aes,fuse-literals,neon,perfmon,predictable-select-expensive,sha2,use-postra-scheduler,v8a") }, + { 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-a720"), str_lit("CONTEXTIDREL2,a720,addr-lsl-fast,altnzcv,alu-lsl-fast,am,amvs,bf16,bti,ccdp,ccidx,ccpp,cmp-bcc-fusion,complxnum,crc,dit,dotprod,ecv,el2vmsa,el3,enable-select-opt,ete,fgt,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-adrp-add,fuse-aes,hcx,i8mm,jsconv,lor,lse,lse2,mec,mpam,mte,neon,nv,pan,pan-rwv,pauth,perfmon,predictable-select-expensive,predres,ras,rcpc,rcpc-immo,rdm,rme,sb,sel2,spe,spe-eef,specrestrict,ssbs,sve,sve2,sve2-bitperm,tlb-rmi,tracev8.4,trbe,uaops,use-postra-scheduler,use-scalar-inc-vl,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8.7a,v8a,v9.1a,v9.2a,v9a,vh,wfxt,xs") }, + { str_lit("cortex-a73"), str_lit("a73,aes,crc,crypto,el2vmsa,el3,enable-select-opt,fp-armv8,fuse-adrp-add,fuse-aes,neon,perfmon,predictable-select-expensive,sha2,v8a") }, + { str_lit("cortex-a75"), str_lit("CONTEXTIDREL2,a75,aes,ccpp,crc,crypto,dotprod,el2vmsa,el3,enable-select-opt,fp-armv8,fullfp16,fuse-adrp-add,fuse-aes,lor,lse,neon,pan,pan-rwv,perfmon,predictable-select-expensive,ras,rcpc,rdm,sha2,uaops,v8.1a,v8.2a,v8a,vh") }, + { str_lit("cortex-a76"), str_lit("CONTEXTIDREL2,a76,addr-lsl-fast,aes,alu-lsl-fast,ccpp,crc,crypto,dotprod,el2vmsa,el3,enable-select-opt,fp-armv8,fullfp16,fuse-adrp-add,fuse-aes,lor,lse,neon,pan,pan-rwv,perfmon,predictable-select-expensive,ras,rcpc,rdm,sha2,ssbs,uaops,v8.1a,v8.2a,v8a,vh") }, + { 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("cortex-x4"), str_lit("CONTEXTIDREL2,addr-lsl-fast,altnzcv,alu-lsl-fast,am,amvs,bf16,bti,ccdp,ccidx,ccpp,complxnum,cortex-x4,crc,dit,dotprod,ecv,el2vmsa,el3,enable-select-opt,ete,fgt,flagm,fp-armv8,fp16fml,fptoint,fullfp16,fuse-adrp-add,fuse-aes,hcx,i8mm,jsconv,lor,lse,lse2,mec,mpam,mte,neon,nv,pan,pan-rwv,pauth,perfmon,predictable-select-expensive,predres,ras,rcpc,rcpc-immo,rdm,rme,sb,sel2,spe,spe-eef,specrestrict,ssbs,sve,sve2,sve2-bitperm,tlb-rmi,tracev8.4,trbe,uaops,use-postra-scheduler,use-scalar-inc-vl,v8.1a,v8.2a,v8.3a,v8.4a,v8.5a,v8.6a,v8.7a,v8a,v9.1a,v9.2a,v9a,vh,wfxt,xs") }, + { str_lit("cyclone"), str_lit("aes,alternate-sextload-cvt-f32-pattern,apple-a7,apple-a7-sysreg,arith-bcc-fusion,arith-cbz-fusion,crypto,disable-latency-sched-heuristic,el2vmsa,el3,fp-armv8,fuse-aes,fuse-crypto-eor,neon,perfmon,sha2,store-pair-suppress,v8a,zcm,zcz,zcz-fp-workaround,zcz-gp") }, + { str_lit("exynos-m3"), str_lit("addr-lsl-fast,aes,alu-lsl-fast,crc,crypto,el2vmsa,el3,exynos-cheap-as-move,exynosm3,force-32bit-jump-tables,fp-armv8,fuse-address,fuse-adrp-add,fuse-aes,fuse-csel,fuse-literals,neon,perfmon,predictable-select-expensive,sha2,store-pair-suppress,use-postra-scheduler,v8a") }, + { str_lit("exynos-m4"), str_lit("CONTEXTIDREL2,addr-lsl-fast,aes,alu-lsl-fast,arith-bcc-fusion,arith-cbz-fusion,ccpp,crc,crypto,dotprod,el2vmsa,el3,exynos-cheap-as-move,exynosm4,force-32bit-jump-tables,fp-armv8,fullfp16,fuse-address,fuse-adrp-add,fuse-aes,fuse-arith-logic,fuse-csel,fuse-literals,lor,lse,neon,pan,pan-rwv,perfmon,ras,rdm,sha2,store-pair-suppress,uaops,use-postra-scheduler,v8.1a,v8.2a,v8a,vh,zcz,zcz-gp") }, + { 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("") }, + // TargetArch_riscv64: + { str_lit("generic"), str_lit("64bit") }, + { str_lit("generic-rv32"), str_lit("32bit") }, + { str_lit("generic-rv64"), str_lit("64bit") }, + { str_lit("rocket"), str_lit("") }, + { str_lit("rocket-rv32"), str_lit("32bit,zicsr,zifencei") }, + { str_lit("rocket-rv64"), str_lit("64bit,zicsr,zifencei") }, + { str_lit("sifive-7-series"), str_lit("no-default-unroll,short-forward-branch-opt,sifive7") }, + { str_lit("sifive-e20"), str_lit("32bit,c,m,zicsr,zifencei") }, + { str_lit("sifive-e21"), str_lit("32bit,a,c,m,zicsr,zifencei") }, + { str_lit("sifive-e24"), str_lit("32bit,a,c,f,m,zicsr,zifencei") }, + { str_lit("sifive-e31"), str_lit("32bit,a,c,m,zicsr,zifencei") }, + { str_lit("sifive-e34"), str_lit("32bit,a,c,f,m,zicsr,zifencei") }, + { str_lit("sifive-e76"), str_lit("32bit,a,c,f,m,no-default-unroll,short-forward-branch-opt,sifive7,zicsr,zifencei") }, + { str_lit("sifive-p450"), str_lit("64bit,a,auipc-addi-fusion,c,conditional-cmv-fusion,d,f,fast-unaligned-access,lui-addi-fusion,m,no-default-unroll,za64rs,zba,zbb,zbs,zfhmin,zic64b,zicbom,zicbop,zicboz,ziccamoa,ziccif,zicclsm,ziccrse,zicsr,zifencei,zihintntl,zihintpause,zihpm") }, + { str_lit("sifive-p670"), str_lit("64bit,a,auipc-addi-fusion,c,conditional-cmv-fusion,d,f,fast-unaligned-access,lui-addi-fusion,m,no-default-unroll,v,za64rs,zba,zbb,zbs,zfhmin,zic64b,zicbom,zicbop,zicboz,ziccamoa,ziccif,zicclsm,ziccrse,zicsr,zifencei,zihintntl,zihintpause,zihpm,zvbb,zvbc,zve32f,zve32x,zve64d,zve64f,zve64x,zvkb,zvkg,zvkn,zvknc,zvkned,zvkng,zvknhb,zvks,zvksc,zvksed,zvksg,zvksh,zvkt,zvl128b,zvl32b,zvl64b") }, + { str_lit("sifive-s21"), str_lit("64bit,a,c,m,zicsr,zifencei") }, + { str_lit("sifive-s51"), str_lit("64bit,a,c,m,zicsr,zifencei") }, + { str_lit("sifive-s54"), str_lit("64bit,a,c,d,f,m,zicsr,zifencei") }, + { str_lit("sifive-s76"), str_lit("64bit,a,c,d,f,m,no-default-unroll,short-forward-branch-opt,sifive7,zicsr,zifencei,zihintpause") }, + { str_lit("sifive-u54"), str_lit("64bit,a,c,d,f,m,zicsr,zifencei") }, + { str_lit("sifive-u74"), str_lit("64bit,a,c,d,f,m,no-default-unroll,short-forward-branch-opt,sifive7,zicsr,zifencei") }, + { str_lit("sifive-x280"), str_lit("64bit,a,c,d,dlen-factor-2,f,m,no-default-unroll,short-forward-branch-opt,sifive7,v,zba,zbb,zfh,zfhmin,zicsr,zifencei,zve32f,zve32x,zve64d,zve64f,zve64x,zvfh,zvfhmin,zvl128b,zvl256b,zvl32b,zvl512b,zvl64b") }, + { str_lit("syntacore-scr1-base"), str_lit("32bit,c,no-default-unroll,zicsr,zifencei") }, + { str_lit("syntacore-scr1-max"), str_lit("32bit,c,m,no-default-unroll,zicsr,zifencei") }, + { str_lit("veyron-v1"), str_lit("64bit,a,auipc-addi-fusion,c,d,f,ld-add-fusion,lui-addi-fusion,m,shifted-zextw-fusion,ventana-veyron,xventanacondops,zba,zbb,zbc,zbs,zexth-fusion,zextw-fusion,zicbom,zicbop,zicboz,zicntr,zicsr,zifencei,zihintpause,zihpm") }, + { str_lit("xiangshan-nanhu"), str_lit("64bit,a,c,d,f,m,svinval,zba,zbb,zbc,zbkb,zbkc,zbkx,zbs,zicbom,zicboz,zicsr,zifencei,zkn,zknd,zkne,zknh,zksed,zksh") }, +}; diff --git a/src/cached.cpp b/src/cached.cpp new file mode 100644 index 000000000..efdadce7b --- /dev/null +++ b/src/cached.cpp @@ -0,0 +1,474 @@ +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 + +Array cache_gather_files(Checker *c) { + 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); + + return files; +} + +Array cache_gather_envs() { + auto envs = array_make(heap_allocator()); + { + #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); + return envs; +} + +// returns false if different, true if it is the same +gb_internal bool try_cached_build(Checker *c, Array const &args) { + TEMPORARY_ALLOCATOR_GUARD(); + + auto files = cache_gather_files(c); + auto envs = cache_gather_envs(); + defer (array_free(&envs)); + + 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; + + if (check_if_exists_directory_otherwise_create(cache_dir)) { + return false; + } + + if (check_if_exists_file_otherwise_create(files_path)) { + return false; + } + if (check_if_exists_file_otherwise_create(args_path)) { + return false; + } + if (check_if_exists_file_otherwise_create(env_path)) { + return false; + } + + { + // exists already + LoadedFile loaded_file = {}; + + LoadedFileError file_err = load_file_32( + alloc_cstring(temporary_allocator(), files_path), + &loaded_file, + true + ); + 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) { + return false; + } + + 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) { + return false; + } + if (files[file_count] != path_str) { + return false; + } + + 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) { + return false; + } + } + + if (file_count != files.count) { + return false; + } + } + { + LoadedFile loaded_file = {}; + + LoadedFileError file_err = load_file_32( + alloc_cstring(temporary_allocator(), args_path), + &loaded_file, + true + ); + 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) { + return false; + } + + if (line != args[args_count]) { + return false; + } + } + } + { + LoadedFile loaded_file = {}; + + LoadedFileError file_err = load_file_32( + alloc_cstring(temporary_allocator(), env_path), + &loaded_file, + true + ); + 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) { + return false; + } + + if (line != envs[env_count]) { + return false; + } + } + } + + return try_copy_executable_from_cache(); +} + +void write_cached_build(Checker *c, Array const &args) { + auto files = cache_gather_files(c); + defer (array_free(&files)); + auto envs = cache_gather_envs(); + defer (array_free(&envs)); + + { + char const *path_c = alloc_cstring(temporary_allocator(), build_context.build_cache_data.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(), build_context.build_cache_data.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(), build_context.build_cache_data.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)); + } + } +} + diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 77ba6b435..ea902387b 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -89,6 +89,7 @@ gb_internal void check_or_else_split_types(CheckerContext *c, Operand *x, String gb_internal void check_or_else_expr_no_value_error(CheckerContext *c, String const &name, Operand const &x, Type *type_hint) { + ERROR_BLOCK(); gbString t = type_to_string(x.type); error(x.expr, "'%.*s' does not return a value, value is of type %s", LIT(name), t); if (is_type_union(type_deref(x.type))) { @@ -154,6 +155,11 @@ gb_internal bool does_require_msgSend_stret(Type *return_type) { return false; } + // No objc here so this doesn't matter, right? + if (build_context.metrics.arch == TargetArch_riscv64) { + return false; + } + // if (build_context.metrics.arch == TargetArch_arm32) { // i64 struct_limit = type_size_of(t_uintptr); // // NOTE(bill): This is technically wrong @@ -469,8 +475,8 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan } // Integer only - case BuiltinProc_simd_add_sat: - case BuiltinProc_simd_sub_sat: + case BuiltinProc_simd_saturating_add: + case BuiltinProc_simd_saturating_sub: case BuiltinProc_simd_bit_and: case BuiltinProc_simd_bit_or: case BuiltinProc_simd_bit_xor: @@ -500,8 +506,8 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan Type *elem = base_array_type(x.type); switch (id) { - case BuiltinProc_simd_add_sat: - case BuiltinProc_simd_sub_sat: + case BuiltinProc_simd_saturating_add: + case BuiltinProc_simd_saturating_sub: if (!is_type_integer(elem)) { gbString xs = type_to_string(x.type); error(x.expr, "'%.*s' expected a #simd type with an integer element, got '%s'", LIT(builtin_name), xs); @@ -662,6 +668,91 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan return true; } + case BuiltinProc_simd_gather: + case BuiltinProc_simd_scatter: + case BuiltinProc_simd_masked_load: + case BuiltinProc_simd_masked_store: + case BuiltinProc_simd_masked_expand_load: + case BuiltinProc_simd_masked_compress_store: + { + // gather (ptr: #simd[N]rawptr, values: #simd[N]T, mask: #simd[N]int_or_bool) -> #simd[N]T + // scatter(ptr: #simd[N]rawptr, values: #simd[N]T, mask: #simd[N]int_or_bool) + + // masked_load (ptr: rawptr, values: #simd[N]T, mask: #simd[N]int_or_bool) -> #simd[N]T + // masked_store(ptr: rawptr, values: #simd[N]T, mask: #simd[N]int_or_bool) + // masked_expand_load (ptr: rawptr, values: #simd[N]T, mask: #simd[N]int_or_bool) -> #simd[N]T + // masked_compress_store(ptr: rawptr, values: #simd[N]T, mask: #simd[N]int_or_bool) + + Operand ptr = {}; + Operand values = {}; + Operand mask = {}; + check_expr(c, &ptr, ce->args[0]); if (ptr.mode == Addressing_Invalid) return false; + check_expr(c, &values, ce->args[1]); if (values.mode == Addressing_Invalid) return false; + check_expr(c, &mask, ce->args[2]); if (mask.mode == Addressing_Invalid) return false; + if (!is_type_simd_vector(values.type)) { error(values.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); return false; } + if (!is_type_simd_vector(mask.type)) { error(mask.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); return false; } + + if (id == BuiltinProc_simd_gather || id == BuiltinProc_simd_scatter) { + if (!is_type_simd_vector(ptr.type)) { error(ptr.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); return false; } + Type *ptr_elem = base_array_type(ptr.type); + if (!is_type_rawptr(ptr_elem)) { + gbString s = type_to_string(ptr.type); + error(ptr.expr, "Expected a simd vector of 'rawptr' for the addresses, got %s", s); + gb_string_free(s); + return false; + } + } else { + if (!is_type_pointer(ptr.type)) { + gbString s = type_to_string(ptr.type); + error(ptr.expr, "Expected a pointer type for the address, got %s", s); + gb_string_free(s); + return false; + } + } + Type *mask_elem = base_array_type(mask.type); + + if (!is_type_integer(mask_elem) && !is_type_boolean(mask_elem)) { + gbString s = type_to_string(mask.type); + error(mask.expr, "Expected a simd vector of integers or booleans for the mask, got %s", s); + gb_string_free(s); + return false; + } + + if (id == BuiltinProc_simd_gather || id == BuiltinProc_simd_scatter) { + i64 ptr_count = get_array_type_count(ptr.type); + i64 values_count = get_array_type_count(values.type); + i64 mask_count = get_array_type_count(mask.type); + if (ptr_count != values_count || + values_count != mask_count || + mask_count != ptr_count) { + gbString s = type_to_string(mask.type); + error(mask.expr, "All simd vectors must be of the same length, got %lld vs %lld vs %lld", cast(long long)ptr_count, cast(long long)values_count, cast(long long)mask_count); + gb_string_free(s); + return false; + } + } else { + i64 values_count = get_array_type_count(values.type); + i64 mask_count = get_array_type_count(mask.type); + if (values_count != mask_count) { + gbString s = type_to_string(mask.type); + error(mask.expr, "All simd vectors must be of the same length, got %lld vs %lld", cast(long long)values_count, cast(long long)mask_count); + gb_string_free(s); + return false; + } + } + + if (id == BuiltinProc_simd_gather || + id == BuiltinProc_simd_masked_load || + id == BuiltinProc_simd_masked_expand_load) { + operand->mode = Addressing_Value; + operand->type = values.type; + } else { + operand->mode = Addressing_NoValue; + operand->type = nullptr; + } + return true; + } + case BuiltinProc_simd_extract: { Operand x = {}; @@ -774,6 +865,29 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan return true; } + case BuiltinProc_simd_reduce_any: + case BuiltinProc_simd_reduce_all: + { + Operand x = {}; + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) return false; + + if (!is_type_simd_vector(x.type)) { + error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + Type *elem = base_array_type(x.type); + if (!is_type_boolean(elem)) { + gbString xs = type_to_string(x.type); + error(x.expr, "'%.*s' expected a #simd type with a boolean element, got '%s'", LIT(builtin_name), xs); + gb_string_free(xs); + return false; + } + + operand->mode = Addressing_Value; + operand->type = t_untyped_bool; + return true; + } + case BuiltinProc_simd_shuffle: { @@ -1078,29 +1192,43 @@ 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; - String base_dir = dir_from_path(get_file_path_string(call->file_id)); + String path; + if (gb_path_is_absolute((char*)original_string.text)) { + path = original_string; + } else { + String base_dir = dir_from_path(get_file_path_string(call->file_id)); - BlockingMutex *ignore_mutex = nullptr; - String path = {}; - bool ok = determine_path_from_string(ignore_mutex, call, base_dir, original_string, &path); - gb_unused(ok); + BlockingMutex *ignore_mutex = nullptr; + bool ok = determine_path_from_string(ignore_mutex, call, base_dir, original_string, &path); + 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) { @@ -1108,60 +1236,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; } @@ -1253,7 +1399,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; } @@ -1261,6 +1407,210 @@ 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); +} + +gb_internal LoadDirectiveResult check_load_directory_directive(CheckerContext *c, Operand *operand, Ast *call, Type *type_hint, bool err_on_not_found) { + ast_node(ce, CallExpr, call); + ast_node(bd, BasicDirective, ce->proc); + String name = bd->name.string; + GB_ASSERT(name == "load_directory"); + + if (ce->args.count != 1) { + error(ce->args[0], "'#%.*s' expects 1 argument, got %td", LIT(name), ce->args.count); + return LoadDirective_Error; + } + + Ast *arg = ce->args[0]; + Operand o = {}; + check_expr(c, &o, arg); + if (o.mode != Addressing_Constant) { + error(arg, "'#%.*s' expected a constant string argument", LIT(name)); + return LoadDirective_Error; + } + + if (!is_type_string(o.type)) { + gbString str = type_to_string(o.type); + error(arg, "'#%.*s' expected a constant string, got %s", LIT(name), str); + gb_string_free(str); + return LoadDirective_Error; + } + + GB_ASSERT(o.value.kind == ExactValue_String); + + init_core_load_directory_file(c->checker); + + operand->type = t_load_directory_file_slice; + operand->mode = Addressing_Value; + + + String original_string = o.value.value_string; + String path; + if (gb_path_is_absolute((char*)original_string.text)) { + path = original_string; + } else { + String base_dir = dir_from_path(get_file_path_string(call->file_id)); + + BlockingMutex *ignore_mutex = nullptr; + bool ok = determine_path_from_string(ignore_mutex, call, base_dir, original_string, &path); + gb_unused(ok); + } + MUTEX_GUARD(&c->info->load_directory_mutex); + + + gbFileError file_error = gbFileError_None; + + Array file_caches = {}; + + LoadDirectoryCache **cache_ptr = string_map_get(&c->info->load_directory_cache, path); + LoadDirectoryCache *cache = cache_ptr ? *cache_ptr : nullptr; + if (cache) { + file_error = cache->file_error; + } + defer ({ + if (cache == nullptr) { + LoadDirectoryCache *new_cache = gb_alloc_item(permanent_allocator(), LoadDirectoryCache); + new_cache->path = path; + new_cache->files = file_caches; + new_cache->file_error = file_error; + string_map_set(&c->info->load_directory_cache, path, new_cache); + + 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); + } + }); + + + LoadDirectiveResult result = LoadDirective_Success; + + + if (cache == nullptr) { + Array list = {}; + ReadDirectoryError rd_err = read_directory(path, &list); + defer (array_free(&list)); + + if (list.count == 1) { + GB_ASSERT(path != list[0].fullpath); + } + + + switch (rd_err) { + case ReadDirectory_InvalidPath: + error(call, "%.*s error - invalid path: %.*s", LIT(name), LIT(original_string)); + return LoadDirective_NotFound; + case ReadDirectory_NotExists: + error(call, "%.*s error - path does not exist: %.*s", LIT(name), LIT(original_string)); + return LoadDirective_NotFound; + case ReadDirectory_Permission: + error(call, "%.*s error - unknown error whilst reading path, %.*s", LIT(name), LIT(original_string)); + return LoadDirective_Error; + case ReadDirectory_NotDir: + error(call, "%.*s error - expected a directory, got a file: %.*s", LIT(name), LIT(original_string)); + return LoadDirective_Error; + case ReadDirectory_Empty: + error(call, "%.*s error - empty directory: %.*s", LIT(name), LIT(original_string)); + return LoadDirective_NotFound; + case ReadDirectory_Unknown: + error(call, "%.*s error - unknown error whilst reading path %.*s", LIT(name), LIT(original_string)); + return LoadDirective_Error; + } + + isize files_to_reserve = list.count+1; // always reserve 1 + + 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 (fi.is_dir) { + continue; + } + + 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; + } + } + + array_sort(file_caches, file_cache_sort_cmp); + + } + + 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) { ast_node(ce, CallExpr, call); @@ -1286,8 +1636,50 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o operand->type = t_source_code_location; operand->mode = Addressing_Value; + } else if (name == "caller_expression") { + if (ce->args.count > 1) { + error(ce->args[0], "'#caller_expression' expects either 0 or 1 arguments, got %td", ce->args.count); + } + if (ce->args.count > 0) { + Ast *arg = ce->args[0]; + Operand o = {}; + Entity *e = check_ident(c, &o, arg, nullptr, nullptr, true); + if (e == nullptr || (e->flags & EntityFlag_Param) == 0) { + error(ce->args[0], "'#caller_expression' expected a valid earlier parameter name"); + } + arg->Ident.entity = e; + } + + operand->type = t_string; + 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") { + return check_load_directory_directive(c, operand, call, type_hint, true) == LoadDirective_Success; } else if (name == "load_hash") { if (ce->args.count != 2) { if (ce->args.count == 0) { @@ -1335,37 +1727,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; @@ -1375,26 +1738,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); } @@ -1405,64 +1751,71 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o return true; } return false; - } else if (name == "load_or") { - error(call, "'#load_or' has now been removed in favour of '#load(path) or_else default'"); - - if (ce->args.count != 2) { + } else if (name == "hash") { + if (ce->args.count != 2) { if (ce->args.count == 0) { - error(ce->close, "'#load_or' expects 2 arguments, got 0"); + error(ce->close, "'#hash' expects 2 argument, got 0"); } else { - error(ce->args[0], "'#load_or' expects 2 arguments, got %td", ce->args.count); + error(ce->args[0], "'#hash' expects 2 argument, got %td", ce->args.count); } return false; } - Ast *arg = ce->args[0]; + Ast *arg0 = ce->args[0]; + Ast *arg1 = ce->args[1]; Operand o = {}; - check_expr(c, &o, arg); + check_expr(c, &o, arg0); if (o.mode != Addressing_Constant) { - error(arg, "'#load_or' expected a constant string argument"); + error(arg0, "'#hash' expected a constant string argument"); return false; } if (!is_type_string(o.type)) { gbString str = type_to_string(o.type); - error(arg, "'#load_or' expected a constant string, got %s", str); + error(arg0, "'#hash' expected a constant string, got %s", str); gb_string_free(str); return false; } - Ast *default_arg = ce->args[1]; - Operand default_op = {}; - check_expr_with_type_hint(c, &default_op, default_arg, t_u8_slice); - if (default_op.mode != Addressing_Constant) { - error(arg, "'#load_or' expected a constant '[]byte' argument"); + 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 (!are_types_identical(base_type(default_op.type), t_u8_slice)) { - gbString str = type_to_string(default_op.type); - error(arg, "'#load_or' expected a constant '[]byte', got %s", str); + 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); - String original_string = o.value.value_string; + GB_ASSERT(o_hash.value.kind == ExactValue_String); - operand->type = t_u8_slice; - operand->mode = Addressing_Constant; - LoadFileCache *cache = nullptr; - if (cache_load_file_directive(c, call, original_string, false, &cache)) { - operand->value = exact_value_string(cache->data); - } else { - operand->value = default_op.value; + 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); return false; } - if (!is_type_boolean(operand->type) || operand->mode != Addressing_Constant) { + + // operand->type can be nil if the condition is a procedure, for example: #assert(assert()) + // So let's check it before we use it, so we get the same error as if we wrote `#exists(assert()) + if (operand->type == nullptr || !is_type_boolean(operand->type) || operand->mode != Addressing_Constant) { gbString str = expr_to_string(ce->args[0]); error(call, "'%s' is not a constant boolean", str); gb_string_free(str); @@ -1479,6 +1832,7 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o } if (!operand->value.value_bool) { + ERROR_BLOCK(); gbString arg1 = expr_to_string(ce->args[0]); gbString arg2 = {}; @@ -1504,6 +1858,7 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o operand->type = t_untyped_bool; operand->mode = Addressing_Constant; } else if (name == "panic") { + ERROR_BLOCK(); if (ce->args.count != 1) { error(call, "'#panic' expects 1 argument, got %td", ce->args.count); return false; @@ -1514,11 +1869,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; @@ -1544,9 +1901,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]); @@ -1581,6 +1950,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)); } @@ -1630,6 +2013,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; @@ -1663,7 +2048,12 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As if (ce->args.count > 0) { if (ce->args[0]->kind == Ast_FieldValue) { - if (id != BuiltinProc_soa_zip) { + switch (id) { + case BuiltinProc_soa_zip: + case BuiltinProc_quaternion: + // okay + break; + default: error(call, "'field = value' calling is not allowed on built-in procedures"); return false; } @@ -1674,13 +2064,21 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As bool ok = check_builtin_simd_operation(c, operand, call, id, type_hint); if (!ok) { operand->type = t_invalid; + operand->mode = Addressing_Value; } - operand->mode = Addressing_Value; operand->value = {}; operand->expr = call; return ok; } + if (BuiltinProc__atomic_begin < id && id < BuiltinProc__atomic_end) { + if (build_context.metrics.arch == TargetArch_riscv64) { + if (!check_target_feature_is_enabled(str_lit("a"), nullptr)) { + error(call, "missing required target feature \"a\" for atomics, enable it by setting a different -microarch or explicitly adding it through -target-features"); + } + } + } + switch (id) { default: GB_PANIC("Implement built-in procedure: %.*s", LIT(builtin_name)); @@ -1928,6 +2326,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)); @@ -2001,6 +2400,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)); @@ -2041,6 +2441,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); @@ -2088,6 +2496,8 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As t = default_type(t); add_type_info_type(c, t); + GB_ASSERT(t_type_info_ptr != nullptr); + add_type_info_type(c, t_type_info_ptr); if (is_operand_value(o) && is_type_typeid(t)) { add_package_dependency(c, "runtime", "__type_info_of"); @@ -2141,6 +2551,10 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_swizzle: { // swizzle :: proc(v: [N]T, ..int) -> [M]T + if (!operand->type) { + return false; + } + Type *original_type = operand->type; Type *type = base_type(original_type); i64 max_count = 0; @@ -2194,9 +2608,12 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As arg_count++; } - if (arg_count > max_count) { + if (false && arg_count > max_count) { error(call, "Too many 'swizzle' indices, %td > %td", arg_count, max_count); return false; + } else if (arg_count < 2) { + error(call, "Not enough 'swizzle' indices, %td < 2", arg_count); + return false; } if (type->kind == Type_Array) { @@ -2294,61 +2711,150 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } case BuiltinProc_quaternion: { - // quaternion :: proc(real, imag, jmag, kmag: float_type) -> complex_type - Operand x = *operand; - Operand y = {}; - Operand z = {}; - Operand w = {}; + bool first_is_field_value = (ce->args[0]->kind == Ast_FieldValue); + + bool fail = false; + for (Ast *arg : ce->args) { + bool mix = false; + if (first_is_field_value) { + mix = arg->kind != Ast_FieldValue; + } else { + mix = arg->kind == Ast_FieldValue; + } + if (mix) { + error(arg, "Mixture of 'field = value' and value elements in the procedure call '%.*s' is not allowed", LIT(builtin_name)); + fail = true; + break; + } + } + + if (fail) { + operand->type = t_untyped_quaternion; + operand->mode = Addressing_Constant; + operand->value = exact_value_quaternion(0.0, 0.0, 0.0, 0.0); + break; + } + + // quaternion :: proc(imag, jmag, kmag, real: float_type) -> complex_type + Operand xyzw[4] = {}; + + u32 first_index = 0; // NOTE(bill): Invalid will be the default till fixed operand->type = t_invalid; operand->mode = Addressing_Invalid; - check_expr(c, &y, ce->args[1]); - if (y.mode == Addressing_Invalid) { - return false; - } - check_expr(c, &z, ce->args[2]); - if (y.mode == Addressing_Invalid) { - return false; - } - check_expr(c, &w, ce->args[3]); - if (y.mode == Addressing_Invalid) { - return false; - } + if (first_is_field_value) { + u32 fields_set[4] = {}; // 0 unset, 1 xyzw, 2 real/etc - convert_to_typed(c, &x, y.type); if (x.mode == Addressing_Invalid) return false; - convert_to_typed(c, &y, x.type); if (y.mode == Addressing_Invalid) return false; - convert_to_typed(c, &z, x.type); if (z.mode == Addressing_Invalid) return false; - convert_to_typed(c, &w, x.type); if (w.mode == Addressing_Invalid) return false; - if (x.mode == Addressing_Constant && - y.mode == Addressing_Constant && - z.mode == Addressing_Constant && - w.mode == Addressing_Constant) { - x.value = exact_value_to_float(x.value); - y.value = exact_value_to_float(y.value); - z.value = exact_value_to_float(z.value); - w.value = exact_value_to_float(w.value); - if (is_type_numeric(x.type) && x.value.kind == ExactValue_Float) { - x.type = t_untyped_float; + auto const check_field = [&fields_set, &builtin_name](CheckerContext *c, Operand *o, Ast *arg, i32 *index) -> bool { + *index = -1; + + ast_node(field, FieldValue, arg); + String name = {}; + if (field->field->kind == Ast_Ident) { + name = field->field->Ident.token.string; + } else { + error(field->field, "Expected an identifier for field argument"); + return false; + } + + u32 style = 0; + + if (name == "x") { + *index = 0; style = 1; + } else if (name == "y") { + *index = 1; style = 1; + } else if (name == "z") { + *index = 2; style = 1; + } else if (name == "w") { + *index = 3; style = 1; + } else if (name == "imag") { + *index = 0; style = 2; + } else if (name == "jmag") { + *index = 1; style = 2; + } else if (name == "kmag") { + *index = 2; style = 2; + } else if (name == "real") { + *index = 3; style = 2; + } else { + error(field->field, "Unknown name for '%.*s', expected (w, x, y, z; or real, imag, jmag, kmag), got '%.*s'", LIT(builtin_name), LIT(name)); + return false; + } + + if (fields_set[*index]) { + error(field->field, "Previously assigned field: '%.*s'", LIT(name)); + } + fields_set[*index] = style; + + check_expr(c, o, field->value); + return o->mode != Addressing_Invalid; + }; + + Operand *refs[4] = {&xyzw[0], &xyzw[1], &xyzw[2], &xyzw[3]}; + + for (i32 i = 0; i < 4; i++) { + i32 index = -1; + Operand o = {}; + bool ok = check_field(c, &o, ce->args[i], &index); + if (!ok || index < 0) { + return false; + } + first_index = cast(u32)index; + *refs[index] = o; } - if (is_type_numeric(y.type) && y.value.kind == ExactValue_Float) { - y.type = t_untyped_float; + + for (i32 i = 0; i < 4; i++) { + GB_ASSERT(fields_set[i]); } - if (is_type_numeric(z.type) && z.value.kind == ExactValue_Float) { - z.type = t_untyped_float; + for (i32 i = 1; i < 4; i++) { + if (fields_set[i] != fields_set[i-1]) { + error(call, "Mixture of xyzw and real/etc is not allowed with '%.*s'", LIT(builtin_name)); + break; + } } - if (is_type_numeric(w.type) && w.value.kind == ExactValue_Float) { - w.type = t_untyped_float; + } else { + error(call, "'%.*s' requires that all arguments are named (w, x, y, z; or real, imag, jmag, kmag)", LIT(builtin_name)); + + for (i32 i = 0; i < 4; i++) { + check_expr(c, &xyzw[i], ce->args[i]); + if (xyzw[i].mode == Addressing_Invalid) { + return false; + } } } - if (!(are_types_identical(x.type, y.type) && are_types_identical(x.type, z.type) && are_types_identical(x.type, w.type))) { - gbString tx = type_to_string(x.type); - gbString ty = type_to_string(y.type); - gbString tz = type_to_string(z.type); - gbString tw = type_to_string(w.type); - error(call, "Mismatched types to 'quaternion', '%s' vs '%s' vs '%s' vs '%s'", tx, ty, tz, tw); + + for (u32 i = 0; i < 4; i++ ){ + u32 j = (i + first_index) % 4; + if (j == first_index) { + convert_to_typed(c, &xyzw[j], xyzw[(first_index+1)%4].type); if (xyzw[j].mode == Addressing_Invalid) return false; + } else { + convert_to_typed(c, &xyzw[j], xyzw[first_index].type); if (xyzw[j].mode == Addressing_Invalid) return false; + } + } + if (xyzw[0].mode == Addressing_Constant && + xyzw[1].mode == Addressing_Constant && + xyzw[2].mode == Addressing_Constant && + xyzw[3].mode == Addressing_Constant) { + for (i32 i = 0; i < 4; i++) { + xyzw[i].value = exact_value_to_float(xyzw[i].value); + } + for (i32 i = 0; i < 4; i++) { + if (is_type_numeric(xyzw[i].type) && xyzw[i].value.kind == ExactValue_Float) { + xyzw[i].type = t_untyped_float; + } + } + } + + if (!(are_types_identical(xyzw[0].type, xyzw[1].type) && + are_types_identical(xyzw[0].type, xyzw[2].type) && + are_types_identical(xyzw[0].type, xyzw[3].type))) { + gbString tx = type_to_string(xyzw[0].type); + gbString ty = type_to_string(xyzw[1].type); + gbString tz = type_to_string(xyzw[2].type); + gbString tw = type_to_string(xyzw[3].type); + error(call, "Mismatched types to 'quaternion', 'x=%s' vs 'y=%s' vs 'z=%s' vs 'w=%s'", tx, ty, tz, tw); gb_string_free(tw); gb_string_free(tz); gb_string_free(ty); @@ -2356,31 +2862,35 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } - if (!is_type_float(x.type)) { - gbString s = type_to_string(x.type); + if (!is_type_float(xyzw[0].type)) { + gbString s = type_to_string(xyzw[0].type); error(call, "Arguments have type '%s', expected a floating point", s); gb_string_free(s); return false; } - if (is_type_endian_specific(x.type)) { - gbString s = type_to_string(x.type); + if (is_type_endian_specific(xyzw[0].type)) { + gbString s = type_to_string(xyzw[0].type); error(call, "Arguments with a specified endian are not allow, expected a normal floating point, got '%s'", s); gb_string_free(s); return false; } - if (x.mode == Addressing_Constant && y.mode == Addressing_Constant && z.mode == Addressing_Constant && w.mode == Addressing_Constant) { - f64 r = exact_value_to_float(x.value).value_float; - f64 i = exact_value_to_float(y.value).value_float; - f64 j = exact_value_to_float(z.value).value_float; - f64 k = exact_value_to_float(w.value).value_float; + + operand->mode = Addressing_Value; + + if (xyzw[0].mode == Addressing_Constant && + xyzw[1].mode == Addressing_Constant && + xyzw[2].mode == Addressing_Constant && + xyzw[3].mode == Addressing_Constant) { + f64 r = exact_value_to_float(xyzw[3].value).value_float; + f64 i = exact_value_to_float(xyzw[0].value).value_float; + f64 j = exact_value_to_float(xyzw[1].value).value_float; + f64 k = exact_value_to_float(xyzw[2].value).value_float; operand->value = exact_value_quaternion(r, i, j, k); operand->mode = Addressing_Constant; - } else { - operand->mode = Addressing_Value; } - BasicKind kind = core_type(x.type)->Basic.kind; + BasicKind kind = core_type(xyzw[first_index].type)->Basic.kind; switch (kind) { case Basic_f16: operand->type = t_quaternion64; break; case Basic_f32: operand->type = t_quaternion128; break; @@ -2402,6 +2912,10 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As // imag :: proc(x: type) -> float_type Operand *x = operand; + if (!x->type) { + return false; + } + if (is_type_untyped(x->type)) { if (x->mode == Addressing_Constant) { if (is_type_numeric(x->type)) { @@ -2462,6 +2976,10 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As // kmag :: proc(x: type) -> float_type Operand *x = operand; + if (!x->type) { + return false; + } + if (is_type_untyped(x->type)) { if (x->mode == Addressing_Constant) { if (is_type_numeric(x->type)) { @@ -2511,6 +3029,10 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_conj: { // conj :: proc(x: type) -> type Operand *x = operand; + if (!x->type) { + return false; + } + Type *t = x->type; Type *elem = core_array_type(t); @@ -2551,10 +3073,14 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } case BuiltinProc_expand_values: { + if (!operand->type) { + return false; + } + Type *type = base_type(operand->type); if (!is_type_struct(type) && !is_type_array(type)) { gbString type_str = type_to_string(operand->type); - error(call, "Expected a struct or array type, got '%s'", type_str); + error(call, "Expected a struct or array type to 'expand_values', got '%s'", type_str); gb_string_free(type_str); return false; } @@ -2590,13 +3116,18 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As check_multi_expr_or_type(c, operand, ce->args[0]); + if (!operand->type) { + return false; + } + Type *original_type = operand->type; Type *type = base_type(operand->type); + if (operand->mode == Addressing_Type && is_type_enumerated_array(type)) { // Okay } else if (!is_type_ordered(type) || !(is_type_numeric(type) || is_type_string(type))) { gbString type_str = type_to_string(original_type); - error(call, "Expected a ordered numeric type to 'min', got '%s'", type_str); + error(call, "Expected an ordered numeric type to 'min', got '%s'", type_str); gb_string_free(type_str); return false; } @@ -2664,6 +3195,10 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } + if (ce->args.count <= 1) { + error(call, "Too few arguments for 'min', two or more are required"); + return false; + } bool all_constant = operand->mode == Addressing_Constant; @@ -2682,7 +3217,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As if (!is_type_ordered(b.type) || !(is_type_numeric(b.type) || is_type_string(b.type))) { gbString type_str = type_to_string(b.type); error(call, - "Expected a ordered numeric type to 'min', got '%s'", + "Expected an ordered numeric type to 'min', got '%s'", type_str); gb_string_free(type_str); return false; @@ -2758,6 +3293,10 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As check_multi_expr_or_type(c, operand, ce->args[0]); + if (!operand->type) { + return false; + } + Type *original_type = operand->type; Type *type = base_type(operand->type); @@ -2765,7 +3304,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As // Okay } else if (!is_type_ordered(type) || !(is_type_numeric(type) || is_type_string(type))) { gbString type_str = type_to_string(original_type); - error(call, "Expected a ordered numeric type to 'max', got '%s'", type_str); + error(call, "Expected an ordered numeric type to 'max', got '%s'", type_str); gb_string_free(type_str); return false; } @@ -2837,6 +3376,11 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As gb_string_free(type_str); return false; } + + if (ce->args.count <= 1) { + error(call, "Too few arguments for 'max', two or more are required"); + return false; + } bool all_constant = operand->mode == Addressing_Constant; @@ -2856,7 +3400,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As if (!is_type_ordered(b.type) || !(is_type_numeric(b.type) || is_type_string(b.type))) { gbString type_str = type_to_string(b.type); error(arg, - "Expected a ordered numeric type to 'max', got '%s'", + "Expected an ordered numeric type to 'max', got '%s'", type_str); gb_string_free(type_str); return false; @@ -2928,6 +3472,10 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_abs: { // abs :: proc(n: numeric) -> numeric + if (!operand->type) { + return false; + } + if (!(is_type_numeric(operand->type) && !is_type_array(operand->type))) { gbString type_str = type_to_string(operand->type); error(call, "Expected a numeric type to 'abs', got '%s'", type_str); @@ -2983,10 +3531,14 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_clamp: { // clamp :: proc(a, min, max: ordered) -> ordered + if (!operand->type) { + return false; + } + Type *type = operand->type; if (!is_type_ordered(type) || !(is_type_numeric(type) || is_type_string(type))) { gbString type_str = type_to_string(operand->type); - error(call, "Expected a ordered numeric or string type to 'clamp', got '%s'", type_str); + error(call, "Expected an ordered numeric or string type to 'clamp', got '%s'", type_str); gb_string_free(type_str); return false; } @@ -3003,7 +3555,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } if (!is_type_ordered(y.type) || !(is_type_numeric(y.type) || is_type_string(y.type))) { gbString type_str = type_to_string(y.type); - error(call, "Expected a ordered numeric or string type to 'clamp', got '%s'", type_str); + error(call, "Expected an ordered numeric or string type to 'clamp', got '%s'", type_str); gb_string_free(type_str); return false; } @@ -3014,7 +3566,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } if (!is_type_ordered(z.type) || !(is_type_numeric(z.type) || is_type_string(z.type))) { gbString type_str = type_to_string(z.type); - error(call, "Expected a ordered numeric or string type to 'clamp', got '%s'", type_str); + error(call, "Expected an ordered numeric or string type to 'clamp', got '%s'", type_str); gb_string_free(type_str); return false; } @@ -3087,7 +3639,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As mix = arg->kind == Ast_FieldValue; } if (mix) { - error(arg, "Mixture of 'field = value' and value elements in the procedure call 'soa_zip' is not allowed"); + error(arg, "Mixture of 'field = value' and value elements in the procedure call '%.*s' is not allowed", LIT(builtin_name)); fail = true; break; } @@ -3204,6 +3756,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As elem->Struct.tags = gb_alloc_array(permanent_allocator(), String, fields.count); elem->Struct.node = dummy_node_struct; type_set_offsets(elem); + wait_signal_set(&elem->Struct.fields_wait_signal); } Type *soa_type = make_soa_struct_slice(c, dummy_node_soa, nullptr, elem); @@ -3235,8 +3788,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); @@ -3302,7 +3855,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } } else { GB_ASSERT(t->kind == Type_Matrix); - operand->type = alloc_type_matrix(t->Matrix.elem, t->Matrix.column_count, t->Matrix.row_count); + operand->type = alloc_type_matrix(t->Matrix.elem, t->Matrix.column_count, t->Matrix.row_count, nullptr, nullptr, t->Matrix.is_row_major); } operand->type = check_matrix_type_hint(operand->type, type_hint); break; @@ -3370,7 +3923,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } operand->mode = Addressing_Value; - operand->type = alloc_type_matrix(elem, xt->Array.count, yt->Array.count); + operand->type = alloc_type_matrix(elem, xt->Array.count, yt->Array.count, nullptr, nullptr, false); operand->type = check_matrix_type_hint(operand->type, type_hint); break; } @@ -3473,6 +4026,58 @@ 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_constant_log2: { + Operand o = {}; + check_expr(c, &o, ce->args[0]); + + if (!is_type_integer(o.type) && (o.mode != Addressing_Constant)) { + error(ce->args[0], "Expected a constant integer for '%.*s'", LIT(builtin_name)); + return false; + } + + int log2 = big_int_log2(&o.value.value_integer); + + operand->mode = Addressing_Constant; + operand->value = exact_value_i64(cast(i64)log2); + operand->type = t_untyped_integer; + break; + } + case BuiltinProc_soa_struct: { Operand x = {}; Operand y = {}; @@ -3577,6 +4182,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As soa_struct->Struct.tags[i] = old_struct->Struct.tags[i]; } } + wait_signal_set(&soa_struct->Struct.fields_wait_signal); Token token = {}; token.string = str_lit("Base_Type"); @@ -3898,7 +4504,50 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } } - operand->mode = Addressing_OptionalOk; + operand->mode = Addressing_Value; + operand->type = make_optional_ok_type(default_type(x.type)); + } + break; + + case BuiltinProc_saturating_add: + case BuiltinProc_saturating_sub: + { + 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; @@ -4370,16 +5019,14 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As check_assignment(c, &x, elem, builtin_name); Type *t = type_deref(operand->type); - switch (id) { - case BuiltinProc_atomic_add: - case BuiltinProc_atomic_sub: - if (!is_type_numeric(t)) { + if (id != BuiltinProc_atomic_exchange) { + if (!is_type_integer_like(t)) { gbString str = type_to_string(t); - error(operand->expr, "Expected a numeric type for '%.*s', got %s", LIT(builtin_name), str); + error(operand->expr, "Expected an integer type for '%.*s', got %s", LIT(builtin_name), str); gb_string_free(str); } else if (is_type_different_to_arch_endianness(t)) { gbString str = type_to_string(t); - error(operand->expr, "Expected a numeric type of the same platform endianness for '%.*s', got %s", LIT(builtin_name), str); + error(operand->expr, "Expected an integer type of the same platform endianness for '%.*s', got %s", LIT(builtin_name), str); gb_string_free(str); } } @@ -4415,19 +5062,16 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } Type *t = type_deref(operand->type); - switch (id) { - case BuiltinProc_atomic_add_explicit: - case BuiltinProc_atomic_sub_explicit: - if (!is_type_numeric(t)) { + if (id != BuiltinProc_atomic_exchange_explicit) { + if (!is_type_integer_like(t)) { gbString str = type_to_string(t); - error(operand->expr, "Expected a numeric type for '%.*s', got %s", LIT(builtin_name), str); + error(operand->expr, "Expected an integer type for '%.*s', got %s", LIT(builtin_name), str); gb_string_free(str); } else if (is_type_different_to_arch_endianness(t)) { gbString str = type_to_string(t); - error(operand->expr, "Expected a numeric type of the same platform endianness for '%.*s', got %s", LIT(builtin_name), str); + error(operand->expr, "Expected an integer type of the same platform endianness for '%.*s', got %s", LIT(builtin_name), str); gb_string_free(str); } - break; } operand->type = elem; @@ -4620,6 +5264,16 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } + if (sz >= 64) { + if (is_type_unsigned(x.type)) { + add_package_dependency(c, "runtime", "umodti3", true); + add_package_dependency(c, "runtime", "udivti3", true); + } else { + add_package_dependency(c, "runtime", "modti3", true); + add_package_dependency(c, "runtime", "divti3", true); + } + } + operand->type = x.type; operand->mode = Addressing_Value; } @@ -4733,15 +5387,10 @@ 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: case TargetArch_amd64: @@ -4750,6 +5399,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) { @@ -4763,6 +5415,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: @@ -4789,8 +5490,10 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As switch (bt->kind) { case Type_Basic: switch (bt->Basic.kind) { + case Basic_complex32: operand->type = t_f16; break; case Basic_complex64: operand->type = t_f32; break; case Basic_complex128: operand->type = t_f64; break; + case Basic_quaternion64: operand->type = t_f16; break; case Basic_quaternion128: operand->type = t_f32; break; case Basic_quaternion256: operand->type = t_f64; break; } @@ -4989,6 +5692,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 = {}; @@ -5016,6 +5747,59 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As break; } break; + + case BuiltinProc_type_has_shared_fields: + { + Type *u = check_type(c, ce->args[0]); + Type *ut = base_type(u); + if (ut == nullptr || ut == t_invalid) { + error(ce->args[0], "Expected a type for '%.*s'", LIT(builtin_name)); + return false; + } + if (ut->kind != Type_Struct || ut->Struct.soa_kind != StructSoa_None) { + gbString t = type_to_string(ut); + error(ce->args[0], "Expected a struct type for '%.*s', got %s", LIT(builtin_name), t); + gb_string_free(t); + return false; + } + + Type *v = check_type(c, ce->args[1]); + Type *vt = base_type(v); + if (vt == nullptr || vt == t_invalid) { + error(ce->args[1], "Expected a type for '%.*s'", LIT(builtin_name)); + return false; + } + if (vt->kind != Type_Struct || vt->Struct.soa_kind != StructSoa_None) { + gbString t = type_to_string(vt); + error(ce->args[1], "Expected a struct type for '%.*s', got %s", LIT(builtin_name), t); + gb_string_free(t); + return false; + } + + bool is_shared = true; + + for (Entity *v_field : vt->Struct.fields) { + bool found = false; + for (Entity *u_field : ut->Struct.fields) { + if (v_field->token.string == u_field->token.string && + are_types_identical(v_field->type, u_field->type)) { + found = true; + break; + } + } + + if (!found) { + is_shared = false; + break; + } + } + + operand->mode = Addressing_Constant; + operand->value = exact_value_bool(is_shared); + operand->type = t_untyped_bool; + break; + } + case BuiltinProc_type_field_type: { Operand op = {}; @@ -5117,6 +5901,254 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } break; + case BuiltinProc_type_union_tag_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 *u = operand->type; + + if (!is_type_union(u)) { + error(operand->expr, "Expected a union type for '%.*s'", LIT(builtin_name)); + operand->mode = Addressing_Invalid; + operand->type = t_invalid; + return false; + } + + u = base_type(u); + GB_ASSERT(u->kind == Type_Union); + + operand->mode = Addressing_Type; + operand->type = union_tag_type(u); + } + break; + + case BuiltinProc_type_union_tag_offset: + { + 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 *u = operand->type; + + if (!is_type_union(u)) { + error(operand->expr, "Expected a union type for '%.*s'", LIT(builtin_name)); + operand->mode = Addressing_Invalid; + operand->type = t_invalid; + return false; + } + + u = base_type(u); + GB_ASSERT(u->kind == Type_Union); + + // NOTE(jakubtomsu): forces calculation of variant_block_size + type_size_of(u); + i64 tag_offset = u->Union.variant_block_size; + GB_ASSERT(tag_offset > 0); + + operand->mode = Addressing_Constant; + operand->type = t_untyped_integer; + operand->value = exact_value_i64(tag_offset); + } + break; + + case BuiltinProc_type_union_base_tag_value: + { + 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 *u = operand->type; + + if (!is_type_union(u)) { + error(operand->expr, "Expected a union type for '%.*s'", LIT(builtin_name)); + operand->mode = Addressing_Invalid; + operand->type = t_invalid; + return false; + } + + u = base_type(u); + GB_ASSERT(u->kind == Type_Union); + + operand->mode = Addressing_Constant; + operand->type = t_untyped_integer; + 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) { + error(operand->expr, "Expected a type for '%.*s'", LIT(builtin_name)); + operand->mode = Addressing_Invalid; + operand->type = t_invalid; + return false; + } + + Type *u = operand->type; + + if (!is_type_union(u)) { + error(operand->expr, "Expected a union type for '%.*s'", LIT(builtin_name)); + operand->mode = Addressing_Invalid; + operand->type = t_invalid; + return false; + } + + u = base_type(u); + GB_ASSERT(u->kind == Type_Union); + + operand->mode = Addressing_Constant; + operand->type = t_untyped_integer; + operand->value = exact_value_i64(u->Union.variants.count); + } break; + + case BuiltinProc_type_variant_type_of: + { + 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 *u = operand->type; + + if (!is_type_union(u)) { + error(operand->expr, "Expected a union type for '%.*s'", LIT(builtin_name)); + operand->mode = Addressing_Invalid; + operand->type = t_invalid; + return false; + } + + u = base_type(u); + GB_ASSERT(u->kind == Type_Union); + Operand x = {}; + check_expr_or_type(c, &x, ce->args[1]); + if (!is_type_integer(x.type) || x.mode != Addressing_Constant) { + error(call, "Expected a constant integer for '%.*s", LIT(builtin_name)); + operand->mode = Addressing_Type; + operand->type = t_invalid; + return false; + } + + i64 index = big_int_to_i64(&x.value.value_integer); + if (index < 0 || index >= u->Union.variants.count) { + error(call, "Variant tag out of bounds index for '%.*s", LIT(builtin_name)); + operand->mode = Addressing_Type; + operand->type = t_invalid; + return false; + } + + operand->mode = Addressing_Type; + operand->type = u->Union.variants[index]; + } + break; + + case BuiltinProc_type_variant_index_of: + { + 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 *u = operand->type; + + if (!is_type_union(u)) { + error(operand->expr, "Expected a union type for '%.*s'", LIT(builtin_name)); + operand->mode = Addressing_Invalid; + operand->type = t_invalid; + return false; + } + + Type *v = check_type(c, ce->args[1]); + u = base_type(u); + GB_ASSERT(u->kind == Type_Union); + + i64 index = -1; + for_array(i, u->Union.variants) { + Type *vt = u->Union.variants[i]; + if (union_variant_index_types_equal(v, vt)) { + index = i64(i); + break; + } + } + + if (index < 0) { + error(operand->expr, "Expected a variant type for '%.*s'", LIT(builtin_name)); + operand->mode = Addressing_Invalid; + operand->type = t_invalid; + return false; + } + + operand->mode = Addressing_Constant; + operand->type = t_untyped_integer; + operand->value = exact_value_i64(index); + } + break; + case BuiltinProc_type_struct_field_count: operand->value = exact_value_i64(0); if (operand->mode != Addressing_Type) { @@ -5130,6 +6162,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); @@ -5281,15 +6338,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)); } @@ -5321,20 +6372,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)); @@ -5385,7 +6427,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } - operand->value = exact_value_bool(is_type_subtype_of(op_src.type, op_dst.type)); + operand->value = exact_value_bool(is_type_subtype_of_and_allow_polymorphic(op_src.type, op_dst.type)); operand->mode = Addressing_Constant; operand->type = t_untyped_bool; } break; @@ -5411,6 +6453,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)); @@ -5436,6 +6479,26 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } break; + case BuiltinProc_type_bit_set_backing_type: + { + Operand op = {}; + Type *type = check_type(c, ce->args[0]); + Type *bt = base_type(type); + if (bt == nullptr || bt == t_invalid) { + error(ce->args[0], "Expected a type for '%.*s'", LIT(builtin_name)); + return false; + } + if (bt->kind != Type_BitSet) { + gbString s = type_to_string(type); + error(ce->args[0], "Expected a bit_set type for '%.*s', got %s", LIT(builtin_name), s); + return false; + } + + operand->mode = Addressing_Type; + operand->type = bit_set_to_int(bt); + break; + } + case BuiltinProc_type_equal_proc: { Operand op = {}; @@ -5517,6 +6580,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 = {}; @@ -5604,6 +6712,11 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } + 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 = {}; Operand timeout = {}; @@ -5656,6 +6769,11 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } + 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 = {}; check_expr(c, &ptr, ce->args[0]); if (ptr.mode == Addressing_Invalid) return false; diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 71b897a84..1d792dad8 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -88,9 +88,18 @@ gb_internal Type *check_init_variable(CheckerContext *ctx, Entity *e, Operand *o e->type = t_invalid; return nullptr; } else if (is_type_polymorphic(t)) { + Entity *e2 = entity_of_node(operand->expr); + if (e2 == nullptr) { + e->type = t_invalid; + return nullptr; + } + if (e2->state.load() != EntityState_Resolved) { + e->type = t; + return nullptr; + } 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)); + error(operand->expr, "Invalid use of a non-specialized polymorphic type '%s' in %.*s", str, LIT(context_name)); e->type = t_invalid; return nullptr; } else if (is_type_empty_union(t)) { @@ -138,11 +147,10 @@ gb_internal void check_init_variables(CheckerContext *ctx, Entity **lhs, isize l } if (o->type && is_type_no_copy(o->type)) { - begin_error_block(); + ERROR_BLOCK(); if (check_no_copy_assignment(*o, str_lit("initialization"))) { error_line("\tInitialization of a #no_copy type must be either implicitly zero, a constant literal, or a return value from a call expression"); } - end_error_block(); } } if (rhs_count > 0 && lhs_count != rhs_count) { @@ -150,6 +158,160 @@ 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->kind = new_entity->kind; + original_entity->decl_info = new_entity->decl_info; + original_entity->aliased_of = new_entity; + + original_entity->identifier.store(new_entity->identifier); + + 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 + offset, cast(u8 *)new_entity + offset, size); +} + +gb_internal bool check_override_as_type_due_to_aliasing(CheckerContext *ctx, Entity *e, Entity *entity, Ast *init, Type *named_type) { + 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. + + if (e->type != nullptr && is_type_typed(e->type)) { + return false; + } + + 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 || @@ -160,6 +322,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); @@ -211,6 +380,7 @@ gb_internal bool is_type_distinct(Ast *node) { case Ast_UnionType: case Ast_EnumType: case Ast_ProcType: + case Ast_BitFieldType: return true; case Ast_PointerType: @@ -367,49 +537,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 +562,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 +572,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 +622,7 @@ gb_internal void check_const_decl(CheckerContext *ctx, Entity *e, Ast *type_expr return; } + if (entity != nullptr) { if (e->type != nullptr) { Operand x = {}; @@ -601,6 +698,13 @@ gb_internal bool sig_compare(TypeCheckSig *a, TypeCheckSig *b, Type *x, Type *y) } gb_internal bool signature_parameter_similar_enough(Type *x, Type *y) { + if (is_type_bit_set(x)) { + x = bit_set_to_int(x); + } + if (is_type_bit_set(y)) { + y = bit_set_to_int(y); + } + if (sig_compare(is_type_pointer, x, y)) { return true; } @@ -647,6 +751,14 @@ gb_internal bool signature_parameter_similar_enough(Type *x, Type *y) { return true; } + if (sig_compare(is_type_slice, x, y)) { + Type *s1 = core_type(x); + Type *s2 = core_type(y); + if (signature_parameter_similar_enough(s1->Slice.elem, s2->Slice.elem)) { + return true; + } + } + return are_types_identical(x, y); } @@ -663,16 +775,56 @@ gb_internal bool are_signatures_similar_enough(Type *a_, Type *b_) { if (a->result_count != b->result_count) { return false; } + + if (a->c_vararg != b->c_vararg) { + return false; + } + + if (a->variadic != b->variadic) { + return false; + } + + if (a->variadic && a->variadic_index != b->variadic_index) { + return false; + } + for (isize i = 0; i < a->param_count; i++) { Type *x = core_type(a->params->Tuple.variables[i]->type); Type *y = core_type(b->params->Tuple.variables[i]->type); + + if (x->kind == Type_BitSet && x->BitSet.underlying) { + x = core_type(x->BitSet.underlying); + } + if (y->kind == Type_BitSet && y->BitSet.underlying) { + y = core_type(y->BitSet.underlying); + } + + // Allow a `#c_vararg args: ..any` with `#c_vararg args: ..foo`. + if (a->variadic && i == a->variadic_index) { + GB_ASSERT(x->kind == Type_Slice); + GB_ASSERT(y->kind == Type_Slice); + Type *x_elem = core_type(x->Slice.elem); + Type *y_elem = core_type(y->Slice.elem); + if (is_type_any(x_elem) || is_type_any(y_elem)) { + continue; + } + } + if (!signature_parameter_similar_enough(x, y)) { return false; } } for (isize i = 0; i < a->result_count; i++) { - Type *x = base_type(a->results->Tuple.variables[i]->type); - Type *y = base_type(b->results->Tuple.variables[i]->type); + Type *x = core_type(a->results->Tuple.variables[i]->type); + Type *y = core_type(b->results->Tuple.variables[i]->type); + + if (x->kind == Type_BitSet && x->BitSet.underlying) { + x = core_type(x->BitSet.underlying); + } + if (y->kind == Type_BitSet && y->BitSet.underlying) { + y = core_type(y->BitSet.underlying); + } + if (!signature_parameter_similar_enough(x, y)) { return false; } @@ -699,7 +851,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 +876,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 +891,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; @@ -801,6 +973,43 @@ gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeCon } } +gb_internal void check_foreign_procedure(CheckerContext *ctx, Entity *e, DeclInfo *d) { + GB_ASSERT(e != nullptr); + GB_ASSERT(e->kind == Entity_Procedure); + String name = e->Procedure.link_name; + + mutex_lock(&ctx->info->foreign_mutex); + + auto *fp = &ctx->info->foreigns; + StringHashKey key = string_hash_string(name); + Entity **found = string_map_get(fp, key); + if (found && e != *found) { + Entity *f = *found; + TokenPos pos = f->token.pos; + Type *this_type = base_type(e->type); + Type *other_type = base_type(f->type); + if (is_type_proc(this_type) && is_type_proc(other_type)) { + if (!are_signatures_similar_enough(this_type, other_type)) { + error(d->proc_lit, + "Redeclaration of foreign procedure '%.*s' with different type signatures\n" + "\tat %s", + LIT(name), token_pos_to_string(pos)); + } + } else if (!signature_parameter_similar_enough(this_type, other_type)) { + error(d->proc_lit, + "Foreign entity '%.*s' previously declared elsewhere with a different type\n" + "\tat %s", + LIT(name), token_pos_to_string(pos)); + } + } else if (name == "main") { + error(d->proc_lit, "The link name 'main' is reserved for internal use"); + } else { + string_map_set(fp, key, e); + } + + mutex_unlock(&ctx->info->foreign_mutex); +} + gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { GB_ASSERT(e->type == nullptr); if (d->proc_lit->kind != Ast_ProcLit) { @@ -862,7 +1071,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,32 +1095,135 @@ 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\""); } break; } + e->Procedure.entry_point_only = ac.entry_point_only; e->Procedure.is_export = ac.is_export; + + bool has_instrumentation = false; + if (pl->body == nullptr) { + has_instrumentation = false; + if (ac.no_instrumentation != Instrumentation_Default) { + error(e->token, "@(no_instrumentation) is not allowed on foreign procedures"); + } + } else { + AstFile *file = e->token.pos.file_id ? global_files[e->token.pos.file_id] : nullptr; + if (file) { + has_instrumentation = (file->flags & AstFile_NoInstrumentation) == 0; + } + + switch (ac.no_instrumentation) { + case Instrumentation_Enabled: has_instrumentation = true; break; + case Instrumentation_Default: break; + case Instrumentation_Disabled: has_instrumentation = false; break; + } + } + + auto const is_valid_instrumentation_call = [](Type *type) -> bool { + if (type == nullptr || type->kind != Type_Proc) { + return false; + } + if (type->Proc.calling_convention != ProcCC_Contextless) { + return false; + } + if (type->Proc.result_count != 0) { + return false; + } + if (type->Proc.param_count != 3) { + return false; + } + Type *p0 = type->Proc.params->Tuple.variables[0]->type; + Type *p1 = type->Proc.params->Tuple.variables[1]->type; + Type *p3 = type->Proc.params->Tuple.variables[2]->type; + return is_type_rawptr(p0) && is_type_rawptr(p1) && are_types_identical(p3, t_source_code_location); + }; + + static char const *instrumentation_proc_type_str = "proc \"contextless\" (proc_address: rawptr, call_site_return_address: rawptr, loc: runtime.Source_Code_Location)"; + + if (ac.instrumentation_enter && ac.instrumentation_exit) { + error(e->token, "A procedure cannot be marked with both @(instrumentation_enter) and @(instrumentation_exit)"); + + has_instrumentation = false; + e->flags |= EntityFlag_Require; + } else if (ac.instrumentation_enter) { + if (!is_valid_instrumentation_call(e->type)) { + init_core_source_code_location(ctx->checker); + gbString s = type_to_string(e->type); + error(e->token, "@(instrumentation_enter) procedures must have the type '%s', got %s", instrumentation_proc_type_str, s); + gb_string_free(s); + } + MUTEX_GUARD(&ctx->info->instrumentation_mutex); + if (ctx->info->instrumentation_enter_entity != nullptr) { + error(e->token, "@(instrumentation_enter) has already been set"); + } else { + ctx->info->instrumentation_enter_entity = e; + } + + has_instrumentation = false; + e->flags |= EntityFlag_Require; + } else if (ac.instrumentation_exit) { + init_core_source_code_location(ctx->checker); + if (!is_valid_instrumentation_call(e->type)) { + gbString s = type_to_string(e->type); + error(e->token, "@(instrumentation_exit) procedures must have the type '%s', got %s", instrumentation_proc_type_str, s); + gb_string_free(s); + } + MUTEX_GUARD(&ctx->info->instrumentation_mutex); + if (ctx->info->instrumentation_exit_entity != nullptr) { + error(e->token, "@(instrumentation_exit) has already been set"); + } else { + ctx->info->instrumentation_exit_entity = e; + } + + has_instrumentation = false; + e->flags |= EntityFlag_Require; + } + + e->Procedure.has_instrumentation = has_instrumentation; + + 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; @@ -943,7 +1255,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) { @@ -1008,10 +1320,19 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { } else { pt->require_results = true; } + } else if (d->foreign_require_results && pt->result_count != 0) { + pt->require_results = true; } if (ac.link_name.len > 0) { - 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) { @@ -1025,54 +1346,16 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { name = e->Procedure.link_name; } Entity *foreign_library = init_entity_foreign_library(ctx, e); - - if (is_arch_wasm() && foreign_library != nullptr) { - String module_name = str_lit("env"); - GB_ASSERT (foreign_library->kind == Entity_LibraryName); - if (foreign_library->LibraryName.paths.count != 1) { - error(foreign_library->token, "'foreign import' for '%.*s' architecture may only have one path, got %td", - LIT(target_arch_names[build_context.metrics.arch]), foreign_library->LibraryName.paths.count); - } - - 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); - } - e->Procedure.is_foreign = true; e->Procedure.link_name = name; + e->Procedure.foreign_library = foreign_library; - mutex_lock(&ctx->info->foreign_mutex); - - auto *fp = &ctx->info->foreigns; - StringHashKey key = string_hash_string(name); - Entity **found = string_map_get(fp, key); - if (found && e != *found) { - Entity *f = *found; - TokenPos pos = f->token.pos; - Type *this_type = base_type(e->type); - Type *other_type = base_type(f->type); - if (is_type_proc(this_type) && is_type_proc(other_type)) { - if (!are_signatures_similar_enough(this_type, other_type)) { - error(d->proc_lit, - "Redeclaration of foreign procedure '%.*s' with different type signatures\n" - "\tat %s", - LIT(name), token_pos_to_string(pos)); - } - } else if (!are_types_identical(this_type, other_type)) { - error(d->proc_lit, - "Foreign entity '%.*s' previously declared elsewhere with a different type\n" - "\tat %s", - LIT(name), token_pos_to_string(pos)); - } - } else if (name == "main") { - error(d->proc_lit, "The link name 'main' is reserved for internal use"); + if (is_arch_wasm() && foreign_library != nullptr) { + // NOTE(bill): this must be delayed because the foreign import paths might not be evaluated yet until much later + mpsc_enqueue(&ctx->info->foreign_decls_to_check, e); } else { - string_map_set(fp, key, e); + check_foreign_procedure(ctx, e, d); } - - mutex_unlock(&ctx->info->foreign_mutex); } else { String name = e->token.string; if (e->Procedure.link_name.len > 0) { @@ -1119,7 +1402,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); @@ -1140,7 +1423,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; @@ -1176,8 +1462,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) { @@ -1201,7 +1487,7 @@ gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast TokenPos pos = f->token.pos; Type *this_type = base_type(e->type); Type *other_type = base_type(f->type); - if (!are_types_identical(this_type, other_type)) { + if (!signature_parameter_similar_enough(this_type, other_type)) { error(e->token, "Foreign entity '%.*s' previously declared elsewhere with a different type\n" "\tat %s", @@ -1226,6 +1512,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"); } @@ -1286,6 +1575,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++) { @@ -1300,8 +1593,12 @@ gb_internal void check_proc_group_decl(CheckerContext *ctx, Entity *pg_entity, D continue; } - begin_error_block(); - defer (end_error_block()); + + 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; @@ -1339,6 +1636,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; @@ -1506,6 +1804,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; } @@ -1546,6 +1855,7 @@ gb_internal bool check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de Entity *uvar = entry.uvar; Entity *prev = scope_insert_no_mutex(ctx->scope, uvar); if (prev != nullptr) { + ERROR_BLOCK(); error(e->token, "Namespace collision while 'using' procedure argument '%.*s' of: %.*s", LIT(e->token.string), LIT(prev->token.string)); error_line("%.*s != %.*s\n", LIT(uvar->token.string), LIT(prev->token.string)); break; @@ -1620,5 +1930,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 5cc548739..231ece2f4 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -79,7 +79,6 @@ gb_internal Type * check_type_expr (CheckerContext *c, Ast *exp gb_internal Type * make_optional_ok_type (Type *value, bool typed=true); gb_internal Entity * check_selector (CheckerContext *c, Operand *operand, Ast *node, Type *type_hint); gb_internal Entity * check_ident (CheckerContext *c, Operand *o, Ast *n, Type *named_type, Type *type_hint, bool allow_import_name); -gb_internal Entity * find_polymorphic_record_entity (CheckerContext *c, Type *original_type, isize param_count, Array const &ordered_operands, bool *failure); gb_internal void check_not_tuple (CheckerContext *c, Operand *operand); gb_internal void convert_to_typed (CheckerContext *c, Operand *operand, Type *target_type); gb_internal gbString expr_to_string (Ast *expression); @@ -100,15 +99,16 @@ gb_internal void check_union_type (CheckerContext *c, Type *un gb_internal Type * check_init_variable (CheckerContext *c, Entity *e, Operand *operand, String context_name); -gb_internal void check_assignment_error_suggestion(CheckerContext *c, Operand *o, Type *type); +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); gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 id, Type *type_hint); -gb_internal void check_promote_optional_ok(CheckerContext *c, Operand *x, Type **val_type_, Type **ok_type_); +gb_internal void check_promote_optional_ok(CheckerContext *c, Operand *x, Type **val_type_, Type **ok_type_, bool change_operand=true); gb_internal void check_or_else_right_type(CheckerContext *c, Ast *expr, String const &name, Type *right_type); gb_internal void check_or_else_split_types(CheckerContext *c, Operand *x, String const &name, Type **left_type_, Type **right_type_); @@ -119,6 +119,16 @@ gb_internal bool is_diverging_expr(Ast *expr); gb_internal isize get_procedure_param_count_excluding_defaults(Type *pt, isize *param_count_); +gb_internal bool is_expr_inferred_fixed_array(Ast *type_expr); + +gb_internal Entity *find_polymorphic_record_entity(GenTypesData *found_gen_types, isize param_count, Array const &ordered_operands); + +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); + +gb_internal bool is_exact_value_zero(ExactValue const &v); + enum LoadDirectiveResult { LoadDirective_Success = 0, LoadDirective_Error = 1, @@ -184,6 +194,8 @@ gb_internal void populate_check_did_you_mean_objc_entity(StringSet *set, Entity gb_internal void check_did_you_mean_objc_entity(String const &name, Entity *e, bool is_type, char const *prefix = "") { + if (build_context.terse_errors) { return; } + ERROR_BLOCK(); GB_ASSERT(e->kind == Entity_TypeName); GB_ASSERT(e->TypeName.objc_metadata != nullptr); @@ -204,6 +216,8 @@ gb_internal void check_did_you_mean_objc_entity(String const &name, Entity *e, b } gb_internal void check_did_you_mean_type(String const &name, Array const &fields, char const *prefix = "") { + if (build_context.terse_errors) { return; } + ERROR_BLOCK(); DidYouMeanAnswers d = did_you_mean_make(heap_allocator(), fields.count, name); @@ -217,6 +231,8 @@ gb_internal void check_did_you_mean_type(String const &name, Array con gb_internal void check_did_you_mean_type(String const &name, Slice const &fields, char const *prefix = "") { + if (build_context.terse_errors) { return; } + ERROR_BLOCK(); DidYouMeanAnswers d = did_you_mean_make(heap_allocator(), fields.count, name); @@ -229,6 +245,8 @@ gb_internal void check_did_you_mean_type(String const &name, Slice con } gb_internal void check_did_you_mean_scope(String const &name, Scope *scope, char const *prefix = "") { + if (build_context.terse_errors) { return; } + ERROR_BLOCK(); DidYouMeanAnswers d = did_you_mean_make(heap_allocator(), scope->elements.count, name); @@ -265,9 +283,21 @@ 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) { + + if (x != nullptr && 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 != nullptr && x->kind == Ast_CallExpr) { error(o->expr, "'%s' call does not return a value and cannot be used as a value", err); } else { error(o->expr, "'%s' used as a value", err); @@ -472,7 +502,9 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E nctx.no_polymorphic_errors = false; // NOTE(bill): Reset scope from the failed procedure type - scope_reset(scope); + scope->head_child.store(nullptr, std::memory_order_relaxed); + string_map_clear(&scope->elements); + ptr_set_clear(&scope->imported); // LEAK NOTE(bill): Cloning this AST may be leaky but this is not really an issue due to arena-based allocation Ast *cloned_proc_type_node = clone_ast(pt->node); @@ -520,13 +552,15 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E final_proc_type->Proc.is_poly_specialized = true; final_proc_type->Proc.is_polymorphic = true; - final_proc_type->Proc.variadic = src->Proc.variadic; - final_proc_type->Proc.require_results = src->Proc.require_results; - final_proc_type->Proc.c_vararg = src->Proc.c_vararg; - final_proc_type->Proc.has_named_results = src->Proc.has_named_results; - final_proc_type->Proc.diverging = src->Proc.diverging; - final_proc_type->Proc.return_by_pointer = src->Proc.return_by_pointer; - final_proc_type->Proc.optional_ok = src->Proc.optional_ok; + final_proc_type->Proc.variadic = src->Proc.variadic; + final_proc_type->Proc.require_results = src->Proc.require_results; + final_proc_type->Proc.c_vararg = src->Proc.c_vararg; + final_proc_type->Proc.has_named_results = src->Proc.has_named_results; + final_proc_type->Proc.diverging = src->Proc.diverging; + final_proc_type->Proc.return_by_pointer = src->Proc.return_by_pointer; + final_proc_type->Proc.optional_ok = src->Proc.optional_ok; + final_proc_type->Proc.enable_target_feature = src->Proc.enable_target_feature; + final_proc_type->Proc.require_target_feature = src->Proc.require_target_feature; for (isize i = 0; i < operands.count; i++) { @@ -550,6 +584,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); @@ -558,6 +593,16 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E entity->file = base_entity->file; entity->pkg = base_entity->pkg; entity->flags = 0; + + entity->Procedure.optimization_mode = base_entity->Procedure.optimization_mode; + + if (base_entity->flags & EntityFlag_Cold) { + entity->flags |= EntityFlag_Cold; + } + if (base_entity->flags & EntityFlag_Disabled) { + entity->flags |= EntityFlag_Disabled; + } + d->entity = entity; AstFile *file = nullptr; @@ -613,7 +658,7 @@ gb_internal bool check_cast_internal(CheckerContext *c, Operand *x, Type *type); #define MAXIMUM_TYPE_DISTANCE 10 -gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand, Type *type) { +gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand, Type *type, bool allow_array_programming) { if (c == nullptr) { GB_ASSERT(operand->mode == Addressing_Value); GB_ASSERT(is_type_typed(operand->type)); @@ -822,7 +867,7 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand if (dst->Union.variants.count == 1) { Type *vt = dst->Union.variants[0]; - i64 score = check_distance_between_types(c, operand, vt); + i64 score = check_distance_between_types(c, operand, vt, allow_array_programming); if (score >= 0) { return score+2; } @@ -830,7 +875,7 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand i64 prev_lowest_score = -1; i64 lowest_score = -1; for (Type *vt : dst->Union.variants) { - i64 score = check_distance_between_types(c, operand, vt); + i64 score = check_distance_between_types(c, operand, vt, allow_array_programming); if (score >= 0) { if (lowest_score < 0) { lowest_score = score; @@ -852,20 +897,6 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand } } - if (is_type_relative_pointer(dst)) { - i64 score = check_distance_between_types(c, operand, dst->RelativePointer.pointer_type); - if (score >= 0) { - return score+2; - } - } - - if (is_type_relative_multi_pointer(dst)) { - i64 score = check_distance_between_types(c, operand, dst->RelativeMultiPointer.pointer_type); - if (score >= 0) { - return score+2; - } - } - if (is_type_proc(dst)) { if (are_types_identical(src, dst)) { return 3; @@ -886,19 +917,21 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand } } - if (is_type_array(dst)) { - Type *elem = base_array_type(dst); - i64 distance = check_distance_between_types(c, operand, elem); - if (distance >= 0) { - return distance + 6; + if (allow_array_programming) { + if (is_type_array(dst)) { + Type *elem = base_array_type(dst); + i64 distance = check_distance_between_types(c, operand, elem, allow_array_programming); + if (distance >= 0) { + return distance + 6; + } } - } - if (is_type_simd_vector(dst)) { - Type *dst_elem = base_array_type(dst); - i64 distance = check_distance_between_types(c, operand, dst_elem); - if (distance >= 0) { - return distance + 6; + if (is_type_simd_vector(dst)) { + Type *dst_elem = base_array_type(dst); + i64 distance = check_distance_between_types(c, operand, dst_elem, allow_array_programming); + if (distance >= 0) { + return distance + 6; + } } } @@ -908,7 +941,7 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand } if (dst->Matrix.row_count == dst->Matrix.column_count) { Type *dst_elem = base_array_type(dst); - i64 distance = check_distance_between_types(c, operand, dst_elem); + i64 distance = check_distance_between_types(c, operand, dst_elem, allow_array_programming); if (distance >= 0) { return distance + 7; } @@ -956,9 +989,9 @@ gb_internal i64 assign_score_function(i64 distance, bool is_variadic=false) { } -gb_internal bool check_is_assignable_to_with_score(CheckerContext *c, Operand *operand, Type *type, i64 *score_, bool is_variadic=false) { +gb_internal bool check_is_assignable_to_with_score(CheckerContext *c, Operand *operand, Type *type, i64 *score_, bool is_variadic=false, bool allow_array_programming=true) { i64 score = 0; - i64 distance = check_distance_between_types(c, operand, type); + i64 distance = check_distance_between_types(c, operand, type, allow_array_programming); bool ok = distance >= 0; if (ok) { score = assign_score_function(distance, is_variadic); @@ -968,9 +1001,9 @@ gb_internal bool check_is_assignable_to_with_score(CheckerContext *c, Operand *o } -gb_internal bool check_is_assignable_to(CheckerContext *c, Operand *operand, Type *type) { +gb_internal bool check_is_assignable_to(CheckerContext *c, Operand *operand, Type *type, bool allow_array_programming=true) { i64 score = 0; - return check_is_assignable_to_with_score(c, operand, type, &score); + return check_is_assignable_to_with_score(c, operand, type, &score, /*is_variadic*/false, allow_array_programming); } gb_internal bool internal_check_is_assignable_to(Type *src, Type *dst) { @@ -1005,12 +1038,6 @@ gb_internal AstPackage *get_package_of_type(Type *type) { case Type_DynamicArray: type = type->DynamicArray.elem; continue; - case Type_RelativePointer: - type = type->RelativePointer.pointer_type; - continue; - case Type_RelativeMultiPointer: - type = type->RelativeMultiPointer.pointer_type; - continue; } return nullptr; } @@ -1024,16 +1051,19 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ return; } + // Grab definite or indefinite article matching `context_name`, or "" if not found. + String article = error_article(context_name); + if (is_type_untyped(operand->type)) { Type *target_type = type; if (type == nullptr || is_type_any(type)) { if (type == nullptr && is_type_untyped_uninit(operand->type)) { - error(operand->expr, "Use of --- in %.*s", LIT(context_name)); + error(operand->expr, "Use of --- in %.*s%.*s", LIT(article), LIT(context_name)); operand->mode = Addressing_Invalid; return; } if (type == nullptr && is_type_untyped_nil(operand->type)) { - error(operand->expr, "Use of untyped nil in %.*s", LIT(context_name)); + error(operand->expr, "Use of untyped nil in %.*s%.*s", LIT(article), LIT(context_name)); operand->mode = Addressing_Invalid; return; } @@ -1088,9 +1118,10 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ // TODO(bill): is this a good enough error message? error(operand->expr, - "Cannot assign overloaded procedure group '%s' to '%s' in %.*s", + "Cannot assign overloaded procedure group '%s' to '%s' in %.*s%.*s", expr_str, op_type_str, + LIT(article), LIT(context_name)); operand->mode = Addressing_Invalid; } @@ -1116,21 +1147,28 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ switch (operand->mode) { case Addressing_Builtin: error(operand->expr, - "Cannot assign built-in procedure '%s' in %.*s", + "Cannot assign built-in procedure '%s' to %.*s%.*s", expr_str, + LIT(article), LIT(context_name)); break; case Addressing_Type: if (is_type_polymorphic(operand->type)) { error(operand->expr, - "Cannot assign '%s' which is a polymorphic type in %.*s", + "Cannot assign '%s', a polymorphic type, to %.*s%.*s", op_type_str, + LIT(article), LIT(context_name)); } else { + ERROR_BLOCK(); error(operand->expr, - "Cannot assign '%s' which is a type in %.*s", + "Cannot assign '%s', a type, to %.*s%.*s", op_type_str, + LIT(article), LIT(context_name)); + if (type && are_types_identical(type, t_any)) { + error_line("\tSuggestion: 'typeid_of(%s)'", expr_str); + } } break; default: @@ -1156,12 +1194,85 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ ERROR_BLOCK(); error(operand->expr, - "Cannot assign value '%s' of type '%s%s' to '%s%s' in %.*s", + "Cannot assign value '%s' of type '%s%s' to '%s%s' in %.*s%.*s", expr_str, op_type_str, op_type_extra, type_str, type_extra, + LIT(article), LIT(context_name)); check_assignment_error_suggestion(c, operand, type); + + Type *src = base_type(operand->type); + Type *dst = base_type(type); + if (context_name == "procedure argument") { + 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 && + x->Proc.diverging != y->Proc.diverging) { + + gbString s_expected = type_to_string(y); + if (y->Proc.diverging) { + s_expected = gb_string_appendc(s_expected, " -> !"); + } + + gbString s_got = type_to_string(x); + if (x->Proc.diverging) { + s_got = gb_string_appendc(s_got, " -> !"); + } + + error_line("\tNote: One of the procedures is diverging while the other isn't\n"); + error_line("\t Expected: %s\n", s_expected); + error_line("\t Got: %s\n", s_got); + gb_string_free(s_got); + gb_string_free(s_expected); + } else if (same_inputs && !same_outputs) { + gbString s_expected = type_to_string(y->Proc.results); + gbString s_got = type_to_string(x->Proc.results); + 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; } @@ -1233,7 +1344,7 @@ gb_internal bool is_polymorphic_type_assignable(CheckerContext *c, Type *poly, T } case Type_Pointer: if (source->kind == Type_Pointer) { - isize level = check_is_assignable_to_using_subtype(source->Pointer.elem, poly->Pointer.elem); + isize level = check_is_assignable_to_using_subtype(source->Pointer.elem, poly->Pointer.elem, /*level*/0, /*src_is_ptr*/false, /*allow_polymorphic*/true); if (level > 0) { return true; } @@ -1350,6 +1461,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; @@ -1384,11 +1505,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); @@ -1405,9 +1531,18 @@ gb_internal bool is_polymorphic_type_assignable(CheckerContext *c, Type *poly, T return ok; } - // return check_is_assignable_to(c, &o, poly); + + // NOTE(bill): Check for subtypes of + // 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; @@ -1518,6 +1653,55 @@ gb_internal bool check_cycle(CheckerContext *c, Entity *curr, bool report) { return false; } +struct CIdentSuggestion { + String name; + String msg; +}; + +// NOTE(bill): this linear look-up table might be slow but because it's an error case, it should be fine +gb_internal CIdentSuggestion const c_ident_suggestions[] = { + {str_lit("while"), str_lit("'for'? Odin only has one loop construct: 'for'")}, + + {str_lit("sizeof"), str_lit("'size_of'?")}, + {str_lit("alignof"), str_lit("'align_of'?")}, + {str_lit("offsetof"), str_lit("'offset_of'?")}, + + {str_lit("_Bool"), str_lit("'bool'?")}, + + {str_lit("char"), str_lit("'u8', 'i8', or 'c.char' (which is part of 'core:c')?")}, + {str_lit("short"), str_lit("'i16' or 'c.short' (which is part of 'core:c')?")}, + {str_lit("long"), str_lit("'c.long' (which is part of 'core:c')?")}, + {str_lit("float"), str_lit("'f32'?")}, + {str_lit("double"), str_lit("'f64'?")}, + {str_lit("unsigned"), str_lit("'c.uint' (which is part of 'core:c')?")}, + {str_lit("signed"), str_lit("'c.int' (which is part of 'core:c')?")}, + + {str_lit("size_t"), str_lit("'uint', or 'c.size_t' (which is part of 'core:c')?")}, + {str_lit("ssize_t"), str_lit("'int', or 'c.ssize_t' (which is part of 'core:c')?")}, + + {str_lit("uintptr_t"), str_lit("'uintptr'?")}, + {str_lit("intptr_t"), str_lit("'uintptr' or `int` or something else?")}, + {str_lit("ptrdiff_t"), str_lit("'int' or 'c.ptrdiff_t' (which is part of 'core:c')?")}, + {str_lit("intmax_t"), str_lit("'c.intmax_t' (which is part of 'core:c')?")}, + {str_lit("uintmax_t"), str_lit("'c.uintmax_t' (which is part of 'core:c')?")}, + + {str_lit("uint8_t"), str_lit("'u8'?")}, + {str_lit("int8_t"), str_lit("'i8'?")}, + {str_lit("uint16_t"), str_lit("'u16'?")}, + {str_lit("int16_t"), str_lit("'i16'?")}, + {str_lit("uint32_t"), str_lit("'u32'?")}, + {str_lit("int32_t"), str_lit("'i32'?")}, + {str_lit("uint64_t"), str_lit("'u64'?")}, + {str_lit("int64_t"), str_lit("'i64'?")}, + {str_lit("uint128_t"), str_lit("'u128'?")}, + {str_lit("int128_t"), str_lit("'i128'?")}, + + {str_lit("float32"), str_lit("'f32'?")}, + {str_lit("float64"), str_lit("'f64'?")}, + {str_lit("float32_t"), str_lit("'f32'?")}, + {str_lit("float64_t"), str_lit("'f64'?")}, +}; + gb_internal Entity *check_ident(CheckerContext *c, Operand *o, Ast *n, Type *named_type, Type *type_hint, bool allow_import_name) { GB_ASSERT(n->kind == Ast_Ident); o->mode = Addressing_Invalid; @@ -1529,7 +1713,16 @@ gb_internal Entity *check_ident(CheckerContext *c, Operand *o, Ast *n, Type *nam if (is_blank_ident(name)) { error(n, "'_' cannot be used as a value"); } else { + ERROR_BLOCK(); error(n, "Undeclared name: %.*s", LIT(name)); + + // NOTE(bill): Loads of checks for C programmers + + for (CIdentSuggestion const &suggestion : c_ident_suggestions) { + if (name == suggestion.name) { + error_line("\tSuggestion: Did you mean %.*s\n", LIT(suggestion.msg)); + } + } } o->type = t_invalid; o->mode = Addressing_Invalid; @@ -1655,7 +1848,7 @@ gb_internal Entity *check_ident(CheckerContext *c, Operand *o, Ast *n, Type *nam if (check_cycle(c, e, true)) { o->type = t_invalid; } - if (o->type != nullptr && type->kind == Type_Named && o->type->Named.type_name->TypeName.is_type_alias) { + if (o->type != nullptr && o->type->kind == Type_Named && o->type->Named.type_name->TypeName.is_type_alias) { o->type = base_type(o->type); } @@ -1663,7 +1856,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: @@ -1696,6 +1889,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) { @@ -1717,17 +1917,34 @@ 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; } break; + case Token_Mul: + { + ERROR_BLOCK(); + error(op, "Operator '%.*s' is not a valid unary operator in Odin", LIT(op.string)); + if (is_type_pointer(o->type)) { + str = expr_to_string(o->expr); + error_line("\tSuggestion: Did you mean '%s^'?\n", str); + o->type = type_deref(o->type); + } else if (is_type_multi_pointer(o->type)) { + str = expr_to_string(o->expr); + error_line("\tSuggestion: The value is a multi-pointer, did you mean '%s[0]'?\n", str); + o->type = type_deref(o->type, true); + } + } + break; default: error(op, "Unknown operator '%.*s'", LIT(op.string)); return false; @@ -1876,33 +2093,55 @@ gb_internal bool check_representable_as_constant(CheckerContext *c, ExactValue i BigInt i = v.value_integer; - i64 bit_size = type_size_of(type); + i64 byte_size = type_size_of(type); BigInt umax = {}; BigInt imin = {}; BigInt imax = {}; - if (bit_size < 16) { - big_int_from_u64(&umax, unsigned_integer_maxs[bit_size]); - big_int_from_i64(&imin, signed_integer_mins[bit_size]); - big_int_from_i64(&imax, signed_integer_maxs[bit_size]); - } else { + if (c->bit_field_bit_size > 0) { + i64 bit_size = gb_min(cast(i64)(8*byte_size), cast(i64)c->bit_field_bit_size); + big_int_from_u64(&umax, 1); big_int_from_i64(&imin, 1); big_int_from_i64(&imax, 1); - BigInt bi128 = {}; - BigInt bi127 = {}; - big_int_from_i64(&bi128, 128); - big_int_from_i64(&bi127, 127); + BigInt bu = {}; + BigInt bi = {}; + big_int_from_i64(&bu, bit_size); + big_int_from_i64(&bi, bit_size-1); - big_int_shl_eq(&umax, &bi128); + big_int_shl_eq(&umax, &bu); mp_decr(&umax); - big_int_shl_eq(&imin, &bi127); + big_int_shl_eq(&imin, &bi); big_int_neg(&imin, &imin); - big_int_shl_eq(&imax, &bi127); + big_int_shl_eq(&imax, &bi); mp_decr(&imax); + } else { + if (byte_size < 16) { + big_int_from_u64(&umax, unsigned_integer_maxs[byte_size]); + big_int_from_i64(&imin, signed_integer_mins[byte_size]); + big_int_from_i64(&imax, signed_integer_maxs[byte_size]); + } else { + big_int_from_u64(&umax, 1); + big_int_from_i64(&imin, 1); + big_int_from_i64(&imax, 1); + + BigInt bi128 = {}; + BigInt bi127 = {}; + big_int_from_i64(&bi128, 128); + big_int_from_i64(&bi127, 127); + + big_int_shl_eq(&umax, &bi128); + mp_decr(&umax); + + big_int_shl_eq(&imin, &bi127); + big_int_neg(&imin, &imin); + + big_int_shl_eq(&imax, &bi127); + mp_decr(&imax); + } } switch (type->Basic.kind) { @@ -2061,48 +2300,85 @@ gb_internal bool check_representable_as_constant(CheckerContext *c, ExactValue i } -gb_internal bool check_integer_exceed_suggestion(CheckerContext *c, Operand *o, Type *type) { +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; + bool size_changed = false; + if (max_bit_size > 0) { + size_changed = (bit_size != max_bit_size); + bit_size = gb_min(bit_size, max_bit_size); + } BigInt *bi = &o->value.value_integer; if (is_type_unsigned(type)) { + BigInt one = big_int_make_u64(1); + BigInt max_size = big_int_make_u64(1); + BigInt bits = big_int_make_i64(bit_size); + big_int_shl_eq(&max_size, &bits); + big_int_sub_eq(&max_size, &one); + if (big_int_is_neg(bi)) { error_line("\tA negative value cannot be represented by the unsigned integer type '%s'\n", b); + BigInt dst = {}; + big_int_neg(&dst, bi); + if (big_int_cmp(&dst, &max_size) < 0) { + big_int_sub_eq(&dst, &one); + String dst_str = big_int_to_string(temporary_allocator(), &dst); + gbString t = type_to_string(type); + error_line("\tSuggestion: ~%s(%.*s)\n", t, LIT(dst_str)); + gb_string_free(t); + } } else { - BigInt one = big_int_make_u64(1); - BigInt max_size = big_int_make_u64(1); - BigInt bits = big_int_make_i64(8*sz); - big_int_shl_eq(&max_size, &bits); - big_int_sub_eq(&max_size, &one); String max_size_str = big_int_to_string(temporary_allocator(), &max_size); - error_line("\tThe maximum value that can be represented by '%s' is '%.*s'\n", b, LIT(max_size_str)); + + if (size_changed) { + error_line("\tThe maximum value that can be represented with that bit_field's field of '%s | %u' is '%.*s'\n", b, bit_size, LIT(max_size_str)); + } else { + error_line("\tThe maximum value that can be represented by '%s' is '%.*s'\n", b, LIT(max_size_str)); + } } } else { BigInt zero = big_int_make_u64(0); BigInt one = big_int_make_u64(1); BigInt max_size = big_int_make_u64(1); - BigInt bits = big_int_make_i64(8*sz - 1); + BigInt bits = big_int_make_i64(bit_size - 1); big_int_shl_eq(&max_size, &bits); + + String max_size_str = {}; if (big_int_is_neg(bi)) { big_int_neg(&max_size, &max_size); - String max_size_str = big_int_to_string(temporary_allocator(), &max_size); - error_line("\tThe minimum value that can be represented by '%s' is '%.*s'\n", b, LIT(max_size_str)); + max_size_str = big_int_to_string(temporary_allocator(), &max_size); } else { big_int_sub_eq(&max_size, &one); - String max_size_str = big_int_to_string(temporary_allocator(), &max_size); + max_size_str = big_int_to_string(temporary_allocator(), &max_size); + } + + if (size_changed) { + error_line("\tThe maximum value that can be represented with that bit_field's field of '%s | %u' is '%.*s'\n", b, bit_size, LIT(max_size_str)); + } else { error_line("\tThe maximum value that can be represented by '%s' is '%.*s'\n", b, LIT(max_size_str)); } } - gb_string_free(b); return true; } return false; } -gb_internal void check_assignment_error_suggestion(CheckerContext *c, Operand *o, Type *type) { +gb_internal void check_assignment_error_suggestion(CheckerContext *c, Operand *o, Type *type, i64 max_bit_size) { gbString a = expr_to_string(o->expr); gbString b = type_to_string(type); defer( @@ -2133,8 +2409,23 @@ gb_internal void check_assignment_error_suggestion(CheckerContext *c, Operand *o error_line("\t whereas slices in general are assumed to be mutable.\n"); } else if (is_type_u8_slice(src) && are_types_identical(dst, t_string) && o->mode != Addressing_Constant) { error_line("\tSuggestion: the expression may be casted to %s\n", b); - } else if (check_integer_exceed_suggestion(c, o, type)) { + } else if (check_integer_exceed_suggestion(c, o, type, max_bit_size)) { return; + } else if (is_expr_inferred_fixed_array(c->type_hint_expr) && is_type_array_like(type) && is_type_array_like(o->type)) { + gbString s = expr_to_string(c->type_hint_expr); + error_line("\tSuggestion: make sure that `%s` is attached to the compound literal directly\n", s); + gb_string_free(s); + } else if (is_type_pointer(type) && + o->mode == Addressing_Variable && + are_types_identical(type_deref(type), o->type)) { + gbString s = expr_to_string(o->expr); + error_line("\tSuggestion: Did you mean `&%s`\n", s); + gb_string_free(s); + } else if (is_type_pointer(o->type) && + are_types_identical(type_deref(o->type), type)) { + gbString s = expr_to_string(o->expr); + error_line("\tSuggestion: Did you mean `%s^`\n", s); + gb_string_free(s); } } @@ -2203,18 +2494,22 @@ gb_internal bool check_is_expressible(CheckerContext *ctx, Operand *o, Type *typ ERROR_BLOCK(); - if (is_type_numeric(o->type) && is_type_numeric(type)) { if (!is_type_integer(o->type) && is_type_integer(type)) { error(o->expr, "'%s' truncated to '%s', got %s", a, b, s); } else { + i64 max_bit_size = 0; + if (ctx->bit_field_bit_size) { + max_bit_size = ctx->bit_field_bit_size; + } + if (are_types_identical(o->type, type)) { error(o->expr, "Numeric value '%s' from '%s' cannot be represented by '%s'", s, a, b); } else { error(o->expr, "Cannot convert numeric value '%s' from '%s' to '%s' from '%s'", s, a, b, c); } - check_assignment_error_suggestion(ctx, o, type); + check_assignment_error_suggestion(ctx, o, type, max_bit_size); } } else { error(o->expr, "Cannot convert '%s' to '%s' from '%s', got %s", a, b, c, s); @@ -2225,6 +2520,11 @@ gb_internal bool check_is_expressible(CheckerContext *ctx, Operand *o, Type *typ } gb_internal bool check_is_not_addressable(CheckerContext *c, Operand *o) { + if (o->expr && o->expr->kind == Ast_SelectorExpr) { + if (o->expr->SelectorExpr.is_bit_field) { + return true; + } + } if (o->mode == Addressing_OptionalOk) { Ast *expr = unselector_expr(o->expr); if (expr->kind != Ast_TypeAssertion) { @@ -2255,35 +2555,82 @@ 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) { - if (!(build_context.strict_style || (check_vet_flags(expr) & VetFlag_Style))) { - return; - } +gb_internal ExactValue exact_bit_set_all_set_mask(Type *type) { + type = base_type(type); + GB_ASSERT(type->kind == Type_BitSet); - Entity *e = entity_of_node(expr); - if (e != nullptr && (e->flags & EntityFlag_OldForOrSwitchValue) != 0) { - GB_ASSERT(e->kind == Entity_Variable); + i64 lower = type->BitSet.lower; + i64 upper = type->BitSet.upper; + Type *elem = type->BitSet.elem; + Type *underlying = type->BitSet.underlying; + bool is_backed = underlying != nullptr; + gb_unused(is_backed); - begin_error_block(); - defer (end_error_block()); + BigInt b_lower = {}; + BigInt b_upper = {}; + big_int_from_i64(&b_lower, lower); + big_int_from_i64(&b_upper, upper); - 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 with '-strict-style'."); + BigInt one = {}; + big_int_from_u64(&one, 1); - 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)); + BigInt mask = {}; + + if (elem == nullptr) { + big_int_from_i64(&mask, -1); + } else if (is_type_enum(elem)) { + Type *e = base_type(elem); + GB_ASSERT(e->kind == Type_Enum); + gb_unused(e); + + if ((big_int_cmp(&e->Enum.min_value->value_integer, &b_lower) == 0 || is_backed) && + big_int_cmp(&e->Enum.max_value->value_integer, &b_upper) == 0) { + + i64 lower_base = is_backed ? gb_min(0, lower) : lower; + BigInt b_lower_base = {}; + big_int_from_i64(&b_lower_base, lower_base); + + for (Entity *f : e->Enum.fields) { + if (f->kind != Entity_Constant) { + continue; + } + if (f->Constant.value.kind != ExactValue_Integer) { + continue; + } + + BigInt shift_amount = f->Constant.value.value_integer; + big_int_sub_eq(&shift_amount, &b_lower_base); + + + BigInt value = {}; + big_int_shl(&value, &one, &shift_amount); + + big_int_or(&mask, &mask, &value); } - } else { - 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 with '-strict-style'."); - error_line("\tSuggestion: Prefer doing 'switch &%.*s in ...'\n", LIT(e->token.string)); + } else { + // TODO(bill): enum range based"); + big_int_from_i64(&mask, -1); + } + } else { + i64 lower_base = lower; + for (i64 x = lower; x <= upper; x++) { + BigInt shift_amount = {}; + big_int_from_i64(&shift_amount, x - lower_base); + + BigInt value = {}; + big_int_shl(&value, &one, &shift_amount); + + big_int_or(&mask, &mask, &value); } } + + + ExactValue res = {}; + res.kind = ExactValue_Integer; + res.value_integer = mask; + return res; } gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast *node) { @@ -2298,6 +2645,8 @@ gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast * Entity *e = entity_of_node(ue->expr); if (e != nullptr && (e->flags & EntityFlag_Param) != 0) { error(op, "Cannot take the pointer address of '%s' which is a procedure parameter", str); + } else if (e != nullptr && (e->flags & EntityFlag_BitFieldField) != 0) { + error(op, "Cannot take the pointer address of '%s' which is a bit_field's field", str); } else { switch (o->mode) { case Addressing_Constant: @@ -2309,10 +2658,12 @@ gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast * break; default: { - begin_error_block(); - defer (end_error_block()); + 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)) { @@ -2322,9 +2673,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; @@ -2339,18 +2698,13 @@ gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast * ast_node(ue, UnaryExpr, node); if (ast_node_expect(ue->expr, Ast_IndexExpr)) { ast_node(ie, IndexExpr, ue->expr); - Type *soa_type = type_of_expr(ie->expr); + Type *soa_type = type_deref(type_of_expr(ie->expr)); GB_ASSERT(is_type_soa_struct(soa_type)); o->type = alloc_type_soa_pointer(soa_type); } else { 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); } @@ -2376,6 +2730,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); @@ -2413,6 +2772,10 @@ gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast * } o->value = exact_unary_operator_value(op.kind, o->value, precision, is_unsigned); + if (op.kind == Token_Xor && is_type_bit_set(type)) { + ExactValue mask = exact_bit_set_all_set_mask(type); + o->value = exact_binary_operator_value(Token_And, o->value, mask); + } if (is_type_typed(type)) { if (node != nullptr) { @@ -2759,7 +3122,7 @@ gb_internal void check_shift(CheckerContext *c, Operand *x, Operand *y, Ast *nod x->mode = Addressing_Value; if (type_hint) { if (is_type_integer(type_hint)) { - x->type = type_hint; + convert_to_typed(c, x, type_hint); } else { gbString x_str = expr_to_string(x->expr); gbString to_type = type_to_string(type_hint); @@ -2872,6 +3235,13 @@ gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type } } + if (is_type_bit_field(src)) { + return are_types_identical(core_type(src->BitField.backing_type), dst); + } + if (is_type_bit_field(dst)) { + return are_types_identical(src, core_type(dst->BitField.backing_type)); + } + if (is_type_integer(src) && is_type_rune(dst)) { return true; } @@ -2983,6 +3353,13 @@ gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type } // proc <-> proc if (is_type_proc(src) && is_type_proc(dst)) { + if (is_type_polymorphic(dst)) { + if (is_type_polymorphic(src) && + operand->mode == Addressing_Variable) { + return true; + } + return false; + } return true; } @@ -2995,6 +3372,14 @@ gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type return true; } + + if (is_type_array(dst)) { + Type *elem = base_array_type(dst); + if (check_is_castable_to(c, operand, elem)) { + return true; + } + } + if (is_type_simd_vector(src) && is_type_simd_vector(dst)) { if (src->SimdVector.count != dst->SimdVector.count) { return false; @@ -3053,7 +3438,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; @@ -3062,7 +3447,6 @@ gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type) { bool is_const_expr = x->mode == Addressing_Constant; bool can_convert = check_cast_internal(c, x, type); - if (!can_convert) { TEMPORARY_ALLOCATOR_GUARD(); gbString expr_str = expr_to_string(x->expr, temporary_allocator()); @@ -3071,7 +3455,7 @@ gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type) { x->mode = Addressing_Invalid; - begin_error_block(); + ERROR_BLOCK(); error(x->expr, "Cannot cast '%s' as '%s' from '%s'", expr_str, to_type, from_type); if (is_const_expr) { gbString val_str = exact_value_to_string(x->value); @@ -3094,37 +3478,70 @@ gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type) { } check_cast_error_suggestion(c, x, type); - end_error_block(); - return; } 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); + } else { + Type *src = core_type(x->type); + Type *dst = core_type(type); + if (src != dst) { + bool const REQUIRE = true; + if (is_type_integer_128bit(src) && is_type_float(dst)) { + add_package_dependency(c, "runtime", "floattidf_unsigned", REQUIRE); + add_package_dependency(c, "runtime", "floattidf", REQUIRE); + } else if (is_type_integer_128bit(dst) && is_type_float(src)) { + add_package_dependency(c, "runtime", "fixunsdfti", REQUIRE); + add_package_dependency(c, "runtime", "fixunsdfdi", REQUIRE); + } else if (src == t_f16 && is_type_float(dst)) { + add_package_dependency(c, "runtime", "gnu_h2f_ieee", REQUIRE); + add_package_dependency(c, "runtime", "extendhfsf2", REQUIRE); + } else if (is_type_float(dst) && dst == t_f16) { + add_package_dependency(c, "runtime", "truncsfhf2", REQUIRE); + add_package_dependency(c, "runtime", "truncdfhf2", REQUIRE); + 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; - // } + Operand src = *o; Type *src_t = o->type; Type *dst_t = t; @@ -3177,10 +3594,11 @@ gb_internal bool check_transmute(CheckerContext *c, Ast *node, Operand *o, Type if (are_types_identical(src_bt, dst_bt)) { return true; } - if (is_type_integer(src_t) && is_type_integer(dst_t)) { + if ((is_type_integer(src_t) && is_type_integer(dst_t)) || + is_type_integer(src_t) && is_type_bit_set(dst_t)) { if (types_have_same_internal_endian(src_t, dst_t)) { ExactValue src_v = exact_value_to_integer(o->value); - GB_ASSERT(src_v.kind == ExactValue_Integer); + GB_ASSERT(src_v.kind == ExactValue_Integer || src_v.kind == ExactValue_Invalid); BigInt v = src_v.value_integer; BigInt smax = {}; @@ -3208,6 +3626,37 @@ 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)) && + check_is_castable_to(c, &src, dst_t)) { + 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; @@ -3223,6 +3672,13 @@ gb_internal bool check_binary_array_expr(CheckerContext *c, Token op, Operand *x } } } + if (is_type_simd_vector(x->type) && !is_type_simd_vector(y->type)) { + if (check_is_assignable_to(c, y, x->type)) { + if (check_binary_op(c, x, op)) { + return true; + } + } + } return false; } @@ -3244,6 +3700,13 @@ gb_internal Type *check_matrix_type_hint(Type *matrix, Type *type_hint) { Type *th = base_type(type_hint); if (are_types_identical(th, xt)) { return type_hint; + } else if (xt->kind == Type_Matrix && th->kind == Type_Matrix) { + if (!are_types_identical(xt->Matrix.elem, th->Matrix.elem)) { + // ignore + } if (xt->Matrix.row_count == th->Matrix.row_count && + xt->Matrix.column_count == th->Matrix.column_count) { + return type_hint; + } } else if (xt->kind == Type_Matrix && th->kind == Type_Array) { if (!are_types_identical(xt->Matrix.elem, th->Array.elem)) { // ignore @@ -3278,14 +3741,23 @@ gb_internal void check_binary_matrix(CheckerContext *c, Token const &op, Operand if (xt->Matrix.column_count != yt->Matrix.row_count) { goto matrix_error; } + + if (xt->Matrix.is_row_major != yt->Matrix.is_row_major) { + goto matrix_error; + } + 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; } } else { - x->type = alloc_type_matrix(xt->Matrix.elem, xt->Matrix.row_count, yt->Matrix.column_count); + bool is_row_major = xt->Matrix.is_row_major && yt->Matrix.is_row_major; + x->type = alloc_type_matrix(xt->Matrix.elem, xt->Matrix.row_count, yt->Matrix.column_count, nullptr, nullptr, is_row_major); } goto matrix_success; } else if (yt->kind == Type_Array) { @@ -3299,10 +3771,10 @@ gb_internal void check_binary_matrix(CheckerContext *c, Token const &op, Operand // Treat arrays as column vectors x->mode = Addressing_Value; - if (type_hint == nullptr && xt->Matrix.row_count == yt->Array.count) { + if (xt->Matrix.row_count == yt->Array.count) { x->type = y->type; } else { - x->type = alloc_type_matrix(xt->Matrix.elem, xt->Matrix.row_count, 1); + x->type = alloc_type_matrix(xt->Matrix.elem, xt->Matrix.row_count, 1, nullptr, nullptr, xt->Matrix.is_row_major); } goto matrix_success; } @@ -3330,10 +3802,10 @@ gb_internal void check_binary_matrix(CheckerContext *c, Token const &op, Operand // Treat arrays as row vectors x->mode = Addressing_Value; - if (type_hint == nullptr && yt->Matrix.column_count == xt->Array.count) { + if (yt->Matrix.column_count == xt->Array.count) { x->type = x->type; } else { - x->type = alloc_type_matrix(yt->Matrix.elem, 1, yt->Matrix.column_count); + x->type = alloc_type_matrix(yt->Matrix.elem, 1, yt->Matrix.column_count, nullptr, nullptr, yt->Matrix.is_row_major); } goto matrix_success; } else if (are_types_identical(yt->Matrix.elem, xt)) { @@ -3387,10 +3859,10 @@ gb_internal void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Typ // NOTE(bill): Allow comparisons between types if (is_ise_expr(be->left)) { // Evalute the right before the left for an '.X' expression - check_expr_or_type(c, y, be->right, type_hint); + check_expr_or_type(c, y, be->right, nullptr /* ignore type hint */); check_expr_or_type(c, x, be->left, y->type); } else { - check_expr_or_type(c, x, be->left, type_hint); + check_expr_or_type(c, x, be->left, nullptr /* ignore type hint */); check_expr_or_type(c, y, be->right, x->type); } bool xt = x->mode == Addressing_Type; @@ -3419,6 +3891,12 @@ gb_internal void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Typ // IMPORTANT NOTE(bill): This uses right-left evaluation in type checking only no in check_expr(c, y, be->right); Type *rhs_type = type_deref(y->type); + if (rhs_type == nullptr) { + error(y->expr, "Cannot use '%.*s' on an expression with no value", LIT(op.string)); + x->mode = Addressing_Invalid; + x->expr = node; + return; + } if (is_type_bit_set(rhs_type)) { Type *elem = base_type(rhs_type)->BitSet.elem; @@ -3558,6 +4036,60 @@ gb_internal void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Typ return; } + bool REQUIRE = true; + Type *bt = base_type(x->type); + if (op.kind == Token_Mod || op.kind == Token_ModEq || + op.kind == Token_ModMod || op.kind == Token_ModModEq) { + if (bt->kind == Type_Basic) switch (bt->Basic.kind) { + case Basic_u128: add_package_dependency(c, "runtime", "umodti3", REQUIRE); break; + case Basic_i128: add_package_dependency(c, "runtime", "modti3", REQUIRE); break; + } + } else if (op.kind == Token_Quo || op.kind == Token_QuoEq) { + if (bt->kind == Type_Basic) switch (bt->Basic.kind) { + case Basic_complex32: add_package_dependency(c, "runtime", "quo_complex32"); break; + case Basic_complex64: add_package_dependency(c, "runtime", "quo_complex64"); break; + case Basic_complex128: add_package_dependency(c, "runtime", "quo_complex128"); break; + case Basic_quaternion64: add_package_dependency(c, "runtime", "quo_quaternion64"); break; + case Basic_quaternion128: add_package_dependency(c, "runtime", "quo_quaternion128"); break; + case Basic_quaternion256: add_package_dependency(c, "runtime", "quo_quaternion256"); break; + + case Basic_u128: add_package_dependency(c, "runtime", "udivti3", REQUIRE); break; + case Basic_i128: add_package_dependency(c, "runtime", "divti3", REQUIRE); break; + } + } else if (op.kind == Token_Mul || op.kind == Token_MulEq) { + if (bt->kind == Type_Basic) switch (bt->Basic.kind) { + case Basic_quaternion64: add_package_dependency(c, "runtime", "mul_quaternion64"); break; + case Basic_quaternion128: add_package_dependency(c, "runtime", "mul_quaternion128"); break; + case Basic_quaternion256: add_package_dependency(c, "runtime", "mul_quaternion256"); break; + + + case Basic_u128: + case Basic_i128: + if (is_arch_wasm()) { + add_package_dependency(c, "runtime", "__multi3", REQUIRE); + } + break; + } + } else if (op.kind == Token_Shl || op.kind == Token_ShlEq) { + if (bt->kind == Type_Basic) switch (bt->Basic.kind) { + case Basic_u128: + case Basic_i128: + if (is_arch_wasm()) { + add_package_dependency(c, "runtime", "__ashlti3", REQUIRE); + } + break; + } + } else if (op.kind == Token_Shr || op.kind == Token_ShrEq) { + if (bt->kind == Type_Basic) switch (bt->Basic.kind) { + case Basic_u128: + case Basic_i128: + if (is_arch_wasm()) { + add_package_dependency(c, "runtime", "__lshrti3", REQUIRE); + } + break; + } + } + if (token_is_shift(op.kind)) { check_shift(c, x, y, node, type_hint); return; @@ -3726,25 +4258,6 @@ gb_internal void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Typ return; } - if (op.kind == Token_Quo || op.kind == Token_QuoEq) { - Type *bt = base_type(x->type); - if (bt->kind == Type_Basic) switch (bt->Basic.kind) { - case Basic_complex32: add_package_dependency(c, "runtime", "quo_complex32"); break; - case Basic_complex64: add_package_dependency(c, "runtime", "quo_complex64"); break; - case Basic_complex128: add_package_dependency(c, "runtime", "quo_complex128"); break; - case Basic_quaternion64: add_package_dependency(c, "runtime", "quo_quaternion64"); break; - case Basic_quaternion128: add_package_dependency(c, "runtime", "quo_quaternion128"); break; - case Basic_quaternion256: add_package_dependency(c, "runtime", "quo_quaternion256"); break; - } - } else if (op.kind == Token_Mul || op.kind == Token_MulEq) { - Type *bt = base_type(x->type); - if (bt->kind == Type_Basic) switch (bt->Basic.kind) { - case Basic_quaternion64: add_package_dependency(c, "runtime", "mul_quaternion64"); break; - case Basic_quaternion128: add_package_dependency(c, "runtime", "mul_quaternion128"); break; - case Basic_quaternion256: add_package_dependency(c, "runtime", "mul_quaternion256"); break; - } - } - x->mode = Addressing_Value; } @@ -3891,7 +4404,7 @@ gb_internal void update_untyped_expr_value(CheckerContext *c, Ast *e, ExactValue } } -gb_internal void convert_untyped_error(CheckerContext *c, Operand *operand, Type *target_type) { +gb_internal void convert_untyped_error(CheckerContext *c, Operand *operand, Type *target_type, bool ignore_error_block=false) { gbString expr_str = expr_to_string(operand->expr); gbString type_str = type_to_string(target_type); gbString from_type_str = type_to_string(operand->type); @@ -3905,7 +4418,9 @@ gb_internal void convert_untyped_error(CheckerContext *c, Operand *operand, Type } } } - ERROR_BLOCK(); + if (!ignore_error_block) { + begin_error_block(); + } error(operand->expr, "Cannot convert untyped value '%s' to '%s' from '%s'%s", expr_str, type_str, from_type_str, extra_text); if (operand->value.kind == ExactValue_String) { @@ -3920,6 +4435,11 @@ gb_internal void convert_untyped_error(CheckerContext *c, Operand *operand, Type gb_string_free(type_str); gb_string_free(expr_str); operand->mode = Addressing_Invalid; + + if (!ignore_error_block) { + end_error_block(); + } + } gb_internal ExactValue convert_exact_value_for_type(ExactValue v, Type *type) { @@ -3941,8 +4461,8 @@ gb_internal ExactValue convert_exact_value_for_type(ExactValue v, Type *type) { } gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *target_type) { - GB_ASSERT_NOT_NULL(target_type); - if (operand->mode == Addressing_Invalid || + // GB_ASSERT_NOT_NULL(target_type); + if (target_type == nullptr || operand->mode == Addressing_Invalid || operand->mode == Addressing_Type || is_type_typed(operand->type) || target_type == t_invalid) { @@ -3983,7 +4503,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; @@ -4042,15 +4563,27 @@ gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *tar break; } + case Type_SimdVector: { + Type *elem = base_array_type(t); + if (check_is_assignable_to(c, operand, elem)) { + operand->mode = Addressing_Value; + } else { + operand->mode = Addressing_Invalid; + convert_untyped_error(c, operand, target_type); + return; + } + + break; + } + case Type_Matrix: { Type *elem = base_array_type(t); if (check_is_assignable_to(c, operand, elem)) { if (t->Matrix.row_count != t->Matrix.column_count) { operand->mode = Addressing_Invalid; - begin_error_block(); - defer (end_error_block()); + ERROR_BLOCK(); - convert_untyped_error(c, operand, target_type); + convert_untyped_error(c, operand, target_type, true); error_line("\tNote: Only a square matrix types can be initialized with a scalar value\n"); return; } else { @@ -4066,6 +4599,27 @@ gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *tar case Type_Union: + // IMPORTANT NOTE HACK(bill): This is just to allow for comparisons against `0` with the `os.Error` type + // as a kind of transition period + if (!build_context.strict_style && + operand->mode == Addressing_Constant && + target_type->kind == Type_Named && + (c->pkg == nullptr || c->pkg->name != "os") && + target_type->Named.name == "Error") { + Entity *e = target_type->Named.type_name; + if (e->pkg && e->pkg->name == "os") { + if (is_exact_value_zero(operand->value) && + (operand->value.kind == ExactValue_Integer || + operand->value.kind == ExactValue_Float)) { + operand->mode = Addressing_Value; + // target_type = t_untyped_nil; + operand->value = empty_exact_value; + update_untyped_expr_value(c, operand->expr, operand->value); + break; + } + } + } + // "fallthrough" if (!is_operand_nil(*operand) && !is_operand_uninit(*operand)) { TEMPORARY_ALLOCATOR_GUARD(); @@ -4104,17 +4658,21 @@ 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) { - begin_error_block(); - defer (end_error_block()); + ERROR_BLOCK(); GB_ASSERT(first_success_index >= 0); operand->mode = Addressing_Invalid; - convert_untyped_error(c, operand, target_type); + convert_untyped_error(c, operand, target_type, true); error_line("Ambiguous type conversion to '%s', which variant did you mean:\n\t", type_str); i32 j = 0; @@ -4136,13 +4694,13 @@ gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *tar } else if (is_type_untyped_uninit(operand->type)) { target_type = t_untyped_uninit; } else if (!is_type_untyped_nil(operand->type) || !type_has_nil(target_type)) { - begin_error_block(); - defer (end_error_block()); + ERROR_BLOCK(); operand->mode = Addressing_Invalid; - convert_untyped_error(c, operand, target_type); + convert_untyped_error(c, operand, target_type, true); if (count > 0) { error_line("'%s' is a union which only excepts the following types:\n", type_str); + error_line("\t"); for (i32 i = 0; i < count; i++) { Type *v = t->Union.variants[i]; @@ -4196,7 +4754,8 @@ gb_internal bool check_index_value(CheckerContext *c, Type *main_type, bool open check_expr_with_type_hint(c, &operand, index_value, type_hint); if (operand.mode == Addressing_Invalid) { if (value) *value = 0; - return false; + // NOTE(bill): return true here to propagate the errors better + return true; } Type *index_type = t_int; @@ -4352,7 +4911,8 @@ gb_internal ExactValue get_constant_field_single(CheckerContext *c, ExactValue v String name = fv->field->Ident.token.string; Selection sub_sel = lookup_field(node->tav.type, name, false); defer (array_free(&sub_sel.index)); - if (sub_sel.index[0] == index) { + if (sub_sel.index.count > 0 && + sub_sel.index[0] == index) { value = fv->value->tav.value; found = true; break; @@ -4416,7 +4976,7 @@ gb_internal ExactValue get_constant_field_single(CheckerContext *c, ExactValue v TypeAndValue tav = fv->value->tav; if (success_) *success_ = true; if (finish_) *finish_ = false; - return tav.value;; + return tav.value; } } @@ -4440,8 +5000,12 @@ gb_internal ExactValue get_constant_field_single(CheckerContext *c, ExactValue v if (success_) *success_ = true; if (finish_) *finish_ = false; return tav.value; + } else if (is_type_proc(tav.type)) { + if (success_) *success_ = true; + if (finish_) *finish_ = false; + return tav.value; } else { - GB_ASSERT(is_type_untyped_nil(tav.type)); + GB_ASSERT_MSG(is_type_untyped_nil(tav.type), "%s", type_to_string(tav.type)); if (success_) *success_ = true; if (finish_) *finish_ = false; return tav.value; @@ -4491,7 +5055,6 @@ gb_internal ExactValue get_constant_field(CheckerContext *c, Operand const *oper return value; } } - if (success_) *success_ = true; return value; } else if (value.kind == ExactValue_Quaternion) { @@ -4575,7 +5138,8 @@ gb_internal bool is_entity_declared_for_selector(Entity *entity, Scope *import_s if (entity->kind == Entity_Builtin) { // NOTE(bill): Builtin's are in the universal scope which is part of every scopes hierarchy // This means that we should just ignore the found result through it - *allow_builtin = entity->scope == import_scope || entity->scope != builtin_pkg->scope; + *allow_builtin = entity->scope == import_scope || + (entity->scope != builtin_pkg->scope && entity->scope != intrinsics_pkg->scope); } else if ((entity->scope->flags&ScopeFlag_Global) == ScopeFlag_Global && (import_scope->flags&ScopeFlag_Global) == 0) { is_declared = false; } @@ -4585,7 +5149,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) { @@ -4673,10 +5257,18 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod Selection sel = {}; // NOTE(bill): Not used if it's an import name if (!c->allow_arrow_right_selector_expr && se->token.kind == Token_ArrowRight) { + ERROR_BLOCK(); error(node, "Illegal use of -> selector shorthand outside of a call"); - operand->mode = Addressing_Invalid; - operand->expr = node; - return nullptr; + gbString x = expr_to_string(se->expr); + gbString y = expr_to_string(se->selector); + error_line("\tSuggestion: Did you mean '%s.%s'?\n", x, y); + gb_string_free(y); + gb_string_free(x); + + // TODO(bill): Should this terminate here or propagate onwards? + // operand->mode = Addressing_Invalid; + // operand->expr = node; + // return nullptr; } operand->expr = node; @@ -4702,7 +5294,14 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod add_entity_use(c, op_expr, e); expr_entity = e; - if (e != nullptr && e->kind == Entity_ImportName && selector->kind == Ast_Ident) { + if (e != nullptr && (e->kind == Entity_Procedure || e->kind == Entity_ProcGroup) && selector->kind == Ast_Ident) { + gbString sel_str = expr_to_string(selector); + error(node, "'%s' is not declared by by '%.*s'", sel_str, LIT(e->token.string)); + gb_string_free(sel_str); + operand->mode = Addressing_Invalid; + operand->expr = node; + return nullptr; + } else if (e != nullptr && e->kind == Entity_ImportName && selector->kind == Ast_Ident) { // IMPORTANT NOTE(bill): This is very sloppy code but it's also very fragile // It pretty much needs to be in this order and this way // If you can clean this up, please do but be really careful @@ -4710,10 +5309,19 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod Scope *import_scope = e->ImportName.scope; String entity_name = selector->Ident.token.string; + if (import_scope == nullptr) { + ERROR_BLOCK(); + error(node, "'%.*s' is not imported in this file, '%.*s' is unavailable", LIT(import_name), LIT(entity_name)); + operand->mode = Addressing_Invalid; + operand->expr = node; + return nullptr; + } + check_op_expr = false; entity = scope_lookup_current(import_scope, entity_name); bool allow_builtin = false; if (!is_entity_declared_for_selector(entity, import_scope, &allow_builtin)) { + ERROR_BLOCK(); error(node, "'%.*s' is not declared by '%.*s'", LIT(entity_name), LIT(import_name)); operand->mode = Addressing_Invalid; operand->expr = node; @@ -4784,28 +5392,34 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod } } + if (operand->type && is_type_soa_struct(type_deref(operand->type))) { + complete_soa_type(c->checker, type_deref(operand->type), false); + } if (entity == nullptr && selector->kind == Ast_Ident) { String field_name = selector->Ident.token.string; Type *t = type_deref(operand->type); if (t == nullptr) { error(operand->expr, "Cannot use a selector expression on 0-value expression"); - } else if (is_type_dynamic_array(t)) { - init_mem_allocator(c->checker); - } - sel = lookup_field(operand->type, field_name, operand->mode == Addressing_Type); - entity = sel.entity; + } else { + if (is_type_dynamic_array(t)) { + init_mem_allocator(c->checker); + } + sel = lookup_field(operand->type, field_name, operand->mode == Addressing_Type); + entity = sel.entity; - // NOTE(bill): Add type info needed for fields like 'names' - if (entity != nullptr && (entity->flags&EntityFlag_TypeField)) { - add_type_info_type(c, operand->type); - } - if (is_type_enum(operand->type)) { - add_type_info_type(c, operand->type); + // NOTE(bill): Add type info needed for fields like 'names' + if (entity != nullptr && (entity->flags&EntityFlag_TypeField)) { + add_type_info_type(c, operand->type); + } + if (is_type_enum(operand->type)) { + add_type_info_type(c, operand->type); + } } } - if (entity == nullptr && selector->kind == Ast_Ident && is_type_array(type_deref(operand->type))) { + if (entity == nullptr && selector->kind == Ast_Ident && operand->type != nullptr && + (is_type_array(type_deref(operand->type)) || is_type_simd_vector(type_deref(operand->type)))) { String field_name = selector->Ident.token.string; if (1 < field_name.len && field_name.len <= 4) { u8 swizzles_xyzw[4] = {'x', 'y', 'z', 'w'}; @@ -4860,8 +5474,10 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod Type *original_type = operand->type; Type *array_type = base_type(type_deref(original_type)); - GB_ASSERT(array_type->kind == Type_Array); - i64 array_count = array_type->Array.count; + GB_ASSERT(array_type->kind == Type_Array || array_type->kind == Type_SimdVector); + + i64 array_count = get_array_type_count(array_type); + for (u8 i = 0; i < index_count; i++) { u8 idx = indices>>(i*2) & 3; if (idx >= array_count) { @@ -4881,7 +5497,6 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod se->swizzle_count = index_count; se->swizzle_indices = indices; - AddressingMode prev_mode = operand->mode; operand->mode = Addressing_SwizzleValue; operand->type = determine_swizzle_array_type(original_type, type_hint, index_count); @@ -4895,6 +5510,10 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod break; } + if (array_type->kind == Type_SimdVector) { + operand->mode = Addressing_Value; + } + Entity *swizzle_entity = alloc_entity_variable(nullptr, make_token_ident(field_name), operand->type, EntityState_Resolved); add_type_and_value(c, operand->expr, operand->mode, operand->type, operand->value); return swizzle_entity; @@ -4914,6 +5533,8 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod error(op_expr, "Type '%s' has no field '%s'", op_str, sel_str); } } else { + ERROR_BLOCK(); + error(op_expr, "'%s' of type '%s' has no field '%s'", op_str, type_str, sel_str); if (operand->type != nullptr && selector->kind == Ast_Ident) { @@ -5008,6 +5629,11 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod operand->type = entity->type; operand->expr = node; + if (entity->flags & EntityFlag_BitFieldField) { + add_package_dependency(c, "runtime", "__write_bits"); + add_package_dependency(c, "runtime", "__read_bits"); + } + switch (entity->kind) { case Entity_Constant: operand->value = entity->Constant.value; @@ -5021,6 +5647,9 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod } break; case Entity_Variable: + if (sel.is_bit_field) { + se->is_bit_field = true; + } if (sel.indirect) { operand->mode = Addressing_Variable; } else if (operand->mode == Addressing_Context) { @@ -5126,7 +5755,7 @@ gb_internal bool check_identifier_exists(Scope *s, Ast *node, bool nested = fals gb_internal bool check_no_copy_assignment(Operand const &o, String const &context) { if (o.type && is_type_no_copy(o.type)) { Ast *expr = unparen_expr(o.expr); - if (expr && o.mode != Addressing_Constant) { + if (expr && o.mode != Addressing_Constant && o.mode != Addressing_Type) { if (expr->kind == Ast_CallExpr) { // Okay } else { @@ -5569,10 +6198,14 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A Operand *variadic_operand = &ordered_operands[pt->variadic_index]; if (vari_expand) { - GB_ASSERT(variadic_operands.count != 0); - *variadic_operand = variadic_operands[0]; - variadic_operand->type = default_type(variadic_operand->type); - actually_variadic = true; + if (variadic_operands.count == 0) { + error(call, "'..' in the wrong position"); + } else { + GB_ASSERT(variadic_operands.count != 0); + *variadic_operand = variadic_operands[0]; + variadic_operand->type = default_type(variadic_operand->type); + actually_variadic = true; + } } else { AstFile *f = call->file(); @@ -5606,9 +6239,6 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A for (isize i = 0; i < pt->param_count; i++) { if (!visited[i]) { Entity *e = pt->params->Tuple.variables[i]; - if (is_blank_ident(e->token)) { - continue; - } if (e->kind == Entity_Variable) { if (e->Variable.param_value.kind != ParameterValue_Invalid) { ordered_operands[i].mode = Addressing_Value; @@ -5639,14 +6269,20 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A } auto eval_param_and_score = [](CheckerContext *c, Operand *o, Type *param_type, CallArgumentError &err, bool param_is_variadic, Entity *e, bool show_error) -> i64 { + bool allow_array_programming = !(e && (e->flags & EntityFlag_NoBroadcast)); i64 s = 0; - if (!check_is_assignable_to_with_score(c, o, param_type, &s, param_is_variadic)) { + if (!check_is_assignable_to_with_score(c, o, param_type, &s, param_is_variadic, allow_array_programming)) { bool ok = false; - if (e && e->flags & EntityFlag_AnyInt) { + if (e && (e->flags & EntityFlag_AnyInt)) { if (is_type_integer(param_type)) { ok = check_is_castable_to(c, o, param_type); } } + if (!allow_array_programming && check_is_assignable_to_with_score(c, o, param_type, nullptr, param_is_variadic, !allow_array_programming)) { + if (show_error) { + error(o->expr, "'#no_broadcast' disallows automatic broadcasting a value across all elements of an array-like type in a procedure argument"); + } + } if (ok) { s = assign_score_function(MAXIMUM_TYPE_DISTANCE); } else { @@ -5655,7 +6291,6 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A } err = CallArgumentError_WrongTypes; } - } else if (show_error) { check_assignment(c, o, param_type, str_lit("procedure argument")); } @@ -5738,20 +6373,25 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A } if (param_is_variadic) { - continue; + if (!named_variadic_param) { + continue; + } } - score += eval_param_and_score(c, o, e->type, err, param_is_variadic, e, show_error); + score += eval_param_and_score(c, o, e->type, err, false, e, show_error); } } if (variadic) { - Type *slice = pt->params->Tuple.variables[pt->variadic_index]->type; + Entity *var_entity = pt->params->Tuple.variables[pt->variadic_index]; + Type *slice = var_entity->type; GB_ASSERT(is_type_slice(slice)); Type *elem = base_type(slice)->Slice.elem; 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; } @@ -5771,7 +6411,24 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A return CallArgumentError_MultipleVariadicExpand; } } - score += eval_param_and_score(c, o, t, err, true, nullptr, show_error); + score += eval_param_and_score(c, o, t, err, true, var_entity, show_error); + } + + if (!vari_expand && variadic_operands.count != 0) { + // 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(slice, 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{slice, variadic_operands.count}); + } + } } } @@ -5899,10 +6556,27 @@ gb_internal bool evaluate_where_clauses(CheckerContext *ctx, Ast *call_expr, Sco } } - if (call_expr) error(call_expr, "at caller location"); + if (call_expr) { + TokenPos pos = ast_token(call_expr).pos; + error_line("%s at caller location\n", token_pos_to_string(pos)); + } } 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); + } + } + } } @@ -6244,12 +6918,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 = {}; @@ -6280,13 +6959,26 @@ 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) { - gb_sort_array(valids.data, valids.count, valid_index_and_score_cmp); + array_sort(valids, valid_index_and_score_cmp); i64 best_score = valids[0].score; Entity *best_entity = proc_entities[valids[0].index]; GB_ASSERT(best_entity != nullptr); @@ -6333,8 +7025,7 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, }; if (valids.count == 0) { - begin_error_block(); - defer (end_error_block()); + ERROR_BLOCK(); error(operand->expr, "No procedures or ambiguous call for procedure group '%s' that match with the given arguments", expr_name); if (positional_operands.count == 0 && named_operands.count == 0) { @@ -6349,9 +7040,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 = {}; @@ -6381,7 +7136,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; @@ -6424,11 +7183,14 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, data.result_type = t_invalid; } else if (valids.count > 1) { - begin_error_block(); - defer (end_error_block()); + 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]; @@ -6641,6 +7403,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 @@ -6658,12 +7422,17 @@ 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); if (index >= 0) { TypeTuple *params = get_record_polymorphic_params(original_type); - Entity *e = params->variables[i]; + Entity *e = params->variables[index]; if (e->kind == Entity_Constant) { check_expr_with_type_hint(c, &operands[i], fv->value, e->type); continue; @@ -6696,7 +7465,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; @@ -6714,7 +7486,7 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O Array ordered_operands = operands; if (!named_fields) { - ordered_operands = array_make(permanent_allocator(), param_count); + ordered_operands = array_make(permanent_allocator(), operands.count); array_copy(&ordered_operands, operands, 0); } else { TEMPORARY_ALLOCATOR_GUARD(); @@ -6902,8 +7674,12 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O } { - bool failure = false; - Entity *found_entity = find_polymorphic_record_entity(c, original_type, param_count, ordered_operands, &failure); + GenTypesData *found_gen_types = ensure_polymorphic_record_entity_has_gen_types(c, original_type); + + mutex_lock(&found_gen_types->mutex); + defer (mutex_unlock(&found_gen_types->mutex)); + Entity *found_entity = find_polymorphic_record_entity(found_gen_types, param_count, ordered_operands); + if (found_entity) { operand->mode = Addressing_Type; operand->type = found_entity->type; @@ -6953,14 +7729,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(bt); + 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, ", "); @@ -6973,8 +7744,10 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O s = write_type_to_string(s, v->type, false); } } else if (v->kind == Entity_Constant) { - s = gb_string_append_fmt(s, "="); - s = write_exact_value_to_string(s, v->Constant.value); + if (v->Constant.value.kind != ExactValue_Invalid) { + s = gb_string_append_fmt(s, "="); + s = write_exact_value_to_string(s, v->Constant.value); + } } } s = gb_string_append_fmt(s, ")"); @@ -7042,13 +7815,16 @@ 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_or" + name == "hash" || + name == "caller_expression" ) { operand->mode = Addressing_Builtin; operand->builtin_id = BuiltinProc_DIRECTIVE; @@ -7190,6 +7966,14 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c } } add_entity_use(c, operand->expr, initial_entity); + + if (initial_entity->Procedure.entry_point_only) { + if (c->curr_proc_decl && c->curr_proc_decl->entity == c->info->entry_point) { + // Okay + } else { + error(operand->expr, "Procedures with the attribute '@(entry_point_only)' can only be called directly from the user-level entry point procedure"); + } + } } if (operand->mode != Addressing_ProcGroup) { @@ -7234,6 +8018,7 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c pt = data.gen_entity->type; } } + pt = base_type(pt); if (pt->kind == Type_Proc && pt->Proc.calling_convention == ProcCC_Odin) { if ((c->scope->flags & ScopeFlag_ContextDefined) == 0) { @@ -7261,8 +8046,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) { @@ -7270,7 +8058,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'"); } } } @@ -7278,6 +8066,53 @@ 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_BLOCK(); + error(call, "Inlined procedure enables target feature '%.*s', this requires the calling procedure to at least enable the same feature", LIT(invalid)); + + error_line("\tSuggested Example: @(enable_target_feature=\"%.*s\")\n", LIT(invalid)); + } + } + } + } } operand->expr = call; @@ -7381,13 +8216,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: @@ -7397,17 +8237,6 @@ gb_internal bool check_set_index_data(Operand *o, Type *t, bool indirection, i64 } return true; - case Type_RelativeMultiPointer: - { - Type *pointer_type = base_type(t->RelativeMultiPointer.pointer_type); - GB_ASSERT(pointer_type->kind == Type_MultiPointer); - o->type = pointer_type->MultiPointer.elem; - if (o->mode != Addressing_Constant) { - o->mode = Addressing_Variable; - } - } - return true; - case Type_DynamicArray: o->type = t->DynamicArray.elem; if (o->mode != Addressing_Constant) { @@ -7420,7 +8249,7 @@ gb_internal bool check_set_index_data(Operand *o, Type *t, bool indirection, i64 *max_count = t->Struct.soa_count; } o->type = t->Struct.soa_elem; - if (o->mode == Addressing_SoaVariable || o->mode == Addressing_Variable) { + if (o->mode == Addressing_SoaVariable || o->mode == Addressing_Variable || indirection) { o->mode = Addressing_SoaVariable; } else { o->mode = Addressing_Value; @@ -7432,8 +8261,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; } @@ -7636,6 +8465,8 @@ gb_internal ExprKind check_implicit_selector_expr(CheckerContext *c, Operand *o, String name = ise->selector->Ident.token.string; if (is_type_enum(th)) { + ERROR_BLOCK(); + Type *bt = base_type(th); GB_ASSERT(bt->kind == Type_Enum); @@ -7644,6 +8475,15 @@ gb_internal ExprKind check_implicit_selector_expr(CheckerContext *c, Operand *o, error(node, "Undeclared name '%.*s' for type '%s'", LIT(name), typ); check_did_you_mean_type(name, bt->Enum.fields); + } else if (is_type_bit_set(th) && is_type_enum(th->BitSet.elem)) { + ERROR_BLOCK(); + + gbString typ = type_to_string(th); + gbString str = expr_to_string(node); + error(node, "Cannot convert enum value to '%s'", typ); + error_line("\tSuggestion: Did you mean '{ %s }'?\n", str); + gb_string_free(typ); + gb_string_free(str); } else { gbString typ = type_to_string(th); gbString str = expr_to_string(node); @@ -7658,7 +8498,7 @@ gb_internal ExprKind check_implicit_selector_expr(CheckerContext *c, Operand *o, } -gb_internal void check_promote_optional_ok(CheckerContext *c, Operand *x, Type **val_type_, Type **ok_type_) { +gb_internal void check_promote_optional_ok(CheckerContext *c, Operand *x, Type **val_type_, Type **ok_type_, bool change_operand) { switch (x->mode) { case Addressing_MapIndex: case Addressing_OptionalOk: @@ -7676,22 +8516,28 @@ gb_internal void check_promote_optional_ok(CheckerContext *c, Operand *x, Type * Type *pt = base_type(type_of_expr(expr->CallExpr.proc)); if (is_type_proc(pt)) { Type *tuple = pt->Proc.results; - add_type_and_value(c, x->expr, x->mode, tuple, x->value); if (pt->Proc.result_count >= 2) { if (ok_type_) *ok_type_ = tuple->Tuple.variables[1]->type; } - expr->CallExpr.optional_ok_one = false; - x->type = tuple; + if (change_operand) { + expr->CallExpr.optional_ok_one = false; + x->type = tuple; + add_type_and_value(c, x->expr, x->mode, tuple, x->value); + } return; } } Type *tuple = make_optional_ok_type(x->type); + if (ok_type_) *ok_type_ = tuple->Tuple.variables[1]->type; - add_type_and_value(c, x->expr, x->mode, tuple, x->value); - x->type = tuple; - GB_ASSERT(is_type_tuple(type_of_expr(x->expr))); + + if (change_operand) { + add_type_and_value(c, x->expr, x->mode, tuple, x->value); + x->type = tuple; + GB_ASSERT(is_type_tuple(type_of_expr(x->expr))); + } } @@ -7779,11 +8625,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); @@ -7857,37 +8702,75 @@ gb_internal ExprKind check_basic_directive_expr(CheckerContext *c, Operand *o, A o->mode = Addressing_Constant; String name = bd->name.string; if (name == "file") { + String file = get_file_path_string(bd->token.pos.file_id); + if (build_context.obfuscate_source_code_locations) { + file = obfuscate_string(file, "F"); + } o->type = t_untyped_string; - o->value = exact_value_string(get_file_path_string(bd->token.pos.file_id)); + 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) { + line = obfuscate_i32(line); + } o->type = t_untyped_integer; - o->value = exact_value_i64(bd->token.pos.line); + o->value = exact_value_i64(line); } else if (name == "procedure") { if (c->curr_proc_decl == nullptr) { error(node, "#procedure may only be used within procedures"); o->type = t_untyped_string; o->value = exact_value_string(str_lit("")); } else { + String p = c->proc_name; + if (build_context.obfuscate_source_code_locations) { + p = obfuscate_string(p, "P"); + } o->type = t_untyped_string; - o->value = exact_value_string(c->proc_name); + o->value = exact_value_string(p); } } else if (name == "caller_location") { init_core_source_code_location(c->checker); error(node, "#caller_location may only be used as a default argument parameter"); o->type = t_source_code_location; o->mode = Addressing_Value; + } else if (name == "caller_expression") { + error(node, "#caller_expression may only be used as a default argument parameter"); + o->type = t_string; + o->mode = Addressing_Value; + } else if (name == "branch_location") { + if (!c->in_defer) { + error(node, "#branch_location may only be used within a 'defer' statement"); + } else if (c->curr_proc_decl) { + Entity *e = c->curr_proc_decl->entity; + if (e != nullptr) { + GB_ASSERT(e->kind == Entity_Procedure); + e->Procedure.uses_branch_location = true; + } + } + o->type = t_source_code_location; + o->mode = Addressing_Value; } else { if (name == "location") { init_core_source_code_location(c->checker); - error(node, "'#%.*s' must be used in a call expression", LIT(name)); + error(node, "'#location' must be used as a call, i.e. #location(proc), where #location() defaults to the procedure in which it was used."); o->type = t_source_code_location; o->mode = Addressing_Value; } else if ( name == "assert" || name == "defined" || name == "config" || + name == "exists" || name == "load" || name == "load_hash" || + name == "load_directory" || name == "load_or" ) { error(node, "'#%.*s' must be used as a call", LIT(name)); @@ -7939,11 +8822,6 @@ gb_internal ExprKind check_ternary_if_expr(CheckerContext *c, Operand *o, Ast *n return kind; } - if (x.type == nullptr || x.type == t_invalid || - y.type == nullptr || y.type == t_invalid) { - return kind; - } - bool use_type_hint = type_hint != nullptr && (is_operand_nil(x) || is_operand_nil(y)); convert_to_typed(c, &x, use_type_hint ? type_hint : y.type); @@ -8179,6 +9057,7 @@ gb_internal ExprKind check_or_return_expr(CheckerContext *c, Operand *o, Ast *no // NOTE(bill): allow implicit conversion between boolean types // within 'or_return' to improve the experience using third-party code } else if (!check_is_assignable_to(c, &rhs, end_type)) { + ERROR_BLOCK(); // TODO(bill): better error message gbString a = type_to_string(right_type); gbString b = type_to_string(end_type); @@ -8240,7 +9119,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); } } @@ -8261,6 +9140,7 @@ gb_internal ExprKind check_or_branch_expr(CheckerContext *c, Operand *o, Ast *no switch (be->token.kind) { case Token_or_break: + node->viral_state_flags |= ViralStateFlag_ContainsOrBreak; if ((c->stmt_flags & Stmt_BreakAllowed) == 0 && label == nullptr) { error(be->token, "'%.*s' only allowed in non-inline loops or 'switch' statements", LIT(name)); } @@ -8273,6 +9153,10 @@ gb_internal ExprKind check_or_branch_expr(CheckerContext *c, Operand *o, Ast *no } if (label != nullptr) { + if (c->in_defer) { + error(label, "A labelled '%.*s' cannot be used within a 'defer'", LIT(name)); + return Expr_Expr; + } if (label->kind != Ast_Ident) { error(label, "A branch statement's label name must be an identifier"); return Expr_Expr; @@ -8323,50 +9207,74 @@ gb_internal void check_compound_literal_field_values(CheckerContext *c, Slice fields_visited_through_raw_union = {}; defer (string_map_destroy(&fields_visited_through_raw_union)); + String assignment_str = str_lit("structure literal"); + if (bt->kind == Type_BitField) { + assignment_str = str_lit("bit_field literal"); + } + for (Ast *elem : elems) { if (elem->kind != Ast_FieldValue) { error(elem, "Mixture of 'field = value' and value elements in a literal is not allowed"); continue; } ast_node(fv, FieldValue, elem); - if (fv->field->kind != Ast_Ident) { - gbString expr_str = expr_to_string(fv->field); + Ast *ident = fv->field; + if (ident->kind == Ast_ImplicitSelectorExpr) { + gbString expr_str = expr_to_string(ident); + error(ident, "Field names do not start with a '.', remove the '.' in structure literal", expr_str); + gb_string_free(expr_str); + + ident = ident->ImplicitSelectorExpr.selector; + } + if (ident->kind != Ast_Ident) { + gbString expr_str = expr_to_string(ident); error(elem, "Invalid field name '%s' in structure literal", expr_str); gb_string_free(expr_str); continue; } - String name = fv->field->Ident.token.string; + String name = ident->Ident.token.string; Selection sel = lookup_field(type, name, o->mode == Addressing_Type); bool is_unknown = sel.entity == nullptr; if (is_unknown) { - error(fv->field, "Unknown field '%.*s' in structure literal", LIT(name)); + error(ident, "Unknown field '%.*s' in structure literal", LIT(name)); continue; } - Entity *field = bt->Struct.fields[sel.index[0]]; - add_entity_use(c, fv->field, field); + Entity *field = nullptr; + if (bt->kind == Type_Struct) { + field = bt->Struct.fields[sel.index[0]]; + } else if (bt->kind == Type_BitField) { + field = bt->BitField.fields[sel.index[0]]; + } else { + GB_PANIC("Unknown type"); + } + + + add_entity_use(c, ident, field); if (string_set_update(&fields_visited, name)) { if (sel.index.count > 1) { if (String *found = string_map_get(&fields_visited_through_raw_union, sel.entity->token.string)) { - error(fv->field, "Field '%.*s' is already initialized due to a previously assigned struct #raw_union field '%.*s'", LIT(sel.entity->token.string), LIT(*found)); + error(ident, "Field '%.*s' is already initialized due to a previously assigned struct #raw_union field '%.*s'", LIT(sel.entity->token.string), LIT(*found)); } else { - error(fv->field, "Duplicate or reused field '%.*s' in structure literal", LIT(sel.entity->token.string)); + error(ident, "Duplicate or reused field '%.*s' in %.*s", LIT(sel.entity->token.string), LIT(assignment_str)); } } else { - error(fv->field, "Duplicate field '%.*s' in structure literal", LIT(field->token.string)); + error(ident, "Duplicate field '%.*s' in %.*s", LIT(field->token.string), LIT(assignment_str)); } continue; } else if (String *found = string_map_get(&fields_visited_through_raw_union, sel.entity->token.string)) { - error(fv->field, "Field '%.*s' is already initialized due to a previously assigned struct #raw_union field '%.*s'", LIT(sel.entity->token.string), LIT(*found)); + error(ident, "Field '%.*s' is already initialized due to a previously assigned struct #raw_union field '%.*s'", LIT(sel.entity->token.string), LIT(*found)); continue; } if (sel.indirect) { - error(fv->field, "Cannot assign to the %d-nested anonymous indirect field '%.*s' in a structure literal", cast(int)sel.index.count-1, LIT(name)); + error(ident, "Cannot assign to the %d-nested anonymous indirect field '%.*s' in a %.*s", cast(int)sel.index.count-1, LIT(name), LIT(assignment_str)); continue; } if (sel.index.count > 1) { + GB_ASSERT(bt->kind == Type_Struct); + if (is_constant) { Type *ft = type; for (i32 index : sel.index) { @@ -8382,6 +9290,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; @@ -8408,6 +9320,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; @@ -8427,10 +9342,52 @@ gb_internal void check_compound_literal_field_values(CheckerContext *c, Slicetype, str_lit("structure literal")); + u8 prev_bit_field_bit_size = c->bit_field_bit_size; + if (field->kind == Entity_Variable && field->Variable.bit_field_bit_size) { + // HACK NOTE(bill): This is a bit of a hack, but it will work fine for this use case + c->bit_field_bit_size = field->Variable.bit_field_bit_size; + } + + check_assignment(c, &o, field->type, assignment_str); + + c->bit_field_bit_size = prev_bit_field_bit_size; } } +gb_internal bool is_expr_inferred_fixed_array(Ast *type_expr) { + type_expr = unparen_expr(type_expr); + if (type_expr == nullptr) { + return false; + } + + // [?]Type + if (type_expr->kind == Ast_ArrayType && type_expr->ArrayType.count != nullptr) { + Ast *count = type_expr->ArrayType.count; + if (count->kind == Ast_UnaryExpr && + count->UnaryExpr.op.kind == Token_Question) { + return true; + } + } + return false; +} + +gb_internal bool check_for_dynamic_literals(CheckerContext *c, Ast *node, AstCompoundLit *cl) { + if (cl->elems.count > 0 && (check_feature_flags(c, node) & OptInFeatureFlag_DynamicLiterals) == 0) { + ERROR_BLOCK(); + error(node, "Compound literals of dynamic types are disabled by default"); + error_line("\tSuggestion: If you want to enable them for this specific file, add '#+feature dynamic-literals' at the top of the file\n"); + error_line("\tWarning: Please understand that dynamic literals will implicitly allocate using the current 'context.allocator' in that scope\n"); + if (build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR) { + error_line("\tWarning: As '-default-to-panic-allocator' has been set, the dynamic compound literal may not be initialized as expected\n"); + } else if (build_context.ODIN_DEFAULT_TO_PANIC_ALLOCATOR) { + error_line("\tWarning: As '-default-to-panic-allocator' has been set, the dynamic compound literal may not be initialized as expected\n"); + } + return false; + } + + return cl->elems.count > 0; +} + gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast *node, Type *type_hint) { ExprKind kind = Expr_Expr; ast_node(cl, CompoundLit, node); @@ -8441,20 +9398,35 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * } bool is_to_be_determined_array_count = false; bool is_constant = true; - if (cl->type != nullptr) { + + Ast *type_expr = cl->type; + + bool used_type_hint_expr = false; + if (type_expr == nullptr && c->type_hint_expr != nullptr) { + if (is_expr_inferred_fixed_array(c->type_hint_expr)) { + type_expr = clone_ast(c->type_hint_expr); + used_type_hint_expr = true; + } + } + + if (type_expr != nullptr) { type = nullptr; // [?]Type - if (cl->type->kind == Ast_ArrayType && cl->type->ArrayType.count != nullptr) { - Ast *count = cl->type->ArrayType.count; - if (count->kind == Ast_UnaryExpr && - count->UnaryExpr.op.kind == Token_Question) { - type = alloc_type_array(check_type(c, cl->type->ArrayType.elem), -1); - is_to_be_determined_array_count = true; + if (type_expr->kind == Ast_ArrayType) { + Ast *count = type_expr->ArrayType.count; + 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 (cl->type->ArrayType.tag != nullptr) { - Ast *tag = cl->type->ArrayType.tag; + if (type_expr->ArrayType.tag != nullptr) { + Ast *tag = type_expr->ArrayType.tag; GB_ASSERT(tag->kind == Ast_BasicDirective); String name = tag->BasicDirective.name.string; if (name == "soa") { @@ -8463,10 +9435,9 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * } } } - } - if (cl->type->kind == Ast_DynamicArrayType && cl->type->DynamicArrayType.tag != nullptr) { + } else if (type_expr->kind == Ast_DynamicArrayType && type_expr->DynamicArrayType.tag != nullptr) { if (cl->elems.count > 0) { - Ast *tag = cl->type->DynamicArrayType.tag; + Ast *tag = type_expr->DynamicArrayType.tag; GB_ASSERT(tag->kind == Ast_BasicDirective); String name = tag->BasicDirective.name.string; if (name == "soa") { @@ -8477,7 +9448,7 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * } if (type == nullptr) { - type = check_type(c, cl->type); + type = check_type(c, type_expr); } } @@ -8503,6 +9474,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 @@ -8525,6 +9502,7 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * break; } + wait_signal_until_available(&t->Struct.fields_wait_signal); isize field_count = t->Struct.fields.count; isize min_field_count = t->Struct.fields.count; for (isize i = min_field_count-1; i >= 0; i--) { @@ -8610,11 +9588,6 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * elem_type = t->DynamicArray.elem; context_name = str_lit("dynamic array literal"); is_constant = false; - - if (!build_context.no_dynamic_literals) { - add_package_dependency(c, "runtime", "__dynamic_array_reserve"); - add_package_dependency(c, "runtime", "__dynamic_array_append"); - } } else if (t->kind == Type_SimdVector) { elem_type = t->SimdVector.elem; context_name = str_lit("simd vector literal"); @@ -8789,8 +9762,9 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * if (t->kind == Type_DynamicArray) { - if (build_context.no_dynamic_literals && cl->elems.count) { - error(node, "Compound literals of dynamic types have been disabled"); + if (check_for_dynamic_literals(c, node, cl)) { + add_package_dependency(c, "runtime", "__dynamic_array_reserve"); + add_package_dependency(c, "runtime", "__dynamic_array_append"); } } @@ -9030,15 +10004,15 @@ 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); } } if (unhandled.count > 0) { - begin_error_block(); - defer (end_error_block()); + ERROR_BLOCK(); if (unhandled.count == 1) { error_no_newline(node, "Unhandled enumerated array case: %.*s", LIT(unhandled[0]->token.string)); @@ -9049,9 +10023,11 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * error_line("\t%.*s\n", LIT(f->token.string)); } } - error_line("\n"); - error_line("\tSuggestion: Was '#partial %s{...}' wanted?\n", type_to_string(type)); + if (!build_context.terse_errors) { + error_line("\n"); + error_line("\tSuggestion: Was '#partial %s{...}' wanted?\n", type_to_string(type)); + } } } @@ -9177,9 +10153,7 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * } } - if (build_context.no_dynamic_literals && cl->elems.count) { - error(node, "Compound literals of dynamic types have been disabled"); - } else { + if (check_for_dynamic_literals(c, node, cl)) { add_map_reserve_dependencies(c); add_map_set_dependencies(c); } @@ -9192,10 +10166,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; @@ -9212,6 +10190,22 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * is_constant = o->mode == Addressing_Constant; } + if (elem->kind == Ast_BinaryExpr) { + switch (elem->BinaryExpr.op.kind) { + case Token_Or: + { + gbString x = expr_to_string(elem->BinaryExpr.left); + gbString y = expr_to_string(elem->BinaryExpr.right); + gbString e = expr_to_string(elem); + error(elem, "Was the following intended? '%s, %s'; if not, surround the expression with parentheses '(%s)'", x, y, e); + gb_string_free(e); + gb_string_free(y); + gb_string_free(x); + } + break; + } + } + check_assignment(c, o, t->BitSet.elem, str_lit("bit_set literal")); if (o->mode == Addressing_Constant) { i64 lower = t->BitSet.lower; @@ -9220,7 +10214,9 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * if (lower <= v && v <= upper) { // okay } else { - error(elem, "Bit field value out of bounds, %lld not in the range %lld .. %lld", v, lower, upper); + gbString s = expr_to_string(o->expr); + error(elem, "Bit field value out of bounds, %s (%lld) not in the range %lld .. %lld", s, v, lower, upper); + gb_string_free(s); continue; } } @@ -9228,6 +10224,21 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * } break; } + case Type_BitField: { + if (cl->elems.count == 0) { + break; // NOTE(bill): No need to init + } + is_constant = false; + if (cl->elems[0]->kind != Ast_FieldValue) { + gbString type_str = type_to_string(type); + error(node, "%s ('bit_field') compound literals are only allowed to contain 'field = value' elements", type_str); + gb_string_free(type_str); + } else { + check_compound_literal_field_values(c, cl->elems, o, type, is_constant); + } + break; + } + default: { if (cl->elems.count == 0) { @@ -9259,7 +10270,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); @@ -9629,8 +10642,6 @@ gb_internal ExprKind check_index_expr(CheckerContext *c, Operand *o, Ast *node, // Okay } else if (is_type_string(t)) { // Okay - } else if (is_type_relative_multi_pointer(t)) { - // Okay } else if (is_type_matrix(t)) { // Okay } else { @@ -9673,23 +10684,30 @@ gb_internal ExprKind check_index_expr(CheckerContext *c, Operand *o, Ast *node, bool ok = check_index_value(c, t, false, ie->index, max_count, &index, index_type_hint); if (is_const) { if (index < 0) { + ERROR_BLOCK(); gbString str = expr_to_string(o->expr); error(o->expr, "Cannot index a constant '%s'", str); - error_line("\tSuggestion: store the constant into a variable in order to index it with a variable index\n"); + if (!build_context.terse_errors) { + error_line("\tSuggestion: store the constant into a variable in order to index it with a variable index\n"); + } gb_string_free(str); o->mode = Addressing_Invalid; o->expr = node; return kind; - } else if (ok) { - ExactValue value = type_and_value_of_expr(ie->expr).value; + } else if (ok && !is_type_matrix(t)) { + TypeAndValue tav = type_and_value_of_expr(ie->expr); + ExactValue value = tav.value; o->mode = Addressing_Constant; bool success = false; bool finish = false; o->value = get_constant_field_single(c, value, cast(i32)index, &success, &finish); if (!success) { + ERROR_BLOCK(); gbString str = expr_to_string(o->expr); error(o->expr, "Cannot index a constant '%s' with index %lld", str, cast(long long)index); - error_line("\tSuggestion: store the constant into a variable in order to index it with a variable index\n"); + if (!build_context.terse_errors) { + error_line("\tSuggestion: store the constant into a variable in order to index it with a variable index\n"); + } gb_string_free(str); o->mode = Addressing_Invalid; o->expr = node; @@ -9763,15 +10781,21 @@ 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; - case Type_RelativeMultiPointer: - valid = true; - o->type = type_deref(o->type); - break; - case Type_EnumeratedArray: { gbString str = expr_to_string(o->expr); @@ -9848,16 +10872,6 @@ gb_internal ExprKind check_slice_expr(CheckerContext *c, Operand *o, Ast *node, x[i:n] -> []T */ o->type = alloc_type_slice(t->MultiPointer.elem); - } else if (t->kind == Type_RelativeMultiPointer && se->high != nullptr) { - /* - x[:] -> [^]T - x[i:] -> [^]T - x[:n] -> []T - x[i:n] -> []T - */ - Type *pointer_type = base_type(t->RelativeMultiPointer.pointer_type); - GB_ASSERT(pointer_type->kind == Type_MultiPointer); - o->type = alloc_type_slice(pointer_type->MultiPointer.elem); } @@ -9875,9 +10889,12 @@ gb_internal ExprKind check_slice_expr(CheckerContext *c, Operand *o, Ast *node, } } if (!all_constant) { + ERROR_BLOCK(); gbString str = expr_to_string(o->expr); error(o->expr, "Cannot slice '%s' with non-constant indices", str); - error_line("\tSuggestion: store the constant into a variable in order to index it with a variable index\n"); + if (!build_context.terse_errors) { + error_line("\tSuggestion: store the constant into a variable in order to index it with a variable index\n"); + } gb_string_free(str); o->mode = Addressing_Value; // NOTE(bill): Keep subsequent values going without erring o->expr = node; @@ -9941,7 +10958,7 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast case Token_context: { if (c->proc_name.len == 0 && c->curr_proc_sig == nullptr) { - error(node, "'context' is only allowed within procedures %p", c->curr_proc_decl); + error(node, "'context' is only allowed within procedures"); return kind; } if (unparen_expr(c->assignment_lhs_hint) == node) { @@ -10064,6 +11081,7 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast case_end; case_ast_node(re, OrReturnExpr, node); + node->viral_state_flags |= ViralStateFlag_ContainsOrReturn; return check_or_return_expr(c, o, node, type_hint); case_end; @@ -10114,10 +11132,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"); @@ -10214,34 +11232,18 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast } else if (t->kind == Type_SoaPointer) { o->mode = Addressing_SoaVariable; o->type = type_deref(t); - } else if (t->kind == Type_RelativePointer) { - if (o->mode != Addressing_Variable) { - gbString str = expr_to_string(o->expr); - gbString typ = type_to_string(o->type); - error(o->expr, "Cannot dereference relative pointer '%s' of type '%s' as it does not have a variable addressing mode", str, typ); - gb_string_free(typ); - gb_string_free(str); - } - - // NOTE(bill): This is required because when dereferencing, the original type has been lost - add_type_info_type(c, o->type); - - Type *ptr_type = base_type(t->RelativePointer.pointer_type); - GB_ASSERT(ptr_type->kind == Type_Pointer); - o->mode = Addressing_Variable; - o->type = ptr_type->Pointer.elem; } else { gbString str = expr_to_string(o->expr); gbString typ = type_to_string(o->type); - begin_error_block(); + ERROR_BLOCK(); error(o->expr, "Cannot dereference '%s' of type '%s'", str, typ); if (o->type && is_type_multi_pointer(o->type)) { - error_line("\tDid you mean '%s[0]'?\n", str); + if (!build_context.terse_errors) { + error_line("\tDid you mean '%s[0]'?\n", str); + } } - end_error_block(); - gb_string_free(typ); gb_string_free(str); o->mode = Addressing_Invalid; @@ -10298,6 +11300,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: @@ -10762,6 +11765,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; @@ -10772,6 +11778,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 && @@ -10785,6 +11794,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; @@ -10826,6 +11838,9 @@ gb_internal gbString write_expr_to_string(gbString str, Ast *node, bool shorthan if (f->flags&FieldFlag_any_int) { str = gb_string_appendc(str, "#any_int "); } + if (f->flags&FieldFlag_no_broadcast) { + str = gb_string_appendc(str, "#no_broadcast "); + } if (f->flags&FieldFlag_const) { str = gb_string_appendc(str, "#const "); } @@ -10892,6 +11907,18 @@ gb_internal gbString write_expr_to_string(gbString str, Ast *node, bool shorthan if (field->flags&FieldFlag_c_vararg) { str = gb_string_appendc(str, "#c_vararg "); } + if (field->flags&FieldFlag_any_int) { + str = gb_string_appendc(str, "#any_int "); + } + if (field->flags&FieldFlag_no_broadcast) { + str = gb_string_appendc(str, "#no_broadcast "); + } + if (field->flags&FieldFlag_const) { + str = gb_string_appendc(str, "#const "); + } + if (field->flags&FieldFlag_subtype) { + str = gb_string_appendc(str, "#subtype "); + } str = write_expr_to_string(str, field->type, shorthand); } @@ -11035,6 +12062,32 @@ gb_internal gbString write_expr_to_string(gbString str, Ast *node, bool shorthan case_end; + case_ast_node(f, BitFieldField, node); + str = write_expr_to_string(str, f->name, shorthand); + str = gb_string_appendc(str, ": "); + str = write_expr_to_string(str, f->type, shorthand); + str = gb_string_appendc(str, " | "); + str = write_expr_to_string(str, f->bit_size, shorthand); + case_end; + case_ast_node(bf, BitFieldType, node); + str = gb_string_appendc(str, "bit_field "); + if (!shorthand) { + str = write_expr_to_string(str, bf->backing_type, shorthand); + } + str = gb_string_appendc(str, " {"); + if (shorthand) { + str = gb_string_appendc(str, "..."); + } else { + for_array(i, bf->fields) { + if (i > 0) { + str = gb_string_appendc(str, ", "); + } + str = write_expr_to_string(str, bf->fields[i], false); + } + } + str = gb_string_appendc(str, "}"); + case_end; + case_ast_node(ia, InlineAsmExpr, node); str = gb_string_appendc(str, "asm("); for_array(i, ia->param_types) { diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index b93be734e..02ad72388 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) { @@ -185,10 +199,20 @@ gb_internal bool check_has_break(Ast *stmt, String const &label, bool implicit) } break; + case Ast_DeferStmt: + return check_has_break(stmt->DeferStmt.stmt, label, implicit); + case Ast_BlockStmt: return check_has_break_list(stmt->BlockStmt.stmts, label, implicit); 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 +223,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 +238,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; } @@ -221,12 +258,41 @@ gb_internal bool check_has_break(Ast *stmt, String const &label, bool implicit) return true; } break; + + case Ast_ExprStmt: + if (stmt->ExprStmt.expr->viral_state_flags & ViralStateFlag_ContainsOrBreak) { + 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 @@ -237,13 +303,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; @@ -285,6 +365,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; @@ -394,8 +477,15 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O rhs->proc_group = nullptr; } } else { + Ast *ident_node = nullptr; + if (node->kind == Ast_Ident) { - ast_node(i, Ident, node); + ident_node = node; + } else if (node->kind == Ast_IndexExpr && node->IndexExpr.expr->kind == Ast_Ident) { + ident_node = node->IndexExpr.expr; + } + if (ident_node != nullptr) { + ast_node(i, Ident, ident_node); e = scope_lookup(ctx->scope, i->token.string); if (e != nullptr && e->kind == Entity_Variable) { used = (e->flags & EntityFlag_Used) != 0; // NOTE(bill): Make backup just in case @@ -421,7 +511,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: { @@ -443,9 +535,8 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O break; } - case Addressing_Context: { + case Addressing_Context: break; - } case Addressing_SoaVariable: break; @@ -468,16 +559,63 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O } Entity *e = entity_of_node(lhs->expr); + Entity *original_e = e; + + Ast *name = unparen_expr(lhs->expr); + while (name->kind == Ast_SelectorExpr) { + name = name->SelectorExpr.expr; + e = entity_of_node(name); + } + if (e == nullptr) { + e = original_e; + } gbString str = expr_to_string(lhs->expr); if (e != nullptr && e->flags & EntityFlag_Param) { + ERROR_BLOCK(); if (e->flags & EntityFlag_Using) { error(lhs->expr, "Cannot assign to '%s' which is from a 'using' procedure parameter", str); } else { error(lhs->expr, "Cannot assign to '%s' which is a procedure parameter", str); } + 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)); + if (offset < 0) { + if (is_type_map(e->type)) { + error_line("\tSuggestion: Did you mean? 'for key, &%.*s in ...'\n", LIT(e->token.string)); + } else { + error_line("\tSuggestion: Did you mean? 'for &%.*s in ...'\n", LIT(e->token.string)); + } + } else { + error_line("\t"); + for (isize i = 0; i < offset-1; i++) { + error_line(" "); + } + error_line("'%.*s' is immutable, declare it as '&%.*s' to make it mutable\n", LIT(e->token.string), LIT(e->token.string)); + } + + } else if (e && e->flags & EntityFlag_SwitchValue) { + 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 { + error_line("\t"); + for (isize i = 0; i < offset-1; i++) { + error_line(" "); + } + error_line("'%.*s' is immutable, declare it as '&%.*s' to make it mutable\n", LIT(e->token.string), LIT(e->token.string)); + } + } } gb_string_free(str); @@ -485,7 +623,17 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O } } + Entity *lhs_e = entity_of_node(lhs->expr); + u8 prev_bit_field_bit_size = ctx->bit_field_bit_size; + if (lhs_e && lhs_e->kind == Entity_Variable && lhs_e->Variable.bit_field_bit_size) { + // HACK NOTE(bill): This is a bit of a hack, but it will work fine for this use case + ctx->bit_field_bit_size = lhs_e->Variable.bit_field_bit_size; + } + check_assignment(ctx, rhs, assignment_type, str_lit("assignment")); + + ctx->bit_field_bit_size = prev_bit_field_bit_size; + if (rhs->mode == Addressing_Invalid) { return nullptr; } @@ -645,7 +793,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) { @@ -670,6 +818,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) { @@ -677,7 +827,8 @@ gb_internal bool check_using_stmt_entity(CheckerContext *ctx, AstUsingStmt *us, if (f->kind == Entity_Variable) { Entity *uvar = alloc_entity_using_variable(e, f->token, f->type, expr); if (!is_ptr && e->flags & EntityFlag_Value) uvar->flags |= EntityFlag_Value; - if (e->flags & EntityFlag_Param) uvar->flags |= EntityFlag_Param; + if (e->flags & EntityFlag_Param) uvar->flags |= EntityFlag_Param; + if (e->flags & EntityFlag_SoaPtrField) uvar->flags |= EntityFlag_SoaPtrField; Entity *prev = scope_insert(ctx->scope, uvar); if (prev != nullptr) { gbString expr_str = expr_to_string(expr); @@ -724,6 +875,25 @@ gb_internal bool check_using_stmt_entity(CheckerContext *ctx, AstUsingStmt *us, return true; } +gb_internal void error_var_decl_identifier(Ast *name) { + GB_ASSERT(name != nullptr); + GB_ASSERT(name->kind != Ast_Ident); + + ERROR_BLOCK(); + gbString s = expr_to_string(name); + defer (gb_string_free(s)); + + error(name, "A variable declaration must be an identifier, got '%s'", s); + if (name->kind == Ast_Implicit) { + String imp = name->Implicit.string; + if (imp == "context") { + error_line("\tSuggestion: '%.*s' is a reserved keyword, would 'ctx' suffice?\n", LIT(imp)); + } else { + error_line("\tNote: '%.*s' is a reserved keyword\n", LIT(imp)); + } + } +} + gb_internal void check_inline_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) { ast_node(irs, UnrollRangeStmt, node); check_open_scope(ctx, node); @@ -835,7 +1005,7 @@ gb_internal void check_inline_range_stmt(CheckerContext *ctx, Ast *node, u32 mod entity = found; } } else { - error(name, "A variable declaration must be an identifier"); + error_var_decl_identifier(name); } if (entity == nullptr) { @@ -867,6 +1037,7 @@ gb_internal void check_inline_range_stmt(CheckerContext *ctx, Ast *node, u32 mod } if (ctx->inline_for_depth >= MAX_INLINE_FOR_DEPTH && prev_inline_for_depth < MAX_INLINE_FOR_DEPTH) { + ERROR_BLOCK(); if (prev_inline_for_depth > 0) { error(node, "Nested '#unroll for' loop cannot be inlined as it exceeds the maximum '#unroll for' depth (%lld levels >= %lld maximum levels)", v, MAX_INLINE_FOR_DEPTH); } else { @@ -900,6 +1071,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; @@ -1085,8 +1259,7 @@ gb_internal void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags } if (unhandled.count > 0) { - begin_error_block(); - defer (end_error_block()); + ERROR_BLOCK(); if (unhandled.count == 1) { error_no_newline(node, "Unhandled switch case: %.*s", LIT(unhandled[0]->token.string)); @@ -1096,11 +1269,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"); + } + } + } } @@ -1174,7 +1359,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; @@ -1293,21 +1477,12 @@ 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; } if (case_type == nullptr) { - case_type = x.type; + case_type = type_deref(x.type); } if (switch_kind == TypeSwitch_Any) { if (!is_type_untyped(case_type)) { @@ -1323,9 +1498,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); @@ -1350,6 +1522,8 @@ gb_internal void check_type_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_ } if (unhandled.count > 0) { + ERROR_BLOCK(); + if (unhandled.count == 1) { gbString s = type_to_string(unhandled[0]); error_no_newline(node, "Unhandled switch case: %s", s); @@ -1448,25 +1622,6 @@ gb_internal bool check_stmt_internal_builtin_proc_id(Ast *expr, BuiltinProcId *i return id != BuiltinProc_Invalid; } -gb_internal bool check_expr_is_stack_variable(Ast *expr) { - /* - expr = unparen_expr(expr); - Entity *e = entity_of_node(expr); - if (e && e->kind == Entity_Variable) { - if (e->flags & (EntityFlag_Static|EntityFlag_Using|EntityFlag_ImplicitReference|EntityFlag_ForValue)) { - // okay - } else if (e->Variable.thread_local_model.len != 0) { - // okay - } else if (e->scope) { - if ((e->scope->flags & (ScopeFlag_Global|ScopeFlag_File|ScopeFlag_Type)) == 0) { - return true; - } - } - } - */ - return false; -} - gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) { ast_node(rs, RangeStmt, node); @@ -1480,12 +1635,15 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) auto vals = array_make(temporary_allocator(), 0, 2); auto entities = array_make(temporary_allocator(), 0, 2); bool is_map = false; - bool use_by_reference_for_value = false; + bool is_bit_set = false; bool is_soa = false; bool is_reverse = rs->reverse; Ast *expr = unparen_expr(rs->expr); + Operand rhs_operand = {}; + + bool is_range = false; bool is_possibly_addressable = true; isize max_val_count = 2; if (is_ast_range(expr)) { @@ -1494,6 +1652,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) Operand y = {}; is_possibly_addressable = false; + is_range = true; bool ok = check_range(ctx, expr, true, &x, &y, nullptr); if (!ok) { @@ -1525,11 +1684,25 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) array_add(&vals, operand.type); array_add(&vals, t_int); add_type_info_type(ctx, operand.type); + if (build_context.no_rtti) { + error(node, "Iteration over an enum type is not allowed runtime type information (RTTI) has been disallowed"); + } goto skip_expr_range_stmt; } } else if (operand.mode != Addressing_Invalid) { + if (operand.mode == Addressing_OptionalOk || operand.mode == Addressing_OptionalOkPtr) { + Ast *expr = unparen_expr(operand.expr); + if (expr->kind != Ast_TypeAssertion) { // Only for procedure calls + Type *end_type = nullptr; + check_promote_optional_ok(ctx, &operand, nullptr, &end_type, false); + if (is_type_boolean(end_type)) { + check_promote_optional_ok(ctx, &operand, nullptr, &end_type, true); + } + } + } bool is_ptr = is_type_pointer(operand.type); Type *t = base_type(type_deref(operand.type)); + switch (t->kind) { case Type_Basic: if (t->Basic.kind == Basic_string || t->Basic.kind == Basic_UntypedString) { @@ -1544,49 +1717,92 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) } break; + case Type_BitSet: + array_add(&vals, t->BitSet.elem); + max_val_count = 1; + is_bit_set = true; + is_possibly_addressable = false; + add_type_info_type(ctx, operand.type); + 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; + is_possibly_addressable = operand.mode == Addressing_Variable || is_ptr; 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; + is_possibly_addressable = 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; + is_possibly_addressable = 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_possibly_addressable = 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: { + is_possibly_addressable = false; + 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); @@ -1595,24 +1811,21 @@ 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) { @@ -1623,8 +1836,12 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) case Type_Struct: if (t->Struct.soa_kind != StructSoa_None) { + if (t->Struct.soa_kind == StructSoa_Fixed) { + is_possibly_addressable = operand.mode == Addressing_Variable || is_ptr; + } else { + is_possibly_addressable = true; + } is_soa = true; - if (is_ptr) use_by_reference_for_value = true; array_add(&vals, t->Struct.soa_elem); array_add(&vals, t_int); } @@ -1638,11 +1855,13 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) defer (gb_string_free(s)); defer (gb_string_free(t)); + ERROR_BLOCK(); + error(operand.expr, "Cannot iterate over '%s' of type '%s'", s, t); if (rs->vals.count == 1) { Type *t = type_deref(operand.type); - if (is_type_map(t) || is_type_bit_set(t)) { + if (t != NULL && (is_type_map(t) || is_type_bit_set(t))) { gbString v = expr_to_string(rs->vals[0]); defer (gb_string_free(v)); error_line("\tSuggestion: place parentheses around the expression\n"); @@ -1687,7 +1906,9 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) } if (found == nullptr) { entity = alloc_entity_variable(ctx->scope, token, type, EntityState_Resolved); - entity->flags |= EntityFlag_ForValue; + if (!is_range) { + entity->flags |= EntityFlag_ForValue; + } entity->flags |= EntityFlag_Value; entity->identifier = name; entity->Variable.for_loop_parent_type = type_of_expr(expr); @@ -1696,12 +1917,9 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) if (is_possibly_addressable && i == addressable_index) { entity->flags &= ~EntityFlag_Value; } else { - char const *idx_name = is_map ? "key" : "index"; + char const *idx_name = is_map ? "key" : (is_bit_set || i == 0) ? "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) { @@ -1719,9 +1937,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) entity = found; } } else { - gbString s = expr_to_string(lhs[i]); - error(name, "A variable declaration must be an identifier, got %s", s); - gb_string_free(s); + error_var_decl_identifier(name); } if (entity == nullptr) { @@ -1773,7 +1989,7 @@ gb_internal void check_value_decl_stmt(CheckerContext *ctx, Ast *node, u32 mod_f for (Ast *name : vd->names) { Entity *entity = nullptr; if (name->kind != Ast_Ident) { - error(name, "A variable declaration must be an identifier"); + error_var_decl_identifier(name); } else { Token token = name->Ident.token; String str = token.string; @@ -1813,7 +2029,7 @@ gb_internal void check_value_decl_stmt(CheckerContext *ctx, Ast *node, u32 mod_f } if (new_name_count == 0) { - begin_error_block(); + ERROR_BLOCK(); error(node, "No new declarations on the left hand side"); bool all_underscore = true; for (Ast *name : vd->names) { @@ -1831,7 +2047,6 @@ gb_internal void check_value_decl_stmt(CheckerContext *ctx, Ast *node, u32 mod_f error_line("\tSuggestion: Try changing the declaration (:=) to an assignment (=)\n"); } - end_error_block(); } Type *init_type = nullptr; @@ -1845,11 +2060,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++) { @@ -1866,7 +2087,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; @@ -1884,6 +2105,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 == "_") { @@ -1897,17 +2125,19 @@ gb_internal void check_value_decl_stmt(CheckerContext *ctx, Ast *node, u32 mod_f e->Variable.thread_local_model = ac.thread_local_model; } - if (is_arch_wasm() && e->Variable.thread_local_model.len != 0) { - // error(e->token, "@(thread_local) is not supported for this target platform"); - } - - if (ac.is_static && ac.thread_local_model != "") { error(e->token, "The 'static' attribute is not needed if 'thread_local' is applied"); } } + // NOTE(bill): This is to improve error handling for things like `x: [?]T = {...}` + Ast *prev_type_hint_expr = ctx->type_hint_expr; + ctx->type_hint_expr = vd->type; + check_init_variables(ctx, entities, entity_count, vd->values, str_lit("variable declaration")); + + ctx->type_hint_expr = prev_type_hint_expr; + check_arity_match(ctx, vd, false); for (isize i = 0; i < entity_count; i++) { @@ -1936,7 +2166,7 @@ gb_internal void check_value_decl_stmt(CheckerContext *ctx, Ast *node, u32 mod_f TokenPos pos = f->token.pos; Type *this_type = base_type(e->type); Type *other_type = base_type(f->type); - if (!are_types_identical(this_type, other_type)) { + if (!signature_parameter_similar_enough(this_type, other_type)) { error(e->token, "Foreign entity '%.*s' previously declared elsewhere with a different type\n" "\tat %s", @@ -2029,13 +2259,13 @@ gb_internal void check_expr_stmt(CheckerContext *ctx, Ast *node) { } Ast *expr = strip_or_return_expr(operand.expr); - if (expr->kind == Ast_CallExpr) { + if (expr && expr->kind == Ast_CallExpr) { BuiltinProcId builtin_id = BuiltinProc_Invalid; bool do_require = false; AstCallExpr *ce = &expr->CallExpr; Type *t = base_type(type_of_expr(ce->proc)); - if (t->kind == Type_Proc) { + if (t && t->kind == Type_Proc) { do_require = t->Proc.require_results; } else if (check_stmt_internal_builtin_proc_id(ce->proc, &builtin_id)) { auto const &bp = builtin_procs[builtin_id]; @@ -2043,11 +2273,19 @@ 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->kind == Ast_SelectorCallExpr) { + } else if (expr && expr->kind == Ast_SelectorCallExpr) { BuiltinProcId builtin_id = BuiltinProc_Invalid; bool do_require = false; @@ -2073,6 +2311,9 @@ gb_internal void check_expr_stmt(CheckerContext *ctx, Ast *node) { } return; } + + ERROR_BLOCK(); + gbString expr_str = expr_to_string(operand.expr); error(node, "Expression is not used: '%s'", expr_str); gb_string_free(expr_str); @@ -2266,29 +2507,6 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) { if (is_type_untyped(o->type)) { update_untyped_expr_type(ctx, o->expr, e->type, true); } - - - // NOTE(bill): This is very basic escape analysis - // This needs to be improved tremendously, and a lot of it done during the - // middle-end (or LLVM side) to improve checks and error messages - Ast *expr = unparen_expr(o->expr); - if (expr->kind == Ast_UnaryExpr && expr->UnaryExpr.op.kind == Token_And) { - Ast *x = unparen_expr(expr->UnaryExpr.expr); - if (x->kind == Ast_CompoundLit) { - error(expr, "Cannot return the address to a stack value from a procedure"); - } else if (x->kind == Ast_IndexExpr) { - Ast *array = x->IndexExpr.expr; - if (is_type_array_like(type_of_expr(array)) && check_expr_is_stack_variable(array)) { - gbString t = type_to_string(type_of_expr(array)); - error(expr, "Cannot return the address to an element of stack variable from a procedure, of type %s", t); - gb_string_free(t); - } - } else { - if (check_expr_is_stack_variable(x)) { - error(expr, "Cannot return the address to a stack variable from a procedure"); - } - } - } } } @@ -2296,16 +2514,73 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) { if (o.expr == nullptr) { continue; } - if (o.expr->kind != Ast_CompoundLit || !is_type_slice(o.type)) { - continue; + Ast *expr = unparen_expr(o.expr); + while (expr->kind == Ast_CallExpr && expr->CallExpr.proc->tav.mode == Addressing_Type) { + if (expr->CallExpr.args.count != 1) { + break; + } + Ast *arg = expr->CallExpr.args[0]; + if (arg->kind == Ast_FieldValue || !are_types_identical(arg->tav.type, expr->tav.type)) { + break; + } + expr = unparen_expr(arg); } - ast_node(cl, CompoundLit, o.expr); - if (cl->elems.count == 0) { - continue; + + auto unsafe_return_error = [](Operand const &o, char const *msg, Type *extra_type=nullptr) { + gbString s = expr_to_string(o.expr); + if (extra_type) { + gbString t = type_to_string(extra_type); + error(o.expr, "It is unsafe to return %s ('%s') of type ('%s') from a procedure, as it uses the current stack frame's memory", msg, s, t); + gb_string_free(t); + } else { + error(o.expr, "It is unsafe to return %s ('%s') from a procedure, as it uses the current stack frame's memory", msg, s); + } + gb_string_free(s); + }; + + + // NOTE(bill): This is very basic escape analysis + // This needs to be improved tremendously, and a lot of it done during the + // middle-end (or LLVM side) to improve checks and error messages + if (expr->kind == Ast_CompoundLit && is_type_slice(o.type)) { + ast_node(cl, CompoundLit, expr); + if (cl->elems.count == 0) { + continue; + } + unsafe_return_error(o, "a compound literal of a slice"); + } else if (expr->kind == Ast_UnaryExpr && expr->UnaryExpr.op.kind == Token_And) { + Ast *x = unparen_expr(expr->UnaryExpr.expr); + Entity *e = entity_of_node(x); + if (is_entity_local_variable(e)) { + unsafe_return_error(o, "the address of a local variable"); + } else if (x->kind == Ast_CompoundLit) { + unsafe_return_error(o, "the address of a compound literal"); + } else if (x->kind == Ast_IndexExpr) { + Entity *f = entity_of_node(x->IndexExpr.expr); + if (f && (is_type_array_like(f->type) || is_type_matrix(f->type))) { + if (is_entity_local_variable(f)) { + unsafe_return_error(o, "the address of an indexed variable", f->type); + } + } + } else if (x->kind == Ast_MatrixIndexExpr) { + Entity *f = entity_of_node(x->MatrixIndexExpr.expr); + if (f && (is_type_matrix(f->type) && is_entity_local_variable(f))) { + unsafe_return_error(o, "the address of an indexed variable", f->type); + } + } + } else if (expr->kind == Ast_SliceExpr) { + Ast *x = unparen_expr(expr->SliceExpr.expr); + Entity *e = entity_of_node(x); + if (is_entity_local_variable(e) && is_type_array(e->type)) { + unsafe_return_error(o, "a slice of a local variable"); + } else if (x->kind == Ast_CompoundLit) { + unsafe_return_error(o, "a slice of a compound literal"); + } + } else if (o.mode == Addressing_Constant && is_type_slice(o.type)) { + ERROR_BLOCK(); + unsafe_return_error(o, "a compound literal of a slice"); + error_line("\tNote: A constant slice value will use the memory of the current stack frame\n"); } - gbString s = type_to_string(o.type); - error(o.expr, "It is unsafe to return a compound literal of a slice ('%s') with elements from a procedure, as the contents of the slice uses the current stack frame's memory", s); - gb_string_free(s); } } @@ -2325,6 +2600,25 @@ gb_internal void check_for_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) { check_expr(ctx, &o, fs->cond); if (o.mode != Addressing_Invalid && !is_type_boolean(o.type)) { error(fs->cond, "Non-boolean condition in 'for' statement"); + } else { + Ast *cond = unparen_expr(o.expr); + if (cond && cond->kind == Ast_BinaryExpr && + cond->BinaryExpr.left && cond->BinaryExpr.right && + cond->BinaryExpr.op.kind == Token_GtEq && + type_of_expr(cond->BinaryExpr.left) != nullptr && + is_type_unsigned(type_of_expr(cond->BinaryExpr.left)) && + cond->BinaryExpr.right->tav.value.kind == ExactValue_Integer && + is_exact_value_zero(cond->BinaryExpr.right->tav.value)) { + warning(cond, "Expression is always true since unsigned numbers are always >= 0"); + } else if (cond && cond->kind == Ast_BinaryExpr && + cond->BinaryExpr.left && cond->BinaryExpr.right && + cond->BinaryExpr.op.kind == Token_LtEq && + type_of_expr(cond->BinaryExpr.right) != nullptr && + is_type_unsigned(type_of_expr(cond->BinaryExpr.right)) && + cond->BinaryExpr.left->tav.value.kind == ExactValue_Integer && + is_exact_value_zero(cond->BinaryExpr.left->tav.value)) { + warning(cond, "Expression is always true since unsigned numbers are always >= 0"); + } } } if (fs->post != nullptr) { @@ -2445,6 +2739,7 @@ gb_internal void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) error(bs->label, "A branch statement's label name must be an identifier"); return; } + Ast *ident = bs->label; String name = ident->Ident.token.string; Operand o = {}; @@ -2476,6 +2771,10 @@ gb_internal void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) break; } + + if (ctx->in_defer) { + error(bs->label, "A labelled '%.*s' cannot be used within a 'defer'", LIT(token.string)); + } } case_end; diff --git a/src/check_type.cpp b/src/check_type.cpp index d66b196bc..44108ccbe 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1,4 +1,6 @@ gb_internal ParameterValue handle_parameter_value(CheckerContext *ctx, Type *in_type, Type **out_type_, Ast *expr, bool allow_caller_location); +gb_internal Type *determine_type_from_polymorphic(CheckerContext *ctx, Type *poly_type, Operand const &operand); +gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_params, bool *is_variadic_, isize *variadic_index_, bool *success_, isize *specialization_count_, Array const *operands); gb_internal void populate_using_array_index(CheckerContext *ctx, Ast *node, AstField *field, Type *t, String name, i32 idx) { t = base_type(t); @@ -17,20 +19,23 @@ 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); } } -gb_internal void populate_using_entity_scope(CheckerContext *ctx, Ast *node, AstField *field, Type *t) { +gb_internal void populate_using_entity_scope(CheckerContext *ctx, Ast *node, AstField *field, Type *t, isize level) { if (t == nullptr) { return; } + Type *original_type = t; t = base_type(type_deref(t)); gbString str = nullptr; defer (gb_string_free(str)); @@ -44,16 +49,18 @@ gb_internal void populate_using_entity_scope(CheckerContext *ctx, Ast *node, Ast String name = f->token.string; Entity *e = scope_lookup_current(ctx->scope, name); if (e != nullptr && name != "_") { + gbString ot = type_to_string(original_type); // TODO(bill): Better type error if (str != nullptr) { - error(e->token, "'%.*s' is already declared in '%s'", LIT(name), str); + error(e->token, "'%.*s' is already declared in '%s', through 'using' from '%s'", LIT(name), str, ot); } else { - error(e->token, "'%.*s' is already declared", LIT(name)); + error(e->token, "'%.*s' is already declared, through 'using' from '%s'", LIT(name), ot); } + gb_string_free(ot); } else { add_entity(ctx, ctx->scope, nullptr, f); if (f->flags & EntityFlag_Using) { - populate_using_entity_scope(ctx, node, field, f->type); + populate_using_entity_scope(ctx, node, field, f->type, level+1); } } } @@ -87,6 +94,8 @@ gb_internal bool does_field_type_allow_using(Type *t) { return true; } else if (is_type_array(t)) { return t->Array.count <= 4; + } else if (is_type_bit_field(t)) { + return true; } return false; } @@ -184,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; @@ -196,7 +206,7 @@ gb_internal void check_struct_fields(CheckerContext *ctx, Ast *node, Slicenames.count > 0) { @@ -219,13 +229,13 @@ gb_internal void check_struct_fields(CheckerContext *ctx, Ast *node, Slice 1) { gbAllocator a = heap_allocator(); String str = big_int_to_string(a, &v); - error(node, "#align too large, %.*s", LIT(str)); + error(node, "#%s too large, %.*s", msg, LIT(str)); gb_free(a, str.text); return false; } i64 align = big_int_to_i64(&v); if (align < 1 || !gb_is_power_of_two(cast(isize)align)) { - error(node, "#align must be a power of 2, got %lld", align); + error(node, "#%s must be a power of 2, got %lld", msg, align); return false; } *align_ = align; @@ -251,40 +261,310 @@ gb_internal bool check_custom_align(CheckerContext *ctx, Ast *node, i64 *align_) } } - error(node, "#align must be an integer"); + error(node, "#%s must be an integer", msg); return false; } -gb_internal Entity *find_polymorphic_record_entity(CheckerContext *ctx, Type *original_type, isize param_count, Array const &ordered_operands, bool *failure) { - rw_mutex_shared_lock(&ctx->info->gen_types_mutex); // @@global +gb_internal GenTypesData *ensure_polymorphic_record_entity_has_gen_types(CheckerContext *ctx, Type *original_type) { + mutex_lock(&ctx->info->gen_types_mutex); // @@global - auto *found_gen_types = map_get(&ctx->info->gen_types, original_type); - if (found_gen_types == nullptr) { - rw_mutex_shared_unlock(&ctx->info->gen_types_mutex); // @@global - return nullptr; + GenTypesData *found_gen_types = nullptr; + auto *found_gen_types_ptr = map_get(&ctx->info->gen_types, original_type); + if (found_gen_types_ptr == nullptr) { + GenTypesData *gen_types = gb_alloc_item(permanent_allocator(), GenTypesData); + gen_types->types = array_make(heap_allocator()); + map_set(&ctx->info->gen_types, original_type, gen_types); + found_gen_types_ptr = map_get(&ctx->info->gen_types, original_type); + } + found_gen_types = *found_gen_types_ptr; + GB_ASSERT(found_gen_types != nullptr); + mutex_unlock(&ctx->info->gen_types_mutex); // @@global + return found_gen_types; +} + + +gb_internal void add_polymorphic_record_entity(CheckerContext *ctx, Ast *node, Type *named_type, Type *original_type) { + GB_ASSERT(is_type_named(named_type)); + gbAllocator a = heap_allocator(); + Scope *s = ctx->scope->parent; + + Entity *e = nullptr; + { + Token token = ast_token(node); + token.kind = Token_String; + token.string = named_type->Named.name; + + Ast *node = ast_ident(nullptr, token); + + e = alloc_entity_type_name(s, token, named_type); + e->state = EntityState_Resolved; + e->file = ctx->file; + e->pkg = ctx->pkg; + add_entity_use(ctx, node, e); } - rw_mutex_shared_lock(&found_gen_types->mutex); // @@local - defer (rw_mutex_shared_unlock(&found_gen_types->mutex)); // @@local + named_type->Named.type_name = e; + GB_ASSERT(original_type->kind == Type_Named); + e->TypeName.objc_class_name = original_type->Named.type_name->TypeName.objc_class_name; + // TODO(bill): Is this even correct? Or should the metadata be copied? + e->TypeName.objc_metadata = original_type->Named.type_name->TypeName.objc_metadata; - rw_mutex_shared_unlock(&ctx->info->gen_types_mutex); // @@global + auto *found_gen_types = ensure_polymorphic_record_entity_has_gen_types(ctx, original_type); + mutex_lock(&found_gen_types->mutex); + defer (mutex_unlock(&found_gen_types->mutex)); + for (Entity *prev : found_gen_types->types) { + if (prev == e) { + return; + } + } + array_add(&found_gen_types->types, e); +} + + +bool check_constant_parameter_value(Type *type, Ast *expr) { + if (!is_type_constant_type(type)) { + gbString str = type_to_string(type); + defer (gb_string_free(str)); + error(expr, "A parameter must be a valid constant type, got %s", str); + return true; + } + return false; +} + +gb_internal Type *check_record_polymorphic_params(CheckerContext *ctx, Ast *polymorphic_params, + bool *is_polymorphic_, + Array *poly_operands) { + Type *polymorphic_params_type = nullptr; + GB_ASSERT(is_polymorphic_ != nullptr); + + if (polymorphic_params == nullptr) { + if (!*is_polymorphic_) { + *is_polymorphic_ = polymorphic_params != nullptr && poly_operands == nullptr; + } + return polymorphic_params_type; + } + + + // bool is_variadic = false; + // isize variadic_index = 0; + // bool success = false; + // isize specialization_count = 0; + // polymorphic_params_type = check_get_params(ctx, ctx->scope, polymorphic_params, &is_variadic, &variadic_index, &success, &specialization_count, poly_operands); + // if (success) { + // return nullptr; + // } + + bool can_check_fields = true; + ast_node(field_list, FieldList, polymorphic_params); + Slice params = field_list->list; + if (params.count != 0) { + isize variable_count = 0; + for_array(i, params) { + Ast *field = params[i]; + if (ast_node_expect(field, Ast_Field)) { + ast_node(f, Field, field); + variable_count += gb_max(f->names.count, 1); + } + } + + auto entities = array_make(permanent_allocator(), 0, variable_count); + + i32 field_group_index = -1; + for_array(i, params) { + Ast *param = params[i]; + if (param->kind != Ast_Field) { + continue; + } + field_group_index += 1; + ast_node(p, Field, param); + Ast *type_expr = p->type; + Ast *default_value = unparen_expr(p->default_value); + 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; + } + + if (type_expr != nullptr) { + if (type_expr->kind == Ast_Ellipsis) { + type_expr = type_expr->Ellipsis.expr; + error(param, "A polymorphic parameter cannot be variadic"); + } + if (type_expr->kind == Ast_TypeidType) { + is_type_param = true; + if (type_expr->TypeidType.specialization != nullptr) { + Ast *s = type_expr->TypeidType.specialization; + specialization = check_type(ctx, s); + } + type = alloc_type_generic(ctx->scope, 0, str_lit(""), specialization); + } else { + type = check_type(ctx, type_expr); + if (is_type_polymorphic(type)) { + is_type_polymorphic_type = true; + } + } + } + + ParameterValue param_value = {}; + if (default_value != nullptr) { + Type *out_type = nullptr; + param_value = handle_parameter_value(ctx, type, &out_type, default_value, false); + if (type == nullptr && out_type != nullptr) { + type = out_type; + } + if (param_value.kind != ParameterValue_Constant && param_value.kind != ParameterValue_Nil) { + error(default_value, "Invalid parameter value"); + param_value = {}; + } + } + + + if (type == nullptr) { + error(params[i], "Invalid parameter type"); + type = t_invalid; + } + if (is_type_untyped(type)) { + if (is_type_untyped_uninit(type)) { + error(params[i], "Cannot determine parameter type from ---"); + } else { + error(params[i], "Cannot determine parameter type from a nil"); + } + type = t_invalid; + } + + if (is_type_polymorphic_type && !is_type_proc(type)) { + gbString str = type_to_string(type); + error(params[i], "Parameter types cannot be polymorphic, got %s", str); + gb_string_free(str); + type = t_invalid; + } + + if (!is_type_param && check_constant_parameter_value(type, params[i])) { + // failed + } + + Scope *scope = ctx->scope; + for_array(j, p->names) { + Ast *name = p->names[j]; + if (!ast_node_expect2(name, Ast_Ident, Ast_PolyType)) { + continue; + } + if (name->kind == Ast_PolyType) { + name = name->PolyType.type; + } + Entity *e = nullptr; + + Token token = name->Ident.token; + + if (poly_operands != nullptr) { + Operand operand = {}; + operand.type = t_invalid; + if (entities.count < poly_operands->count) { + operand = (*poly_operands)[entities.count]; + } else if (param_value.kind != ParameterValue_Invalid) { + operand.mode = Addressing_Constant; + operand.value = param_value.value; + } + if (is_type_param) { + 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; + e->flags |= EntityFlag_PolyConst; + } else { + Type *t = operand.type; + if (is_type_proc(type)) { + t = determine_type_from_polymorphic(ctx, type, operand); + } + if (is_type_polymorphic(base_type(t))) { + *is_polymorphic_ = true; + can_check_fields = false; + } + if (e == nullptr) { + e = alloc_entity_const_param(scope, token, t, operand.value, is_type_polymorphic(t)); + e->Constant.param_value = param_value; + e->Constant.field_group_index = field_group_index; + } + } + } else { + if (is_type_param) { + e = alloc_entity_type_name(scope, token, type); + e->TypeName.is_type_alias = true; + e->flags |= EntityFlag_PolyConst; + } else { + e = alloc_entity_const_param(scope, token, type, param_value.value, is_type_polymorphic(type)); + e->Constant.field_group_index = field_group_index; + e->Constant.param_value = param_value; + } + } + + e->state = EntityState_Resolved; + add_entity(ctx, scope, name, e); + array_add(&entities, e); + } + } + + if (entities.count > 0) { + Type *tuple = alloc_type_tuple(); + tuple->Tuple.variables = slice_from_array(entities); + polymorphic_params_type = tuple; + } + } + + if (!*is_polymorphic_) { + *is_polymorphic_ = polymorphic_params != nullptr && poly_operands == nullptr; + } + return polymorphic_params_type; +} + +gb_internal bool check_record_poly_operand_specialization(CheckerContext *ctx, Type *record_type, Array *poly_operands, bool *is_polymorphic_) { + if (poly_operands == nullptr) { + return false; + } + for (isize i = 0; i < poly_operands->count; i++) { + Operand o = (*poly_operands)[i]; + if (is_type_polymorphic(o.type)) { + return false; + } + if (record_type == o.type) { + // NOTE(bill): Cycle + return false; + } + if (o.mode == Addressing_Type) { + // NOTE(bill): ANNOYING EDGE CASE FOR `where` clauses + // TODO(bill, 2021-03-27): Is this even a valid HACK?! + Entity *entity = entity_of_node(o.expr); + if (entity != nullptr && + entity->kind == Entity_TypeName && + entity->type == t_typeid) { + *is_polymorphic_ = true; + return false; + } + } + } + return true; +} + +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); @@ -334,262 +614,7 @@ gb_internal Entity *find_polymorphic_record_entity(CheckerContext *ctx, Type *or } } return nullptr; -} - - -gb_internal void add_polymorphic_record_entity(CheckerContext *ctx, Ast *node, Type *named_type, Type *original_type) { - GB_ASSERT(is_type_named(named_type)); - gbAllocator a = heap_allocator(); - Scope *s = ctx->scope->parent; - - Entity *e = nullptr; - { - Token token = ast_token(node); - token.kind = Token_String; - token.string = named_type->Named.name; - - Ast *node = ast_ident(nullptr, token); - - e = alloc_entity_type_name(s, token, named_type); - e->state = EntityState_Resolved; - e->file = ctx->file; - e->pkg = ctx->pkg; - add_entity_use(ctx, node, e); - } - - named_type->Named.type_name = e; - GB_ASSERT(original_type->kind == Type_Named); - e->TypeName.objc_class_name = original_type->Named.type_name->TypeName.objc_class_name; - // TODO(bill): Is this even correct? Or should the metadata be copied? - e->TypeName.objc_metadata = original_type->Named.type_name->TypeName.objc_metadata; - - rw_mutex_lock(&ctx->info->gen_types_mutex); - auto *found_gen_types = map_get(&ctx->info->gen_types, original_type); - if (found_gen_types) { - rw_mutex_lock(&found_gen_types->mutex); - array_add(&found_gen_types->types, e); - rw_mutex_unlock(&found_gen_types->mutex); - } else { - GenTypesData gen_types = {}; - gen_types.types = array_make(heap_allocator()); - array_add(&gen_types.types, e); - map_set(&ctx->info->gen_types, original_type, gen_types); - } - rw_mutex_unlock(&ctx->info->gen_types_mutex); -} - - -bool check_constant_parameter_value(Type *type, Ast *expr) { - if (!is_type_constant_type(type)) { - gbString str = type_to_string(type); - defer (gb_string_free(str)); - error(expr, "A parameter must be a valid constant type, got %s", str); - return true; - } - return false; -} - -gb_internal Type *check_record_polymorphic_params(CheckerContext *ctx, Ast *polymorphic_params, - bool *is_polymorphic_, - Ast *node, Array *poly_operands) { - Type *polymorphic_params_type = nullptr; - bool can_check_fields = true; - GB_ASSERT(is_polymorphic_ != nullptr); - - if (polymorphic_params == nullptr) { - if (!*is_polymorphic_) { - *is_polymorphic_ = polymorphic_params != nullptr && poly_operands == nullptr; - } - return polymorphic_params_type; - } - - ast_node(field_list, FieldList, polymorphic_params); - Slice params = field_list->list; - if (params.count != 0) { - isize variable_count = 0; - for_array(i, params) { - Ast *field = params[i]; - if (ast_node_expect(field, Ast_Field)) { - ast_node(f, Field, field); - variable_count += gb_max(f->names.count, 1); - } - } - - auto entities = array_make(permanent_allocator(), 0, variable_count); - - for_array(i, params) { - Ast *param = params[i]; - if (param->kind != Ast_Field) { - continue; - } - ast_node(p, Field, param); - Ast *type_expr = p->type; - Ast *default_value = unparen_expr(p->default_value); - Type *type = nullptr; - bool is_type_param = false; - bool is_type_polymorphic_type = false; - if (type_expr == nullptr && default_value == nullptr) { - error(param, "Expected a type for this parameter"); - continue; - } - - if (type_expr != nullptr) { - if (type_expr->kind == Ast_Ellipsis) { - type_expr = type_expr->Ellipsis.expr; - error(param, "A polymorphic parameter cannot be variadic"); - } - 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); - } - type = alloc_type_generic(ctx->scope, 0, str_lit(""), specialization); - } else { - type = check_type(ctx, type_expr); - if (is_type_polymorphic(type)) { - is_type_polymorphic_type = true; - } - } - } - - ParameterValue param_value = {}; - if (default_value != nullptr) { - Type *out_type = nullptr; - param_value = handle_parameter_value(ctx, type, &out_type, default_value, false); - if (type == nullptr && out_type != nullptr) { - type = out_type; - } - if (param_value.kind != ParameterValue_Constant && param_value.kind != ParameterValue_Nil) { - error(default_value, "Invalid parameter value"); - param_value = {}; - } - } - - - if (type == nullptr) { - error(params[i], "Invalid parameter type"); - type = t_invalid; - } - if (is_type_untyped(type)) { - if (is_type_untyped_uninit(type)) { - error(params[i], "Cannot determine parameter type from ---"); - } else { - error(params[i], "Cannot determine parameter type from a nil"); - } - type = t_invalid; - } - - if (is_type_polymorphic_type) { - gbString str = type_to_string(type); - error(params[i], "Parameter types cannot be polymorphic, got %s", str); - gb_string_free(str); - type = t_invalid; - } - - if (!is_type_param && check_constant_parameter_value(type, params[i])) { - // failed - } - - Scope *scope = ctx->scope; - for_array(j, p->names) { - Ast *name = p->names[j]; - if (!ast_node_expect2(name, Ast_Ident, Ast_PolyType)) { - continue; - } - if (name->kind == Ast_PolyType) { - name = name->PolyType.type; - } - Entity *e = nullptr; - - Token token = name->Ident.token; - - if (poly_operands != nullptr) { - Operand operand = {}; - operand.type = t_invalid; - if (entities.count < poly_operands->count) { - operand = (*poly_operands)[entities.count]; - } else if (param_value.kind != ParameterValue_Invalid) { - operand.mode = Addressing_Constant; - operand.value = param_value.value; - } - if (is_type_param) { - if (is_type_polymorphic(base_type(operand.type))) { - *is_polymorphic_ = true; - can_check_fields = false; - } - e = alloc_entity_type_name(scope, token, operand.type); - e->TypeName.is_type_alias = true; - e->flags |= EntityFlag_PolyConst; - } else { - if (is_type_polymorphic(base_type(operand.type))) { - *is_polymorphic_ = true; - can_check_fields = false; - } - if (e == nullptr) { - e = alloc_entity_constant(scope, token, operand.type, operand.value); - e->Constant.param_value = param_value; - } - } - } else { - if (is_type_param) { - e = alloc_entity_type_name(scope, token, type); - e->TypeName.is_type_alias = true; - e->flags |= EntityFlag_PolyConst; - } else { - e = alloc_entity_constant(scope, token, type, param_value.value); - e->Constant.param_value = param_value; - } - } - - e->state = EntityState_Resolved; - add_entity(ctx, scope, name, e); - array_add(&entities, e); - } - } - - if (entities.count > 0) { - Type *tuple = alloc_type_tuple(); - tuple->Tuple.variables = slice_from_array(entities); - polymorphic_params_type = tuple; - } - } - - if (!*is_polymorphic_) { - *is_polymorphic_ = polymorphic_params != nullptr && poly_operands == nullptr; - } - - return polymorphic_params_type; -} - -gb_internal bool check_record_poly_operand_specialization(CheckerContext *ctx, Type *record_type, Array *poly_operands, bool *is_polymorphic_) { - if (poly_operands == nullptr) { - return false; - } - for (isize i = 0; i < poly_operands->count; i++) { - Operand o = (*poly_operands)[i]; - if (is_type_polymorphic(o.type)) { - return false; - } - if (record_type == o.type) { - // NOTE(bill): Cycle - return false; - } - if (o.mode == Addressing_Type) { - // NOTE(bill): ANNOYING EDGE CASE FOR `where` clauses - // TODO(bill, 2021-03-27): Is this even a valid HACK?! - Entity *entity = entity_of_node(o.expr); - if (entity != nullptr && - entity->kind == Entity_TypeName && - entity->type == t_typeid) { - *is_polymorphic_ = true; - return false; - } - } - } - return true; -} +}; gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast *node, Array *poly_operands, Type *named_type, Type *original_type_for_poly) { @@ -613,22 +638,22 @@ gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast * scope_reserve(ctx->scope, min_field_count); - rw_mutex_lock(&struct_type->Struct.fields_mutex); - defer (rw_mutex_unlock(&struct_type->Struct.fields_mutex)); - if (st->is_raw_union && min_field_count > 1) { struct_type->Struct.is_raw_union = true; 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); @@ -643,30 +668,63 @@ gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast * gb_unused(where_clause_ok); } check_struct_fields(ctx, node, &struct_type->Struct.fields, &struct_type->Struct.tags, st->fields, min_field_count, struct_type, context); + wait_signal_set(&struct_type->Struct.fields_wait_signal); } - if (st->align != nullptr) { - if (st->is_packed) { - syntax_error(st->align, "'#align' cannot be applied with '#packed'"); - return; - } - i64 custom_align = 1; - if (check_custom_align(ctx, st->align, &custom_align)) { - struct_type->Struct.custom_align = custom_align; - } +#define ST_ALIGN(_name) if (st->_name != nullptr) { \ + if (st->is_packed) { \ + error(st->_name, "'#%s' cannot be applied with '#packed'", #_name); \ + return; \ + } \ + i64 align = 1; \ + if (check_custom_align(ctx, st->_name, &align, #_name)) { \ + struct_type->Struct.custom_##_name = align; \ + } \ } + + ST_ALIGN(min_field_align); + ST_ALIGN(max_field_align); + ST_ALIGN(align); + if (struct_type->Struct.custom_align < struct_type->Struct.custom_min_field_align) { + error(st->align, "#align(%lld) is defined to be less than #min_field_align(%lld)", + cast(long long)struct_type->Struct.custom_align, + cast(long long)struct_type->Struct.custom_min_field_align); + } + if (struct_type->Struct.custom_max_field_align != 0 && + struct_type->Struct.custom_align > struct_type->Struct.custom_max_field_align) { + error(st->align, "#align(%lld) is defined to be greater than #max_field_align(%lld)", + cast(long long)struct_type->Struct.custom_align, + cast(long long)struct_type->Struct.custom_max_field_align); + } + if (struct_type->Struct.custom_max_field_align != 0 && + struct_type->Struct.custom_min_field_align > struct_type->Struct.custom_max_field_align) { + error(st->align, "#min_field_align(%lld) is defined to be greater than #max_field_align(%lld)", + cast(long long)struct_type->Struct.custom_min_field_align, + cast(long long)struct_type->Struct.custom_max_field_align); + + i64 a = gb_min(struct_type->Struct.custom_min_field_align, struct_type->Struct.custom_max_field_align); + i64 b = gb_max(struct_type->Struct.custom_min_field_align, struct_type->Struct.custom_max_field_align); + // NOTE(bill): sort them to keep code consistent + struct_type->Struct.custom_min_field_align = a; + struct_type->Struct.custom_max_field_align = b; + } + +#undef ST_ALIGN } gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *node, Array *poly_operands, Type *named_type, Type *original_type_for_poly) { GB_ASSERT(is_type_union(union_type)); 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); @@ -701,7 +759,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); @@ -739,14 +797,14 @@ 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; } if (ut->align != nullptr) { i64 custom_align = 1; - if (check_custom_align(ctx, ut->align, &custom_align)) { + if (check_custom_align(ctx, ut->align, &custom_align, "align")) { if (variants.count == 0) { error(ut->align, "An empty union cannot have a custom alignment"); } else { @@ -760,12 +818,15 @@ gb_internal void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *nam ast_node(et, EnumType, node); GB_ASSERT(is_type_enum(enum_type)); + enum_type->Enum.base_type = t_int; + 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; } @@ -781,7 +842,6 @@ gb_internal void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *nam // NOTE(bill): Must be up here for the 'check_init_constant' system enum_type->Enum.base_type = base_type; - enum_type->Enum.scope = ctx->scope; auto fields = array_make(permanent_allocator(), 0, et->fields.count); @@ -898,6 +958,237 @@ gb_internal void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *nam enum_type->Enum.max_value_index = max_value_index; } + +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); + + 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; + } + + auto fields = array_make(permanent_allocator(), 0, bf->fields.count); + auto bit_sizes = array_make (permanent_allocator(), 0, bf->fields.count); + auto tags = array_make (permanent_allocator(), 0, bf->fields.count); + + u64 maximum_bit_size = 8 * type_size_of(backing_type); + u64 total_bit_size = 0; + + for_array(i, bf->fields) { + i32 field_src_index = cast(i32)i; + Ast *field = bf->fields[i]; + if (field->kind != Ast_BitFieldField) { + error(field, "Invalid AST for a bit_field"); + continue; + } + ast_node(f, BitFieldField, field); + if (f->name == nullptr || f->name->kind != Ast_Ident) { + error(field, "A bit_field's field name must be an identifier"); + continue; + } + CommentGroup *docs = f->docs; + CommentGroup *comment = f->comment; + + String name = f->name->Ident.token.string; + + if (f->type == nullptr) { + error(field, "A bit_field's field must have a type"); + continue; + } + + Type *type = check_type(ctx, f->type); + if (type_size_of(type) > 8) { + error(f->type, "The type of a bit_field's field must be <= 8 bytes, got %lld", cast(long long)type_size_of(type)); + } + + if (is_type_untyped(type)) { + gbString s = type_to_string(type); + error(f->type, "The type of a bit_field's field must be a typed integer, enum, or boolean, got %s", s); + gb_string_free(s); + } else if (!(is_type_integer(type) || is_type_enum(type) || is_type_boolean(type))) { + gbString s = type_to_string(type); + error(f->type, "The type of a bit_field's field must be an integer, enum, or boolean, got %s", s); + gb_string_free(s); + } + + if (f->bit_size == nullptr) { + error(field, "A bit_field's field must have a specified bit size"); + continue; + } + + + Operand o = {}; + check_expr(ctx, &o, f->bit_size); + if (o.mode != Addressing_Constant) { + error(f->bit_size, "A bit_field's specified bit size must be a constant"); + o.mode = Addressing_Invalid; + } + if (o.value.kind == ExactValue_Float) { + o.value = exact_value_to_integer(o.value); + } + if (f->bit_size->kind == Ast_BinaryExpr && f->bit_size->BinaryExpr.op.kind == Token_Or) { + gbString s = expr_to_string(f->bit_size); + error(f->bit_size, "Wrap the expression in parentheses, e.g. (%s)", s); + gb_string_free(s); + } + + ExactValue bit_size = o.value; + + if (bit_size.kind != ExactValue_Integer) { + gbString s = expr_to_string(f->bit_size); + error(f->bit_size, "Expected an integer constant value for the specified bit size, got %s", s); + gb_string_free(s); + } + + if (scope_lookup_current(ctx->scope, name) != nullptr) { + error(f->name, "'%.*s' is already declared in this bit_field", LIT(name)); + } else { + i64 bit_size_i64 = exact_value_to_i64(bit_size); + u8 bit_size_u8 = 0; + if (bit_size_i64 <= 0) { + error(f->bit_size, "A bit_field's specified bit size cannot be <= 0, got %lld", cast(long long)bit_size_i64); + bit_size_i64 = 1; + } + if (bit_size_i64 > 64) { + error(f->bit_size, "A bit_field's specified bit size cannot exceed 64 bits, got %lld", cast(long long)bit_size_i64); + bit_size_i64 = 64; + } + i64 sz = 8*type_size_of(type); + if (bit_size_i64 > sz) { + error(f->bit_size, "A bit_field's specified bit size cannot exceed its type, got %lld, expect <=%lld", cast(long long)bit_size_i64, cast(long long)sz); + bit_size_i64 = sz; + } + + bit_size_u8 = cast(u8)bit_size_i64; + + Entity *e = alloc_entity_field(ctx->scope, f->name->Ident.token, type, false, field_src_index); + e->Variable.docs = docs; + e->Variable.comment = comment; + e->Variable.bit_field_bit_size = bit_size_u8; + e->flags |= EntityFlag_BitFieldField; + + add_entity(ctx, ctx->scope, nullptr, e); + array_add(&fields, e); + array_add(&bit_sizes, bit_size_u8); + + String tag = f->tag.string; + if (tag.len != 0 && !unquote_string(permanent_allocator(), &tag, 0, tag.text[0] == '`')) { + error(f->tag, "Invalid string literal"); + tag = {}; + } + array_add(&tags, tag); + + add_entity_use(ctx, field, e); + + total_bit_size += bit_size_u8; + } + } + + GB_ASSERT(fields.count <= bf->fields.count); + + auto bit_offsets = slice_make(permanent_allocator(), fields.count); + i64 curr_offset = 0; + for_array(i, bit_sizes) { + bit_offsets[i] = curr_offset; + curr_offset += cast(i64)bit_sizes[i]; + } + + if (total_bit_size > maximum_bit_size) { + gbString s = type_to_string(backing_type); + 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; + if (all_ones && all_booleans) { + for_array(i, bit_sizes) { + all_ones = bit_sizes[i] == 1; + if (!all_ones) { + break; + } + all_booleans = is_type_boolean(fields[i]->type); + if (!all_booleans) { + break; + } + } + if (all_ones && all_booleans) { + 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 { + char const *msg = "This 'bit_field' might be 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"; + warning(node, msg); + } + } + } + } + + + bit_field_type->BitField.fields = slice_from_array(fields); + bit_field_type->BitField.bit_sizes = slice_from_array(bit_sizes); + bit_field_type->BitField.bit_offsets = bit_offsets; + bit_field_type->BitField.tags = tags.data; +} + gb_internal bool is_type_valid_bit_set_range(Type *t) { if (is_type_integer(t)) { return true; @@ -980,11 +1271,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; } @@ -1047,7 +1341,7 @@ gb_internal void check_bit_set_type(CheckerContext *c, Type *type, Type *named_t } if (!is_valid) { if (actual_lower != lower) { - error(bs->elem, "bit_set range is greater than %lld bits, %lld bits are required (internal the lower changed was changed 0 as an underlying type was set)", bits, bits_required); + error(bs->elem, "bit_set range is greater than %lld bits, %lld bits are required (internally the lower bound was changed to 0 as an underlying type was set)", bits, bits_required); } else { error(bs->elem, "bit_set range is greater than %lld bits, %lld bits are required", bits, bits_required); } @@ -1117,7 +1411,7 @@ gb_internal void check_bit_set_type(CheckerContext *c, Type *type, Type *named_t if (upper - lower >= bits) { i64 bits_required = upper-lower+1; if (lower_changed) { - error(bs->elem, "bit_set range is greater than %lld bits, %lld bits are required (internal the lower changed was changed 0 as an underlying type was set)", bits, bits_required); + error(bs->elem, "bit_set range is greater than %lld bits, %lld bits are required (internally the lower bound was changed to 0 as an underlying type was set)", bits, bits_required); } else { error(bs->elem, "bit_set range is greater than %lld bits, %lld bits are required", bits, bits_required); } @@ -1153,6 +1447,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; } @@ -1161,8 +1459,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]; @@ -1202,6 +1500,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; } @@ -1210,8 +1512,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]; @@ -1276,11 +1578,30 @@ gb_internal Type *determine_type_from_polymorphic(CheckerContext *ctx, Type *pol return poly_type; } if (show_error) { + ERROR_BLOCK(); gbString pts = type_to_string(poly_type); gbString ots = type_to_string(operand.type, true); defer (gb_string_free(pts)); defer (gb_string_free(ots)); error(operand.expr, "Cannot determine polymorphic type from parameter: '%s' to '%s'", ots, pts); + + Type *pt = poly_type; + while (pt && pt->kind == Type_Generic && pt->Generic.specialized) { + pt = pt->Generic.specialized; + } + if (is_type_slice(pt) && + (is_type_dynamic_array(operand.type) || is_type_array(operand.type))) { + Ast *expr = unparen_expr(operand.expr); + if (expr->kind == Ast_CompoundLit) { + gbString es = type_to_string(base_any_array_type(operand.type)); + error_line("\tSuggestion: Try using a slice compound literal instead '[]%s{...}'\n", es); + gb_string_free(es); + } else { + gbString os = expr_to_string(operand.expr); + error_line("\tSuggestion: Try slicing the value with '%s[:]'\n", os); + gb_string_free(os); + } + } } return t_invalid; } @@ -1295,7 +1616,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; } @@ -1303,6 +1624,25 @@ gb_internal bool is_expr_from_a_parameter(CheckerContext *ctx, Ast *expr) { return false; } +gb_internal bool is_caller_expression(Ast *expr) { + if (expr->kind == Ast_BasicDirective && expr->BasicDirective.name.string == "caller_expression") { + return true; + } + + Ast *call = unparen_expr(expr); + if (call->kind != Ast_CallExpr) { + return false; + } + + ast_node(ce, CallExpr, call); + if (ce->proc->kind != Ast_BasicDirective) { + return false; + } + + ast_node(bd, BasicDirective, ce->proc); + String name = bd->name.string; + return name == "caller_expression"; +} gb_internal ParameterValue handle_parameter_value(CheckerContext *ctx, Type *in_type, Type **out_type_, Ast *expr, bool allow_caller_location) { ParameterValue param_value = {}; @@ -1324,7 +1664,19 @@ gb_internal ParameterValue handle_parameter_value(CheckerContext *ctx, Type *in_ if (in_type) { check_assignment(ctx, &o, in_type, str_lit("parameter value")); } + } else if (is_caller_expression(expr)) { + if (expr->kind != Ast_BasicDirective) { + check_builtin_procedure_directive(ctx, &o, expr, t_string); + } + param_value.kind = ParameterValue_Expression; + o.type = t_string; + o.mode = Addressing_Value; + o.expr = expr; + + if (in_type) { + check_assignment(ctx, &o, in_type, str_lit("parameter value")); + } } else { if (in_type) { check_expr_with_type_hint(ctx, &o, expr, in_type); @@ -1461,7 +1813,7 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para bool is_using = (p->flags&FieldFlag_using) != 0; if ((check_vet_flags(param) & VetFlag_UsingParam) && is_using) { ERROR_BLOCK(); - error(param, "'using' on a procedure parameter is now allowed when '-vet' or '-vet-using-param' is applied"); + error(param, "'using' on a procedure parameter is not allowed when '-vet' or '-vet-using-param' is applied"); error_line("\t'using' is considered bad practice to use as a statement/procedure parameter outside of immediate refactoring\n"); } @@ -1469,6 +1821,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; @@ -1477,6 +1830,14 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para error(param, "Invalid AST: Invalid variadic parameter with multiple names"); success = false; } + + if (default_value != nullptr) { + error(type_expr, "A variadic parameter may not have a default value"); + success = false; + } + + GB_ASSERT(original_type_expr->kind == Ast_Ellipsis); + type_expr = ast_array_type(type_expr->file(), original_type_expr->Ellipsis.token, nullptr, type_expr); } if (type_expr->kind == Ast_TypeidType) { ast_node(tt, TypeidType, type_expr); @@ -1500,6 +1861,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; @@ -1532,12 +1894,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) { @@ -1546,6 +1908,7 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para case ParameterValue_Nil: break; case ParameterValue_Location: + case ParameterValue_Expression: case ParameterValue_Value: gbString str = type_to_string(type); error(params[i], "A default value for a parameter must not be a polymorphic constant type, got %s", str); @@ -1644,10 +2007,18 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para error(name, "'#any_int' can only be applied to variable fields"); p->flags &= ~FieldFlag_any_int; } + if (p->flags&FieldFlag_no_broadcast) { + error(name, "'#no_broadcast' can only be applied to variable fields"); + p->flags &= ~FieldFlag_no_broadcast; + } if (p->flags&FieldFlag_by_ptr) { 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; @@ -1701,7 +2072,13 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para } } } - if (type != t_invalid && !check_is_assignable_to(ctx, &op, type)) { + + bool allow_array_programming = true; + if (p->flags&FieldFlag_no_broadcast) { + allow_array_programming = false; + } + + if (type != t_invalid && !check_is_assignable_to(ctx, &op, type, allow_array_programming)) { bool ok = true; if (p->flags&FieldFlag_any_int) { if ((!is_type_integer(op.type) && !is_type_enum(op.type)) || (!is_type_integer(type) && !is_type_enum(type))) { @@ -1732,8 +2109,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 } } @@ -1743,6 +2120,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) { @@ -1761,6 +2160,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 @@ -1772,11 +2176,26 @@ 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; } + if (p->flags&FieldFlag_no_broadcast) { + param->flags |= EntityFlag_NoBroadcast; + } + if (p->flags&FieldFlag_any_int) { if (!is_type_integer(param->type) && !is_type_enum(param->type)) { gbString str = type_to_string(param->type); @@ -1791,6 +2210,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); @@ -1804,18 +2227,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; @@ -1953,8 +2365,27 @@ gb_internal Type *check_get_results(CheckerContext *ctx, Scope *scope, Ast *_res return tuple; } +gb_internal void check_procedure_param_polymorphic_type(CheckerContext *ctx, Type *type, Ast *type_expr) { + if (type == nullptr || type_expr == nullptr || ctx->in_polymorphic_specialization) { return; } + if (!is_type_polymorphic_record_unspecialized(type)) { return; } + bool invalid_polymorpic_type_use = false; + switch (type_expr->kind) { + case_ast_node(pt, Ident, type_expr); + invalid_polymorpic_type_use = true; + case_end; + case_ast_node(pt, SelectorExpr, type_expr); + invalid_polymorpic_type_use = true; + case_end; + } + + if (invalid_polymorpic_type_use) { + gbString expr_str = expr_to_string(type_expr); + defer (gb_string_free(expr_str)); + error(type_expr, "Invalid use of a non-specialized polymorphic type '%s'", expr_str); + } +} // NOTE(bill): 'operands' is for generating non generic procedure type gb_internal bool check_procedure_type(CheckerContext *ctx, Type *type, Ast *proc_type_node, Array const *operands) { @@ -2009,8 +2440,12 @@ gb_internal bool check_procedure_type(CheckerContext *ctx, Type *type, Ast *proc bool success = true; isize specialization_count = 0; Type *params = check_get_params(c, c->scope, pt->params, &variadic, &variadic_index, &success, &specialization_count, operands); - Type *results = check_get_results(c, c->scope, pt->results); + bool no_poly_return = c->disallow_polymorphic_return_types; + c->disallow_polymorphic_return_types = c->scope == c->polymorphic_scope; + // NOTE(zen3ger): if the parapoly scope is the current proc's scope, then the return types shall not declare new poly vars + Type *results = check_get_results(c, c->scope, pt->results); + c->disallow_polymorphic_return_types = no_poly_return; isize param_count = 0; isize result_count = 0; @@ -2070,33 +2505,34 @@ gb_internal bool check_procedure_type(CheckerContext *ctx, Type *type, Ast *proc type->Proc.diverging = pt->diverging; type->Proc.optional_ok = optional_ok; - if (param_count > 0) { - Entity *end = params->Tuple.variables[param_count-1]; - if (end->flags&EntityFlag_CVarArg) { + bool is_polymorphic = false; + for (isize i = 0; i < param_count; i++) { + Entity *e = params->Tuple.variables[i]; + + if (e->kind != Entity_Variable) { + is_polymorphic = true; + } else if (is_type_polymorphic(e->type)) { + check_procedure_param_polymorphic_type(c, e->type, e->Variable.type_expr); + is_polymorphic = true; + } + + if (e->flags&EntityFlag_CVarArg) { + if (i != param_count - 1) { + error(e->token, "#c_vararg can only be applied to the last parameter"); + continue; + } + switch (cc) { default: type->Proc.c_vararg = true; break; case ProcCC_Odin: case ProcCC_Contextless: - error(end->token, "Calling convention does not support #c_vararg"); + error(e->token, "Calling convention does not support #c_vararg"); break; } } } - - - bool is_polymorphic = false; - for (isize i = 0; i < param_count; i++) { - Entity *e = params->Tuple.variables[i]; - if (e->kind != Entity_Variable) { - is_polymorphic = true; - break; - } else if (is_type_polymorphic(e->type)) { - is_polymorphic = true; - break; - } - } for (isize i = 0; i < result_count; i++) { Entity *e = results->Tuple.variables[i]; if (e->kind != Entity_Variable) { @@ -2117,9 +2553,15 @@ gb_internal i64 check_array_count(CheckerContext *ctx, Operand *o, Ast *e) { if (e == nullptr) { return 0; } - if (e->kind == Ast_UnaryExpr && - e->UnaryExpr.op.kind == Token_Question) { - return -1; + if (e->kind == Ast_UnaryExpr) { + Token op = e->UnaryExpr.op; + if (op.kind == Token_Question) { + return -1; + } + if (e->UnaryExpr.expr == nullptr) { + error(op, "Invalid array count '[%.*s]'", LIT(op.string)); + return 0; + } } check_expr_or_type(ctx, o, e); @@ -2157,6 +2599,8 @@ gb_internal i64 check_array_count(CheckerContext *ctx, Operand *o, Ast *e) { return 0; } + ERROR_BLOCK(); + gbString s = expr_to_string(o->expr); error(e, "Array count must be a constant integer, got %s", s); gb_string_free(s); @@ -2226,6 +2670,78 @@ gb_internal void map_cell_size_and_len(Type *type, i64 *size_, i64 *len_) { if (len_) *len_ = len; } +gb_internal Type *get_map_cell_type(Type *type) { + i64 size, len; + i64 elem_size = type_size_of(type); + map_cell_size_and_len(type, &size, &len); + + if (size == len*elem_size) { + return type; + } + + i64 padding = size - len*elem_size; + GB_ASSERT(padding > 0); + + // Padding exists + Type *s = alloc_type_struct(); + Scope *scope = create_scope(nullptr, nullptr); + s->Struct.fields = slice_make(permanent_allocator(), 2); + s->Struct.fields[0] = alloc_entity_field(scope, make_token_ident("v"), alloc_type_array(type, len), false, 0, EntityState_Resolved); + s->Struct.fields[1] = alloc_entity_field(scope, make_token_ident("_"), alloc_type_array(t_u8, padding), false, 1, EntityState_Resolved); + s->Struct.scope = scope; + wait_signal_set(&s->Struct.fields_wait_signal); + gb_unused(type_size_of(s)); + + return s; +} + +gb_internal void init_map_internal_debug_types(Type *type) { + GB_ASSERT(type->kind == Type_Map); + GB_ASSERT(t_allocator != nullptr); + if (type->Map.debug_metadata_type != nullptr) return; + + Type *key = type->Map.key; + Type *value = type->Map.value; + GB_ASSERT(key != nullptr); + GB_ASSERT(value != nullptr); + + Type *key_cell = get_map_cell_type(key); + Type *value_cell = get_map_cell_type(value); + + Type *metadata_type = alloc_type_struct(); + Scope *metadata_scope = create_scope(nullptr, nullptr); + metadata_type->Struct.fields = slice_make(permanent_allocator(), 5); + metadata_type->Struct.fields[0] = alloc_entity_field(metadata_scope, make_token_ident("key"), key, false, 0, EntityState_Resolved); + metadata_type->Struct.fields[1] = alloc_entity_field(metadata_scope, make_token_ident("value"), value, false, 1, EntityState_Resolved); + metadata_type->Struct.fields[2] = alloc_entity_field(metadata_scope, make_token_ident("hash"), t_uintptr, false, 2, EntityState_Resolved); + metadata_type->Struct.fields[3] = alloc_entity_field(metadata_scope, make_token_ident("key_cell"), key_cell, false, 3, EntityState_Resolved); + metadata_type->Struct.fields[4] = alloc_entity_field(metadata_scope, make_token_ident("value_cell"), value_cell, false, 4, EntityState_Resolved); + metadata_type->Struct.scope = metadata_scope; + metadata_type->Struct.node = nullptr; + wait_signal_set(&metadata_type->Struct.fields_wait_signal); + + gb_unused(type_size_of(metadata_type)); + + // NOTE(bill): ^struct{key: Key, value: Value, hash: uintptr} + metadata_type = alloc_type_pointer(metadata_type); + + + Scope *scope = create_scope(nullptr, nullptr); + Type *debug_type = alloc_type_struct(); + debug_type->Struct.fields = slice_make(permanent_allocator(), 3); + debug_type->Struct.fields[0] = alloc_entity_field(scope, make_token_ident("data"), metadata_type, false, 0, EntityState_Resolved); + debug_type->Struct.fields[1] = alloc_entity_field(scope, make_token_ident("len"), t_int, false, 1, EntityState_Resolved); + debug_type->Struct.fields[2] = alloc_entity_field(scope, make_token_ident("allocator"), t_allocator, false, 2, EntityState_Resolved); + debug_type->Struct.scope = scope; + debug_type->Struct.node = nullptr; + wait_signal_set(&debug_type->Struct.fields_wait_signal); + + gb_unused(type_size_of(debug_type)); + + type->Map.debug_metadata_type = debug_type; +} + + gb_internal void init_map_internal_types(Type *type) { GB_ASSERT(type->kind == Type_Map); GB_ASSERT(t_allocator != nullptr); @@ -2282,6 +2798,18 @@ gb_internal void check_map_type(CheckerContext *ctx, Type *type, Ast *node) { GB_ASSERT(type->kind == Type_Map); ast_node(mt, MapType, node); + if (mt->key == NULL) { + if (mt->value != NULL) { + Type *value = check_type(ctx, mt->value); + gbString str = type_to_string(value); + error(node, "Missing map key type, got 'map[]%s'", str); + gb_string_free(str); + return; + } + error(node, "Missing map key type, got 'map[]T'"); + return; + } + Type *key = check_type(ctx, mt->key); Type *value = check_type(ctx, mt->value); @@ -2307,8 +2835,6 @@ gb_internal void check_map_type(CheckerContext *ctx, Type *type, Ast *node) { init_core_map_type(ctx->checker); init_map_internal_types(type); - - // error(node, "'map' types are not yet implemented"); } gb_internal void check_matrix_type(CheckerContext *ctx, Type **type, Ast *node) { @@ -2368,11 +2894,126 @@ gb_internal void check_matrix_type(CheckerContext *ctx, Type **type, Ast *node) } type_assign:; - *type = alloc_type_matrix(elem, row_count, column_count, generic_row, generic_column); + *type = alloc_type_matrix(elem, row_count, column_count, generic_row, generic_column, mt->is_row_major); return; } +struct SoaTypeWorkerData { + CheckerContext ctx; + Type * type; + bool wait_to_finish; +}; + + +gb_internal bool complete_soa_type(Checker *checker, Type *t, bool wait_to_finish) { + Type *original_type = t; + gb_unused(original_type); + + t = base_type(t); + if (t == nullptr || !is_type_soa_struct(t)) { + return true; + } + + MUTEX_GUARD(&t->Struct.soa_mutex); + + if (t->Struct.fields_wait_signal.futex.load()) { + return true; + } + + isize field_count = 0; + i32 extra_field_count = 0; + switch (t->Struct.soa_kind) { + case StructSoa_Fixed: extra_field_count = 0; break; + case StructSoa_Slice: extra_field_count = 1; break; + case StructSoa_Dynamic: extra_field_count = 3; break; + } + + Scope *scope = t->Struct.scope; + i64 soa_count = t->Struct.soa_count; + Type *elem = t->Struct.soa_elem; + Type *old_struct = base_type(elem); + GB_ASSERT(old_struct->kind == Type_Struct); + + if (wait_to_finish) { + wait_signal_until_available(&old_struct->Struct.fields_wait_signal); + } else { + GB_ASSERT(old_struct->Struct.fields_wait_signal.futex.load()); + } + + field_count = old_struct->Struct.fields.count; + + t->Struct.fields = slice_make(permanent_allocator(), field_count+extra_field_count); + t->Struct.tags = gb_alloc_array(permanent_allocator(), String, field_count+extra_field_count); + + + auto const &add_entity = [](Scope *scope, Entity *entity) { + String name = entity->token.string; + if (!is_blank_ident(name)) { + Entity *ie = scope_insert(scope, entity); + if (ie != nullptr) { + redeclaration_error(name, entity, ie); + } + } + }; + + + for_array(i, old_struct->Struct.fields) { + Entity *old_field = old_struct->Struct.fields[i]; + if (old_field->kind == Entity_Variable) { + Type *field_type = nullptr; + if (t->Struct.soa_kind == StructSoa_Fixed) { + GB_ASSERT(soa_count >= 0); + field_type = alloc_type_array(old_field->type, soa_count); + } else { + 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; + } + + t->Struct.tags[i] = old_struct->Struct.tags[i]; + } + + if (t->Struct.soa_kind != StructSoa_Fixed) { + Entity *len_field = alloc_entity_field(scope, make_token_ident("__$len"), t_int, false, cast(i32)field_count+0); + t->Struct.fields[field_count+0] = len_field; + add_entity(scope, len_field); + len_field->flags |= EntityFlag_Used; + + if (t->Struct.soa_kind == StructSoa_Dynamic) { + Entity *cap_field = alloc_entity_field(scope, make_token_ident("__$cap"), t_int, false, cast(i32)field_count+1); + t->Struct.fields[field_count+1] = cap_field; + add_entity(scope, cap_field); + cap_field->flags |= EntityFlag_Used; + + init_mem_allocator(checker); + Entity *allocator_field = alloc_entity_field(scope, make_token_ident("allocator"), t_allocator, false, cast(i32)field_count+2); + t->Struct.fields[field_count+2] = allocator_field; + add_entity(scope, allocator_field); + allocator_field->flags |= EntityFlag_Used; + } + } + + // add_type_info_type(ctx, original_type); + + wait_signal_set(&t->Struct.fields_wait_signal); + return true; +} + +gb_internal WORKER_TASK_PROC(complete_soa_type_worker) { + SoaTypeWorkerData *wd = cast(SoaTypeWorkerData *)data; + complete_soa_type(wd->ctx.checker, wd->type, wd->wait_to_finish); + return 0; +} + gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_expr, Ast *elem_expr, Type *elem, i64 count, Type *generic_type, StructSoaKind soa_kind) { @@ -2380,15 +3021,16 @@ gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_e bool is_polymorphic = is_type_polymorphic(elem); - if ((!is_polymorphic || soa_kind == StructSoa_Fixed) && !is_type_struct(elem) && !is_type_raw_union(elem) && !(is_type_array(elem) && bt_elem->Array.count <= 4)) { + if (!is_polymorphic && !is_type_struct(elem) && !is_type_raw_union(elem) && !(is_type_array(elem) && bt_elem->Array.count <= 4)) { gbString str = type_to_string(elem); error(elem_expr, "Invalid type for an #soa array, expected a struct or array of length 4 or below, got '%s'", str); gb_string_free(str); return alloc_type_array(elem, count, generic_type); } - Type *soa_struct = nullptr; - Scope *scope = nullptr; + Type * soa_struct = nullptr; + Scope *scope = nullptr; + bool is_complete = false; isize field_count = 0; i32 extra_field_count = 0; @@ -2397,39 +3039,43 @@ gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_e case StructSoa_Slice: extra_field_count = 1; break; case StructSoa_Dynamic: extra_field_count = 3; break; } - if (is_polymorphic && soa_kind != StructSoa_Fixed) { + + soa_struct = alloc_type_struct(); + soa_struct->Struct.soa_kind = soa_kind; + soa_struct->Struct.soa_elem = elem; + soa_struct->Struct.is_polymorphic = is_polymorphic; + soa_struct->Struct.node = array_typ_expr; + + if (count > I32_MAX) { + count = I32_MAX; + error(array_typ_expr, "Array count too large for an #soa struct, got %lld", cast(long long)count); + } + soa_struct->Struct.soa_count = cast(i32)count; + + scope = create_scope(ctx->info, ctx->scope); + soa_struct->Struct.scope = scope; + + if (elem && elem->kind == Type_Named) { + add_declaration_dependency(ctx, elem->Named.type_name); + } + + if (is_polymorphic) { field_count = 0; - soa_struct = alloc_type_struct(); soa_struct->Struct.fields = slice_make(permanent_allocator(), field_count+extra_field_count); soa_struct->Struct.tags = gb_alloc_array(permanent_allocator(), String, field_count+extra_field_count); - soa_struct->Struct.node = array_typ_expr; - soa_struct->Struct.soa_kind = soa_kind; - soa_struct->Struct.soa_elem = elem; soa_struct->Struct.soa_count = 0; - soa_struct->Struct.is_polymorphic = true; - scope = create_scope(ctx->info, ctx->scope); - soa_struct->Struct.scope = scope; + is_complete = true; + } else if (is_type_array(elem)) { Type *old_array = base_type(elem); field_count = cast(isize)old_array->Array.count; - soa_struct = alloc_type_struct(); soa_struct->Struct.fields = slice_make(permanent_allocator(), field_count+extra_field_count); soa_struct->Struct.tags = gb_alloc_array(permanent_allocator(), String, field_count+extra_field_count); - soa_struct->Struct.node = array_typ_expr; - soa_struct->Struct.soa_kind = soa_kind; - soa_struct->Struct.soa_elem = elem; - if (count > I32_MAX) { - count = I32_MAX; - error(array_typ_expr, "Array count too large for an #soa struct, got %lld", cast(long long)count); - } - soa_struct->Struct.soa_count = cast(i32)count; - scope = create_scope(ctx->info, ctx->scope); string_map_init(&scope->elements, 8); - soa_struct->Struct.scope = scope; String params_xyzw[4] = { str_lit("x"), @@ -2444,7 +3090,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]; @@ -2453,67 +3099,65 @@ 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; + } else { GB_ASSERT(is_type_struct(elem)); Type *old_struct = base_type(elem); - field_count = old_struct->Struct.fields.count; - soa_struct = alloc_type_struct(); - soa_struct->Struct.fields = slice_make(permanent_allocator(), field_count+extra_field_count); - soa_struct->Struct.tags = gb_alloc_array(permanent_allocator(), String, field_count+extra_field_count); - soa_struct->Struct.node = array_typ_expr; - soa_struct->Struct.soa_kind = soa_kind; - soa_struct->Struct.soa_elem = elem; - if (count > I32_MAX) { - count = I32_MAX; - error(array_typ_expr, "Array count too large for an #soa struct, got %lld", cast(long long)count); - } - soa_struct->Struct.soa_count = cast(i32)count; + if (old_struct->Struct.fields_wait_signal.futex.load()) { + field_count = old_struct->Struct.fields.count; - scope = create_scope(ctx->info, old_struct->Struct.scope->parent); - soa_struct->Struct.scope = scope; + soa_struct->Struct.fields = slice_make(permanent_allocator(), field_count+extra_field_count); + soa_struct->Struct.tags = gb_alloc_array(permanent_allocator(), String, field_count+extra_field_count); - for_array(i, old_struct->Struct.fields) { - Entity *old_field = old_struct->Struct.fields[i]; - if (old_field->kind == Entity_Variable) { - Type *field_type = nullptr; - if (soa_kind == StructSoa_Fixed) { - GB_ASSERT(count >= 0); - field_type = alloc_type_array(old_field->type, count); + for_array(i, old_struct->Struct.fields) { + Entity *old_field = old_struct->Struct.fields[i]; + if (old_field->kind == Entity_Variable) { + Type *field_type = nullptr; + if (soa_kind == StructSoa_Fixed) { + GB_ASSERT(count >= 0); + field_type = alloc_type_array(old_field->type, count); + } else { + 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 { - field_type = alloc_type_pointer(old_field->type); + soa_struct->Struct.fields[i] = old_field; } - 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); - } else { - soa_struct->Struct.fields[i] = old_field; - } - soa_struct->Struct.tags[i] = old_struct->Struct.tags[i]; + soa_struct->Struct.tags[i] = old_struct->Struct.tags[i]; + } + is_complete = true; } } - if (soa_kind != StructSoa_Fixed) { - Entity *len_field = alloc_entity_field(scope, empty_token, t_int, false, cast(i32)field_count+0); + if (is_complete && soa_kind != StructSoa_Fixed) { + Entity *len_field = alloc_entity_field(scope, make_token_ident("__$len"), t_int, false, cast(i32)field_count+0); soa_struct->Struct.fields[field_count+0] = len_field; add_entity(ctx, scope, nullptr, len_field); add_entity_use(ctx, nullptr, len_field); if (soa_kind == StructSoa_Dynamic) { - Entity *cap_field = alloc_entity_field(scope, empty_token, t_int, false, cast(i32)field_count+1); + Entity *cap_field = alloc_entity_field(scope, make_token_ident("__$cap"), t_int, false, cast(i32)field_count+1); soa_struct->Struct.fields[field_count+1] = cap_field; add_entity(ctx, scope, nullptr, cap_field); add_entity_use(ctx, nullptr, cap_field); - Token token = {}; - token.string = str_lit("allocator"); init_mem_allocator(ctx->checker); - Entity *allocator_field = alloc_entity_field(scope, token, t_allocator, false, cast(i32)field_count+2); + Entity *allocator_field = alloc_entity_field(scope, make_token_ident("allocator"), t_allocator, false, cast(i32)field_count+2); soa_struct->Struct.fields[field_count+2] = allocator_field; add_entity(ctx, scope, nullptr, allocator_field); add_entity_use(ctx, nullptr, allocator_field); @@ -2525,7 +3169,18 @@ gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_e Entity *base_type_entity = alloc_entity_type_name(scope, token, elem, EntityState_Resolved); add_entity(ctx, scope, nullptr, base_type_entity); - add_type_info_type(ctx, soa_struct); + if (is_complete) { + add_type_info_type(ctx, soa_struct); + wait_signal_set(&soa_struct->Struct.fields_wait_signal); + } else { + SoaTypeWorkerData *wd = gb_alloc_item(permanent_allocator(), SoaTypeWorkerData); + wd->ctx = *ctx; + wd->type = soa_struct; + wd->wait_to_finish = true; + + mpsc_enqueue(&ctx->checker->soa_types_to_complete, soa_struct); + thread_pool_add_task(complete_soa_type_worker, wd); + } return soa_struct; } @@ -2544,6 +3199,113 @@ gb_internal Type *make_soa_struct_dynamic_array(CheckerContext *ctx, Ast *array_ return make_soa_struct_internal(ctx, array_typ_expr, elem_expr, elem, -1, nullptr, StructSoa_Dynamic); } +gb_internal void check_array_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_type) { + ast_node(at, ArrayType, e); + if (at->count != nullptr) { + Operand o = {}; + i64 count = check_array_count(ctx, &o, at->count); + Type *generic_type = nullptr; + + Type *elem = check_type_expr(ctx, at->elem, nullptr); + + if (o.mode == Addressing_Type && o.type->kind == Type_Generic) { + generic_type = o.type; + } else if (o.mode == Addressing_Type && is_type_enum(o.type)) { + Type *index = o.type; + Type *bt = base_type(index); + GB_ASSERT(bt->kind == Type_Enum); + + Type *t = alloc_type_enumerated_array(elem, index, bt->Enum.min_value, bt->Enum.max_value, bt->Enum.fields.count, Token_Invalid); + + bool is_sparse = false; + if (at->tag != nullptr) { + GB_ASSERT(at->tag->kind == Ast_BasicDirective); + String name = at->tag->BasicDirective.name.string; + if (name == "sparse") { + is_sparse = true; + } else { + error(at->tag, "Invalid tag applied to an enumerated array, got #%.*s", LIT(name)); + } + } + + if (!is_sparse && t->EnumeratedArray.count > bt->Enum.fields.count) { + ERROR_BLOCK(); + + error(e, "Non-contiguous enumeration used as an index in an enumerated array"); + long long ea_count = cast(long long)t->EnumeratedArray.count; + long long enum_count = cast(long long)bt->Enum.fields.count; + error_line("\tenumerated array length: %lld\n", ea_count); + error_line("\tenum field count: %lld\n", enum_count); + error_line("\tSuggestion: prepend #sparse to the enumerated array to allow for non-contiguous elements\n"); + if (2*enum_count < ea_count) { + error_line("\tWarning: the number of named elements is much smaller than the length of the array, are you sure this is what you want?\n"); + error_line("\t this warning will be removed if #sparse is applied\n"); + } + } + t->EnumeratedArray.is_sparse = is_sparse; + + *type = t; + + return; + } + + if (count < 0) { + error(at->count, "? can only be used in conjuction with compound literals"); + count = 0; + } + + + if (at->tag != nullptr) { + GB_ASSERT(at->tag->kind == Ast_BasicDirective); + String name = at->tag->BasicDirective.name.string; + if (name == "soa") { + *type = make_soa_struct_fixed(ctx, e, at->elem, elem, count, generic_type); + } else if (name == "simd") { + if (!is_type_valid_vector_elem(elem) && !is_type_polymorphic(elem)) { + gbString str = type_to_string(elem); + error(at->elem, "Invalid element type for #simd, expected an integer, float, boolean, or 'rawptr' with no specific endianness, got '%s'", str); + gb_string_free(str); + *type = alloc_type_array(elem, count, generic_type); + return; + } + + if (generic_type != nullptr) { + // Ignore + } else if (count < 1 || !is_power_of_two(count)) { + error(at->count, "Invalid length for #simd, expected a power of two length, got '%lld'", cast(long long)count); + *type = alloc_type_array(elem, count, generic_type); + return; + } + + *type = alloc_type_simd_vector(count, elem, generic_type); + + if (count > SIMD_ELEMENT_COUNT_MAX) { + error(at->count, "#simd support a maximum element count of %d, got %lld", SIMD_ELEMENT_COUNT_MAX, cast(long long)count); + } + } else { + error(at->tag, "Invalid tag applied to array, got #%.*s", LIT(name)); + *type = alloc_type_array(elem, count, generic_type); + } + } else { + *type = alloc_type_array(elem, count, generic_type); + } + } else { + Type *elem = check_type(ctx, at->elem); + + if (at->tag != nullptr) { + GB_ASSERT(at->tag->kind == Ast_BasicDirective); + String name = at->tag->BasicDirective.name.string; + if (name == "soa") { + *type = make_soa_struct_slice(ctx, e, at->elem, elem); + } else { + error(at->tag, "Invalid tag applied to array, got #%.*s", LIT(name)); + *type = alloc_type_slice(elem); + } + } else { + *type = alloc_type_slice(elem); + } + } +} gb_internal bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_type) { GB_ASSERT_NOT_NULL(type); if (e == nullptr) { @@ -2625,6 +3387,9 @@ gb_internal bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, T } Type *t = alloc_type_generic(ctx->scope, 0, token.string, specific); if (ctx->allow_polymorphic_types) { + if (ctx->disallow_polymorphic_return_types) { + error(ident, "Undeclared polymorphic parameter '%.*s' in return type", LIT(token.string)); + } Scope *ps = ctx->polymorphic_scope; Scope *s = ctx->scope; Scope *entity_scope = s; @@ -2675,6 +3440,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; @@ -2699,21 +3469,31 @@ gb_internal bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, T Type *elem = t_invalid; Operand o = {}; + + if (unparen_expr(pt->type) == nullptr) { + error(e, "Invalid pointer type"); + return false; + } + check_expr_or_type(&c, &o, pt->type); if (o.mode != Addressing_Invalid && o.mode != Addressing_Type) { - // NOTE(bill): call check_type_expr again to get a consistent error message - begin_error_block(); - elem = check_type_expr(&c, pt->type, nullptr); if (o.mode == Addressing_Variable) { gbString s = expr_to_string(pt->type); - error_line("\tSuggestion: ^ is used for pointer types, did you mean '&%s'?\n", s); + 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); } - end_error_block(); } else { elem = o.type; } + if (pt->tag != nullptr) { GB_ASSERT(pt->tag->kind == Ast_BasicDirective); String name = pt->tag->BasicDirective.name.string; @@ -2743,149 +3523,14 @@ gb_internal bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, T case_end; case_ast_node(rt, RelativeType, e); - GB_ASSERT(rt->tag->kind == Ast_CallExpr); - ast_node(ce, CallExpr, rt->tag); - - Type *base_integer = nullptr; - - if (ce->args.count != 1) { - error(rt->type, "#relative expected 1 type argument, got %td", ce->args.count); - } else { - base_integer = check_type(ctx, ce->args[0]); - if (!is_type_integer(base_integer)) { - error(rt->type, "#relative base types must be an integer"); - base_integer = nullptr; - } else if (type_size_of(base_integer) > 64) { - error(rt->type, "#relative base integer types be less than or equal to 64-bits"); - base_integer = nullptr; - } - } - - Type *relative_type = nullptr; - Type *base_type = check_type(ctx, rt->type); - if (!is_type_pointer(base_type) && !is_type_multi_pointer(base_type)) { - error(rt->type, "#relative types can only be a pointer or multi-pointer"); - relative_type = base_type; - } else if (base_integer == nullptr) { - relative_type = base_type; - } else { - if (is_type_pointer(base_type)) { - relative_type = alloc_type_relative_pointer(base_type, base_integer); - } else if (is_type_multi_pointer(base_type)) { - relative_type = alloc_type_relative_multi_pointer(base_type, base_integer); - } - } - GB_ASSERT(relative_type != nullptr); - - *type = relative_type; + error(e, "#relative types have been removed from the compiler. Prefer \"core:relative\"."); + *type = t_invalid; set_base_type(named_type, *type); return true; case_end; case_ast_node(at, ArrayType, e); - if (at->count != nullptr) { - Operand o = {}; - i64 count = check_array_count(ctx, &o, at->count); - Type *generic_type = nullptr; - - Type *elem = check_type_expr(ctx, at->elem, nullptr); - - if (o.mode == Addressing_Type && o.type->kind == Type_Generic) { - generic_type = o.type; - } else if (o.mode == Addressing_Type && is_type_enum(o.type)) { - Type *index = o.type; - Type *bt = base_type(index); - GB_ASSERT(bt->kind == Type_Enum); - - Type *t = alloc_type_enumerated_array(elem, index, bt->Enum.min_value, bt->Enum.max_value, bt->Enum.fields.count, Token_Invalid); - - bool is_sparse = false; - if (at->tag != nullptr) { - GB_ASSERT(at->tag->kind == Ast_BasicDirective); - String name = at->tag->BasicDirective.name.string; - if (name == "sparse") { - is_sparse = true; - } else { - error(at->tag, "Invalid tag applied to an enumerated array, got #%.*s", LIT(name)); - } - } - - if (!is_sparse && t->EnumeratedArray.count > bt->Enum.fields.count) { - error(e, "Non-contiguous enumeration used as an index in an enumerated array"); - long long ea_count = cast(long long)t->EnumeratedArray.count; - long long enum_count = cast(long long)bt->Enum.fields.count; - error_line("\tenumerated array length: %lld\n", ea_count); - error_line("\tenum field count: %lld\n", enum_count); - error_line("\tSuggestion: prepend #sparse to the enumerated array to allow for non-contiguous elements\n"); - if (2*enum_count < ea_count) { - error_line("\tWarning: the number of named elements is much smaller than the length of the array, are you sure this is what you want?\n"); - error_line("\t this warning will be removed if #sparse is applied\n"); - } - } - t->EnumeratedArray.is_sparse = is_sparse; - - *type = t; - - goto array_end; - } - - if (count < 0) { - error(at->count, "? can only be used in conjuction with compound literals"); - count = 0; - } - - - if (at->tag != nullptr) { - GB_ASSERT(at->tag->kind == Ast_BasicDirective); - String name = at->tag->BasicDirective.name.string; - if (name == "soa") { - *type = make_soa_struct_fixed(ctx, e, at->elem, elem, count, generic_type); - } else if (name == "simd") { - if (!is_type_valid_vector_elem(elem) && !is_type_polymorphic(elem)) { - gbString str = type_to_string(elem); - error(at->elem, "Invalid element type for #simd, expected an integer, float, or boolean with no specific endianness, got '%s'", str); - gb_string_free(str); - *type = alloc_type_array(elem, count, generic_type); - goto array_end; - } - - if (generic_type != nullptr) { - // Ignore - } else if (count < 1 || !is_power_of_two(count)) { - error(at->count, "Invalid length for #simd, expected a power of two length, got '%lld'", cast(long long)count); - *type = alloc_type_array(elem, count, generic_type); - goto array_end; - } - - *type = alloc_type_simd_vector(count, elem, generic_type); - - if (count > SIMD_ELEMENT_COUNT_MAX) { - error(at->count, "#simd support a maximum element count of %d, got %lld", SIMD_ELEMENT_COUNT_MAX, cast(long long)count); - } - } else { - error(at->tag, "Invalid tag applied to array, got #%.*s", LIT(name)); - *type = alloc_type_array(elem, count, generic_type); - } - } else { - *type = alloc_type_array(elem, count, generic_type); - } - } else { - Type *elem = check_type(ctx, at->elem); - - if (at->tag != nullptr) { - GB_ASSERT(at->tag->kind == Ast_BasicDirective); - String name = at->tag->BasicDirective.name.string; - if (name == "soa") { - *type = make_soa_struct_slice(ctx, e, at->elem, elem); - } else { - error(at->tag, "Invalid tag applied to array, got #%.*s", LIT(name)); - *type = alloc_type_slice(elem); - } - } else { - *type = alloc_type_slice(elem); - } - } - array_end: + check_array_type_internal(ctx, e, type, named_type); set_base_type(named_type, *type); return true; case_end; @@ -2960,6 +3605,20 @@ gb_internal bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, T return true; case_end; + case_ast_node(bf, BitFieldType, e); + bool ips = ctx->in_polymorphic_specialization; + defer (ctx->in_polymorphic_specialization = ips); + ctx->in_polymorphic_specialization = false; + + *type = alloc_type_bit_field(); + set_base_type(named_type, *type); + check_open_scope(ctx, e); + check_bit_field_type(ctx, *type, named_type, e); + check_close_scope(ctx); + (*type)->BitField.node = e; + return true; + case_end; + case_ast_node(pt, ProcType, e); bool ips = ctx->in_polymorphic_specialization; @@ -3041,9 +3700,51 @@ gb_internal Type *check_type_expr(CheckerContext *ctx, Ast *e, Type *named_type) if (!ok) { gbString err_str = expr_to_string(e); + defer (gb_string_free(err_str)); + + begin_error_block(); error(e, "'%s' is not a type", err_str); - gb_string_free(err_str); + type = t_invalid; + + + // NOTE(bill): Check for common mistakes from C programmers + // e.g. T[] and T[N] + // e.g. *T + Ast *node = unparen_expr(e); + if (node && node->kind == Ast_IndexExpr) { + gbString index_str = nullptr; + if (node->IndexExpr.index) { + index_str = expr_to_string(node->IndexExpr.index); + } + defer (gb_string_free(index_str)); + + gbString type_str = expr_to_string(node->IndexExpr.expr); + defer (gb_string_free(type_str)); + + error_line("\tSuggestion: Did you mean '[%s]%s'?\n", index_str ? index_str : "", type_str); + end_error_block(); + + // NOTE(bill): Minimize error propagation of bad array syntax by treating this like a type + if (node->IndexExpr.expr != nullptr) { + Ast *pseudo_array_expr = ast_array_type(e->file(), ast_token(node->IndexExpr.expr), node->IndexExpr.index, node->IndexExpr.expr); + check_array_type_internal(ctx, pseudo_array_expr, &type, nullptr); + } + } else if (node && node->kind == Ast_UnaryExpr && node->UnaryExpr.op.kind == Token_Mul) { + gbString type_str = expr_to_string(node->UnaryExpr.expr); + defer (gb_string_free(type_str)); + + error_line("\tSuggestion: Did you mean '^%s'?\n", type_str); + end_error_block(); + + // NOTE(bill): Minimize error propagation of bad array syntax by treating this like a type + if (node->UnaryExpr.expr != nullptr) { + Ast *pseudo_pointer_expr = ast_pointer_type(e->file(), ast_token(node->UnaryExpr.expr), node->UnaryExpr.expr); + return check_type_expr(ctx, pseudo_pointer_expr, named_type); + } + } else { + end_error_block(); + } } if (type == nullptr) { diff --git a/src/checker.cpp b/src/checker.cpp index 29f22bd9c..5d3263789 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) { @@ -224,7 +217,7 @@ gb_internal Scope *create_scope(CheckerInfo *info, Scope *parent) { if (parent != nullptr && parent != builtin_pkg->scope) { Scope *prev_head_child = parent->head_child.exchange(s, std::memory_order_acq_rel); if (prev_head_child) { - prev_head_child->next.store(s, std::memory_order_release); + s->next.store(prev_head_child, std::memory_order_release); } } @@ -313,6 +306,7 @@ gb_internal void add_scope(CheckerContext *c, Ast *node, Scope *scope) { case Ast_StructType: node->StructType.scope = scope; break; case Ast_UnionType: node->UnionType.scope = scope; break; case Ast_EnumType: node->EnumType.scope = scope; break; + case Ast_BitFieldType: node->BitFieldType.scope = scope; break; default: GB_PANIC("Invalid node for add_scope: %.*s", LIT(ast_strings[node->kind])); } } @@ -334,6 +328,7 @@ gb_internal Scope *scope_of_node(Ast *node) { case Ast_StructType: return node->StructType.scope; case Ast_UnionType: return node->UnionType.scope; case Ast_EnumType: return node->EnumType.scope; + case Ast_BitFieldType: return node->BitFieldType.scope; } GB_PANIC("Invalid node for add_scope: %.*s", LIT(ast_strings[node->kind])); return nullptr; @@ -342,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)); @@ -355,6 +351,7 @@ gb_internal void check_open_scope(CheckerContext *c, Ast *node) { case Ast_EnumType: case Ast_UnionType: case Ast_BitSetType: + case Ast_BitFieldType: scope->flags |= ScopeFlag_Type; break; } @@ -375,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; @@ -502,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) { @@ -513,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; @@ -529,20 +533,32 @@ gb_internal u64 check_vet_flags(CheckerContext *c) { c->curr_proc_decl->proc_lit) { file = c->curr_proc_decl->proc_lit->file(); } - if (file && file->vet_flags_set) { - return file->vet_flags; - } - return build_context.vet_flags; + + return ast_file_vet_flags(file); } gb_internal u64 check_vet_flags(Ast *node) { AstFile *file = node->file(); - if (file && file->vet_flags_set) { - return file->vet_flags; - } - return build_context.vet_flags; + return ast_file_vet_flags(file); } +gb_internal u64 check_feature_flags(CheckerContext *c, Ast *node) { + AstFile *file = c->file; + if (file == nullptr && + c->curr_proc_decl && + c->curr_proc_decl->proc_lit) { + file = c->curr_proc_decl->proc_lit->file(); + } + if (file == nullptr) { + file = node->file(); + } + if (file != nullptr && file->feature_flags_set) { + return file->feature_flags; + } + return 0; +} + + enum VettedEntityKind { VettedEntity_Invalid, @@ -649,7 +665,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; @@ -668,7 +684,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; @@ -677,20 +693,48 @@ gb_internal bool check_vet_unused(Checker *c, Entity *e, VettedEntity *ve) { return false; } -gb_internal void check_scope_usage(Checker *c, Scope *scope, u64 vet_flags) { - bool vet_unused = (vet_flags & VetFlag_Unused) != 0; - bool vet_shadowing = (vet_flags & (VetFlag_Shadowing|VetFlag_Using)) != 0; - +gb_internal void check_scope_usage_internal(Checker *c, Scope *scope, u64 vet_flags, bool per_entity) { + u64 original_vet_flags = vet_flags; Array vetted_entities = {}; array_init(&vetted_entities, heap_allocator()); + defer (array_free(&vetted_entities)); rw_mutex_shared_lock(&scope->mutex); for (auto const &entry : scope->elements) { Entity *e = entry.value; if (e == nullptr) continue; + + vet_flags = original_vet_flags; + if (per_entity) { + vet_flags = ast_file_vet_flags(e->file); + } + + bool vet_unused = (vet_flags & VetFlag_Unused) != 0; + bool vet_shadowing = (vet_flags & (VetFlag_Shadowing|VetFlag_Using)) != 0; + bool vet_unused_procedures = (vet_flags & VetFlag_UnusedProcedures) != 0; + if (vet_unused_procedures && e->pkg && e->pkg->kind == Package_Runtime) { + vet_unused_procedures = false; + } + VettedEntity ve_unused = {}; VettedEntity ve_shadowed = {}; - bool is_unused = vet_unused && check_vet_unused(c, e, &ve_unused); + bool is_unused = false; + if (vet_unused && check_vet_unused(c, e, &ve_unused)) { + is_unused = true; + } else if (vet_unused_procedures && + e->kind == Entity_Procedure) { + if (e->flags&EntityFlag_Used) { + is_unused = false; + } else if (e->flags & EntityFlag_Require) { + is_unused = false; + } else if (e->pkg && e->pkg->kind == Package_Init && e->token.string == "main") { + is_unused = false; + } else { + is_unused = true; + ve_unused.kind = VettedEntity_Unused; + ve_unused.entity = e; + } + } bool is_shadowed = vet_shadowing && check_vet_shadowing(c, e, &ve_shadowed); if (is_unused && is_shadowed) { VettedEntity ve_both = ve_shadowed; @@ -700,23 +744,43 @@ gb_internal void check_scope_usage(Checker *c, Scope *scope, u64 vet_flags) { array_add(&vetted_entities, ve_unused); } else if (is_shadowed) { array_add(&vetted_entities, ve_shadowed); + } else if (e->kind == Entity_Variable && (e->flags & (EntityFlag_Param|EntityFlag_Using|EntityFlag_Static)) == 0 && !e->Variable.is_global) { + i64 sz = type_size_of(e->type); + // TODO(bill): When is a good size warn? + // Is >256 KiB good enough? + if (sz > 1ll<<18) { + gbString type_str = type_to_string(e->type); + warning(e->token, "Declaration of '%.*s' may cause a stack overflow due to its type '%s' having a size of %lld bytes", LIT(e->token.string), type_str, cast(long long)sz); + gb_string_free(type_str); + } } } rw_mutex_shared_unlock(&scope->mutex); - gb_sort(vetted_entities.data, vetted_entities.count, gb_size_of(VettedEntity), vetted_entity_variable_pos_cmp); + array_sort(vetted_entities, vetted_entity_variable_pos_cmp); for (auto const &ve : vetted_entities) { Entity *e = ve.entity; Entity *other = ve.other; String name = e->token.string; + vet_flags = original_vet_flags; + if (per_entity) { + vet_flags = ast_file_vet_flags(e->file); + } + if (ve.kind == VettedEntity_Shadowed_And_Unused) { error(e->token, "'%.*s' declared but not used, possibly shadows declaration at line %d", LIT(name), other->token.pos.line); } else if (vet_flags) { switch (ve.kind) { case VettedEntity_Unused: - if (vet_flags & VetFlag_Unused) { + if (e->kind == Entity_Variable && (vet_flags & VetFlag_UnusedVariables) != 0) { + error(e->token, "'%.*s' declared but not used", LIT(name)); + } + if (e->kind == Entity_Procedure && (vet_flags & VetFlag_UnusedProcedures) != 0) { + error(e->token, "'%.*s' declared but not used", LIT(name)); + } + if ((e->kind == Entity_ImportName || e->kind == Entity_LibraryName) && (vet_flags & VetFlag_UnusedImports) != 0) { error(e->token, "'%.*s' declared but not used", LIT(name)); } break; @@ -731,20 +795,13 @@ gb_internal void check_scope_usage(Checker *c, Scope *scope, u64 vet_flags) { break; } } - - if (e->kind == Entity_Variable && (e->flags & (EntityFlag_Param|EntityFlag_Using)) == 0) { - i64 sz = type_size_of(e->type); - // TODO(bill): When is a good size warn? - // Is 128 KiB good enough? - if (sz >= 1ll<<17) { - gbString type_str = type_to_string(e->type); - warning(e->token, "Declaration of '%.*s' may cause a stack overflow due to its type '%s' having a size of %lld bytes", LIT(name), type_str, cast(long long)sz); - gb_string_free(type_str); - } - } } - array_free(&vetted_entities); +} + + +gb_internal void check_scope_usage(Checker *c, Scope *scope, u64 vet_flags) { + check_scope_usage_internal(c, scope, vet_flags, false); for (Scope *child = scope->head_child; child != nullptr; child = child->next) { if (child->flags & (ScopeFlag_Proc|ScopeFlag_Type|ScopeFlag_File)) { @@ -770,15 +827,17 @@ gb_internal void add_type_info_dependency(CheckerInfo *info, DeclInfo *d, Type * rw_mutex_unlock(&d->type_info_deps_mutex); } -gb_internal AstPackage *get_core_package(CheckerInfo *info, String name) { + +gb_internal AstPackage *get_runtime_package(CheckerInfo *info) { + String name = str_lit("runtime"); gbAllocator a = heap_allocator(); - String path = get_fullpath_core(a, name); + String path = get_fullpath_base_collection(a, name, nullptr); defer (gb_free(a, path.text)); auto found = string_map_get(&info->packages, path); if (found == nullptr) { gb_printf_err("Name: %.*s\n", LIT(name)); gb_printf_err("Fullpath: %.*s\n", LIT(path)); - + for (auto const &entry : info->packages) { gb_printf_err("%.*s\n", LIT(entry.key)); } @@ -787,14 +846,37 @@ gb_internal AstPackage *get_core_package(CheckerInfo *info, String name) { return *found; } +gb_internal AstPackage *get_core_package(CheckerInfo *info, String name) { + if (name == "runtime") { + return get_runtime_package(info); + } -gb_internal void add_package_dependency(CheckerContext *c, char const *package_name, char const *name) { + gbAllocator a = heap_allocator(); + String path = get_fullpath_core_collection(a, name, nullptr); + defer (gb_free(a, path.text)); + auto found = string_map_get(&info->packages, path); + if (found == nullptr) { + gb_printf_err("Name: %.*s\n", LIT(name)); + gb_printf_err("Fullpath: %.*s\n", LIT(path)); + + for (auto const &entry : info->packages) { + gb_printf_err("%.*s\n", LIT(entry.key)); + } + GB_ASSERT_MSG(found != nullptr, "Missing core package %.*s", LIT(name)); + } + return *found; +} + +gb_internal void add_package_dependency(CheckerContext *c, char const *package_name, char const *name, bool required=false) { String n = make_string_c(name); AstPackage *p = get_core_package(&c->checker->info, make_string_c(package_name)); Entity *e = scope_lookup(p->scope, n); GB_ASSERT_MSG(e != nullptr, "%s", name); GB_ASSERT(c->decl != nullptr); e->flags |= EntityFlag_Used; + if (required) { + e->flags |= EntityFlag_Require; + } add_dependency(c->info, c->decl, e); } @@ -815,6 +897,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); } @@ -968,10 +1054,11 @@ gb_internal void init_universal(void) { add_global_bool_constant("true", true); add_global_bool_constant("false", false); - add_global_string_constant("ODIN_VENDOR", bc->ODIN_VENDOR); - add_global_string_constant("ODIN_VERSION", bc->ODIN_VERSION); - add_global_string_constant("ODIN_ROOT", bc->ODIN_ROOT); + add_global_string_constant("ODIN_VENDOR", bc->ODIN_VENDOR); + add_global_string_constant("ODIN_VERSION", bc->ODIN_VERSION); + add_global_string_constant("ODIN_ROOT", bc->ODIN_ROOT); add_global_string_constant("ODIN_BUILD_PROJECT_NAME", bc->ODIN_BUILD_PROJECT_NAME); + add_global_string_constant("ODIN_WINDOWS_SUBSYSTEM", bc->ODIN_WINDOWS_SUBSYSTEM); { GlobalEnumValue values[TargetOs_COUNT] = { @@ -981,9 +1068,12 @@ gb_internal void init_universal(void) { {"Linux", TargetOs_linux}, {"Essence", TargetOs_essence}, {"FreeBSD", TargetOs_freebsd}, + {"Haiku", TargetOs_haiku}, {"OpenBSD", TargetOs_openbsd}, + {"NetBSD", TargetOs_netbsd}, {"WASI", TargetOs_wasi}, {"JS", TargetOs_js}, + {"Orca", TargetOs_orca}, {"Freestanding", TargetOs_freestanding}, }; @@ -1001,17 +1091,21 @@ gb_internal void init_universal(void) { {"arm64", TargetArch_arm64}, {"wasm32", TargetArch_wasm32}, {"wasm64p32", TargetArch_wasm64p32}, + {"riscv64", TargetArch_riscv64}, }; auto fields = add_global_enum_type(str_lit("Odin_Arch_Type"), values, gb_count_of(values)); 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}, @@ -1067,24 +1161,59 @@ gb_internal void init_universal(void) { scope_insert(intrinsics_pkg->scope, t_atomic_memory_order->Named.type_name); } + { + 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)); + } - add_global_bool_constant("ODIN_DEBUG", bc->ODIN_DEBUG); - add_global_bool_constant("ODIN_DISABLE_ASSERT", bc->ODIN_DISABLE_ASSERT); - add_global_bool_constant("ODIN_DEFAULT_TO_NIL_ALLOCATOR", bc->ODIN_DEFAULT_TO_NIL_ALLOCATOR); - add_global_bool_constant("ODIN_NO_DYNAMIC_LITERALS", bc->no_dynamic_literals); - add_global_bool_constant("ODIN_NO_CRT", bc->no_crt); - add_global_bool_constant("ODIN_USE_SEPARATE_MODULES", bc->use_separate_modules); - add_global_bool_constant("ODIN_TEST", bc->command_kind == Command_test); - add_global_bool_constant("ODIN_NO_ENTRY_POINT", bc->no_entry_point); - add_global_bool_constant("ODIN_FOREIGN_ERROR_PROCEDURES", bc->ODIN_FOREIGN_ERROR_PROCEDURES); - add_global_bool_constant("ODIN_NO_RTTI", bc->no_rtti); + add_global_bool_constant("ODIN_DEBUG", bc->ODIN_DEBUG); + add_global_bool_constant("ODIN_DISABLE_ASSERT", bc->ODIN_DISABLE_ASSERT); + add_global_bool_constant("ODIN_DEFAULT_TO_NIL_ALLOCATOR", bc->ODIN_DEFAULT_TO_NIL_ALLOCATOR); + add_global_bool_constant("ODIN_NO_BOUNDS_CHECK", build_context.no_bounds_check); + add_global_bool_constant("ODIN_NO_TYPE_ASSERT", build_context.no_type_assert); + add_global_bool_constant("ODIN_DEFAULT_TO_PANIC_ALLOCATOR", bc->ODIN_DEFAULT_TO_PANIC_ALLOCATOR); + add_global_bool_constant("ODIN_NO_CRT", bc->no_crt); + add_global_bool_constant("ODIN_USE_SEPARATE_MODULES", bc->use_separate_modules); + add_global_bool_constant("ODIN_TEST", bc->command_kind == Command_test); + add_global_bool_constant("ODIN_NO_ENTRY_POINT", bc->no_entry_point); + add_global_bool_constant("ODIN_FOREIGN_ERROR_PROCEDURES", bc->ODIN_FOREIGN_ERROR_PROCEDURES); + add_global_bool_constant("ODIN_NO_RTTI", bc->no_rtti); - add_global_bool_constant("ODIN_VALGRIND_SUPPORT", bc->ODIN_VALGRIND_SUPPORT); - add_global_bool_constant("ODIN_TILDE", bc->tilde_backend); + add_global_bool_constant("ODIN_VALGRIND_SUPPORT", bc->ODIN_VALGRIND_SUPPORT); + add_global_bool_constant("ODIN_TILDE", bc->tilde_backend); add_global_constant("ODIN_COMPILE_TIMESTAMP", t_untyped_integer, exact_value_i64(odin_compile_timestamp())); - add_global_bool_constant("__ODIN_LLVM_F16_SUPPORTED", lb_use_new_pass_system()); + { + 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()) { + f16_supported = false; + } else if (build_context.metrics.os == TargetOs_darwin && build_context.metrics.arch == TargetArch_amd64) { + // NOTE(laytan): See #3222 for my ramblings on this. + f16_supported = false; + } + add_global_bool_constant("__ODIN_LLVM_F16_SUPPORTED", f16_supported); + } { GlobalEnumValue values[3] = { @@ -1112,6 +1241,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 @@ -1173,7 +1314,7 @@ gb_internal void init_universal(void) { } if (defined_values_double_declaration) { - gb_exit(1); + exit_with_errors(); } @@ -1187,9 +1328,9 @@ gb_internal void init_universal(void) { // intrinsics types for objective-c stuff { - t_objc_object = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_object"), alloc_type_struct()); - t_objc_selector = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_selector"), alloc_type_struct()); - t_objc_class = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_class"), alloc_type_struct()); + t_objc_object = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_object"), alloc_type_struct_complete()); + t_objc_selector = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_selector"), alloc_type_struct_complete()); + t_objc_class = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_class"), alloc_type_struct_complete()); t_objc_id = alloc_type_pointer(t_objc_object); t_objc_SEL = alloc_type_pointer(t_objc_selector); @@ -1220,6 +1361,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); @@ -1229,7 +1371,12 @@ 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->foreign_decls_to_check, 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); + map_init(&i->load_directory_map); } gb_internal void destroy_checker_info(CheckerInfo *i) { @@ -1245,14 +1392,19 @@ 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); + mpsc_destroy(&i->foreign_decls_to_check); map_destroy(&i->objc_msgSend_types); string_map_destroy(&i->load_file_cache); + string_map_destroy(&i->load_directory_cache); + map_destroy(&i->load_directory_map); } gb_internal CheckerContext make_checker_context(Checker *c) { @@ -1290,7 +1442,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; @@ -1326,6 +1478,7 @@ gb_internal void init_checker(Checker *c) { array_init(&c->nested_proc_lits, heap_allocator(), 0, 1<<20); mpsc_init(&c->global_untyped_queue, a); // , 1<<20); + mpsc_init(&c->soa_types_to_complete, a); // , 1<<20); c->builtin_ctx = make_checker_context(c); } @@ -1338,6 +1491,7 @@ gb_internal void destroy_checker(Checker *c) { array_free(&c->nested_proc_lits); array_free(&c->procs_to_check); mpsc_destroy(&c->global_untyped_queue); + mpsc_destroy(&c->soa_types_to_complete); } @@ -1372,6 +1526,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); @@ -1388,6 +1543,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; } @@ -1535,9 +1705,9 @@ gb_internal void add_type_and_value(CheckerContext *ctx, Ast *expr, AddressingMo if (mode == Addressing_Constant || mode == Addressing_Invalid) { expr->tav.value = value; - } else if (mode == Addressing_Value && is_type_typeid(type)) { + } else if (mode == Addressing_Value && type != nullptr && is_type_typeid(type)) { expr->tav.value = value; - } else if (mode == Addressing_Value && is_type_proc(type)) { + } else if (mode == Addressing_Value && type != nullptr && is_type_proc(type)) { expr->tav.value = value; } @@ -1637,6 +1807,26 @@ gb_internal bool add_entity_with_name(CheckerContext *c, Scope *scope, Ast *iden } return true; } + +gb_internal bool add_entity_with_name(CheckerInfo *info, Scope *scope, Ast *identifier, Entity *entity, String name) { + if (scope == nullptr) { + return false; + } + + + if (!is_blank_ident(name)) { + Entity *ie = scope_insert(scope, entity); + if (ie != nullptr) { + return redeclaration_error(name, entity, ie); + } + } + if (identifier != nullptr) { + GB_ASSERT(entity->file != nullptr); + add_entity_definition(info, identifier, entity); + } + return true; +} + gb_internal bool add_entity(CheckerContext *c, Scope *scope, Ast *identifier, Entity *entity) { return add_entity_with_name(c, scope, identifier, entity, entity->token.string); } @@ -1649,13 +1839,14 @@ gb_internal void add_entity_use(CheckerContext *c, Ast *identifier, Entity *enti entity->flags |= EntityFlag_Used; if (entity_has_deferred_procedure(entity)) { Entity *deferred = entity->Procedure.deferred_procedure.entity; - add_entity_use(c, nullptr, deferred); + if (deferred != entity) { + add_entity_use(c, nullptr, deferred); + } } if (identifier == nullptr || identifier->kind != Ast_Ident) { return; } - Ast *empty_ident = nullptr; - entity->identifier.compare_exchange_strong(empty_ident, identifier); + entity->identifier.store(identifier); identifier->Ident.entity = entity; @@ -1818,8 +2009,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; @@ -1843,7 +2033,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 @@ -1961,6 +2151,8 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) { break; case Type_Struct: + if (bt->Struct.fields_wait_signal.futex.load() == 0) + return; if (bt->Struct.scope != nullptr) { for (auto const &entry : bt->Struct.scope->elements) { Entity *e = entry.value; @@ -1981,7 +2173,9 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) { add_type_info_type_internal(c, bt->Struct.polymorphic_params); for_array(i, bt->Struct.fields) { Entity *f = bt->Struct.fields[i]; - add_type_info_type_internal(c, f->type); + if (f && f->type) { + add_type_info_type_internal(c, f->type); + } } add_comparison_procedures_for_fields(c, bt); break; @@ -2010,16 +2204,6 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) { add_type_info_type_internal(c, bt->SimdVector.elem); break; - case Type_RelativePointer: - add_type_info_type_internal(c, bt->RelativePointer.pointer_type); - add_type_info_type_internal(c, bt->RelativePointer.base_integer); - break; - - case Type_RelativeMultiPointer: - add_type_info_type_internal(c, bt->RelativeMultiPointer.pointer_type); - add_type_info_type_internal(c, bt->RelativeMultiPointer.base_integer); - break; - case Type_Matrix: add_type_info_type_internal(c, bt->Matrix.elem); break; @@ -2028,6 +2212,12 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) { add_type_info_type_internal(c, bt->SoaPointer.elem); break; + case Type_BitField: + add_type_info_type_internal(c, bt->BitField.backing_type); + for (Entity *f : bt->BitField.fields) { + add_type_info_type_internal(c, f->type); + } + break; case Type_Generic: break; @@ -2104,7 +2294,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; } @@ -2211,9 +2401,14 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) { Entity *e = entry.value; switch (bt->Struct.soa_kind) { case StructSoa_Dynamic: + add_min_dep_type_info(c, t_type_info_ptr); // append_soa + add_min_dep_type_info(c, t_allocator); /*fallthrough*/ case StructSoa_Slice: + add_min_dep_type_info(c, t_int); + add_min_dep_type_info(c, t_uint); + /*fallthrough*/ case StructSoa_Fixed: add_min_dep_type_info(c, alloc_type_pointer(e->type)); break; @@ -2254,16 +2449,6 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) { add_min_dep_type_info(c, bt->SimdVector.elem); break; - case Type_RelativePointer: - add_min_dep_type_info(c, bt->RelativePointer.pointer_type); - add_min_dep_type_info(c, bt->RelativePointer.base_integer); - break; - - case Type_RelativeMultiPointer: - add_min_dep_type_info(c, bt->RelativeMultiPointer.pointer_type); - add_min_dep_type_info(c, bt->RelativeMultiPointer.base_integer); - break; - case Type_Matrix: add_min_dep_type_info(c, bt->Matrix.elem); break; @@ -2272,6 +2457,13 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) { add_min_dep_type_info(c, bt->SoaPointer.elem); break; + case Type_BitField: + add_min_dep_type_info(c, bt->BitField.backing_type); + for (Entity *f : bt->BitField.fields) { + add_min_dep_type_info(c, f->type); + } + break; + default: GB_PANIC("Unhandled type: %*.s", LIT(type_strings[bt->kind])); break; @@ -2289,7 +2481,6 @@ gb_internal void add_dependency_to_set(Checker *c, Entity *entity) { if (entity->type != nullptr && is_type_polymorphic(entity->type)) { - DeclInfo *decl = decl_info_of_entity(entity); if (decl != nullptr && decl->gen_proc_type == nullptr) { return; @@ -2340,99 +2531,45 @@ gb_internal void force_add_dependency_entity(Checker *c, Scope *scope, String co add_dependency_to_set(c, e); } +gb_internal void collect_testing_procedures_of_package(Checker *c, AstPackage *pkg) { + AstPackage *testing_package = get_core_package(&c->info, str_lit("testing")); + Scope *testing_scope = testing_package->scope; + Entity *test_signature = scope_lookup_current(testing_scope, str_lit("Test_Signature")); + Scope *s = pkg->scope; + for (auto const &entry : s->elements) { + Entity *e = entry.value; + if (e->kind != Entity_Procedure) { + continue; + } -gb_internal void generate_minimum_dependency_set(Checker *c, Entity *start) { - isize entity_count = c->info.entities.count; - isize min_dep_set_cap = next_pow2_isize(entity_count*4); // empirically determined factor + if ((e->flags & EntityFlag_Test) == 0) { + continue; + } - ptr_set_init(&c->info.minimum_dependency_set, min_dep_set_cap); - map_init(&c->info.minimum_dependency_type_info_set); + String name = e->token.string; -#define FORCE_ADD_RUNTIME_ENTITIES(condition, ...) do { \ - if (condition) { \ - String entities[] = {__VA_ARGS__}; \ - for (isize i = 0; i < gb_count_of(entities); i++) { \ - force_add_dependency_entity(c, c->info.runtime_package->scope, entities[i]); \ - } \ - } \ -} while (0) + bool is_tester = true; - // required runtime entities - FORCE_ADD_RUNTIME_ENTITIES(true, - // Odin types - str_lit("Source_Code_Location"), - str_lit("Context"), - str_lit("Allocator"), - str_lit("Logger"), + Type *t = base_type(e->type); + GB_ASSERT(t->kind == Type_Proc); + if (are_types_identical(t, base_type(test_signature->type))) { + // Good + } else { + gbString str = type_to_string(t); + error(e->token, "Testing procedures must have a signature type of proc(^testing.T), got %s", str); + gb_string_free(str); + is_tester = false; + } - // Odin internal procedures - str_lit("__init_context"), - str_lit("cstring_to_string"), - str_lit("_cleanup_runtime"), - - // Pseudo-CRT required procedures - str_lit("memset"), - str_lit("memcpy"), - str_lit("memmove"), - - // Utility procedures - str_lit("memory_equal"), - str_lit("memory_compare"), - str_lit("memory_compare_zero"), - ); - - FORCE_ADD_RUNTIME_ENTITIES(!build_context.tilde_backend, - // Extended data type internal procedures - str_lit("umodti3"), - str_lit("udivti3"), - str_lit("modti3"), - str_lit("divti3"), - str_lit("fixdfti"), - str_lit("fixunsdfti"), - str_lit("fixunsdfdi"), - str_lit("floattidf"), - str_lit("floattidf_unsigned"), - str_lit("truncsfhf2"), - str_lit("truncdfhf2"), - str_lit("gnu_h2f_ieee"), - str_lit("gnu_f2h_ieee"), - str_lit("extendhfsf2"), - - // WASM Specific - str_lit("__ashlti3"), - str_lit("__multi3"), - ); - - FORCE_ADD_RUNTIME_ENTITIES(!build_context.no_rtti, - // Odin types - str_lit("Type_Info"), - - // Global variables - str_lit("type_table"), - str_lit("__type_info_of"), - ); - - FORCE_ADD_RUNTIME_ENTITIES(!build_context.no_entry_point, - // Global variables - str_lit("args__"), - ); - - FORCE_ADD_RUNTIME_ENTITIES((build_context.no_crt && !is_arch_wasm()), - // NOTE(bill): Only if these exist - str_lit("_tls_index"), - str_lit("_fltused"), - ); - - FORCE_ADD_RUNTIME_ENTITIES(!build_context.no_bounds_check, - // Bounds checking related procedures - str_lit("bounds_check_error"), - str_lit("matrix_bounds_check_error"), - str_lit("slice_expr_error_hi"), - str_lit("slice_expr_error_lo_hi"), - str_lit("multi_pointer_slice_expr_error"), - ); + if (is_tester) { + add_dependency_to_set(c, e); + array_add(&c->info.testing_procedures, e); + } + } +} +gb_internal void generate_minimum_dependency_set_internal(Checker *c, Entity *start) { for_array(i, c->info.definitions) { Entity *e = c->info.definitions[i]; if (e->scope == builtin_pkg->scope) { @@ -2490,6 +2627,11 @@ gb_internal void generate_minimum_dependency_set(Checker *c, Entity *start) { 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); @@ -2534,47 +2676,126 @@ gb_internal void generate_minimum_dependency_set(Checker *c, Entity *start) { } } - - Entity *test_signature = scope_lookup_current(testing_scope, str_lit("Test_Signature")); - - AstPackage *pkg = c->info.init_package; - Scope *s = pkg->scope; + collect_testing_procedures_of_package(c, pkg); - for (auto const &entry : s->elements) { - Entity *e = entry.value; - if (e->kind != Entity_Procedure) { - continue; - } - - if ((e->flags & EntityFlag_Test) == 0) { - continue; - } - - String name = e->token.string; - - bool is_tester = true; - - Type *t = base_type(e->type); - GB_ASSERT(t->kind == Type_Proc); - if (are_types_identical(t, base_type(test_signature->type))) { - // Good - } else { - gbString str = type_to_string(t); - error(e->token, "Testing procedures must have a signature type of proc(^testing.T), got %s", str); - gb_string_free(str); - is_tester = false; - } - - if (is_tester) { - add_dependency_to_set(c, e); - array_add(&c->info.testing_procedures, e); + if (build_context.test_all_packages) { + for (auto const &entry : c->info.packages) { + AstPackage *pkg = entry.value; + collect_testing_procedures_of_package(c, pkg); } } } else if (start != nullptr) { start->flags |= EntityFlag_Used; add_dependency_to_set(c, start); } +} + +gb_internal void generate_minimum_dependency_set(Checker *c, Entity *start) { + isize entity_count = c->info.entities.count; + isize min_dep_set_cap = next_pow2_isize(entity_count*4); // empirically determined factor + + ptr_set_init(&c->info.minimum_dependency_set, min_dep_set_cap); + map_init(&c->info.minimum_dependency_type_info_set); + +#define FORCE_ADD_RUNTIME_ENTITIES(condition, ...) do { \ + if (condition) { \ + String entities[] = {__VA_ARGS__}; \ + for (isize i = 0; i < gb_count_of(entities); i++) { \ + force_add_dependency_entity(c, c->info.runtime_package->scope, entities[i]); \ + } \ + } \ +} while (0) + + // required runtime entities + FORCE_ADD_RUNTIME_ENTITIES(true, + // Odin types + str_lit("Source_Code_Location"), + str_lit("Context"), + str_lit("Allocator"), + str_lit("Logger"), + + // Odin internal procedures + str_lit("__init_context"), + // str_lit("cstring_to_string"), + str_lit("_cleanup_runtime"), + + // Pseudo-CRT required procedures + str_lit("memset"), + + // Utility procedures + str_lit("memory_equal"), + str_lit("memory_compare"), + str_lit("memory_compare_zero"), + ); + + // Only required if no CRT is present + FORCE_ADD_RUNTIME_ENTITIES(build_context.no_crt, + str_lit("memcpy"), + 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"), + // str_lit("udivti3"), + // str_lit("modti3"), + // str_lit("divti3"), + // str_lit("fixdfti"), + // str_lit("fixunsdfti"), + // str_lit("fixunsdfdi"), + // str_lit("floattidf"), + // str_lit("floattidf_unsigned"), + // str_lit("truncsfhf2"), + // str_lit("truncdfhf2"), + // str_lit("gnu_h2f_ieee"), + // str_lit("gnu_f2h_ieee"), + // str_lit("extendhfsf2"), + + // WASM Specific + str_lit("__ashlti3"), + str_lit("__multi3"), + str_lit("__lshrti3"), + ); + + FORCE_ADD_RUNTIME_ENTITIES(!build_context.no_rtti, + // Odin types + str_lit("Type_Info"), + + // Global variables + str_lit("type_table"), + str_lit("__type_info_of"), + ); + + FORCE_ADD_RUNTIME_ENTITIES(!build_context.no_entry_point, + // Global variables + str_lit("args__"), + ); + + FORCE_ADD_RUNTIME_ENTITIES((build_context.no_crt && !is_arch_wasm()), + // NOTE(bill): Only if these exist + str_lit("_tls_index"), + str_lit("_fltused"), + ); + + FORCE_ADD_RUNTIME_ENTITIES(!build_context.no_bounds_check, + // Bounds checking related procedures + str_lit("bounds_check_error"), + str_lit("matrix_bounds_check_error"), + str_lit("slice_expr_error_hi"), + str_lit("slice_expr_error_lo_hi"), + str_lit("multi_pointer_slice_expr_error"), + ); + + add_dependency_to_set(c, c->info.instrumentation_enter_entity); + add_dependency_to_set(c, c->info.instrumentation_exit_entity); + + generate_minimum_dependency_set_internal(c, start); + #undef FORCE_ADD_RUNTIME_ENTITIES } @@ -2806,6 +3027,11 @@ 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); + if (type_info_entity->type == nullptr) { + check_single_global_entity(c, type_info_entity, type_info_entity->decl_info); + } + GB_ASSERT(type_info_entity->type != nullptr); t_type_info = type_info_entity->type; t_type_info_ptr = alloc_type_pointer(t_type_info); @@ -2847,10 +3073,9 @@ gb_internal void init_core_type_info(Checker *c) { t_type_info_map = find_core_type(c, str_lit("Type_Info_Map")); t_type_info_bit_set = find_core_type(c, str_lit("Type_Info_Bit_Set")); t_type_info_simd_vector = find_core_type(c, str_lit("Type_Info_Simd_Vector")); - t_type_info_relative_pointer = find_core_type(c, str_lit("Type_Info_Relative_Pointer")); - t_type_info_relative_multi_pointer = find_core_type(c, str_lit("Type_Info_Relative_Multi_Pointer")); t_type_info_matrix = find_core_type(c, str_lit("Type_Info_Matrix")); t_type_info_soa_pointer = find_core_type(c, str_lit("Type_Info_Soa_Pointer")); + t_type_info_bit_field = find_core_type(c, str_lit("Type_Info_Bit_Field")); t_type_info_named_ptr = alloc_type_pointer(t_type_info_named); t_type_info_integer_ptr = alloc_type_pointer(t_type_info_integer); @@ -2876,10 +3101,9 @@ gb_internal void init_core_type_info(Checker *c) { t_type_info_map_ptr = alloc_type_pointer(t_type_info_map); t_type_info_bit_set_ptr = alloc_type_pointer(t_type_info_bit_set); t_type_info_simd_vector_ptr = alloc_type_pointer(t_type_info_simd_vector); - t_type_info_relative_pointer_ptr = alloc_type_pointer(t_type_info_relative_pointer); - t_type_info_relative_multi_pointer_ptr = alloc_type_pointer(t_type_info_relative_multi_pointer); t_type_info_matrix_ptr = alloc_type_pointer(t_type_info_matrix); t_type_info_soa_pointer_ptr = alloc_type_pointer(t_type_info_soa_pointer); + t_type_info_bit_field_ptr = alloc_type_pointer(t_type_info_bit_field); } gb_internal void init_mem_allocator(Checker *c) { @@ -2907,6 +3131,16 @@ gb_internal void init_core_source_code_location(Checker *c) { t_source_code_location_ptr = alloc_type_pointer(t_source_code_location); } +gb_internal void init_core_load_directory_file(Checker *c) { + if (t_load_directory_file != nullptr) { + return; + } + t_load_directory_file = find_core_type(c, str_lit("Load_Directory_File")); + t_load_directory_file_ptr = alloc_type_pointer(t_load_directory_file); + t_load_directory_file_slice = alloc_type_slice(t_load_directory_file); +} + + gb_internal void init_core_map_type(Checker *c) { if (t_map_info != nullptr) { return; @@ -2971,8 +3205,8 @@ gb_internal DECL_ATTRIBUTE_PROC(foreign_block_decl_attribute) { return true; } else if (name == "link_prefix") { if (ev.kind == ExactValue_String) { - String link_prefix = ev.value_string; - if (!is_foreign_name_valid(link_prefix)) { + String link_prefix = string_trim_whitespace(ev.value_string); + if (link_prefix.len != 0 && !is_foreign_name_valid(link_prefix)) { error(elem, "Invalid link prefix: '%.*s'", LIT(link_prefix)); } else { c->foreign_context.link_prefix = link_prefix; @@ -2981,6 +3215,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 = string_trim_whitespace(ev.value_string); + if (link_suffix.len != 0 && !is_foreign_name_valid(link_suffix)) { + error(elem, "Invalid link suffix: '%.*s'", LIT(link_suffix)); + } else { + c->foreign_context.link_suffix = link_suffix; + } + } 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) { @@ -2999,6 +3245,12 @@ gb_internal DECL_ATTRIBUTE_PROC(foreign_block_decl_attribute) { } c->foreign_context.visibility_kind = kind; return true; + } else if (name == "require_results") { + if (value != nullptr) { + error(elem, "Expected no value for '%.*s'", LIT(name)); + } + c->foreign_context.require_results = true; + return true; } return false; @@ -3095,6 +3347,7 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { linkage == "link_once") { ac->linkage = linkage; } else { + ERROR_BLOCK(); error(elem, "Invalid linkage '%.*s'. Valid kinds:", LIT(linkage)); error_line("\tinternal\n"); error_line("\tstrong\n"); @@ -3267,13 +3520,25 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { if (ev.kind == ExactValue_String) { ac->link_prefix = ev.value_string; - if (!is_foreign_name_valid(ac->link_prefix)) { + if (ac->link_prefix.len != 0 && !is_foreign_name_valid(ac->link_prefix)) { error(elem, "Invalid link prefix: %.*s", LIT(ac->link_prefix)); } } else { 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 (ac->link_suffix.len != 0 && !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); @@ -3288,20 +3553,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)); @@ -3336,18 +3587,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)); @@ -3405,6 +3657,39 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { error(elem, "Expected a string value for '%.*s'", LIT(name)); } return true; + } else if (name == "entry_point_only") { + if (value != nullptr) { + error(value, "'%.*s' expects no parameter", LIT(name)); + } + ac->entry_point_only = true; + return true; + } else if (name == "no_instrumentation") { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind == ExactValue_Invalid) { + ac->no_instrumentation = Instrumentation_Disabled; + } else if (ev.kind == ExactValue_Bool) { + if (ev.value_bool) { + ac->no_instrumentation = Instrumentation_Disabled; + } else { + ac->no_instrumentation = Instrumentation_Enabled; + } + } else { + error(value, "Expected either a boolean or no parameter for '%.*s'", LIT(name)); + return false; + } + return true; + } else if (name == "instrumentation_enter") { + if (value != nullptr) { + error(value, "'%.*s' expects no parameter", LIT(name)); + } + ac->instrumentation_enter = true; + return true; + } else if (name == "instrumentation_exit") { + if (value != nullptr) { + error(value, "'%.*s' expects no parameter", LIT(name)); + } + ac->instrumentation_exit = true; + return true; } return false; } @@ -3422,6 +3707,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) { @@ -3440,6 +3731,7 @@ gb_internal DECL_ATTRIBUTE_PROC(var_decl_attribute) { model == "localexec") { ac->thread_local_model = model; } else { + ERROR_BLOCK(); error(elem, "Invalid thread local model '%.*s'. Valid models:", LIT(model)); error_line("\tdefault\n"); error_line("\tlocaldynamic\n"); @@ -3490,6 +3782,7 @@ gb_internal DECL_ATTRIBUTE_PROC(var_decl_attribute) { linkage == "link_once") { ac->linkage = linkage; } else { + ERROR_BLOCK(); error(elem, "Invalid linkage '%.*s'. Valid kinds:", LIT(linkage)); error_line("\tinternal\n"); error_line("\tstrong\n"); @@ -3512,13 +3805,24 @@ gb_internal DECL_ATTRIBUTE_PROC(var_decl_attribute) { ExactValue ev = check_decl_attribute_value(c, value); if (ev.kind == ExactValue_String) { ac->link_prefix = ev.value_string; - if (!is_foreign_name_valid(ac->link_prefix)) { + if (ac->link_prefix.len != 0 && !is_foreign_name_valid(ac->link_prefix)) { error(elem, "Invalid link prefix: %.*s", LIT(ac->link_prefix)); } } else { 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 (ac->link_suffix.len != 0 && !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) { @@ -3544,6 +3848,16 @@ gb_internal DECL_ATTRIBUTE_PROC(const_decl_attribute) { } else if (name == "private") { // NOTE(bill): Handled elsewhere `check_collect_value_decl` return true; + } else if (name == "static" || + name == "thread_local" || + name == "require" || + 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; } return false; } @@ -3583,8 +3897,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 = {}; @@ -3643,9 +3959,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)); } } } @@ -3658,6 +3976,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; + } + } } } @@ -3713,6 +4037,8 @@ gb_internal bool check_arity_match(CheckerContext *c, AstValueDecl *vd, bool is_ gb_string_free(str); return false; } else if (is_global) { + ERROR_BLOCK(); + Ast *n = vd->values[rhs-1]; error(n, "Expected %td expressions on the right hand side, got %td", lhs, rhs); error_line("Note: Global declarations do not allow for multi-valued expressions"); @@ -3764,6 +4090,7 @@ gb_internal void check_builtin_attributes(CheckerContext *ctx, Entity *e, Array< case Entity_ProcGroup: case Entity_Procedure: case Entity_TypeName: + case Entity_Constant: // Okay break; default: @@ -3832,6 +4159,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]; @@ -3876,6 +4204,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; } @@ -3897,6 +4227,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) { @@ -3931,6 +4266,7 @@ gb_internal void check_collect_value_decl(CheckerContext *c, Ast *decl) { Entity *e = alloc_entity_variable(c->scope, name->Ident.token, nullptr); e->identifier = name; e->file = c->file; + e->Variable.is_global = true; if (entity_visibility_kind != EntityVisiblity_Public) { e->flags |= EntityFlag_NotExported; @@ -3948,6 +4284,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; @@ -4002,6 +4339,7 @@ gb_internal void check_collect_value_decl(CheckerContext *c, Ast *decl) { } ast_node(pl, ProcLit, init); e = alloc_entity_procedure(d->scope, token, nullptr, pl->tags); + d->foreign_require_results = c->foreign_context.require_results; if (fl != nullptr) { GB_ASSERT(fl->kind == Ast_Ident); e->Procedure.foreign_library_ident = fl; @@ -4014,15 +4352,15 @@ gb_internal void check_collect_value_decl(CheckerContext *c, Ast *decl) { if (c->foreign_context.default_cc > 0) { cc = c->foreign_context.default_cc; } else if (is_arch_wasm()) { - begin_error_block(); + ERROR_BLOCK(); error(init, "For wasm related targets, it is required that you either define the" " @(default_calling_convention=) on the foreign block or" " explicitly assign it on the procedure signature"); error_line("\tSuggestion: when dealing with normal Odin code (e.g. js_wasm32), use \"contextless\"; when dealing with Emscripten like code, use \"c\"\n"); - end_error_block(); } } 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; @@ -4066,8 +4404,7 @@ gb_internal void check_collect_value_decl(CheckerContext *c, Ast *decl) { if (e->kind != Entity_Procedure) { if (fl != nullptr) { - begin_error_block(); - defer (end_error_block()); + ERROR_BLOCK(); AstKind kind = init->kind; error(name, "Only procedures and variables are allowed to be in a foreign block, got %.*s", LIT(ast_strings[kind])); @@ -4128,17 +4465,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; } @@ -4221,7 +4563,9 @@ gb_internal void check_collect_entities(CheckerContext *c, Slice const &n case_end; case_ast_node(fb, ForeignBlockDecl, decl); - check_add_foreign_block_decl(c, decl); + if (curr_file != nullptr) { + array_add(&curr_file->delayed_decls_queues[AstDelayQueue_ForeignBlock], decl); + } case_end; default: @@ -4237,6 +4581,14 @@ gb_internal void check_collect_entities(CheckerContext *c, Slice const &n // NOTE(bill): 'when' stmts need to be handled after the other as the condition may refer to something // declared after this stmt in source if (curr_file == nullptr) { + // For 'foreign' block statements that are not in file scope. + for_array(decl_index, nodes) { + Ast *decl = nodes[decl_index]; + if (decl->kind == Ast_ForeignBlockDecl) { + check_add_foreign_block_decl(c, decl); + } + } + for_array(decl_index, nodes) { Ast *decl = nodes[decl_index]; if (decl->kind == Ast_WhenStmt) { @@ -4286,6 +4638,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) { @@ -4297,10 +4651,16 @@ gb_internal void check_all_global_entities(Checker *c) { DeclInfo *d = e->decl_info; check_single_global_entity(c, e, d); if (e->type != nullptr && is_type_typed(e->type)) { + for (Type *t = nullptr; mpsc_dequeue(&c->soa_types_to_complete, &t); /**/) { + complete_soa_type(c, t, false); + } + (void)type_size_of(e->type); (void)type_align_of(e->type); } } + + in_single_threaded_checker_stage = false; } @@ -4387,7 +4747,7 @@ gb_internal void add_import_dependency_node(Checker *c, Ast *decl, PtrMapscope != nullptr); @@ -4508,10 +4868,10 @@ gb_internal Array find_import_path(Checker *c, AstPackage *start continue; } - if (pkg->kind == Package_Runtime) { - // NOTE(bill): Allow cyclic imports within the runtime package for the time being - continue; - } + // if (pkg->kind == Package_Runtime) { + // // NOTE(bill): Allow cyclic imports within the runtime package for the time being + // continue; + // } ImportPathItem item = {pkg, decl}; if (pkg == end) { @@ -4615,12 +4975,18 @@ gb_internal void check_add_import_decl(CheckerContext *ctx, Ast *decl) { } - if (import_name.len == 0) { + if (is_blank_ident(import_name) && !is_blank_ident(id->import_name.string)) { String invalid_name = id->fullpath; invalid_name = get_invalid_import_name(invalid_name); - error(id->token, "Import name %.*s, is not a valid identifier. Perhaps you want to reference the package by a different name like this: import \"%.*s\" ", LIT(invalid_name), LIT(invalid_name)); - error(token, "Import name, %.*s, cannot be use as an import name as it is not a valid identifier", LIT(id->import_name.string)); + ERROR_BLOCK(); + + if (id->import_name.string.len > 0) { + error(token, "Import name '%.*s' cannot be use as an import name as it is not a valid identifier", LIT(id->import_name.string)); + } else { + error(id->token, "Import name '%.*s' is not a valid identifier", LIT(invalid_name)); + error_line("\tSuggestion: Rename the directory or explicitly set an import name like this 'import %.*s'", LIT(id->relpath.string)); + } } else { GB_ASSERT(id->import_name.pos.line != 0); id->import_name.string = import_name; @@ -4672,6 +5038,114 @@ 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; + } + + for (Entity *e = nullptr; mpsc_dequeue(&c->info.foreign_decls_to_check, &e); /**/) { + GB_ASSERT(e != nullptr); + if (e->kind != Entity_Procedure) { + continue; + } + if (!is_arch_wasm()) { + continue; + } + Entity *foreign_library = e->Procedure.foreign_library; + GB_ASSERT(foreign_library != nullptr); + + String name = e->Procedure.link_name; + + String module_name = str_lit("env"); + GB_ASSERT (foreign_library->kind == Entity_LibraryName); + if (foreign_library->LibraryName.paths.count != 1) { + error(foreign_library->token, "'foreign import' for '%.*s' architecture may only have one path, got %td", + LIT(target_arch_names[build_context.metrics.arch]), foreign_library->LibraryName.paths.count); + } + + if (foreign_library->LibraryName.paths.count >= 1) { + module_name = foreign_library->LibraryName.paths[0]; + } + + if (!string_ends_with(module_name, str_lit(".o"))) { + name = concatenate3_strings(permanent_allocator(), module_name, WASM_MODULE_NAME_SEPARATOR, name); + } + e->Procedure.link_name = name; + + check_foreign_procedure(&ctx, e, e->decl_info); + } +} + gb_internal void check_add_foreign_import_decl(CheckerContext *ctx, Ast *decl) { if (decl->state_flags & StateFlag_BeenHandled) return; decl->state_flags |= StateFlag_BeenHandled; @@ -4681,43 +5155,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; } - // 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) { @@ -4732,12 +5189,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) { - 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 @@ -4844,9 +5297,9 @@ gb_internal bool collect_file_decl(CheckerContext *ctx, Ast *decl) { case_end; case_ast_node(fb, ForeignBlockDecl, decl); - if (check_add_foreign_block_decl(ctx, decl)) { - return true; - } + GB_ASSERT(ctx->collect_delayed_decls); + decl->state_flags |= StateFlag_BeenHandled; + array_add(&curr_file->delayed_decls_queues[AstDelayQueue_ForeignBlock], decl); case_end; case_ast_node(ws, WhenStmt, decl); @@ -4911,7 +5364,7 @@ gb_internal void check_create_file_scopes(Checker *c) { for_array(i, c->parser->packages) { AstPackage *pkg = c->parser->packages[i]; - gb_sort_array(pkg->files.data, pkg->files.count, sort_file_by_name); + array_sort(pkg->files, sort_file_by_name); isize total_pkg_decl_count = 0; for_array(j, pkg->files) { @@ -5129,11 +5582,20 @@ gb_internal void check_import_entities(Checker *c) { for_array(i, pkg->files) { AstFile *f = pkg->files[i]; reset_checker_context(&ctx, f, &untyped); - ctx.collect_delayed_decls = false; - correct_type_aliases_in_scope(&ctx, pkg->scope); } + for_array(i, pkg->files) { + AstFile *f = pkg->files[i]; + reset_checker_context(&ctx, f, &untyped); + + ctx.collect_delayed_decls = true; + for (Ast *decl : f->delayed_decls_queues[AstDelayQueue_ForeignBlock]) { + check_add_foreign_block_decl(&ctx, decl); + } + array_clear(&f->delayed_decls_queues[AstDelayQueue_ForeignBlock]); + } + for_array(i, pkg->files) { AstFile *f = pkg->files[i]; reset_checker_context(&ctx, f, &untyped); @@ -5304,6 +5766,59 @@ gb_internal void calculate_global_init_order(Checker *c) { } } +gb_internal void check_procedure_later_from_entity(Checker *c, Entity *e, char const *from_msg) { + if (e == nullptr || e->kind != Entity_Procedure) { + return; + } + if (e->Procedure.is_foreign) { + return; + } + if ((e->flags & EntityFlag_ProcBodyChecked) != 0) { + return; + } + if ((e->flags & EntityFlag_Overridden) != 0) { + // NOTE (zen3ger) Delay checking of a proc alias until the underlying proc is checked. + GB_ASSERT(e->aliased_of != nullptr); + GB_ASSERT(e->aliased_of->kind == Entity_Procedure); + if ((e->aliased_of->flags & EntityFlag_ProcBodyChecked) != 0) { + e->flags |= EntityFlag_ProcBodyChecked; + return; + } + // NOTE (zen3ger) A proc alias *does not* have a body and tags! + check_procedure_later(c, e->file, e->token, e->decl_info, e->type, nullptr, 0); + return; + } + Type *type = base_type(e->type); + if (type == t_invalid) { + return; + } + GB_ASSERT_MSG(type->kind == Type_Proc, "%s", type_to_string(e->type)); + + if (is_type_polymorphic(type) && !type->Proc.is_poly_specialized) { + return; + } + + GB_ASSERT(e->decl_info != nullptr); + + ProcInfo *pi = gb_alloc_item(permanent_allocator(), ProcInfo); + pi->file = e->file; + pi->token = e->token; + pi->decl = e->decl_info; + pi->type = e->type; + + Ast *pl = e->decl_info->proc_lit; + GB_ASSERT(pl != nullptr); + pi->body = pl->ProcLit.body; + pi->tags = pl->ProcLit.tags; + if (pi->body == nullptr) { + return; + } + if (from_msg != nullptr) { + debugf("CHECK PROCEDURE LATER [FROM %s]! %.*s :: %s {...}\n", from_msg, LIT(e->token.string), type_to_string(e->type)); + } + check_procedure_later(c, pi); +} + gb_internal bool check_proc_info(Checker *c, ProcInfo *pi, UntypedExprInfoMap *untyped) { if (pi == nullptr) { @@ -5410,6 +5925,15 @@ gb_internal bool check_proc_info(Checker *c, ProcInfo *pi, UntypedExprInfoMap *u add_untyped_expressions(&c->info, ctx.untyped); + rw_mutex_shared_lock(&ctx.decl->deps_mutex); + for (Entity *dep : ctx.decl->deps) { + if (dep && dep->kind == Entity_Procedure && + (dep->flags & EntityFlag_ProcBodyChecked) == 0) { + check_procedure_later_from_entity(c, dep, NULL); + } + } + rw_mutex_shared_unlock(&ctx.decl->deps_mutex); + return true; } @@ -5432,30 +5956,7 @@ gb_internal void check_unchecked_bodies(Checker *c) { global_procedure_body_in_worker_queue = false; for (Entity *e : c->info.minimum_dependency_set) { - if (e == nullptr || e->kind != Entity_Procedure) { - continue; - } - if (e->Procedure.is_foreign) { - continue; - } - if ((e->flags & EntityFlag_ProcBodyChecked) == 0) { - GB_ASSERT(e->decl_info != nullptr); - - ProcInfo *pi = gb_alloc_item(permanent_allocator(), ProcInfo); - pi->file = e->file; - pi->token = e->token; - pi->decl = e->decl_info; - pi->type = e->type; - - Ast *pl = e->decl_info->proc_lit; - GB_ASSERT(pl != nullptr); - pi->body = pl->ProcLit.body; - pi->tags = pl->ProcLit.tags; - if (pi->body == nullptr) { - continue; - } - check_procedure_later(c, pi); - } + check_procedure_later_from_entity(c, e, "check_unchecked_bodies"); } if (!global_procedure_body_in_worker_queue) { @@ -5497,35 +5998,27 @@ gb_internal void check_safety_all_procedures_for_unchecked(Checker *c) { } } -gb_internal void check_test_procedures(Checker *c) { - if (build_context.test_names.entries.count == 0) { - return; - } +gb_internal GB_COMPARE_PROC(init_procedures_cmp); +gb_internal GB_COMPARE_PROC(fini_procedures_cmp); - AstPackage *pkg = c->info.init_package; - Scope *s = pkg->scope; +gb_internal void remove_neighbouring_duplicate_entires_from_sorted_array(Array *array) { + Entity *prev = nullptr; - 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); + for (isize i = 0; i < array->count; /**/) { + Entity *curr = array->data[i]; + if (prev == curr) { + array_ordered_remove(array, i); } else { + prev = curr; i += 1; } } +} + +gb_internal void check_test_procedures(Checker *c) { + array_sort(c->info.testing_procedures, init_procedures_cmp); + remove_neighbouring_duplicate_entires_from_sorted_array(&c->info.testing_procedures); } @@ -5690,6 +6183,11 @@ gb_internal void check_deferred_procedures(Checker *c) { case DeferredProcedure_in_out_by_ptr: attribute = "deferred_in_out_by_ptr"; break; } + if (src == dst) { + error(src->token, "'%.*s' cannot be used as its own %s", LIT(dst->token.string), attribute); + continue; + } + if (is_type_polymorphic(src->type) || is_type_polymorphic(dst->type)) { error(src->token, "'%s' cannot be used with a polymorphic procedure", attribute); continue; @@ -5737,7 +6235,7 @@ gb_internal void check_deferred_procedures(Checker *c) { } if ((src_params == nullptr && dst_params != nullptr) || (src_params != nullptr && dst_params == nullptr)) { - error(src->token, "Deferred procedure '%.*s' parameters do not match the inputs of initial procedure '%.*s'", LIT(src->token.string), LIT(dst->token.string)); + error(src->token, "Deferred procedure '%.*s' parameters do not match the inputs of initial procedure '%.*s'", LIT(dst->token.string), LIT(src->token.string)); continue; } @@ -5750,8 +6248,8 @@ gb_internal void check_deferred_procedures(Checker *c) { gbString s = type_to_string(src_params); gbString d = type_to_string(dst_params); error(src->token, "Deferred procedure '%.*s' parameters do not match the inputs of initial procedure '%.*s':\n\t(%s) =/= (%s)", - LIT(src->token.string), LIT(dst->token.string), - s, d + LIT(dst->token.string), LIT(src->token.string), + d, s ); gb_string_free(d); gb_string_free(s); @@ -5767,7 +6265,7 @@ gb_internal void check_deferred_procedures(Checker *c) { } if ((src_results == nullptr && dst_params != nullptr) || (src_results != nullptr && dst_params == nullptr)) { - error(src->token, "Deferred procedure '%.*s' parameters do not match the results of initial procedure '%.*s'", LIT(src->token.string), LIT(dst->token.string)); + error(src->token, "Deferred procedure '%.*s' parameters do not match the results of initial procedure '%.*s'", LIT(dst->token.string), LIT(src->token.string)); continue; } @@ -5780,8 +6278,8 @@ gb_internal void check_deferred_procedures(Checker *c) { gbString s = type_to_string(src_results); gbString d = type_to_string(dst_params); error(src->token, "Deferred procedure '%.*s' parameters do not match the results of initial procedure '%.*s':\n\t(%s) =/= (%s)", - LIT(src->token.string), LIT(dst->token.string), - s, d + LIT(dst->token.string), LIT(src->token.string), + d, s ); gb_string_free(d); gb_string_free(s); @@ -5833,8 +6331,8 @@ gb_internal void check_deferred_procedures(Checker *c) { gbString s = type_to_string(tsrc); gbString d = type_to_string(dst_params); error(src->token, "Deferred procedure '%.*s' parameters do not match the results of initial procedure '%.*s':\n\t(%s) =/= (%s)", - LIT(src->token.string), LIT(dst->token.string), - s, d + LIT(dst->token.string), LIT(src->token.string), + d, s ); gb_string_free(d); gb_string_free(s); @@ -5873,11 +6371,14 @@ gb_internal void check_unique_package_names(Checker *c) { continue; } + + begin_error_block(); error(curr, "Duplicate declaration of 'package %.*s'", LIT(name)); error_line("\tA package name must be unique\n" "\tThere is no relation between a package name and the directory that contains it, so they can be completely different\n" "\tA package name is required for link name prefixing to have a consistent ABI\n"); - error(prev, "found at previous location"); + error_line("%s found at previous location\n", token_pos_to_string(ast_token(prev).pos)); + end_error_block(); } } @@ -5898,6 +6399,9 @@ gb_internal void check_add_definitions_from_queues(Checker *c) { } gb_internal void check_merge_queues_into_arrays(Checker *c) { + for (Type *t = nullptr; mpsc_dequeue(&c->soa_types_to_complete, &t); /**/) { + complete_soa_type(c, t, false); + } check_add_entities_from_queues(c); check_add_definitions_from_queues(c); } @@ -5943,10 +6447,14 @@ gb_internal GB_COMPARE_PROC(fini_procedures_cmp) { return init_procedures_cmp(b, a); } - gb_internal void check_sort_init_and_fini_procedures(Checker *c) { - gb_sort_array(c->info.init_procedures.data, c->info.init_procedures.count, init_procedures_cmp); - gb_sort_array(c->info.fini_procedures.data, c->info.fini_procedures.count, fini_procedures_cmp); + array_sort(c->info.init_procedures, init_procedures_cmp); + array_sort(c->info.fini_procedures, fini_procedures_cmp); + + // NOTE(bill): remove possible duplicates from the init/fini lists + // NOTE(bill): because the arrays are sorted, you only need to check the previous element + remove_neighbouring_duplicate_entires_from_sorted_array(&c->info.init_procedures); + remove_neighbouring_duplicate_entires_from_sorted_array(&c->info.fini_procedures); } gb_internal void add_type_info_for_type_definitions(Checker *c) { @@ -6041,18 +6549,22 @@ 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); TIME_SECTION("check scope usage"); for (auto const &entry : c->info.files) { AstFile *f = entry.value; - u64 vet_flags = build_context.vet_flags; - if (f->vet_flags_set) { - vet_flags = f->vet_flags; - } + u64 vet_flags = ast_file_vet_flags(f); check_scope_usage(c, f->scope, vet_flags); } + for (auto const &entry : c->info.packages) { + AstPackage *pkg = entry.value; + check_scope_usage_internal(c, pkg->scope, 0, true); + } TIME_SECTION("add basic type information"); // Add "Basic" type information @@ -6091,9 +6603,6 @@ gb_internal void check_parsed_files(Checker *c) { TIME_SECTION("calculate global init order"); calculate_global_init_order(c); - TIME_SECTION("check test procedures"); - check_test_procedures(c); - TIME_SECTION("add type info for type definitions"); add_type_info_for_type_definitions(c); check_merge_queues_into_arrays(c); @@ -6107,8 +6616,21 @@ gb_internal void check_parsed_files(Checker *c) { TIME_SECTION("check bodies have all been checked"); check_unchecked_bodies(c); - check_merge_queues_into_arrays(c); + TIME_SECTION("check #soa types"); + check_merge_queues_into_arrays(c); + thread_pool_wait(); + + TIME_SECTION("update minimum dependency set"); + generate_minimum_dependency_set_internal(c, c->info.entry_point); + + // NOTE(laytan): has to be ran after generate_minimum_dependency_set, + // because that collects the test procedures. + TIME_SECTION("check test procedures"); + check_test_procedures(c); + + check_merge_queues_into_arrays(c); + thread_pool_wait(); TIME_SECTION("check entry point"); if (build_context.build_mode == BuildMode_Executable && !build_context.no_entry_point && build_context.command_kind != Command_test) { @@ -6130,6 +6652,8 @@ gb_internal void check_parsed_files(Checker *c) { error(token, "Undefined entry point procedure 'main'"); } + } else if (build_context.build_mode == BuildMode_DynamicLibrary && build_context.no_entry_point) { + c->info.entry_point = nullptr; } thread_pool_wait(); @@ -6150,6 +6674,17 @@ gb_internal void check_parsed_files(Checker *c) { GB_ASSERT(c->info.entity_queue.count.load(std::memory_order_relaxed) == 0); GB_ASSERT(c->info.definition_queue.count.load(std::memory_order_relaxed) == 0); + TIME_SECTION("check instrumentation calls"); + { + if ((c->info.instrumentation_enter_entity != nullptr) ^ + (c->info.instrumentation_exit_entity != nullptr)) { + Entity *e = c->info.instrumentation_enter_entity; + if (!e) e = c->info.instrumentation_exit_entity; + error(e->token, "Both @(instrumentation_enter) and @(instrumentation_exit) must be defined"); + } + } + + TIME_SECTION("add untyped expression values"); // Add untyped expression values for (UntypedExprInfo u = {}; mpsc_dequeue(&c->global_untyped_queue, &u); /**/) { diff --git a/src/checker.hpp b/src/checker.hpp index a6a5f6788..3951fcefe 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 { @@ -103,9 +109,16 @@ struct DeferredProcedure { }; +enum InstrumentationFlag : i32 { + Instrumentation_Enabled = -1, + Instrumentation_Default = 0, + Instrumentation_Disabled = +1, +}; + struct AttributeContext { String link_name; String link_prefix; + String link_suffix; String link_section; String linkage; isize init_expr_list_count; @@ -113,19 +126,24 @@ struct AttributeContext { String deprecated_message; String warning_message; DeferredProcedure deferred_procedure; - bool is_export : 1; - bool is_static : 1; - bool require_results : 1; - bool require_declaration : 1; - bool has_disabled_proc : 1; - bool disabled_proc : 1; - bool test : 1; - bool init : 1; - bool fini : 1; - bool set_cold : 1; + bool is_export : 1; + bool is_static : 1; + bool require_results : 1; + bool require_declaration : 1; + bool has_disabled_proc : 1; + bool disabled_proc : 1; + bool test : 1; + bool init : 1; + bool fini : 1; + bool set_cold : 1; + 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; + InstrumentationFlag no_instrumentation; String objc_class; String objc_name; @@ -136,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; } @@ -162,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 @@ -180,9 +204,12 @@ struct DeclInfo { Array attributes; Ast * proc_lit; // Ast_ProcLit Type * gen_proc_type; // Precalculated + bool is_using; bool where_clauses_evaluated; + bool foreign_require_results; std::atomic proc_checked_state; + BlockingMutex proc_checked_mutex; isize defer_used; bool defer_use_checked; @@ -200,6 +227,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; }; @@ -292,7 +323,9 @@ struct ForeignContext { Ast * curr_library; ProcCallingConvention default_cc; String link_prefix; + String link_suffix; EntityVisiblityKind visibility_kind; + bool require_results; }; typedef Array CheckerTypePath; @@ -323,13 +356,35 @@ 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; StringMap hashes; }; + +struct LoadDirectoryFile { + String file_name; + String data; +}; + +struct LoadDirectoryCache { + String path; + gbFileError file_error; + Array files; +}; + + struct GenProcsData { Array procs; RwMutex mutex; @@ -337,7 +392,18 @@ struct GenProcsData { struct GenTypesData { Array types; - RwMutex mutex; + 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 @@ -366,6 +432,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; @@ -377,8 +446,8 @@ struct CheckerInfo { RecursiveMutex lazy_mutex; // Mutex required for lazy type checking of specific files - RwMutex gen_types_mutex; - PtrMap gen_types; + BlockingMutex gen_types_mutex; + PtrMap gen_types; BlockingMutex type_info_mutex; // NOT recursive Array type_info_types; @@ -391,6 +460,8 @@ struct CheckerInfo { MPSCQueue entity_queue; MPSCQueue required_global_variable_queue; MPSCQueue required_foreign_imports_through_force_queue; + MPSCQueue foreign_imports_to_check_fullpaths; + MPSCQueue foreign_decls_to_check; MPSCQueue intrinsics_entry_point_usage; @@ -402,6 +473,17 @@ struct CheckerInfo { BlockingMutex all_procedures_mutex; Array all_procedures; + + BlockingMutex instrumentation_mutex; + Entity *instrumentation_enter_entity; + Entity *instrumentation_exit_entity; + + + BlockingMutex load_directory_mutex; + StringMap load_directory_cache; + PtrMap load_directory_map; // Key: Ast_CallExpr * + + }; struct CheckerContext { @@ -419,6 +501,7 @@ struct CheckerContext { u32 state_flags; bool in_defer; Type * type_hint; + Ast * type_hint_expr; String proc_name; DeclInfo * curr_proc_decl; @@ -439,10 +522,12 @@ struct CheckerContext { bool in_enum_type; bool collect_delayed_decls; bool allow_polymorphic_types; + bool disallow_polymorphic_return_types; // NOTE(zen3ger): no poly type decl in return types bool no_polymorphic_errors; bool hide_polymorphic_errors; bool in_polymorphic_specialization; bool allow_arrow_right_selector_expr; + u8 bit_field_bit_size; Scope * polymorphic_scope; Ast *assignment_lhs_hint; @@ -466,6 +551,7 @@ struct Checker { MPSCQueue global_untyped_queue; + MPSCQueue soa_types_to_complete; }; @@ -526,3 +612,9 @@ gb_internal void init_core_context(Checker *c); gb_internal void init_mem_allocator(Checker *c); gb_internal void add_untyped_expressions(CheckerInfo *cinfo, UntypedExprInfoMap *untyped); + + +gb_internal GenTypesData *ensure_polymorphic_record_entity_has_gen_types(CheckerContext *ctx, Type *original_type); + + +gb_internal void init_map_internal_types(Type *type); \ No newline at end of file diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index c89ab2429..2dfd570e4 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -34,11 +34,6 @@ enum BuiltinProcId { BuiltinProc_soa_zip, BuiltinProc_soa_unzip, - - BuiltinProc_transpose, - BuiltinProc_outer_product, - BuiltinProc_hadamard_product, - BuiltinProc_matrix_flatten, BuiltinProc_unreachable, @@ -48,6 +43,15 @@ enum BuiltinProcId { // "Intrinsics" BuiltinProc_is_package_imported, + + BuiltinProc_has_target_feature, + + BuiltinProc_constant_log2, + + BuiltinProc_transpose, + BuiltinProc_outer_product, + BuiltinProc_hadamard_product, + BuiltinProc_matrix_flatten, BuiltinProc_soa_struct, @@ -68,6 +72,9 @@ enum BuiltinProcId { BuiltinProc_overflow_sub, BuiltinProc_overflow_mul, + BuiltinProc_saturating_add, + BuiltinProc_saturating_sub, + BuiltinProc_sqrt, BuiltinProc_fused_mul_add, @@ -92,6 +99,7 @@ enum BuiltinProcId { BuiltinProc_prefetch_write_instruction, BuiltinProc_prefetch_write_data, +BuiltinProc__atomic_begin, BuiltinProc_atomic_type_is_lock_free, BuiltinProc_atomic_thread_fence, BuiltinProc_atomic_signal_fence, @@ -117,6 +125,7 @@ enum BuiltinProcId { BuiltinProc_atomic_compare_exchange_strong_explicit, BuiltinProc_atomic_compare_exchange_weak, BuiltinProc_atomic_compare_exchange_weak_explicit, +BuiltinProc__atomic_end, BuiltinProc_fixed_point_mul, BuiltinProc_fixed_point_div, @@ -136,8 +145,8 @@ BuiltinProc__simd_begin, BuiltinProc_simd_shl_masked, // C logic BuiltinProc_simd_shr_masked, // C logic - BuiltinProc_simd_add_sat, // saturation arithmetic - BuiltinProc_simd_sub_sat, // saturation arithmetic + BuiltinProc_simd_saturating_add, // saturation arithmetic + BuiltinProc_simd_saturating_sub, // saturation arithmetic BuiltinProc_simd_bit_and, BuiltinProc_simd_bit_or, @@ -169,6 +178,9 @@ BuiltinProc__simd_begin, BuiltinProc_simd_reduce_or, BuiltinProc_simd_reduce_xor, + BuiltinProc_simd_reduce_any, + BuiltinProc_simd_reduce_all, + BuiltinProc_simd_shuffle, BuiltinProc_simd_select, @@ -183,6 +195,12 @@ BuiltinProc__simd_begin, BuiltinProc_simd_lanes_rotate_left, BuiltinProc_simd_lanes_rotate_right, + BuiltinProc_simd_gather, + BuiltinProc_simd_scatter, + BuiltinProc_simd_masked_load, + BuiltinProc_simd_masked_store, + BuiltinProc_simd_masked_expand_load, + BuiltinProc_simd_masked_compress_store, // Platform specific SIMD intrinsics BuiltinProc_simd_x86__MM_SHUFFLE, @@ -190,6 +208,7 @@ BuiltinProc__simd_end, // Platform specific intrinsics BuiltinProc_syscall, + BuiltinProc_syscall_bsd, BuiltinProc_x86_cpuid, BuiltinProc_x86_xgetbv, @@ -254,14 +273,27 @@ 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, BuiltinProc_type_is_specialization_of, BuiltinProc_type_is_variant_of, + BuiltinProc_type_union_tag_type, + BuiltinProc_type_union_tag_offset, + BuiltinProc_type_union_base_tag_value, + BuiltinProc_type_union_variant_count, + 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, @@ -276,13 +308,19 @@ BuiltinProc__type_simple_boolean_end, BuiltinProc_type_field_index_of, + BuiltinProc_type_bit_set_backing_type, + BuiltinProc_type_equal_proc, BuiltinProc_type_hasher_proc, BuiltinProc_type_map_info, BuiltinProc_type_map_cell_info, + BuiltinProc_type_has_shared_fields, + BuiltinProc__type_end, + BuiltinProc_procedure_of, + BuiltinProc___entry_point, BuiltinProc_objc_send, @@ -335,11 +373,6 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("soa_zip"), 1, true, Expr_Expr, BuiltinProcPkg_builtin}, {STR_LIT("soa_unzip"), 1, false, Expr_Expr, BuiltinProcPkg_builtin}, - - {STR_LIT("transpose"), 1, false, Expr_Expr, BuiltinProcPkg_builtin}, - {STR_LIT("outer_product"), 2, false, Expr_Expr, BuiltinProcPkg_builtin}, - {STR_LIT("hadamard_product"), 2, false, Expr_Expr, BuiltinProcPkg_builtin}, - {STR_LIT("matrix_flatten"), 1, false, Expr_Expr, BuiltinProcPkg_builtin}, {STR_LIT("unreachable"), 0, false, Expr_Expr, BuiltinProcPkg_builtin, /*diverging*/true}, @@ -350,6 +383,15 @@ 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("constant_log2"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + + {STR_LIT("transpose"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("outer_product"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("hadamard_product"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("matrix_flatten"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("soa_struct"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, // Type @@ -371,6 +413,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("saturating_add"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("saturating_sub"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("sqrt"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("fused_mul_add"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, @@ -395,6 +440,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("prefetch_write_instruction"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("prefetch_write_data"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("atomic_type_is_lock_free"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("atomic_thread_fence"), 1, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("atomic_signal_fence"), 1, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, @@ -420,6 +466,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("atomic_compare_exchange_strong_explicit"), 5, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("atomic_compare_exchange_weak"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("atomic_compare_exchange_weak_explicit"), 5, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("fixed_point_mul"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("fixed_point_div"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, @@ -439,8 +486,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_shl_masked"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_shr_masked"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("simd_add_sat"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("simd_sub_sat"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_saturating_add"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_saturating_sub"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_bit_and"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_bit_or"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, @@ -473,6 +520,10 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_reduce_or"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_reduce_xor"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_reduce_any"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_reduce_all"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + + {STR_LIT("simd_shuffle"), 2, true, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_select"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, @@ -487,12 +538,20 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_lanes_rotate_left"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_lanes_rotate_right"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_gather"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_scatter"), 3, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_masked_load"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_masked_store"), 3, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_masked_expand_load"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_masked_compress_store"), 3, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_x86__MM_SHUFFLE"), 4, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - {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}, @@ -552,14 +611,27 @@ 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}, {STR_LIT("type_is_specialization_of"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("type_is_variant_of"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_is_variant_of"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_union_tag_type"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_union_tag_offset"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_union_base_tag_value"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_union_variant_count"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {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}, @@ -574,14 +646,19 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("type_field_index_of"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_bit_set_backing_type"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_equal_proc"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_hasher_proc"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_map_info"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_map_cell_info"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_has_shared_fields"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {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 90632def3..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" @@ -353,6 +355,27 @@ gb_global bool global_module_path_set = false; #include "thread_pool.cpp" +gb_internal String obfuscate_string(String const &s, char const *prefix) { + if (s.len == 0) { + return s; + } + GB_ASSERT(prefix != nullptr); + u64 hash = gb_fnv64a(s.text, s.len); + gbString res = gb_string_make(permanent_allocator(), prefix); + res = gb_string_append_fmt(res, "x%llx", cast(long long unsigned)hash); + return make_string_c(res); +} + +gb_internal i32 obfuscate_i32(i32 i) { + i32 x = cast(i32)gb_fnv64a(&i, sizeof(i)); + if (x < 0) { + x = 1-x; + } + return cast(i32)x; +} + + + struct StringIntern { StringIntern *next; isize len; @@ -913,7 +936,7 @@ gb_internal void did_you_mean_append(DidYouMeanAnswers *d, String const &target) array_add(&d->distances, dat); } gb_internal Slice did_you_mean_results(DidYouMeanAnswers *d) { - gb_sort_array(d->distances.data, d->distances.count, gb_isize_cmp(gb_offset_of(DistanceAndTarget, distance))); + array_sort(d->distances, gb_isize_cmp(gb_offset_of(DistanceAndTarget, distance))); isize count = 0; for (isize i = 0; i < d->distances.count; i++) { isize distance = d->distances[i].distance; diff --git a/src/common_memory.cpp b/src/common_memory.cpp index c6ee88f03..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; } @@ -163,6 +167,10 @@ gb_internal void platform_virtual_memory_protect(void *memory, isize size); GB_ASSERT(is_protected); } #else + #if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) + #define MAP_ANONYMOUS MAP_ANON + #endif + gb_internal void platform_virtual_memory_init(void) { global_platform_memory_block_sentinel.prev = &global_platform_memory_block_sentinel; global_platform_memory_block_sentinel.next = &global_platform_memory_block_sentinel; @@ -255,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; @@ -270,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; @@ -306,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; @@ -366,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}; } @@ -381,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.cpp b/src/docs.cpp index f00d4e15a..004134a5c 100644 --- a/src/docs.cpp +++ b/src/docs.cpp @@ -237,7 +237,7 @@ gb_internal void print_doc_package(CheckerInfo *info, AstPackage *pkg) { } array_add(&entities, e); } - gb_sort_array(entities.data, entities.count, cmp_entities_for_printing); + array_sort(entities, cmp_entities_for_printing); bool show_docs = (build_context.cmd_doc_flags & CmdDocFlag_Short) == 0; @@ -358,7 +358,7 @@ gb_internal void generate_documentation(Checker *c) { } } - gb_sort_array(pkgs.data, pkgs.count, cmp_ast_package_by_name); + array_sort(pkgs, cmp_ast_package_by_name); for_array(i, pkgs) { print_doc_package(info, pkgs[i]); diff --git a/src/docs_format.cpp b/src/docs_format.cpp index d0bca214b..6378971d0 100644 --- a/src/docs_format.cpp +++ b/src/docs_format.cpp @@ -14,8 +14,8 @@ struct OdinDocVersionType { }; #define OdinDocVersionType_Major 0 -#define OdinDocVersionType_Minor 2 -#define OdinDocVersionType_Patch 4 +#define OdinDocVersionType_Minor 3 +#define OdinDocVersionType_Patch 1 struct OdinDocHeaderBase { u8 magic[8]; @@ -79,11 +79,11 @@ enum OdinDocTypeKind : u32 { OdinDocType_SOAStructFixed = 17, OdinDocType_SOAStructSlice = 18, OdinDocType_SOAStructDynamic = 19, - OdinDocType_RelativePointer = 20, - OdinDocType_RelativeMultiPointer = 21, + OdinDocType_MultiPointer = 22, OdinDocType_Matrix = 23, OdinDocType_SoaPointer = 24, + OdinDocType_BitField = 25, }; enum OdinDocTypeFlag_Basic : u32 { @@ -162,13 +162,17 @@ enum OdinDocEntityFlag : u64 { OdinDocEntityFlag_Foreign = 1ull<<0, OdinDocEntityFlag_Export = 1ull<<1, - OdinDocEntityFlag_Param_Using = 1ull<<2, - OdinDocEntityFlag_Param_Const = 1ull<<3, - OdinDocEntityFlag_Param_AutoCast = 1ull<<4, - OdinDocEntityFlag_Param_Ellipsis = 1ull<<5, - OdinDocEntityFlag_Param_CVararg = 1ull<<6, - OdinDocEntityFlag_Param_NoAlias = 1ull<<7, - OdinDocEntityFlag_Param_AnyInt = 1ull<<8, + OdinDocEntityFlag_Param_Using = 1ull<<2, + OdinDocEntityFlag_Param_Const = 1ull<<3, + OdinDocEntityFlag_Param_AutoCast = 1ull<<4, + OdinDocEntityFlag_Param_Ellipsis = 1ull<<5, + OdinDocEntityFlag_Param_CVararg = 1ull<<6, + OdinDocEntityFlag_Param_NoAlias = 1ull<<7, + OdinDocEntityFlag_Param_AnyInt = 1ull<<8, + OdinDocEntityFlag_Param_ByPtr = 1ull<<9, + OdinDocEntityFlag_Param_NoBroadcast = 1ull<<10, + + OdinDocEntityFlag_BitField_Field = 1ull<<19, OdinDocEntityFlag_Type_Alias = 1ull<<20, @@ -192,7 +196,7 @@ struct OdinDocEntity { u32 reserved_for_init; OdinDocString comment; // line comment OdinDocString docs; // preceding comment - i32 field_group_index; + i32 field_group_index; // For `bit_field`s this is the "bit_size" OdinDocEntityIndex foreign_library; OdinDocString link_name; OdinDocArray attributes; diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index 6816ae8eb..341b3fa6b 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) { @@ -615,6 +615,20 @@ gb_internal OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) { doc_type.types = odin_write_slice(w, types, gb_count_of(types)); } break; + case Type_BitField: + doc_type.kind = OdinDocType_BitField; + { + auto fields = array_make(heap_allocator(), type->BitField.fields.count); + defer (array_free(&fields)); + + for_array(i, type->BitField.fields) { + fields[i] = odin_doc_add_entity(w, type->BitField.fields[i]); + } + doc_type.entities = odin_write_slice(w, fields.data, fields.count); + doc_type.types = odin_doc_type_as_slice(w, type->BitField.backing_type); + } + break; + case Type_Struct: doc_type.kind = OdinDocType_Struct; if (type->Struct.soa_kind != StructSoa_None) { @@ -762,24 +776,6 @@ gb_internal OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) { doc_type.types = odin_doc_type_as_slice(w, type->SimdVector.elem); // TODO(bill): break; - case Type_RelativePointer: - doc_type.kind = OdinDocType_RelativePointer; - { - OdinDocTypeIndex types[2] = {}; - types[0] = odin_doc_type(w, type->RelativePointer.pointer_type); - types[1] = odin_doc_type(w, type->RelativePointer.base_integer); - doc_type.types = odin_write_slice(w, types, gb_count_of(types)); - } - break; - case Type_RelativeMultiPointer: - doc_type.kind = OdinDocType_RelativeMultiPointer; - { - OdinDocTypeIndex types[2] = {}; - types[0] = odin_doc_type(w, type->RelativeMultiPointer.pointer_type); - types[1] = odin_doc_type(w, type->RelativeMultiPointer.base_integer); - doc_type.types = odin_write_slice(w, types, gb_count_of(types)); - } - break; case Type_Matrix: doc_type.kind = OdinDocType_Matrix; @@ -815,7 +811,6 @@ gb_internal OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e) OdinDocEntityIndex doc_entity_index = odin_doc_write_item(w, &w->entities, &doc_entity, &dst); map_set(&w->entity_cache, e, doc_entity_index); - Ast *type_expr = nullptr; Ast *init_expr = nullptr; Ast *decl_node = nullptr; @@ -863,6 +858,10 @@ gb_internal OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e) } break; case Entity_Variable: + if (e->flags & EntityFlag_BitFieldField) { + flags |= OdinDocEntityFlag_BitField_Field; + } + if (e->Variable.is_foreign) { flags |= OdinDocEntityFlag_Foreign; } if (e->Variable.is_export) { flags |= OdinDocEntityFlag_Export; } if (e->Variable.thread_local_model != "") { @@ -873,7 +872,12 @@ gb_internal OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e) if (init_expr == nullptr) { init_expr = e->Variable.init_expr; } - field_group_index = e->Variable.field_group_index; + + if (e->flags & EntityFlag_BitFieldField) { + field_group_index = -cast(i32)e->Variable.bit_field_bit_size; + } else { + field_group_index = e->Variable.field_group_index; + } break; case Entity_Constant: field_group_index = e->Constant.field_group_index; @@ -902,11 +906,13 @@ gb_internal OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e) break; } - if (e->flags & EntityFlag_Using) { flags |= OdinDocEntityFlag_Param_Using; } - if (e->flags & EntityFlag_ConstInput) { flags |= OdinDocEntityFlag_Param_Const; } - if (e->flags & EntityFlag_Ellipsis) { flags |= OdinDocEntityFlag_Param_Ellipsis; } - if (e->flags & EntityFlag_NoAlias) { flags |= OdinDocEntityFlag_Param_NoAlias; } - if (e->flags & EntityFlag_AnyInt) { flags |= OdinDocEntityFlag_Param_AnyInt; } + if (e->flags & EntityFlag_Using) { flags |= OdinDocEntityFlag_Param_Using; } + if (e->flags & EntityFlag_ConstInput) { flags |= OdinDocEntityFlag_Param_Const; } + if (e->flags & EntityFlag_Ellipsis) { flags |= OdinDocEntityFlag_Param_Ellipsis; } + if (e->flags & EntityFlag_NoAlias) { flags |= OdinDocEntityFlag_Param_NoAlias; } + if (e->flags & EntityFlag_AnyInt) { flags |= OdinDocEntityFlag_Param_AnyInt; } + if (e->flags & EntityFlag_ByPtr) { flags |= OdinDocEntityFlag_Param_ByPtr; } + if (e->flags & EntityFlag_NoBroadcast) { flags |= OdinDocEntityFlag_Param_NoBroadcast; } if (e->scope && (e->scope->flags & (ScopeFlag_File|ScopeFlag_Pkg)) && !is_entity_exported(e)) { flags |= OdinDocEntityFlag_Private; @@ -962,9 +968,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); @@ -974,8 +979,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; + auto entry = w->entity_cache.entries[i]; + Entity *e = entry.key; + OdinDocEntityIndex entity_index = entry.value; OdinDocTypeIndex type_index = odin_doc_type(w, e->type); OdinDocEntityIndex foreign_library = 0; @@ -983,6 +989,9 @@ gb_internal void odin_doc_update_entities(OdinDocWriter *w) { switch (e->kind) { case Entity_Variable: + if (w->state == OdinDocWriterState_Writing) { + GB_ASSERT(type_index != 0); + } foreign_library = odin_doc_add_entity(w, e->Variable.foreign_library); break; case Entity_Procedure: @@ -1002,8 +1011,17 @@ gb_internal void odin_doc_update_entities(OdinDocWriter *w) { break; } + if (w->state == OdinDocWriterState_Preparing) { + GB_ASSERT(entity_index == 0); + } else { + GB_ASSERT(entity_index != 0); + } + OdinDocEntity *dst = odin_doc_get_item(w, &w->entities, entity_index); if (dst) { + if (dst->kind == OdinDocEntity_Variable) { + GB_ASSERT(type_index != 0); + } dst->type = type_index; dst->foreign_library = foreign_library; dst->grouped_entities = grouped_entities; @@ -1084,7 +1102,7 @@ gb_internal void odin_doc_write_docs(OdinDocWriter *w) { } debugf("odin_doc_update_entities sort pkgs %s\n", w->state ? "preparing" : "writing"); - gb_sort_array(pkgs.data, pkgs.count, cmp_ast_package_by_name); + array_sort(pkgs, cmp_ast_package_by_name); for_array(i, pkgs) { gbAllocator allocator = heap_allocator(); @@ -1147,7 +1165,7 @@ gb_internal void odin_doc_write_to_file(OdinDocWriter *w, char const *filename) gbFileError err = gb_file_open_mode(&f, gbFileMode_Write, filename); if (err != gbFileError_None) { gb_printf_err("Failed to write .odin-doc to: %s\n", filename); - gb_exit(1); + exit_with_errors(); return; } defer (gb_file_close(&f)); diff --git a/src/entity.cpp b/src/entity.cpp index ce27da3f2..802b381f9 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -43,8 +43,9 @@ enum EntityFlag : u64 { EntityFlag_NoAlias = 1ull<<9, EntityFlag_TypeField = 1ull<<10, EntityFlag_Value = 1ull<<11, + EntityFlag_BitFieldField = 1ull<<12, - + EntityFlag_NoCapture = 1ull<<13, // #no_capture EntityFlag_PolyConst = 1ull<<15, EntityFlag_NotExported = 1ull<<16, @@ -59,7 +60,7 @@ enum EntityFlag : u64 { EntityFlag_ProcBodyChecked = 1ull<<21, EntityFlag_CVarArg = 1ull<<22, - + EntityFlag_NoBroadcast = 1ull<<23, EntityFlag_AnyInt = 1ull<<24, EntityFlag_Disabled = 1ull<<25, @@ -84,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, }; @@ -105,6 +104,7 @@ enum ParameterValueKind { ParameterValue_Constant, ParameterValue_Nil, ParameterValue_Location, + ParameterValue_Expression, ParameterValue_Value, }; @@ -134,9 +134,7 @@ enum EntityConstantFlags : u32 { enum ProcedureOptimizationMode : u8 { ProcedureOptimizationMode_Default, ProcedureOptimizationMode_None, - ProcedureOptimizationMode_Minimal, - ProcedureOptimizationMode_Size, - ProcedureOptimizationMode_Speed, + ProcedureOptimizationMode_FavorSize, }; @@ -209,9 +207,11 @@ 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; + u8 bit_field_bit_size; ParameterValue param_value; @@ -222,11 +222,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; @@ -241,6 +244,7 @@ struct Entity { Ast * foreign_library_ident; String link_name; String link_prefix; + String link_suffix; DeferredProcedure deferred_procedure; struct GenProcsData *gen_procs; @@ -249,8 +253,10 @@ struct Entity { bool is_foreign : 1; bool is_export : 1; bool generated_from_polymorphic : 1; - bool target_feature_disabled : 1; - String target_feature; + bool entry_point_only : 1; + bool has_instrumentation : 1; + bool is_memcpy_like : 1; + bool uses_branch_location : 1; } Procedure; struct { Array entities; @@ -264,6 +270,7 @@ struct Entity { Scope *scope; } ImportName; struct { + Ast *decl; Slice paths; String name; i64 priority_index; @@ -331,6 +338,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; } @@ -476,3 +486,25 @@ gb_internal Entity *strip_entity_wrapping(Ast *expr) { Entity *e = entity_from_expr(expr); return strip_entity_wrapping(e); } + + +gb_internal bool is_entity_local_variable(Entity *e) { + if (e == nullptr) { + return false; + } + if (e->kind != Entity_Variable) { + return false; + } + if (e->Variable.is_global) { + return false; + } + if (e->scope == nullptr) { + return true; + } + if (e->flags & (EntityFlag_ForValue|EntityFlag_SwitchValue|EntityFlag_Static)) { + return false; + } + + return ((e->scope->flags &~ ScopeFlag_ContextDefined) == 0) || + (e->scope->flags & ScopeFlag_Proc) != 0; +} diff --git a/src/error.cpp b/src/error.cpp index e63682829..1492b00c7 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -1,28 +1,77 @@ +enum ErrorValueKind : u32 { + ErrorValue_Error, + ErrorValue_Warning, +}; + +struct ErrorValue { + ErrorValueKind kind; + TokenPos pos; + TokenPos end; + Array msg; + bool seen_newline; +}; + 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; - BlockingMutex mutex; - BlockingMutex error_out_mutex; - BlockingMutex string_mutex; - RecursiveMutex block_mutex; - RecursiveMutex error_buffer_mutex; - Array error_buffer; - Array errors; + RecursiveMutex mutex; + BlockingMutex path_mutex; + + Array error_values; + ErrorValue curr_error_value; + std::atomic curr_error_value_set; }; gb_global ErrorCollector global_error_collector; +gb_internal void push_error_value(TokenPos const &pos, ErrorValueKind kind = ErrorValue_Error) { + GB_ASSERT_MSG(global_error_collector.curr_error_value_set.load() == false, "Possible race condition in error handling system, please report this with an issue"); + ErrorValue ev = {kind, pos}; + ev.msg.allocator = heap_allocator(); + + global_error_collector.curr_error_value = ev; + global_error_collector.curr_error_value_set.store(true); +} + +gb_internal void pop_error_value(void) { + mutex_lock(&global_error_collector.mutex); + if (global_error_collector.curr_error_value_set.load()) { + array_add(&global_error_collector.error_values, global_error_collector.curr_error_value); + + global_error_collector.curr_error_value = {}; + global_error_collector.curr_error_value_set.store(false); + } + mutex_unlock(&global_error_collector.mutex); +} + + +gb_internal void try_pop_error_value(void) { + if (!global_error_collector.in_block.load()) { + pop_error_value(); + } +} + +gb_internal ErrorValue *get_error_value(void) { + GB_ASSERT_MSG(global_error_collector.curr_error_value_set.load() == true, "Possible race condition in error handling system, please report this with an issue"); + return &global_error_collector.curr_error_value; +} + + + gb_internal bool any_errors(void) { return global_error_collector.count.load() != 0; } +gb_internal bool any_warnings(void) { + return global_error_collector.warning_count.load() != 0; +} + gb_internal void init_global_error_collector(void) { - array_init(&global_error_collector.errors, heap_allocator()); - array_init(&global_error_collector.error_buffer, heap_allocator()); + array_init(&global_error_collector.error_values, heap_allocator()); array_init(&global_file_path_strings, heap_allocator(), 1, 4096); array_init(&global_files, heap_allocator(), 1, 4096); } @@ -37,7 +86,8 @@ gb_internal char *token_pos_to_string(TokenPos const &pos); gb_internal bool set_file_path_string(i32 index, String const &path) { bool ok = false; GB_ASSERT(index >= 0); - mutex_lock(&global_error_collector.string_mutex); + mutex_lock(&global_error_collector.path_mutex); + mutex_lock(&global_files_mutex); if (index >= global_file_path_strings.count) { array_resize(&global_file_path_strings, index+1); @@ -48,14 +98,16 @@ gb_internal bool set_file_path_string(i32 index, String const &path) { ok = true; } - mutex_unlock(&global_error_collector.string_mutex); + mutex_unlock(&global_files_mutex); + mutex_unlock(&global_error_collector.path_mutex); return ok; } gb_internal bool thread_safe_set_ast_file_from_id(i32 index, AstFile *file) { bool ok = false; GB_ASSERT(index >= 0); - mutex_lock(&global_error_collector.string_mutex); + mutex_lock(&global_error_collector.path_mutex); + mutex_lock(&global_files_mutex); if (index >= global_files.count) { array_resize(&global_files, index+1); @@ -65,34 +117,38 @@ gb_internal bool thread_safe_set_ast_file_from_id(i32 index, AstFile *file) { global_files[index] = file; ok = true; } - - mutex_unlock(&global_error_collector.string_mutex); + mutex_unlock(&global_files_mutex); + mutex_unlock(&global_error_collector.path_mutex); return ok; } gb_internal String get_file_path_string(i32 index) { GB_ASSERT(index >= 0); - mutex_lock(&global_error_collector.string_mutex); + mutex_lock(&global_error_collector.path_mutex); + mutex_lock(&global_files_mutex); String path = {}; if (index < global_file_path_strings.count) { path = global_file_path_strings[index]; } - mutex_unlock(&global_error_collector.string_mutex); + mutex_unlock(&global_files_mutex); + mutex_unlock(&global_error_collector.path_mutex); return path; } gb_internal AstFile *thread_safe_get_ast_file_from_id(i32 index) { GB_ASSERT(index >= 0); - mutex_lock(&global_error_collector.string_mutex); + mutex_lock(&global_error_collector.path_mutex); + mutex_lock(&global_files_mutex); AstFile *file = nullptr; if (index < global_files.count) { file = global_files[index]; } - mutex_unlock(&global_error_collector.string_mutex); + mutex_unlock(&global_files_mutex); + mutex_unlock(&global_error_collector.path_mutex); return file; } @@ -102,6 +158,8 @@ gb_internal AstFile *thread_safe_get_ast_file_from_id(i32 index) { gb_internal bool global_warnings_as_errors(void); gb_internal bool global_ignore_warnings(void); gb_internal bool show_error_line(void); +gb_internal bool terse_errors(void); +gb_internal bool json_errors(void); gb_internal bool has_ansi_terminal_colours(void); gb_internal gbString get_file_line_as_string(TokenPos const &pos, i32 *offset); @@ -113,97 +171,50 @@ gb_internal void syntax_error(Token const &token, char const *fmt, ...); gb_internal void syntax_error(TokenPos pos, char const *fmt, ...); gb_internal void syntax_warning(Token const &token, char const *fmt, ...); gb_internal void compiler_error(char const *fmt, ...); - -gb_internal void begin_error_block(void) { - mutex_lock(&global_error_collector.block_mutex); - global_error_collector.in_block.store(true); -} - -gb_internal void end_error_block(void) { - mutex_lock(&global_error_collector.error_buffer_mutex); - isize n = global_error_collector.error_buffer.count; - if (n > 0) { - u8 *text = global_error_collector.error_buffer.data; - - bool add_extra_newline = false; - - if (show_error_line()) { - if (n >= 2 && !(text[n-2] == '\n' && text[n-1] == '\n')) { - add_extra_newline = true; - } - } else { - isize newline_count = 0; - for (isize i = 0; i < n; i++) { - if (text[i] == '\n') { - newline_count += 1; - } - } - if (newline_count > 1) { - add_extra_newline = true; - } - } - - if (add_extra_newline) { - // add an extra new line as padding when the error line is being shown - error_line("\n"); - } - - n = global_error_collector.error_buffer.count; - text = gb_alloc_array(permanent_allocator(), u8, n+1); - gb_memmove(text, global_error_collector.error_buffer.data, n); - text[n] = 0; - - - mutex_lock(&global_error_collector.error_out_mutex); - String s = {text, n}; - array_add(&global_error_collector.errors, s); - mutex_unlock(&global_error_collector.error_out_mutex); - - global_error_collector.error_buffer.count = 0; - } - mutex_unlock(&global_error_collector.error_buffer_mutex); - global_error_collector.in_block.store(false); - mutex_unlock(&global_error_collector.block_mutex); -} - -#define ERROR_BLOCK() begin_error_block(); defer (end_error_block()) +gb_internal void print_all_errors(void); #define ERROR_OUT_PROC(name) void name(char const *fmt, va_list va) typedef ERROR_OUT_PROC(ErrorOutProc); gb_internal ERROR_OUT_PROC(default_error_out_va) { - gbFile *f = gb_file_get_standard(gbFileStandard_Error); - char buf[4096] = {}; isize len = gb_snprintf_va(buf, gb_size_of(buf), fmt, va); isize n = len-1; - if (global_error_collector.in_block) { - mutex_lock(&global_error_collector.error_buffer_mutex); - isize cap = global_error_collector.error_buffer.count + n; - array_reserve(&global_error_collector.error_buffer, cap); - u8 *data = global_error_collector.error_buffer.data + global_error_collector.error_buffer.count; - gb_memmove(data, buf, n); - global_error_collector.error_buffer.count += n; - - mutex_unlock(&global_error_collector.error_buffer_mutex); - } else { - mutex_lock(&global_error_collector.error_out_mutex); - { - u8 *text = gb_alloc_array(permanent_allocator(), u8, n+1); - gb_memmove(text, buf, n); - text[n] = 0; - array_add(&global_error_collector.errors, make_string(text, n)); + if (n > 0) { + ErrorValue *ev = get_error_value(); + if (terse_errors()) { + for (isize i = 0; i < n && !ev->seen_newline; i++) { + u8 c = cast(u8)buf[i]; + if (c == '\n') { + ev->seen_newline = true; + } + array_add(&ev->msg, c); + } + } else { + array_add_elems(&ev->msg, (u8 *)buf, n); } - mutex_unlock(&global_error_collector.error_out_mutex); - } - gb_file_write(f, buf, n); } gb_global ErrorOutProc *error_out_va = default_error_out_va; +gb_internal void begin_error_block(void) { + mutex_lock(&global_error_collector.mutex); + global_error_collector.in_block.store(true); +} + +gb_internal void end_error_block(void) { + pop_error_value(); + global_error_collector.in_block.store(false); + mutex_unlock(&global_error_collector.mutex); +} + +#define ERROR_BLOCK() begin_error_block(); defer (end_error_block()) + + + gb_internal void error_out(char const *fmt, ...) { va_list va; va_start(va, fmt); @@ -226,6 +237,7 @@ enum TerminalColour { TerminalColour_Blue, TerminalColour_Purple, TerminalColour_Black, + TerminalColour_Grey, }; gb_internal void terminal_set_colours(TerminalStyle style, TerminalColour foreground) { @@ -245,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; } } } @@ -255,91 +268,245 @@ gb_internal void terminal_reset_colours(void) { } -gb_internal bool show_error_on_line(TokenPos const &pos, TokenPos end) { +gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end) { + get_error_value()->end = end; if (!show_error_line()) { - return false; + 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, - }; - - 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; - if (offset > 0) { - line_text += offset-left; - line_len -= offset-left; - offset = left+MAX_TAB_WIDTH/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 (offset > 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 true; + if (the_line == nullptr) { + return -1; + } else { + return cast(isize)error_start_index_bytes; + } } - return false; + + // 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) { + error_out(""); +} gb_internal void error_out_pos(TokenPos pos) { terminal_set_colours(TerminalStyle_Bold, TerminalColour_White); error_out("%s ", token_pos_to_string(pos)); @@ -356,27 +523,31 @@ 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); - // NOTE(bill): Duplicate error, skip it + + push_error_value(pos, ErrorValue_Error); 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); } @@ -385,23 +556,33 @@ 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); - if (!global_ignore_warnings()) { - // NOTE(bill): Duplicate error, skip it - if (pos.line == 0) { - 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; + + push_error_value(pos, ErrorValue_Warning); + + 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(); + } 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); } @@ -412,69 +593,99 @@ 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); - // NOTE(bill): Duplicate error, skip it + + push_error_value(pos, ErrorValue_Error); + 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); } error_out_va(fmt, va); } + + try_pop_error_value(); mutex_unlock(&global_error_collector.mutex); } 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); - // NOTE(bill): Duplicate error, skip it - if (global_error_collector.prev != pos) { - global_error_collector.prev = pos; - error_out_pos(pos); + + push_error_value(pos, ErrorValue_Warning); + + if (pos.line == 0) { + error_out_empty(); 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) { + } 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(); mutex_unlock(&global_error_collector.mutex); } gb_internal void syntax_error_with_verbose_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) { global_error_collector.count.fetch_add(1); + 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); - // NOTE(bill): Duplicate error, skip it + + push_error_value(pos, ErrorValue_Warning); + if (pos.line == 0) { - error_out_coloured("Syntax_Error: ", TerminalStyle_Normal, TerminalColour_Red); + error_out_empty(); + 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"); show_error_on_line(pos, end); } + + try_pop_error_value(); mutex_unlock(&global_error_collector.mutex); } @@ -484,23 +695,34 @@ 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++; - if (!global_ignore_warnings()) { - // NOTE(bill): Duplicate error, skip it - if (global_error_collector.prev != pos) { - global_error_collector.prev = pos; + + + push_error_value(pos, ErrorValue_Warning); + + 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(); + } 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); - } else if (pos.line == 0) { - error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); - error_out_va(fmt, va); - error_out("\n"); } + 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(); mutex_unlock(&global_error_collector.mutex); } @@ -568,6 +790,10 @@ gb_internal void syntax_error_with_verbose(TokenPos pos, TokenPos end, char cons gb_internal void compiler_error(char const *fmt, ...) { + if (any_errors() || any_warnings()) { + print_all_errors(); + } + va_list va; va_start(va, fmt); @@ -577,3 +803,220 @@ gb_internal void compiler_error(char const *fmt, ...) { GB_DEBUG_TRAP(); gb_exit(1); } + + +gb_internal void exit_with_errors(void) { + if (any_errors() || any_warnings()) { + print_all_errors(); + } + gb_exit(1); +} + + + +gb_internal int error_value_cmp(void const *a, void const *b) { + ErrorValue *x = cast(ErrorValue *)a; + ErrorValue *y = cast(ErrorValue *)b; + return token_pos_cmp(x->pos, y->pos); +} + +gb_global String error_article_table[][2] = { + {str_lit("a "), str_lit("bit_set literal")}, + {str_lit("a "), str_lit("constant declaration")}, + {str_lit("a "), str_lit("dynamiic array literal")}, + {str_lit("a "), str_lit("map index")}, + {str_lit("a "), str_lit("map literal")}, + {str_lit("a "), str_lit("matrix literal")}, + {str_lit("a "), str_lit("polymorphic type argument")}, + {str_lit("a "), str_lit("procedure argument")}, + {str_lit("a "), str_lit("simd vector literal")}, + {str_lit("a "), str_lit("slice literal")}, + {str_lit("a "), str_lit("structure literal")}, + {str_lit("a "), str_lit("variable declaration")}, + {str_lit("an "), str_lit("'any' literal")}, + {str_lit("an "), str_lit("array literal")}, + {str_lit("an "), str_lit("enumerated array literal")}, + +}; + +// Returns definite or indefinite article matching `context_name`, or "" if not found. +gb_internal String error_article(String context_name) { + for (int i = 0; i < gb_count_of(error_article_table); i += 1) { + if (context_name == error_article_table[i][1]) { + return error_article_table[i][0]; + } + } + return str_lit(""); +} + +gb_internal bool errors_already_printed = false; + +gb_internal void print_all_errors(void) { + 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; + case '"': res = gb_string_append_length(res, "\\\"", 2); break; + case '\\': res = gb_string_append_length(res, "\\\\", 2); break; + case '\b': res = gb_string_append_length(res, "\\b", 2); break; + case '\f': res = gb_string_append_length(res, "\\f", 2); break; + case '\r': res = gb_string_append_length(res, "\\r", 2); break; + case '\t': res = gb_string_append_length(res, "\\t", 2); break; + default: + if ('\x00' <= c && c <= '\x1f') { + res = gb_string_append_fmt(res, "\\u%04x", c); + } else { + res = gb_string_append_length(res, &c, 1); + } + break; + } + return res; + }; + + 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)); + + if (json_errors()) { + 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]; + + res = gb_string_append_fmt(res, "\t\t{\n"); + + res = gb_string_append_fmt(res, "\t\t\t\"type\": \""); + if (ev.kind == ErrorValue_Warning) { + res = gb_string_append_fmt(res, "warning"); + } else { + res = gb_string_append_fmt(res, "error"); + } + res = gb_string_append_fmt(res, "\",\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++) { + res = escape_char(res, file.text[k]); + } + res = gb_string_append_fmt(res, "\",\n"); + res = gb_string_append_fmt(res, "\t\t\t\t\"offset\": %d,\n", ev.pos.offset); + res = gb_string_append_fmt(res, "\t\t\t\t\"line\": %d,\n", ev.pos.line); + res = gb_string_append_fmt(res, "\t\t\t\t\"column\": %d,\n", ev.pos.column); + 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"); + + auto lines = split_lines_from_array(ev.msg, heap_allocator()); + defer (array_free(&lines)); + + if (lines.count > 0) { + res = gb_string_append_fmt(res, "\t\t\t\t\""); + + for (isize j = 0; j < lines.count; j++) { + String line = lines[j]; + for (isize k = 0; k < line.len; k++) { + u8 c = line.text[k]; + res = escape_char(res, c); + } + if (j+1 < lines.count) { + res = gb_string_append_fmt(res, "\",\n"); + res = gb_string_append_fmt(res, "\t\t\t\t\""); + } + } + res = gb_string_append_fmt(res, "\"\n"); + } + res = gb_string_append_fmt(res, "\t\t\t]\n"); + res = gb_string_append_fmt(res, "\t\t}"); + if (i+1 != global_error_collector.error_values.count) { + res = gb_string_append_fmt(res, ","); + } + res = gb_string_append_fmt(res, "\n"); + } + + res = gb_string_append_fmt(res, "\t]\n"); + res = gb_string_append_fmt(res, "}\n"); + } else { + for_array(i, global_error_collector.error_values) { + ErrorValue ev = global_error_collector.error_values[i]; + + String_Iterator it = {{ev.msg.data, ev.msg.count}, 0}; + + for (isize line_idx = 0; /**/; line_idx++) { + String line = string_split_iterator(&it, '\n'); + if (line.len == 0) { + break; + } + line = string_trim_trailing_whitespace(line); + res = gb_string_append_length(res, line.text, line.len); + res = gb_string_append_length(res, " \n", 2); + if (line_idx == 0 && terse_errors()) { + break; + } + } + } + } + gbFile *f = gb_file_get_standard(gbFileStandard_Error); + gb_file_write(f, res, gb_string_length(res)); + + errors_already_printed = true; +} diff --git a/src/exact_value.cpp b/src/exact_value.cpp index cd499272f..ceaed84c1 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) { @@ -174,7 +190,7 @@ gb_internal ExactValue exact_value_integer_from_string(String const &string) { -gb_internal f64 float_from_string(String const &string) { +gb_internal f64 float_from_string(String const &string, bool *success = nullptr) { if (string.len < 128) { char buf[128] = {}; isize n = 0; @@ -187,7 +203,13 @@ gb_internal f64 float_from_string(String const &string) { buf[n++] = cast(char)c; } buf[n] = 0; - return atof(buf); + + char *end_ptr; + f64 f = strtod(buf, &end_ptr); + if (success != nullptr) { + *success = *end_ptr == '\0'; + } + return f; } else { TEMPORARY_ALLOCATOR_GUARD(); char *buf = gb_alloc_array(temporary_allocator(), char, string.len+1); @@ -201,7 +223,13 @@ gb_internal f64 float_from_string(String const &string) { buf[n++] = cast(char)c; } buf[n] = 0; - return atof(buf); + + char *end_ptr; + f64 f = strtod(buf, &end_ptr); + if (success != nullptr) { + *success = *end_ptr == '\0'; + } + return f; } /* isize i = 0; @@ -313,7 +341,11 @@ gb_internal ExactValue exact_value_float_from_string(String string) { return exact_value_integer_from_string(string); } - f64 f = float_from_string(string); + bool success; + f64 f = float_from_string(string, &success); + if (!success) { + return {ExactValue_Invalid}; + } return exact_value_float(f); } @@ -338,7 +370,11 @@ gb_internal ExactValue exact_value_from_basic_literal(TokenKind kind, String con } case Token_Rune: { Rune r = GB_RUNE_INVALID; - utf8_decode(string.text, string.len, &r); + if (string.len == 1) { + r = cast(Rune)string.text[0]; + } else { + utf8_decode(string.text, string.len, &r); + } return exact_value_i64(r); } } @@ -556,6 +592,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; } @@ -587,6 +624,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; } @@ -653,6 +691,7 @@ gb_internal void match_exact_values(ExactValue *x, ExactValue *y) { case ExactValue_String: case ExactValue_Quaternion: case ExactValue_Pointer: + case ExactValue_Compound: case ExactValue_Procedure: case ExactValue_Typeid: return; diff --git a/src/gb/gb.h b/src/gb/gb.h index 5dae7a5c4..f74026c7d 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -39,7 +39,7 @@ extern "C" { #endif #endif -#if defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__64BIT__) || defined(__powerpc64__) || defined(__ppc64__) || defined(__aarch64__) +#if defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__64BIT__) || defined(__powerpc64__) || defined(__ppc64__) || defined(__aarch64__) || (defined(__riscv) && __riscv_xlen == 64) #ifndef GB_ARCH_64_BIT #define GB_ARCH_64_BIT 1 #endif @@ -83,6 +83,14 @@ 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 + #endif #else #error This UNIX operating system is not supported #endif @@ -136,12 +144,17 @@ extern "C" { #define GB_CACHE_LINE_SIZE 64 #endif +#elif defined(__riscv) + #ifndef GB_CPU_RISCV + #define GB_CPU_RISCV 1 + #endif + #ifndef GB_CACHE_LINE_SIZE + #define GB_CACHE_LINE_SIZE 64 + #endif #else #error Unknown CPU Type #endif - - #ifndef GB_STATIC_ASSERT #define GB_STATIC_ASSERT3(cond, msg) typedef char static_assertion_##msg[(!!(cond))*2-1] // NOTE(bill): Token pasting madness!! @@ -206,7 +219,7 @@ extern "C" { #endif #include // NOTE(bill): malloc on linux #include - #if !defined(GB_SYSTEM_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__) + #if !defined(GB_SYSTEM_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) && !defined(__HAIKU__) #include #endif #include @@ -247,6 +260,19 @@ extern "C" { #include #define lseek64 lseek #endif + +#if defined(GB_SYSTEM_NETBSD) + #include + #include + #define lseek64 lseek +#endif + +#if defined(GB_SYSTEM_HAIKU) + #include + #include + #include + #define lseek64 lseek +#endif #if defined(GB_SYSTEM_UNIX) #include @@ -448,7 +474,7 @@ typedef i32 b32; // NOTE(bill): Prefer this!!! #define gb_inline __forceinline #endif #else - #define gb_inline __attribute__ ((__always_inline__)) + #define gb_inline inline __attribute__ ((__always_inline__)) #endif #endif @@ -469,6 +495,13 @@ typedef i32 b32; // NOTE(bill): Prefer this!!! #endif #endif +#if !defined(gb_no_asan) + #if defined(_MSC_VER) + #define gb_no_asan __declspec(no_sanitize_address) + #else + #define gb_no_asan __attribute__((disable_sanitizer_instrumentation)) + #endif +#endif // NOTE(bill): Easy to grep // NOTE(bill): Not needed in macros @@ -801,6 +834,20 @@ 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; + isize core_count; + isize thread_count; + isize threads_per_core; +} gbAffinity; #else #error TODO(bill): Unknown system #endif @@ -2494,7 +2541,11 @@ 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) { + if (size != 0) { + memset(ptr, 0, size); + } +} #if defined(_MSC_VER) && !defined(__clang__) @@ -2522,7 +2573,7 @@ gb_inline void *gb_memcopy(void *dest, void const *source, isize n) { void *dest_copy = dest; __asm__ __volatile__("rep movsb" : "+D"(dest_copy), "+S"(source), "+c"(n) : : "memory"); -#elif defined(GB_CPU_ARM) +#elif defined(GB_CPU_ARM) || defined(GB_CPU_RISCV) u8 *s = cast(u8 *)source; u8 *d = cast(u8 *)dest; for (isize i = 0; i < n; i++) { @@ -2984,6 +3035,12 @@ gb_inline u32 gb_thread_current_id(void) { __asm__("mov %%fs:0x10,%0" : "=r"(thread_id)); #elif defined(GB_SYSTEM_LINUX) 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 @@ -3142,11 +3199,11 @@ void gb_affinity_init(gbAffinity *a) { a->core_count = 1; a->threads_per_core = 1; - if (sysctlbyname("hw.logicalcpu", &count, &count_size, NULL, 0) == 0) { + if (sysctlbyname("kern.smp.cpus", &count, &count_size, NULL, 0) == 0) { if (count > 0) { a->thread_count = count; // Get # of physical cores - if (sysctlbyname("hw.physicalcpu", &count, &count_size, NULL, 0) == 0) { + if (sysctlbyname("kern.smp.cores", &count, &count_size, NULL, 0) == 0) { if (count > 0) { a->core_count = count; a->threads_per_core = a->thread_count / count; @@ -3157,6 +3214,14 @@ void gb_affinity_init(gbAffinity *a) { } } } + } else if (sysctlbyname("hw.ncpu", &count, &count_size, NULL, 0) == 0) { + // SMP disabled or unavailable. + if (count > 0) { + a->is_accurate = true; + a->thread_count = count; + a->core_count = count; + a->threads_per_core = 1; + } } } @@ -3184,7 +3249,9 @@ b32 gb_affinity_set(gbAffinity *a, isize core, isize thread_index) { //info.affinity_tag = cast(integer_t)index; //result = thread_policy_set(thread, THREAD_AFFINITY_POLICY, cast(thread_policy_t)&info, THREAD_AFFINITY_POLICY_COUNT); +#if !defined(GB_SYSTEM_HAIKU) result = pthread_setaffinity_np(thread, sizeof(cpuset_t), &mn); +#endif return result == 0; } @@ -3236,6 +3303,54 @@ 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_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 + +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; @@ -3528,7 +3643,7 @@ gb_inline void gb_str_to_upper(char *str) { } -gb_inline isize gb_strlen(char const *str) { +gb_no_asan isize gb_strlen(char const *str) { char const *begin = str; isize const *w; if (str == NULL) { @@ -5457,7 +5572,7 @@ gb_inline b32 gb_file_copy(char const *existing_filename, char const *new_filena } } - gb_free(buf); + gb_mfree(buf); close(new_fd); close(existing_fd); @@ -5634,7 +5749,7 @@ char *gb_path_get_full_name(gbAllocator a, char const *path) { isize path_len = gb_strlen(path); isize cwd_len = gb_strlen(cwd); len = cwd_len + 1 + path_len + 1; - result = gb_alloc_array(a, char, len); + result = gb_alloc_array(a, char, len+1); gb_memmove(result, (void *)cwd, cwd_len); result[cwd_len] = '/'; @@ -6168,11 +6283,18 @@ 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; + } +#elif defined(__riscv) + gb_inline u64 gb_rdtsc(void) { + u64 result = 0; + __asm__ volatile("rdcycle %0" : "=r"(result)); + return result; } #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 eb3687ae2..261d6e7a4 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -7,9 +7,19 @@ struct LinkerData { Array output_temp_paths; String output_base; String output_name; +#if defined(GB_SYSTEM_OSX) + b8 needs_system_library_linked; +#endif }; 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) { + ld->needs_system_library_linked = 1; +} +#endif gb_internal void linker_data_init(LinkerData *ld, CheckerInfo *info, String const &init_fullpath) { gbAllocator ha = heap_allocator(); @@ -18,6 +28,10 @@ gb_internal void linker_data_init(LinkerData *ld, CheckerInfo *info, String cons array_init(&ld->foreign_libraries, ha, 0, 1024); ptr_set_init(&ld->foreign_libraries_set, 1024); +#if defined(GB_SYSTEM_OSX) + ld->needs_system_library_linked = 0; +#endif + if (build_context.out_filepath.len == 0) { ld->output_name = remove_directory_from_path(init_fullpath); ld->output_name = remove_extension_from_path(ld->output_name); @@ -56,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; } @@ -100,8 +167,10 @@ gb_internal i32 linker_stage(LinkerData *gen) { if (is_windows) { String section_name = str_lit("msvc-link"); - if (build_context.use_lld) { - section_name = str_lit("lld-link"); + switch (build_context.linker_choice) { + case Linker_Default: break; + case Linker_lld: section_name = str_lit("lld-link"); break; + case Linker_radlink: section_name = str_lit("rad-link"); break; } timings_start_section(timings, section_name); @@ -126,9 +195,11 @@ gb_internal i32 linker_stage(LinkerData *gen) { } - StringSet libs = {}; - string_set_init(&libs, 64); - defer (string_set_destroy(&libs)); + StringSet min_libs_set = {}; + string_set_init(&min_libs_set, 64); + defer (string_set_destroy(&min_libs_set)); + + String prev_lib = {}; StringSet asm_files = {}; string_set_init(&asm_files, 64); @@ -136,6 +207,11 @@ gb_internal i32 linker_stage(LinkerData *gen) { for (Entity *e : gen->foreign_libraries) { GB_ASSERT(e->kind == Entity_LibraryName); + // NOTE(bill): Add these before the linking values + String extra_linker_flags = string_trim_whitespace(e->LibraryName.extra_linker_flags); + if (extra_linker_flags.len != 0) { + lib_str = gb_string_append_fmt(lib_str, " %.*s", LIT(extra_linker_flags)); + } for_array(i, e->LibraryName.paths) { String lib = string_trim_whitespace(e->LibraryName.paths[i]); // IMPORTANT NOTE(bill): calling `string_to_lower` here is not an issue because @@ -147,14 +223,27 @@ 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 obj_format; -#if defined(GB_ARCH_64_BIT) - obj_format = str_lit("win64"); -#elif defined(GB_ARCH_32_BIT) + 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"); -#endif // GB_ARCH_*_BIT + #endif + result = system_exec_command_line_app("nasm", "\"%.*s\\bin\\nasm\\windows\\nasm.exe\" \"%.*s\" " "-f \"%.*s\" " @@ -172,21 +261,16 @@ gb_internal i32 linker_stage(LinkerData *gen) { } array_add(&gen->output_object_paths, obj_file); } - } else { - if (!string_set_update(&libs, lib)) { + } else if (!string_set_update(&min_libs_set, lib) || + !build_context.min_link_libs) { + if (prev_lib != lib) { lib_str = gb_string_append_fmt(lib_str, " \"%.*s\"", LIT(lib)); } + prev_lib = lib; } } } - for (Entity *e : gen->foreign_libraries) { - GB_ASSERT(e->kind == Entity_LibraryName); - if (e->LibraryName.extra_linker_flags.len != 0) { - lib_str = gb_string_append_fmt(lib_str, " %.*s", LIT(e->LibraryName.extra_linker_flags)); - } - } - if (build_context.build_mode == BuildMode_DynamicLibrary) { link_settings = gb_string_append_fmt(link_settings, " /DLL"); } else { @@ -195,13 +279,15 @@ gb_internal i32 linker_stage(LinkerData *gen) { if (build_context.pdb_filepath != "") { String pdb_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_PDB]); - link_settings = gb_string_append_fmt(link_settings, " /PDB:%.*s", LIT(pdb_path)); + 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) { @@ -220,64 +306,18 @@ gb_internal i32 linker_stage(LinkerData *gen) { String windows_sdk_bin_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Win_SDK_Bin_Path]); defer (gb_free(heap_allocator(), windows_sdk_bin_path.text)); - char const *subsystem_str = build_context.use_subsystem_windows ? "WINDOWS" : "CONSOLE"; - if (!build_context.use_lld) { // msvc - String res_path = {}; - defer (gb_free(heap_allocator(), res_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); - - 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) - ); - - if (result) { - return result; - } - } - - switch (build_context.build_mode) { - case BuildMode_Executable: - link_settings = gb_string_append_fmt(link_settings, " /NOIMPLIB /NOEXP"); - 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 " - "", - LIT(vs_exe_path), object_files, LIT(res_path), LIT(output_filename), - link_settings, - subsystem_str, - LIT(build_context.link_flags), - LIT(build_context.extra_linker_flags), - lib_str - ); - if (result) { - return result; - } - } else { // lld + switch (build_context.linker_choice) { + case Linker_lld: result = system_exec_command_line_app("msvc-lld-link", "\"%.*s\\bin\\lld-link\" %s -OUT:\"%.*s\" %s " - "/nologo /incremental:no /opt:ref /subsystem:%s " + "/nologo /incremental:no /opt:ref /subsystem:%.*s " "%.*s " "%.*s " "%s " "", LIT(build_context.ODIN_ROOT), object_files, LIT(output_filename), link_settings, - subsystem_str, + LIT(build_context.ODIN_WINDOWS_SUBSYSTEM), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags), lib_str @@ -286,10 +326,100 @@ gb_internal i32 linker_stage(LinkerData *gen) { if (result) { return result; } + break; + case Linker_radlink: + result = system_exec_command_line_app("msvc-rad-link", + "\"%.*s\\bin\\radlink\" %s -OUT:\"%.*s\" %s " + "/nologo /incremental:no /opt:ref /subsystem:%.*s " + "%.*s " + "%.*s " + "%s " + "", + LIT(build_context.ODIN_ROOT), object_files, LIT(output_filename), + link_settings, + LIT(build_context.ODIN_WINDOWS_SUBSYSTEM), + LIT(build_context.link_flags), + LIT(build_context.extra_linker_flags), + lib_str + ); + + if (result) { + return result; + } + break; + default: { // msvc + 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) { + 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)); + + 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; + } + } + } 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", + "\"%.*s%.*s\" %s %.*s -OUT:\"%.*s\" %s " + "/nologo /subsystem:%.*s " + "%.*s " + "%.*s " + "%s " + "", + 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), + LIT(build_context.extra_linker_flags), + lib_str + ); + if (result) { + return result; + } + break; + } } } else { timings_start_section(timings, str_lit("ld-link")); + // Link using `clang`, unless overridden by `ODIN_CLANG_PATH` environment variable. + const char* clang_path = gb_get_env("ODIN_CLANG_PATH", permanent_allocator()); + if (clang_path == NULL) { + clang_path = "clang"; + } + // NOTE(vassvik): get cwd, for used for local shared libs linking, since those have to be relative to the exe char cwd[256]; #if !defined(GB_SYSTEM_WINDOWS) @@ -306,12 +436,19 @@ gb_internal i32 linker_stage(LinkerData *gen) { string_set_init(&asm_files, 64); defer (string_set_destroy(&asm_files)); - StringSet libs = {}; - string_set_init(&libs, 64); - defer (string_set_destroy(&libs)); + StringSet min_libs_set = {}; + string_set_init(&min_libs_set, 64); + defer (string_set_destroy(&min_libs_set)); + + String prev_lib = {}; for (Entity *e : gen->foreign_libraries) { GB_ASSERT(e->kind == Entity_LibraryName); + // NOTE(bill): Add these before the linking values + String extra_linker_flags = string_trim_whitespace(e->LibraryName.extra_linker_flags); + if (extra_linker_flags.len != 0) { + lib_str = gb_string_append_fmt(lib_str, " %.*s", LIT(extra_linker_flags)); + } for (String lib : e->LibraryName.paths) { lib = string_trim_whitespace(lib); if (lib.len == 0) { @@ -322,44 +459,99 @@ 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 defined(GB_ARCH_64_BIT) if (is_osx) { obj_format = str_lit("macho64"); } else { obj_format = str_lit("elf64"); } -#elif defined(GB_ARCH_32_BIT) + #elif defined(GB_ARCH_32_BIT) if (is_osx) { obj_format = str_lit("macho32"); } else { obj_format = str_lit("elf32"); } -#endif // GB_ARCH_*_BIT - // Note(bumbread): I'm assuming nasm is installed on the host machine. - // Shipping binaries on unix-likes gets into the weird territorry of - // "which version of glibc" is it linked with. - result = system_exec_command_line_app("nasm", - "nasm \"%.*s\" " - "-f \"%.*s\" " - "-o \"%.*s\" " - "%.*s " - "", - LIT(asm_file), - LIT(obj_format), - LIT(obj_file), - LIT(build_context.extra_assembler_flags) - ); + #endif // GB_ARCH_*_BIT + + if (build_context.metrics.arch == TargetArch_riscv64) { + result = system_exec_command_line_app("clang", + "%s \"%.*s\" " + "-c -o \"%.*s\" " + "-target %.*s -march=rv64gc " + "%.*s " + "", + clang_path, + LIT(asm_file), + LIT(obj_file), + LIT(build_context.metrics.target_triplet), + LIT(build_context.extra_assembler_flags) + ); + } else if (is_osx) { + // `as` comes with MacOS. + result = system_exec_command_line_app("as", + "as \"%.*s\" " + "-o \"%.*s\" " + "%.*s " + "", + LIT(asm_file), + LIT(obj_file), + LIT(build_context.extra_assembler_flags) + ); + } else { + // Note(bumbread): I'm assuming nasm is installed on the host machine. + // Shipping binaries on unix-likes gets into the weird territorry of + // "which version of glibc" is it linked with. + result = system_exec_command_line_app("nasm", + "nasm \"%.*s\" " + "-f \"%.*s\" " + "-o \"%.*s\" " + "%.*s " + "", + LIT(asm_file), + LIT(obj_format), + LIT(obj_file), + LIT(build_context.extra_assembler_flags) + ); + if (result) { + gb_printf_err("executing `nasm` to assemble foreing import of %.*s failed.\n\tSuggestion: `nasm` does not ship with the compiler and should be installed with your system's package manager.\n", LIT(asm_file)); + return result; + } + } array_add(&gen->output_object_paths, obj_file); } else { - if (string_set_update(&libs, lib)) { + if (string_set_update(&min_libs_set, lib) && build_context.min_link_libs) { + continue; + } + + if (prev_lib == lib) { + continue; + } + prev_lib = lib; + + // Do not add libc again, this is added later already, and omitted with + // the `-no-crt` flag, not skipping here would cause duplicate library + // warnings when linking on darwin and might link libc silently even with `-no-crt`. + if (lib == str_lit("System.framework") || lib == str_lit("c")) { continue; } - // NOTE(zangent): Sometimes, you have to use -framework on MacOS. - // This allows you to specify '-f' in a #foreign_system_library, - // without having to implement any new syntax specifically for MacOS. if (build_context.metrics.os == TargetOs_darwin) { if (string_ends_with(lib, str_lit(".framework"))) { // framework thingie @@ -382,14 +574,8 @@ gb_internal i32 linker_stage(LinkerData *gen) { // available at runtime wherever the executable is run, so we make require those to be // local to the executable (unless the system collection is used, in which case we search // the system library paths for the library file). - 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 + if (string_ends_with(lib, str_lit(".a")) || string_ends_with(lib, str_lit(".o")) || string_ends_with(lib, str_lit(".so")) || string_contains_string(lib, str_lit(".so."))) { lib_str = gb_string_append_fmt(lib_str, " -l:\"%.*s\" ", LIT(lib)); - } else if (string_ends_with(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 - lib_str = gb_string_append_fmt(lib_str, " -l:\"%s/%.*s\" ", cwd, LIT(lib)); } else { // dynamic or static system lib, just link regularly searching system library paths lib_str = gb_string_append_fmt(lib_str, " -l%.*s ", LIT(lib)); @@ -399,13 +585,6 @@ gb_internal i32 linker_stage(LinkerData *gen) { } } - for (Entity *e : gen->foreign_libraries) { - GB_ASSERT(e->kind == Entity_LibraryName); - if (e->LibraryName.extra_linker_flags.len != 0) { - lib_str = gb_string_append_fmt(lib_str, " %.*s", LIT(e->LibraryName.extra_linker_flags)); - } - } - gbString object_files = gb_string_make(heap_allocator(), ""); defer (gb_string_free(object_files)); for (String object_path : gen->output_object_paths) { @@ -418,6 +597,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. @@ -448,35 +631,72 @@ gb_internal i32 linker_stage(LinkerData *gen) { link_settings = gb_string_appendc(link_settings, "-Wl,-fini,'_odin_exit_point' "); } - } else if (build_context.metrics.os != TargetOs_openbsd) { - // OpenBSD defaults to PIE executable. do not pass -no-pie for it. - link_settings = gb_string_appendc(link_settings, "-no-pie "); + } + + if (build_context.build_mode == BuildMode_Executable && build_context.reloc_mode == RelocMode_PIC) { + // Do not disable PIE, let the linker choose. (most likely you want it enabled) + } else if (build_context.build_mode != BuildMode_DynamicLibrary) { + if (build_context.metrics.os != TargetOs_openbsd + && build_context.metrics.os != TargetOs_haiku + && build_context.metrics.arch != TargetArch_riscv64 + ) { + // OpenBSD and Haiku default to PIE executable. do not pass -no-pie for it. + link_settings = gb_string_appendc(link_settings, "-no-pie "); + } } gbString platform_lib_str = gb_string_make(heap_allocator(), ""); defer (gb_string_free(platform_lib_str)); if (build_context.metrics.os == TargetOs_darwin) { - platform_lib_str = gb_string_appendc(platform_lib_str, "-lSystem -lm -Wl,-syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -L/usr/local/lib"); - } else { - platform_lib_str = gb_string_appendc(platform_lib_str, "-lc -lm"); - } + platform_lib_str = gb_string_appendc(platform_lib_str, "-Wl,-syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -L/usr/local/lib "); - if (build_context.metrics.os == TargetOs_darwin) { - // This sets a requirement of Mountain Lion and up, but the compiler doesn't work without this limit. - if (build_context.minimum_os_version_string.len) { - link_settings = gb_string_append_fmt(link_settings, " -mmacosx-version-min=%.*s ", LIT(build_context.minimum_os_version_string)); - } else if (build_context.metrics.arch == TargetArch_arm64) { - link_settings = gb_string_appendc(link_settings, " -mmacosx-version-min=12.0.0 "); - } else { - link_settings = gb_string_appendc(link_settings, " -mmacosx-version-min=10.12.0 "); + // Homebrew's default library path, checking if it exists to avoid linking warnings. + if (gb_file_exists("/opt/homebrew/lib")) { + platform_lib_str = gb_string_appendc(platform_lib_str, "-L/opt/homebrew/lib "); + } + + // MacPort's default library path, checking if it exists to avoid linking warnings. + if (gb_file_exists("/opt/local/lib")) { + platform_lib_str = gb_string_appendc(platform_lib_str, "-L/opt/local/lib "); + } + + // Only specify this flag if the user has given a minimum version to target. + // This will cause warnings to show up for mismatched libraries. + if (build_context.minimum_os_version_string_given) { + link_settings = gb_string_append_fmt(link_settings, "-mmacosx-version-min=%.*s ", LIT(build_context.minimum_os_version_string)); + } + + if (build_context.build_mode != BuildMode_DynamicLibrary) { + // This points the linker to where the entry point is + link_settings = gb_string_appendc(link_settings, "-e _main "); } - // This points the linker to where the entry point is - link_settings = gb_string_appendc(link_settings, " -e _main "); } - gbString link_command_line = gb_string_make(heap_allocator(), "clang -Wno-unused-command-line-argument "); + if (!build_context.no_rpath) { + // Set the rpath to the $ORIGIN/@loader_path (the path of the executable), + // so that dynamic libraries are looked for at that path. + if (build_context.metrics.os == TargetOs_darwin) { + link_settings = gb_string_appendc(link_settings, "-Wl,-rpath,@loader_path "); + } else { + link_settings = gb_string_appendc(link_settings, "-Wl,-rpath,\\$ORIGIN "); + } + } + + if (!build_context.no_crt) { + platform_lib_str = gb_string_appendc(platform_lib_str, "-lm "); + if (build_context.metrics.os == TargetOs_darwin) { + // NOTE: adding this causes a warning about duplicate libraries, I think it is + // automatically assumed/added by clang when you don't do `-nostdlib`. + // platform_lib_str = gb_string_appendc(platform_lib_str, "-lSystem "); + } else { + platform_lib_str = gb_string_appendc(platform_lib_str, "-lc "); + } + } + + 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); @@ -485,7 +705,12 @@ gb_internal i32 linker_stage(LinkerData *gen) { link_command_line = gb_string_append_fmt(link_command_line, " %.*s ", LIT(build_context.extra_linker_flags)); link_command_line = gb_string_append_fmt(link_command_line, " %s ", link_settings); - result = system_exec_command_line_app("ld-link", link_command_line); + if (build_context.linker_choice == Linker_lld) { + link_command_line = gb_string_append_fmt(link_command_line, " -fuse-ld=lld"); + result = system_exec_command_line_app("lld-link", link_command_line); + } else { + result = system_exec_command_line_app("ld-link", link_command_line); + } if (result) { return result; @@ -494,7 +719,7 @@ gb_internal i32 linker_stage(LinkerData *gen) { if (is_osx && build_context.ODIN_DEBUG) { // NOTE: macOS links DWARF symbols dynamically. Dsymutil will map the stubs in the exe // to the symbols in the object file - result = system_exec_command_line_app("dsymutil", "dsymutil %.*s", LIT(output_filename)); + result = system_exec_command_line_app("dsymutil", "dsymutil \"%.*s\"", LIT(output_filename)); if (result) { return result; 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 857b255f3..0b2bb7956 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); @@ -192,7 +198,7 @@ gb_internal void lb_add_function_type_attributes(LLVMValueRef fn, lbFunctionType // } LLVMSetFunctionCallConv(fn, cc_kind); if (calling_convention == ProcCC_Odin) { - unsigned context_index = offset+arg_count; + unsigned context_index = arg_index; LLVMAddAttributeAtIndex(fn, context_index, noalias_attr); LLVMAddAttributeAtIndex(fn, context_index, nonnull_attr); LLVMAddAttributeAtIndex(fn, context_index, nocapture_attr); @@ -275,7 +281,7 @@ gb_internal i64 lb_alignof(LLVMTypeRef type) { case LLVMIntegerTypeKind: { unsigned w = LLVMGetIntTypeWidth(type); - return gb_clamp((w + 7)/8, 1, build_context.ptr_size); + return gb_clamp((w + 7)/8, 1, build_context.max_align); } case LLVMHalfTypeKind: return 2; @@ -326,7 +332,7 @@ gb_internal i64 lb_alignof(LLVMTypeRef type) { } -#define LB_ABI_INFO(name) lbFunctionType *name(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count, LLVMTypeRef return_type, bool return_is_defined, bool return_is_tuple, ProcCallingConvention calling_convention, Type *original_type) +#define LB_ABI_INFO(name) lbFunctionType *name(lbModule *m, LLVMTypeRef *arg_types, unsigned arg_count, LLVMTypeRef return_type, bool return_is_defined, bool return_is_tuple, ProcCallingConvention calling_convention, Type *original_type) typedef LB_ABI_INFO(lbAbiInfoType); #define LB_ABI_COMPUTE_RETURN_TYPE(name) lbArgType name(lbFunctionType *ft, LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined, bool return_is_tuple) @@ -374,6 +380,7 @@ namespace lbAbi386 { gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type); gb_internal LB_ABI_INFO(abi_info) { + LLVMContextRef c = m->ctx; lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; ft->args = compute_arg_types(c, arg_types, arg_count); @@ -455,6 +462,7 @@ namespace lbAbiAmd64Win64 { gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type); gb_internal LB_ABI_INFO(abi_info) { + LLVMContextRef c = m->ctx; lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; ft->args = compute_arg_types(c, arg_types, arg_count); @@ -523,6 +531,7 @@ namespace lbAbiAmd64SysV { RegClass_SSEInt16, RegClass_SSEInt32, RegClass_SSEInt64, + RegClass_SSEInt128, RegClass_SSEUp, RegClass_X87, RegClass_X87Up, @@ -558,14 +567,23 @@ namespace lbAbiAmd64SysV { Amd64TypeAttribute_StructRect, }; - gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type); gb_internal void classify_with(LLVMTypeRef t, Array *cls, i64 ix, i64 off); gb_internal void fixup(LLVMTypeRef t, Array *cls); gb_internal lbArgType amd64_type(LLVMContextRef c, LLVMTypeRef type, Amd64TypeAttributeKind attribute_kind, ProcCallingConvention calling_convention); gb_internal Array classify(LLVMTypeRef t); gb_internal LLVMTypeRef llreg(LLVMContextRef c, Array const ®_classes, LLVMTypeRef type); + gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type) { + if (!return_is_defined) { + return lb_arg_type_direct(LLVMVoidTypeInContext(c)); + } + LB_ABI_MODIFY_RETURN_IF_TUPLE_MACRO(); + + return amd64_type(c, return_type, Amd64TypeAttribute_StructRect, ft->calling_convention); + } + gb_internal LB_ABI_INFO(abi_info) { + LLVMContextRef c = m->ctx; lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; ft->calling_convention = calling_convention; @@ -574,12 +592,7 @@ namespace lbAbiAmd64SysV { for (unsigned i = 0; i < arg_count; i++) { ft->args[i] = amd64_type(c, arg_types[i], Amd64TypeAttribute_ByVal, calling_convention); } - - if (return_is_defined) { - ft->ret = amd64_type(c, return_type, Amd64TypeAttribute_StructRect, calling_convention); - } else { - ft->ret = lb_arg_type_direct(LLVMVoidTypeInContext(c)); - } + ft->ret = compute_return_type(ft, c, return_type, return_is_defined, return_is_tuple); return ft; } @@ -608,6 +621,10 @@ namespace lbAbiAmd64SysV { } switch (kind) { case LLVMIntegerTypeKind: + if (LLVM_VERSION_MAJOR >= 18 && sz >= 16) { + return true; + } + return false; case LLVMHalfTypeKind: case LLVMFloatTypeKind: case LLVMDoubleTypeKind: @@ -646,10 +663,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); } @@ -796,10 +813,10 @@ namespace lbAbiAmd64SysV { } } + i64 sz = lb_sizeof(type); if (all_ints) { - i64 sz = lb_sizeof(type); for_array(i, reg_classes) { - GB_ASSERT(sz != 0); + GB_ASSERT(sz > 0); // TODO(bill): is this even correct? BECAUSE LLVM DOES NOT DOCUMENT ANY OF THIS!!! if (sz >= 8) { array_add(&types, LLVMIntTypeInContext(c, 64)); @@ -811,12 +828,16 @@ namespace lbAbiAmd64SysV { } } else { for (isize i = 0; i < reg_classes.count; /**/) { + GB_ASSERT(sz > 0); RegClass reg_class = reg_classes[i]; switch (reg_class) { case RegClass_Int: - // TODO(bill): is this even correct? BECAUSE LLVM DOES NOT DOCUMENT ANY OF THIS!!! - array_add(&types, LLVMIntTypeInContext(c, 64)); - break; + { + i64 rs = gb_min(sz, 8); + array_add(&types, LLVMIntTypeInContext(c, cast(unsigned)(rs*8))); + sz -= rs; + break; + } case RegClass_SSEFv: case RegClass_SSEDv: case RegClass_SSEInt8: @@ -856,15 +877,18 @@ namespace lbAbiAmd64SysV { unsigned vec_len = llvec_len(reg_classes, i+1); LLVMTypeRef vec_type = LLVMVectorType(elem_type, vec_len * elems_per_word); array_add(&types, vec_type); + sz -= lb_sizeof(vec_type); i += vec_len; continue; } break; case RegClass_SSEFs: array_add(&types, LLVMFloatTypeInContext(c)); + sz -= 4; break; case RegClass_SSEDs: array_add(&types, LLVMDoubleTypeInContext(c)); + sz -= 8; break; default: GB_PANIC("Unhandled RegClass"); @@ -876,7 +900,8 @@ namespace lbAbiAmd64SysV { if (types.count == 1) { return types[0]; } - return LLVMStructTypeInContext(c, types.data, cast(unsigned)types.count, false); + + return LLVMStructTypeInContext(c, types.data, cast(unsigned)types.count, sz == 0); } gb_internal void classify_with(LLVMTypeRef t, Array *cls, i64 ix, i64 off) { @@ -893,7 +918,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); @@ -979,28 +1012,6 @@ namespace lbAbiAmd64SysV { break; } } - - gb_internal LB_ABI_COMPUTE_RETURN_TYPE(compute_return_type) { - if (!return_is_defined) { - return lb_arg_type_direct(LLVMVoidTypeInContext(c)); - } else if (lb_is_type_kind(return_type, LLVMStructTypeKind)) { - 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(); - - LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type); - return lb_arg_type_indirect(return_type, attr); - } else if (build_context.metrics.os == TargetOs_windows && lb_is_type_kind(return_type, LLVMIntegerTypeKind) && lb_sizeof(return_type) == 16) { - return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 128), nullptr, nullptr); - } - return non_struct(c, return_type); - } }; @@ -1010,6 +1021,7 @@ namespace lbAbiArm64 { gb_internal bool is_homogenous_aggregate(LLVMContextRef c, LLVMTypeRef type, LLVMTypeRef *base_type_, unsigned *member_count_); gb_internal LB_ABI_INFO(abi_info) { + LLVMContextRef c = m->ctx; lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; ft->args = compute_arg_types(c, arg_types, arg_count); @@ -1145,17 +1157,28 @@ namespace lbAbiArm64 { i64 size = lb_sizeof(return_type); if (size <= 16) { LLVMTypeRef cast_type = nullptr; - if (size <= 1) { - cast_type = LLVMInt8TypeInContext(c); - } else if (size <= 2) { - cast_type = LLVMInt16TypeInContext(c); - } else if (size <= 4) { - cast_type = LLVMInt32TypeInContext(c); + + if (size == 0) { + cast_type = LLVMStructTypeInContext(c, nullptr, 0, false); } else if (size <= 8) { - cast_type = LLVMInt64TypeInContext(c); + cast_type = LLVMIntTypeInContext(c, cast(unsigned)(size*8)); } else { unsigned count = cast(unsigned)((size+7)/8); - cast_type = llvm_array_type(LLVMInt64TypeInContext(c), count); + + LLVMTypeRef llvm_i64 = LLVMIntTypeInContext(c, 64); + LLVMTypeRef *types = gb_alloc_array(temporary_allocator(), LLVMTypeRef, count); + + i64 size_copy = size; + for (unsigned i = 0; i < count; i++) { + if (size_copy >= 8) { + types[i] = llvm_i64; + } else { + types[i] = LLVMIntTypeInContext(c, 8*cast(unsigned)size_copy); + } + size_copy -= 8; + } + GB_ASSERT(size_copy <= 0); + cast_type = LLVMStructTypeInContext(c, types, count, true); } return lb_arg_type_direct(return_type, cast_type, nullptr, nullptr); } else { @@ -1188,17 +1211,27 @@ namespace lbAbiArm64 { i64 size = lb_sizeof(type); if (size <= 16) { LLVMTypeRef cast_type = nullptr; - if (size <= 1) { - cast_type = LLVMIntTypeInContext(c, 8); - } else if (size <= 2) { - cast_type = LLVMIntTypeInContext(c, 16); - } else if (size <= 4) { - cast_type = LLVMIntTypeInContext(c, 32); + if (size == 0) { + cast_type = LLVMStructTypeInContext(c, nullptr, 0, false); } else if (size <= 8) { - cast_type = LLVMIntTypeInContext(c, 64); + cast_type = LLVMIntTypeInContext(c, cast(unsigned)(size*8)); } else { unsigned count = cast(unsigned)((size+7)/8); - cast_type = llvm_array_type(LLVMIntTypeInContext(c, 64), count); + + LLVMTypeRef llvm_i64 = LLVMIntTypeInContext(c, 64); + LLVMTypeRef *types = gb_alloc_array(temporary_allocator(), LLVMTypeRef, count); + + i64 size_copy = size; + for (unsigned i = 0; i < count; i++) { + if (size_copy >= 8) { + types[i] = llvm_i64; + } else { + types[i] = LLVMIntTypeInContext(c, 8*cast(unsigned)size_copy); + } + size_copy -= 8; + } + GB_ASSERT(size_copy <= 0); + cast_type = LLVMStructTypeInContext(c, types, count, true); } args[i] = lb_arg_type_direct(type, cast_type, nullptr, nullptr); } else { @@ -1223,20 +1256,22 @@ namespace lbAbiWasm { enum {MAX_DIRECT_STRUCT_SIZE = 32}; gb_internal LB_ABI_INFO(abi_info) { + LLVMContextRef c = m->ctx; lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; + ft->calling_convention = calling_convention; 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; } gb_internal lbArgType non_struct(LLVMContextRef c, LLVMTypeRef type, bool is_return) { - if (!is_return && type == LLVMIntTypeInContext(c, 128)) { - LLVMTypeRef cast_type = LLVMVectorType(LLVMInt64TypeInContext(c), 2); + if (type == LLVMIntTypeInContext(c, 128)) { + // LLVMTypeRef cast_type = LLVMVectorType(LLVMInt64TypeInContext(c), 2); + LLVMTypeRef cast_type = nullptr; return lb_arg_type_direct(type, cast_type, nullptr, nullptr); } - + if (!is_return && lb_sizeof(type) > 8) { return lb_arg_type_indirect(type, nullptr); } @@ -1257,7 +1292,7 @@ namespace lbAbiWasm { case LLVMPointerTypeKind: return true; case LLVMIntegerTypeKind: - return lb_sizeof(type) <= 8; + return lb_sizeof(type) <= 16; } return false; } @@ -1364,14 +1399,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(); @@ -1388,6 +1423,7 @@ namespace lbAbiArm32 { gb_internal lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined); gb_internal LB_ABI_INFO(abi_info) { + LLVMContextRef c = m->ctx; lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; ft->args = compute_arg_types(c, arg_types, arg_count, calling_convention); @@ -1465,8 +1501,256 @@ namespace lbAbiArm32 { } }; +namespace lbAbiRiscv64 { + + gb_internal bool is_register(LLVMTypeRef type) { + LLVMTypeKind kind = LLVMGetTypeKind(type); + switch (kind) { + case LLVMIntegerTypeKind: + case LLVMHalfTypeKind: + case LLVMFloatTypeKind: + case LLVMDoubleTypeKind: + case LLVMPointerTypeKind: + return true; + } + return false; + } + + gb_internal bool is_float(LLVMTypeRef type) { + LLVMTypeKind kind = LLVMGetTypeKind(type); + switch (kind) { + case LLVMHalfTypeKind: + case LLVMFloatTypeKind: + case LLVMDoubleTypeKind: + return true; + default: + return false; + } + } + + gb_internal lbArgType non_struct(LLVMContextRef c, LLVMTypeRef type) { + LLVMAttributeRef attr = nullptr; + LLVMTypeRef i1 = LLVMInt1TypeInContext(c); + if (type == i1) { + attr = lb_create_enum_attribute(c, "zeroext"); + } + return lb_arg_type_direct(type, nullptr, nullptr, attr); + } + + gb_internal void flatten(lbModule *m, Array *fields, LLVMTypeRef type, bool with_padding) { + LLVMTypeKind kind = LLVMGetTypeKind(type); + switch (kind) { + case LLVMStructTypeKind: { + if (LLVMIsPackedStruct(type)) { + array_add(fields, type); + break; + } + + if (!with_padding) { + auto field_remapping = map_get(&m->struct_field_remapping, cast(void *)type); + if (field_remapping) { + auto remap = *field_remapping; + for_array(i, remap) { + flatten(m, fields, LLVMStructGetTypeAtIndex(type, remap[i]), with_padding); + } + break; + } else { + debugf("no field mapping for type: %s\n", LLVMPrintTypeToString(type)); + } + } + + unsigned elem_count = LLVMCountStructElementTypes(type); + for (unsigned i = 0; i < elem_count; i += 1) { + flatten(m, fields, LLVMStructGetTypeAtIndex(type, i), with_padding); + } + break; + } + case LLVMArrayTypeKind: { + unsigned len = LLVMGetArrayLength(type); + LLVMTypeRef elem = OdinLLVMGetArrayElementType(type); + for (unsigned i = 0; i < len; i += 1) { + flatten(m, fields, elem, with_padding); + } + break; + } + default: + array_add(fields, type); + } + } + + gb_internal lbArgType compute_arg_type(lbModule *m, LLVMTypeRef type, int *gprs_left, int *fprs_left, Type *odin_type) { + LLVMContextRef c = m->ctx; + + int xlen = 8; // 8 byte int register size for riscv64. + + // NOTE: we are requiring both of these to be enabled so we can just hard-code 8. + // int flen = 0; + // if (check_target_feature_is_enabled(str_lit("d"), nullptr)) { + // flen = 8; // Double precision floats are enabled. + // } else if (check_target_feature_is_enabled(str_lit("f"), nullptr)) { + // flen = 4; // Single precision floats are enabled. + // } + int flen = 8; + + LLVMTypeKind kind = LLVMGetTypeKind(type); + i64 size = lb_sizeof(type); + + if (size == 0) { + return lb_arg_type_direct(type, LLVMStructTypeInContext(c, nullptr, 0, false), nullptr, nullptr); + } + + LLVMTypeRef orig_type = type; + + // Flatten down the type so it is easier to check all the ABI conditions. + // Note that we also need to remove all implicit padding fields Odin adds so we keep ABI + // compatibility for struct declarations. + if (kind == LLVMStructTypeKind && size <= gb_max(2*xlen, 2*flen)) { + Array fields = array_make(temporary_allocator(), 0, LLVMCountStructElementTypes(type)); + flatten(m, &fields, type, false); + + if (fields.count == 1) { + type = fields[0]; + } else { + type = LLVMStructTypeInContext(c, fields.data, cast(unsigned)fields.count, false); + } + + kind = LLVMGetTypeKind(type); + size = lb_sizeof(type); + GB_ASSERT_MSG(size == lb_sizeof(orig_type), "flattened: %s of size %d, original: %s of size %d", LLVMPrintTypeToString(type), size, LLVMPrintTypeToString(orig_type), lb_sizeof(orig_type)); + } + + if (is_float(type) && size <= flen && *fprs_left >= 1) { + *fprs_left -= 1; + return non_struct(c, orig_type); + } + + if (kind == LLVMStructTypeKind && size <= 2*flen) { + unsigned elem_count = LLVMCountStructElementTypes(type); + if (elem_count == 2) { + LLVMTypeRef ty1 = LLVMStructGetTypeAtIndex(type, 0); + i64 ty1s = lb_sizeof(ty1); + LLVMTypeRef ty2 = LLVMStructGetTypeAtIndex(type, 1); + i64 ty2s = lb_sizeof(ty2); + + if (is_float(ty1) && is_float(ty2) && ty1s <= flen && ty2s <= flen && *fprs_left >= 2) { + *fprs_left -= 2; + return lb_arg_type_direct(orig_type, type, nullptr, nullptr); + } + + if (is_float(ty1) && is_register(ty2) && ty1s <= flen && ty2s <= xlen && *fprs_left >= 1 && *gprs_left >= 1) { + *fprs_left -= 1; + *gprs_left -= 1; + return lb_arg_type_direct(orig_type, type, nullptr, nullptr); + } + + if (is_register(ty1) && is_float(ty2) && ty1s <= xlen && ty2s <= flen && *gprs_left >= 1 && *fprs_left >= 1) { + *fprs_left -= 1; + *gprs_left -= 1; + return lb_arg_type_direct(orig_type, type, nullptr, nullptr); + } + } + } + + // At this point all the cases for floating point registers are exhausted, fit it into + // integer registers or the stack. + // LLVM automatically handles putting args on the stack so we don't check the amount of registers that are left here. + + if (size <= xlen) { + *gprs_left -= 1; + if (is_register(type)) { + return non_struct(c, orig_type); + } else { + return lb_arg_type_direct(orig_type, LLVMIntTypeInContext(c, cast(unsigned)(size*8)), nullptr, nullptr); + } + } else if (size <= 2*xlen) { + LLVMTypeRef *fields = gb_alloc_array(temporary_allocator(), LLVMTypeRef, 2); + fields[0] = LLVMIntTypeInContext(c, cast(unsigned)(xlen*8)); + fields[1] = LLVMIntTypeInContext(c, cast(unsigned)((size-xlen)*8)); + + *gprs_left -= 2; + return lb_arg_type_direct(orig_type, LLVMStructTypeInContext(c, fields, 2, false), nullptr, nullptr); + } else { + return lb_arg_type_indirect(orig_type, nullptr); + } + } + + gb_internal Array compute_arg_types(lbModule *m, LLVMTypeRef *arg_types, unsigned arg_count, ProcCallingConvention calling_convention, Type *odin_type, int *gprs, int *fprs) { + auto args = array_make(lb_function_type_args_allocator(), arg_count); + + for (unsigned i = 0; i < arg_count; i++) { + LLVMTypeRef type = arg_types[i]; + args[i] = compute_arg_type(m, type, gprs, fprs, odin_type); + } + + return args; + } + + gb_internal lbArgType compute_return_type(lbFunctionType *ft, lbModule *m, LLVMTypeRef return_type, bool return_is_defined, bool return_is_tuple, Type *odin_type, int *agprs) { + LLVMContextRef c = m->ctx; + + if (!return_is_defined) { + return lb_arg_type_direct(LLVMVoidTypeInContext(c)); + } + + // There are two registers for return types. + int gprs = 2; + int fprs = 2; + lbArgType ret = compute_arg_type(m, return_type, &gprs, &fprs, odin_type); + + // Return didn't fit into the return registers, so caller allocates and it is returned via + // an out-pointer. + if (ret.kind == lbArg_Indirect) { + + // Transform multiple return into out pointers if possible. + if (return_is_tuple) { + if (lb_is_type_kind(return_type, LLVMStructTypeKind)) { + int field_count = cast(int)LLVMCountStructElementTypes(return_type); + if (field_count > 1 && field_count <= *agprs) { + ft->original_arg_count = ft->args.count; + ft->multiple_return_original_type = return_type; + + for (int i = 0; i < field_count-1; i++) { + LLVMTypeRef field_type = LLVMStructGetTypeAtIndex(return_type, i); + LLVMTypeRef field_pointer_type = LLVMPointerType(field_type, 0); + lbArgType ret_partial = lb_arg_type_direct(field_pointer_type); + array_add(&ft->args, ret_partial); + *agprs -= 1; + } + GB_ASSERT(*agprs >= 0); + + // override the return type for the last field + LLVMTypeRef new_return_type = LLVMStructGetTypeAtIndex(return_type, field_count-1); + return compute_return_type(ft, m, new_return_type, true, false, odin_type, agprs); + } + } + } + + LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", ret.type); + return lb_arg_type_indirect(ret.type, attr); + } + + return ret; + } + + gb_internal LB_ABI_INFO(abi_info) { + lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); + ft->ctx = m->ctx; + ft->calling_convention = calling_convention; + + int gprs = 8; + int fprs = 8; + + ft->args = compute_arg_types(m, arg_types, arg_count, calling_convention, original_type, &gprs, &fprs); + ft->ret = compute_return_type(ft, m, return_type, return_is_defined, return_is_tuple, original_type, &gprs); + + return ft; + } +} + gb_internal LB_ABI_INFO(lb_get_abi_info_internal) { + LLVMContextRef c = m->ctx; + switch (calling_convention) { case ProcCC_None: case ProcCC_InlineAsm: @@ -1487,33 +1771,35 @@ gb_internal LB_ABI_INFO(lb_get_abi_info_internal) { } case ProcCC_Win64: GB_ASSERT(build_context.metrics.arch == TargetArch_amd64); - return lbAbiAmd64Win64::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + return lbAbiAmd64Win64::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); case ProcCC_SysV: GB_ASSERT(build_context.metrics.arch == TargetArch_amd64); - return lbAbiAmd64SysV::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + return lbAbiAmd64SysV::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); } switch (build_context.metrics.arch) { case TargetArch_amd64: if (build_context.metrics.os == TargetOs_windows) { - return lbAbiAmd64Win64::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + return lbAbiAmd64Win64::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); } else if (build_context.metrics.abi == TargetABI_Win64) { - return lbAbiAmd64Win64::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + return lbAbiAmd64Win64::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); } else if (build_context.metrics.abi == TargetABI_SysV) { - return lbAbiAmd64SysV::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + return lbAbiAmd64SysV::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); } else { - return lbAbiAmd64SysV::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + return lbAbiAmd64SysV::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); } case TargetArch_i386: - return lbAbi386::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + return lbAbi386::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); case TargetArch_arm32: - return lbAbiArm32::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + return lbAbiArm32::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); case TargetArch_arm64: - return lbAbiArm64::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + return lbAbiArm64::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); case TargetArch_wasm32: - return lbAbiWasm::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + return lbAbiWasm::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); case TargetArch_wasm64p32: - return lbAbiWasm::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + return lbAbiWasm::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); + case TargetArch_riscv64: + return lbAbiRiscv64::abi_info(m, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention, original_type); } GB_PANIC("Unsupported ABI"); @@ -1523,7 +1809,7 @@ gb_internal LB_ABI_INFO(lb_get_abi_info_internal) { gb_internal LB_ABI_INFO(lb_get_abi_info) { lbFunctionType *ft = lb_get_abi_info_internal( - c, + m, arg_types, arg_count, return_type, return_is_defined, ALLOW_SPLIT_MULTI_RETURNS && return_is_tuple && is_calling_convention_odin(calling_convention), @@ -1535,7 +1821,7 @@ gb_internal LB_ABI_INFO(lb_get_abi_info) { // This is to make it consistent when and how it is handled if (calling_convention == ProcCC_Odin) { // append the `context` pointer - lbArgType context_param = lb_arg_type_direct(LLVMPointerType(LLVMInt8TypeInContext(c), 0)); + lbArgType context_param = lb_arg_type_direct(LLVMPointerType(LLVMInt8TypeInContext(m->ctx), 0)); array_add(&ft->args, context_param); } diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 276abc2d4..696ced0df 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 @@ -21,6 +24,79 @@ #include "llvm_backend_stmt.cpp" #include "llvm_backend_proc.cpp" +String get_default_microarchitecture() { + String default_march = str_lit("generic"); + if (build_context.metrics.arch == TargetArch_amd64) { + // NOTE(bill): x86-64-v2 is more than enough for everyone + // + // x86-64: CMOV, CMPXCHG8B, FPU, FXSR, MMX, FXSR, SCE, SSE, SSE2 + // x86-64-v2: (close to Nehalem) CMPXCHG16B, LAHF-SAHF, POPCNT, SSE3, SSE4.1, SSE4.2, SSSE3 + // x86-64-v3: (close to Haswell) AVX, AVX2, BMI1, BMI2, F16C, FMA, LZCNT, MOVBE, XSAVE + // x86-64-v4: AVX512F, AVX512BW, AVX512CD, AVX512DQ, AVX512VL + if (ODIN_LLVM_MINIMUM_VERSION_12) { + if (build_context.metrics.os == TargetOs_freestanding) { + default_march = str_lit("x86-64"); + } else { + default_march = str_lit("x86-64-v2"); + } + } + } else if (build_context.metrics.arch == TargetArch_riscv64) { + default_march = str_lit("generic-rv64"); + } + + 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(); + + // NOTE(laytan): for riscv64 to work properly with Odin, we need to enforce some features. + // and we also overwrite the generic target to include more features so we don't default to + // a potato feature set. + if (bc->metrics.arch == TargetArch_riscv64) { + if (microarch == str_lit("generic-rv64")) { + // This is what clang does by default (on -march=rv64gc for General Computing), seems good to also default to. + String features = str_lit("64bit,a,c,d,f,m,relax,zicsr,zifencei"); + + // Update the features string so LLVM uses it later. + if (bc->target_features_string.len > 0) { + bc->target_features_string = concatenate3_strings(permanent_allocator(), features, str_lit(","), bc->target_features_string); + } else { + bc->target_features_string = features; + } + + return features; + } + } + + for (int i = off; i < off+target_microarch_counts[bc->metrics.arch]; i += 1) { + if (microarch_features_list[i].microarch == microarch) { + return microarch_features_list[i].features; + } + } + + GB_PANIC("unknown microarch: %.*s", LIT(microarch)); + return {}; +} gb_internal void lb_add_foreign_library_path(lbModule *m, Entity *e) { if (e == nullptr) { @@ -82,19 +158,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(); @@ -315,7 +400,7 @@ gb_internal void lb_add_callsite_force_inline(lbProcedure *p, lbValue ret_value) gb_internal lbValue lb_hasher_proc_for_type(lbModule *m, Type *type) { type = core_type(type); - GB_ASSERT_MSG(is_type_valid_for_keys(type), "%s", type_to_string(type)); + GB_ASSERT_MSG(is_type_comparable(type), "%s", type_to_string(type)); Type *pt = alloc_type_pointer(type); @@ -914,14 +999,16 @@ gb_internal lbValue lb_const_hash(lbModule *m, lbValue key, Type *key_type) { gb_internal lbValue lb_gen_map_key_hash(lbProcedure *p, lbValue const &map_ptr, lbValue key, lbValue *key_ptr_) { TEMPORARY_ALLOCATOR_GUARD(); - lbValue key_ptr = lb_address_from_load_or_generate_local(p, key); + Type* key_type = base_type(type_deref(map_ptr.type))->Map.key; + + lbValue real_key = lb_emit_conv(p, key, key_type); + + lbValue key_ptr = lb_address_from_load_or_generate_local(p, real_key); key_ptr = lb_emit_conv(p, key_ptr, t_rawptr); if (key_ptr_) *key_ptr_ = key_ptr; - Type* key_type = base_type(type_deref(map_ptr.type))->Map.key; - - lbValue hashed_key = lb_const_hash(p->module, key, key_type); + lbValue hashed_key = lb_const_hash(p->module, real_key, key_type); if (hashed_key.value == nullptr) { lbValue hasher = lb_hasher_proc_for_type(p->module, key_type); @@ -1009,8 +1096,6 @@ gb_internal void lb_internal_dynamic_map_set(lbProcedure *p, lbValue const &map_ } gb_internal lbValue lb_dynamic_map_reserve(lbProcedure *p, lbValue const &map_ptr, isize const capacity, TokenPos const &pos) { - GB_ASSERT(!build_context.no_dynamic_literals); - TEMPORARY_ALLOCATOR_GUARD(); String proc_name = {}; @@ -1034,36 +1119,6 @@ struct lbGlobalVariable { bool is_initialized; }; -gb_internal lbProcedure *lb_create_startup_type_info(lbModule *m) { - if (build_context.no_rtti) { - return nullptr; - } - Type *proc_type = alloc_type_proc(nullptr, nullptr, 0, nullptr, 0, false, ProcCC_CDecl); - - lbProcedure *p = lb_create_dummy_procedure(m, str_lit(LB_STARTUP_TYPE_INFO_PROC_NAME), proc_type); - p->is_startup = true; - LLVMSetLinkage(p->value, LLVMInternalLinkage); - - lb_add_attribute_to_proc(m, p->value, "nounwind"); - if (!LB_USE_GIANT_PACKED_STRUCT) { - lb_add_attribute_to_proc(m, p->value, "optnone"); - lb_add_attribute_to_proc(m, p->value, "noinline"); - } - - lb_begin_procedure_body(p); - - lb_setup_type_info_data(p); - - lb_end_procedure_body(p); - - 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); - } - return p; -} gb_internal lbProcedure *lb_create_objc_names(lbModule *main_module) { if (build_context.metrics.os != TargetOs_darwin) { @@ -1105,7 +1160,54 @@ gb_internal void lb_finalize_objc_names(lbProcedure *p) { lb_end_procedure_body(p); } -gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProcedure *startup_type_info, lbProcedure *objc_names, Array &global_variables) { // Startup Runtime +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); lbProcedure *p = lb_create_dummy_procedure(main_module, str_lit(LB_STARTUP_RUNTIME_PROC_NAME), proc_type); @@ -1115,9 +1217,7 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc lb_begin_procedure_body(p); - if (startup_type_info) { - LLVMBuildCall2(p->builder, lb_type_internal_for_procedures_raw(main_module, startup_type_info->type), startup_type_info->value, nullptr, 0, ""); - } + lb_setup_type_info_data(main_module); if (objc_names) { LLVMBuildCall2(p->builder, lb_type_internal_for_procedures_raw(main_module, objc_names->type), objc_names->value, nullptr, 0, ""); @@ -1142,6 +1242,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)); @@ -1156,6 +1260,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 { @@ -1177,7 +1285,7 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc lbValue data = lb_emit_struct_ep(p, var.var, 0); lbValue ti = lb_emit_struct_ep(p, var.var, 1); lb_emit_store(p, data, lb_emit_conv(p, gp, t_rawptr)); - lb_emit_store(p, ti, lb_type_info(main_module, var_type)); + lb_emit_store(p, ti, lb_type_info(p, var_type)); } else { LLVMTypeRef vt = llvm_addr_type(p->module, var.var); lbValue src0 = lb_emit_conv(p, var.init, t); @@ -1188,8 +1296,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) { @@ -1200,13 +1309,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; } @@ -1229,35 +1332,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; @@ -1306,17 +1417,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(); @@ -1337,7 +1463,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)) { @@ -1363,7 +1491,7 @@ gb_internal WORKER_TASK_PROC(lb_llvm_emit_worker_proc) { if (LLVMTargetMachineEmitToFile(wd->target_machine, wd->m->mod, cast(char *)wd->filepath_obj.text, wd->code_gen_file_type, &llvm_error)) { gb_printf_err("LLVM Error: %s\n", llvm_error); - gb_exit(1); + exit_with_errors(); } debugf("Generated File: %.*s\n", LIT(wd->filepath_obj)); return 0; @@ -1391,10 +1519,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]); @@ -1402,7 +1526,6 @@ gb_internal WORKER_TASK_PROC(lb_llvm_function_pass_per_module) { } if (m == &m->gen->default_module) { - lb_llvm_function_pass_per_function_internal(m, m->gen->startup_type_info); lb_llvm_function_pass_per_function_internal(m, m->gen->startup_runtime); lb_llvm_function_pass_per_function_internal(m, m->gen->cleanup_runtime); lb_llvm_function_pass_per_function_internal(m, m->gen->objc_names); @@ -1419,15 +1542,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; } } @@ -1478,13 +1598,12 @@ gb_internal WORKER_TASK_PROC(lb_llvm_module_pass_worker_proc) { auto passes = array_make(heap_allocator(), 0, 64); defer (array_free(&passes)); - - LLVMPassBuilderOptionsRef pb_options = LLVMCreatePassBuilderOptions(); defer (LLVMDisposePassBuilderOptions(pb_options)); switch (build_context.optimization_level) { case -1: + array_add(&passes, "function(annotation-remarks)"); break; case 0: array_add(&passes, "always-inline"); @@ -1493,6 +1612,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, @@ -1508,13 +1628,14 @@ globalopt, function( mem2reg, instcombine, - simplifycfg), - require, - function( - invalidate - ), - require, - cgscc( + simplifycfg +), +require, +function( + invalidate +), +require, +cgscc( devirt<4>( inline, inline, @@ -1615,10 +1736,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, @@ -1743,11 +1996,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, @@ -1875,6 +2261,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; } @@ -1935,7 +2450,7 @@ verify gb_printf_err("LLVM Error: %s\n", llvm_error); } } - gb_exit(1); + exit_with_errors(); return 1; } #endif @@ -1954,16 +2469,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) { @@ -1977,26 +2495,23 @@ 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) { - for (auto const &entry : gen->modules) { - lbModule *m = entry.value; - if (m->debug_builder != nullptr) { - lb_debug_complete_types(m); - } - } for (auto const &entry : gen->modules) { lbModule *m = entry.value; if (m->debug_builder != nullptr) { @@ -2006,32 +2521,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) { @@ -2055,20 +2583,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 = {}; @@ -2106,43 +2651,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); - gb_exit(1); - return false; - } - } - gb_exit(1); - 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; } @@ -2178,7 +2713,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); @@ -2209,7 +2743,7 @@ gb_internal bool lb_llvm_object_generation(lbGenerator *gen, bool do_threading) if (LLVMTargetMachineEmitToFile(m->target_machine, m->mod, cast(char *)filepath_obj.text, code_gen_file_type, &llvm_error)) { gb_printf_err("LLVM Error: %s\n", llvm_error); - gb_exit(1); + exit_with_errors(); return false; } debugf("Generated File: %.*s\n", LIT(filepath_obj)); @@ -2363,12 +2897,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; @@ -2389,28 +2918,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); - gb_exit(1); - } + lb_verify_function(m, p, true); } @@ -2486,41 +2998,28 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { LLVMCodeModel code_mode = LLVMCodeModelDefault; if (is_arch_wasm()) { code_mode = LLVMCodeModelJITDefault; - } else if (build_context.metrics.os == TargetOs_freestanding) { + } else if (is_arch_x86() && build_context.metrics.os == TargetOs_freestanding) { code_mode = LLVMCodeModelKernel; } - char const *host_cpu_name = LLVMGetHostCPUName(); - char const *llvm_cpu = "generic"; - char const *llvm_features = ""; - if (build_context.microarch.len != 0) { - if (build_context.microarch == "native") { - llvm_cpu = host_cpu_name; - } else { - llvm_cpu = alloc_cstring(permanent_allocator(), build_context.microarch); - } - if (gb_strcmp(llvm_cpu, host_cpu_name) == 0) { - llvm_features = LLVMGetHostCPUFeatures(); - } - } else if (build_context.metrics.arch == TargetArch_amd64) { - // NOTE(bill): x86-64-v2 is more than enough for everyone - // - // x86-64: CMOV, CMPXCHG8B, FPU, FXSR, MMX, FXSR, SCE, SSE, SSE2 - // x86-64-v2: (close to Nehalem) CMPXCHG16B, LAHF-SAHF, POPCNT, SSE3, SSE4.1, SSE4.2, SSSE3 - // x86-64-v3: (close to Haswell) AVX, AVX2, BMI1, BMI2, F16C, FMA, LZCNT, MOVBE, XSAVE - // x86-64-v4: AVX512F, AVX512BW, AVX512CD, AVX512DQ, AVX512VL - if (ODIN_LLVM_MINIMUM_VERSION_12) { - if (build_context.metrics.os == TargetOs_freestanding) { - llvm_cpu = "x86-64"; - } else { - llvm_cpu = "x86-64-v2"; - } + 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); } - if (build_context.target_features_set.entries.count != 0) { - llvm_features = target_features_set_to_cstring(permanent_allocator(), false); - } + debugf("CPU: %.*s, Features: %s\n", LIT(llvm_cpu), llvm_features); // GB_ASSERT_MSG(LLVMTargetHasAsmBackend(target)); @@ -2548,10 +3047,16 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { switch (build_context.reloc_mode) { case RelocMode_Default: - if (build_context.metrics.os == TargetOs_openbsd) { - // Always use PIC for OpenBSD: it defaults to PIE + if (build_context.metrics.os == TargetOs_openbsd || build_context.metrics.os == TargetOs_haiku) { + // Always use PIC for OpenBSD and Haiku: they default to PIE reloc_mode = LLVMRelocPIC; } + + if (build_context.metrics.arch == TargetArch_riscv64) { + // NOTE(laytan): didn't seem to work without this. + reloc_mode = LLVMRelocPIC; + } + break; case RelocMode_Static: reloc_mode = LLVMRelocStatic; @@ -2566,7 +3071,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { for (auto const &entry : gen->modules) { LLVMTargetMachineRef target_machine = LLVMCreateTargetMachine( - target, target_triple, llvm_cpu, + target, target_triple, (const char *)llvm_cpu.text, llvm_features, code_gen_level, reloc_mode, @@ -2574,6 +3079,13 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { lbModule *m = entry.value; m->target_machine = target_machine; LLVMSetModuleDataLayout(m->mod, LLVMCreateTargetDataLayout(target_machine)); + + #if LLVM_VERSION_MAJOR >= 18 + if (build_context.fast_isel) { + LLVMSetTargetMachineFastISel(m->target_machine, true); + } + #endif + array_add(&target_machines, target_machine); } @@ -2637,17 +3149,19 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { { // Add type info data isize max_type_info_count = info->minimum_dependency_type_info_set.count+1; - Type *t = alloc_type_array(t_type_info, max_type_info_count); + Type *t = alloc_type_array(t_type_info_ptr, max_type_info_count); // IMPORTANT NOTE(bill): As LLVM does not have a union type, an array of unions cannot be initialized // at compile time without cheating in some way. This means to emulate an array of unions is to use // a giant packed struct of "corrected" data types. - LLVMTypeRef internal_llvm_type = lb_setup_type_info_data_internal_type(m, max_type_info_count); + LLVMTypeRef internal_llvm_type = lb_type(m, t); LLVMValueRef g = LLVMAddGlobal(m->mod, internal_llvm_type, LB_TYPE_INFO_DATA_NAME); LLVMSetInitializer(g, LLVMConstNull(internal_llvm_type)); LLVMSetLinkage(g, USE_SEPARATE_MODULES ? LLVMExternalLinkage : LLVMInternalLinkage); + LLVMSetUnnamedAddress(g, LLVMGlobalUnnamedAddr); + LLVMSetGlobalConstant(g, true); lbValue value = {}; value.value = g; @@ -2656,15 +3170,11 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { lb_global_type_info_data_entity = alloc_entity_variable(nullptr, make_token_ident(LB_TYPE_INFO_DATA_NAME), t, EntityState_Resolved); lb_add_entity(m, lb_global_type_info_data_entity, value); - if (LB_USE_GIANT_PACKED_STRUCT) { - LLVMSetLinkage(g, LLVMPrivateLinkage); - LLVMSetUnnamedAddress(g, LLVMGlobalUnnamedAddr); - LLVMSetGlobalConstant(g, /*true*/false); - } } { // Type info member buffer // NOTE(bill): Removes need for heap allocation by making it global memory isize count = 0; + isize offsets_extra = 0; for (Type *t : m->info->type_info_types) { isize index = lb_type_info_index(m->info, t, false); @@ -2682,67 +3192,28 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { case Type_Tuple: count += t->Tuple.variables.count; break; + case Type_BitField: + count += t->BitField.fields.count; + // Twice is needed for the bit_offsets + offsets_extra += t->BitField.fields.count; + break; } } - { - char const *name = LB_TYPE_INFO_TYPES_NAME; - Type *t = alloc_type_array(t_type_info_ptr, count); + auto const global_type_info_make = [](lbModule *m, char const *name, Type *elem_type, i64 count) -> lbAddr { + Type *t = alloc_type_array(elem_type, count); LLVMValueRef g = LLVMAddGlobal(m->mod, lb_type(m, t), name); LLVMSetInitializer(g, LLVMConstNull(lb_type(m, t))); LLVMSetLinkage(g, LLVMInternalLinkage); - if (LB_USE_GIANT_PACKED_STRUCT) { - lb_make_global_private_const(g); - } - lb_global_type_info_member_types = lb_addr({g, alloc_type_pointer(t)}); + lb_make_global_private_const(g); + return lb_addr({g, alloc_type_pointer(t)}); + }; - } - { - char const *name = LB_TYPE_INFO_NAMES_NAME; - Type *t = alloc_type_array(t_string, count); - LLVMValueRef g = LLVMAddGlobal(m->mod, lb_type(m, t), name); - LLVMSetInitializer(g, LLVMConstNull(lb_type(m, t))); - LLVMSetLinkage(g, LLVMInternalLinkage); - if (LB_USE_GIANT_PACKED_STRUCT) { - lb_make_global_private_const(g); - } - lb_global_type_info_member_names = lb_addr({g, alloc_type_pointer(t)}); - } - { - char const *name = LB_TYPE_INFO_OFFSETS_NAME; - Type *t = alloc_type_array(t_uintptr, count); - LLVMValueRef g = LLVMAddGlobal(m->mod, lb_type(m, t), name); - LLVMSetInitializer(g, LLVMConstNull(lb_type(m, t))); - LLVMSetLinkage(g, LLVMInternalLinkage); - if (LB_USE_GIANT_PACKED_STRUCT) { - lb_make_global_private_const(g); - } - lb_global_type_info_member_offsets = lb_addr({g, alloc_type_pointer(t)}); - } - - { - char const *name = LB_TYPE_INFO_USINGS_NAME; - Type *t = alloc_type_array(t_bool, count); - LLVMValueRef g = LLVMAddGlobal(m->mod, lb_type(m, t), name); - LLVMSetInitializer(g, LLVMConstNull(lb_type(m, t))); - LLVMSetLinkage(g, LLVMInternalLinkage); - if (LB_USE_GIANT_PACKED_STRUCT) { - lb_make_global_private_const(g); - } - lb_global_type_info_member_usings = lb_addr({g, alloc_type_pointer(t)}); - } - - { - char const *name = LB_TYPE_INFO_TAGS_NAME; - Type *t = alloc_type_array(t_string, count); - LLVMValueRef g = LLVMAddGlobal(m->mod, lb_type(m, t), name); - LLVMSetInitializer(g, LLVMConstNull(lb_type(m, t))); - LLVMSetLinkage(g, LLVMInternalLinkage); - if (LB_USE_GIANT_PACKED_STRUCT) { - lb_make_global_private_const(g); - } - lb_global_type_info_member_tags = lb_addr({g, alloc_type_pointer(t)}); - } + lb_global_type_info_member_types = global_type_info_make(m, LB_TYPE_INFO_TYPES_NAME, t_type_info_ptr, count); + lb_global_type_info_member_names = global_type_info_make(m, LB_TYPE_INFO_NAMES_NAME, t_string, count); + lb_global_type_info_member_offsets = global_type_info_make(m, LB_TYPE_INFO_OFFSETS_NAME, t_uintptr, count+offsets_extra); + lb_global_type_info_member_usings = global_type_info_make(m, LB_TYPE_INFO_USINGS_NAME, t_bool, count); + lb_global_type_info_member_tags = global_type_info_make(m, LB_TYPE_INFO_TAGS_NAME, t_string, count); } } @@ -2829,8 +3300,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))); } @@ -2838,7 +3307,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); @@ -2855,18 +3324,26 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { if (!is_type_any(e->type) && !is_type_union(e->type)) { if (tav.mode != Addressing_Invalid) { if (tav.value.kind != ExactValue_Invalid) { + bool is_rodata = e->kind == Entity_Variable && e->Variable.is_rodata; ExactValue v = tav.value; - lbValue init = lb_const_value(m, tav.type, v); + lbValue init = lb_const_value(m, tav.type, v, false, is_rodata); LLVMSetInitializer(g.value, init.value); var.is_initialized = true; + if (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); @@ -2903,12 +3380,11 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { } } - TIME_SECTION("LLVM Runtime Type Information Creation"); - gen->startup_type_info = lb_create_startup_type_info(default_module); + TIME_SECTION("LLVM Runtime Objective-C Names Creation"); gen->objc_names = lb_create_objc_names(default_module); TIME_SECTION("LLVM Runtime Startup Creation (Global Variables & @(init))"); - gen->startup_runtime = lb_create_startup_runtime(default_module, gen->startup_type_info, gen->objc_names, global_variables); + gen->startup_runtime = lb_create_startup_runtime(default_module, gen->objc_names, global_variables); TIME_SECTION("LLVM Runtime Cleanup Creation & @(fini)"); gen->cleanup_runtime = lb_create_cleanup_runtime(default_module); @@ -2988,7 +3464,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { 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); - gb_exit(1); + exit_with_errors(); return false; } array_add(&gen->output_temp_paths, filepath_ll); @@ -3002,7 +3478,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"); @@ -3047,7 +3538,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { } } - gb_sort_array(gen->foreign_libraries.data, gen->foreign_libraries.count, foreign_library_cmp); + array_sort(gen->foreign_libraries, foreign_library_cmp); return true; } diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 5894dd38a..42d283a1e 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" @@ -62,6 +57,10 @@ #define LB_USE_NEW_PASS_SYSTEM 0 #endif +#if LLVM_VERSION_MAJOR >= 19 +#define LLVMDIBuilderInsertDeclareAtEnd(...) LLVMDIBuilderInsertDeclareRecordAtEnd(__VA_ARGS__) +#endif + gb_internal bool lb_use_new_pass_system(void) { return LB_USE_NEW_PASS_SYSTEM; } @@ -80,10 +79,11 @@ enum lbAddrKind { lbAddr_Context, lbAddr_SoaVariable, - lbAddr_RelativePointer, lbAddr_Swizzle, lbAddr_SwizzleLarge, + + lbAddr_BitField, }; struct lbAddr { @@ -106,9 +106,6 @@ struct lbAddr { lbValue index; Ast *node; } index_set; - struct { - bool deref; - } relative; struct { Type *type; u8 count; // 2, 3, or 4 components @@ -118,6 +115,11 @@ struct lbAddr { Type *type; Slice indices; } swizzle_large; + struct { + Type *type; + i64 bit_offset; + i64 bit_size; + } bitfield; }; }; @@ -132,14 +134,15 @@ enum lbFunctionPassManagerKind { lbFunctionPassManager_default, lbFunctionPassManager_default_without_memcpy, lbFunctionPassManager_none, - lbFunctionPassManager_minimal, - lbFunctionPassManager_size, - lbFunctionPassManager_speed, - lbFunctionPassManager_aggressive, - lbFunctionPassManager_COUNT }; +struct lbPadType { + i64 padding; + i64 padding_align; + LLVMTypeRef type; +}; + struct lbModule { LLVMModuleRef mod; LLVMContextRef ctx; @@ -150,6 +153,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 @@ -179,7 +183,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; @@ -191,8 +196,6 @@ struct lbModule { RecursiveMutex debug_values_mutex; PtrMap debug_values; - Array debug_incomplete_types; - StringMap objc_classes; StringMap objc_selectors; @@ -202,6 +205,15 @@ struct lbModule { PtrMap exact_value_compound_literal_addr_map; // Key: Ast_CompoundLit LLVMPassManagerRef function_pass_managers[lbFunctionPassManager_COUNT]; + + BlockingMutex pad_types_mutex; + Array pad_types; +}; + +struct lbEntityCorrection { + lbModule * other_module; + Entity * e; + char const *cname; }; struct lbGenerator : LinkerData { @@ -217,10 +229,13 @@ struct lbGenerator : LinkerData { std::atomic global_array_index; std::atomic global_generated_index; - lbProcedure *startup_type_info; + isize used_module_count; + lbProcedure *startup_runtime; lbProcedure *cleanup_runtime; lbProcedure *objc_names; + + MPSCQueue entities_to_correct_linkage; }; @@ -299,6 +314,11 @@ enum lbProcedureFlag : u32 { lbProcedureFlag_DebugAllocaCopy = 1<<1, }; +struct lbVariadicReuseSlices { + Type *slice_type; + lbAddr slice_addr; +}; + struct lbProcedure { u32 flags; u16 state_flags; @@ -339,6 +359,14 @@ struct lbProcedure { bool in_multi_assignment; Array raw_input_parameters; + bool uses_branch_location; + TokenPos branch_location_pos; + TokenPos curr_token_pos; + + Array variadic_reuses; + lbAddr variadic_reuse_base_array_ptr; + + LLVMValueRef temp_callee_return_struct_memory; Ast *curr_stmt; Array scope_stack; @@ -365,7 +393,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); @@ -383,7 +411,7 @@ gb_internal lbBlock *lb_create_block(lbProcedure *p, char const *name, bool appe gb_internal lbValue lb_const_nil(lbModule *m, Type *type); gb_internal lbValue lb_const_undef(lbModule *m, Type *type); -gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bool allow_local=true); +gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bool allow_local=true, bool is_rodata=false); gb_internal lbValue lb_const_bool(lbModule *m, Type *type, bool value); gb_internal lbValue lb_const_int(lbModule *m, Type *type, u64 value); @@ -420,7 +448,8 @@ gb_internal lbValue lb_emit_matrix_ev(lbProcedure *p, lbValue s, isize row, isiz gb_internal lbValue lb_emit_arith(lbProcedure *p, TokenKind op, lbValue lhs, lbValue rhs, Type *type); gb_internal lbValue lb_emit_byte_swap(lbProcedure *p, lbValue value, Type *end_type); -gb_internal void lb_emit_defer_stmts(lbProcedure *p, lbDeferExitKind kind, lbBlock *block); +gb_internal void lb_emit_defer_stmts(lbProcedure *p, lbDeferExitKind kind, lbBlock *block, TokenPos pos); +gb_internal void lb_emit_defer_stmts(lbProcedure *p, lbDeferExitKind kind, lbBlock *block, Ast *node); gb_internal lbValue lb_emit_transmute(lbProcedure *p, lbValue value, Type *t); gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left, lbValue right); gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array const &args, ProcInlining inlining = ProcInlining_none); @@ -476,7 +505,7 @@ gb_internal lbValue lb_emit_mul_add(lbProcedure *p, lbValue a, lbValue b, lbValu gb_internal void lb_fill_slice(lbProcedure *p, lbAddr const &slice, lbValue base_elem, lbValue len); -gb_internal lbValue lb_type_info(lbModule *m, Type *type); +gb_internal lbValue lb_type_info(lbProcedure *p, Type *type); gb_internal lbValue lb_find_or_add_entity_string(lbModule *m, String const &str); gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &prefix_name, Ast *expr, lbProcedure *parent = nullptr); @@ -499,12 +528,12 @@ gb_internal lbValue lb_dynamic_map_reserve(lbProcedure *p, lbValue const &map_pt gb_internal lbValue lb_find_procedure_value_from_entity(lbModule *m, Entity *e); gb_internal lbValue lb_find_value_from_entity(lbModule *m, Entity *e); -gb_internal void lb_store_type_case_implicit(lbProcedure *p, Ast *clause, lbValue value); +gb_internal void lb_store_type_case_implicit(lbProcedure *p, Ast *clause, lbValue value, bool is_default_case); gb_internal lbAddr lb_store_range_stmt_val(lbProcedure *p, Ast *stmt_val, lbValue value); gb_internal lbValue lb_emit_source_code_location_const(lbProcedure *p, String const &procedure, TokenPos const &pos); gb_internal lbValue lb_const_source_code_location_const(lbModule *m, String const &procedure, TokenPos const &pos); -gb_internal lbValue lb_handle_param_value(lbProcedure *p, Type *parameter_type, ParameterValue const ¶m_value, TokenPos const &pos); +gb_internal lbValue lb_handle_param_value(lbProcedure *p, Type *parameter_type, ParameterValue const ¶m_value, TypeProc *procedure_type, Ast *call_expression); gb_internal lbValue lb_equal_proc_for_type(lbModule *m, Type *type); gb_internal lbValue lb_hasher_proc_for_type(lbModule *m, Type *type); @@ -550,6 +579,7 @@ gb_internal LLVMValueRef lb_call_intrinsic(lbProcedure *p, const char *name, LLV gb_internal void lb_mem_copy_overlapping(lbProcedure *p, lbValue dst, lbValue src, lbValue len, bool is_volatile=false); gb_internal void lb_mem_copy_non_overlapping(lbProcedure *p, lbValue dst, lbValue src, lbValue len, bool is_volatile=false); gb_internal LLVMValueRef lb_mem_zero_ptr_internal(lbProcedure *p, LLVMValueRef ptr, LLVMValueRef len, unsigned alignment, bool is_volatile); +gb_internal LLVMValueRef lb_mem_zero_ptr_internal(lbProcedure *p, LLVMValueRef ptr, usize len, unsigned alignment, bool is_volatile); gb_internal gb_inline i64 lb_max_zero_init_size(void) { return cast(i64)(4*build_context.int_size); @@ -560,7 +590,11 @@ gb_internal LLVMTypeRef OdinLLVMGetVectorElementType(LLVMTypeRef type); gb_internal String lb_filepath_ll_for_module(lbModule *m); +gb_internal LLVMTypeRef lb_type_internal_for_procedures_raw(lbModule *m, Type *type); +gb_internal lbValue lb_emit_source_code_location_as_global_ptr(lbProcedure *p, String const &procedure, TokenPos const &pos); + +gb_internal LLVMMetadataRef lb_debug_location_from_token_pos(lbProcedure *p, TokenPos pos); gb_internal LLVMTypeRef llvm_array_type(LLVMTypeRef ElementType, uint64_t ElementCount) { #if LB_USE_NEW_PASS_SYSTEM @@ -570,9 +604,12 @@ 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_STARTUP_TYPE_INFO_PROC_NAME "__$startup_type_info" #define LB_TYPE_INFO_DATA_NAME "__$type_info_data" #define LB_TYPE_INFO_TYPES_NAME "__$type_info_types_data" #define LB_TYPE_INFO_NAMES_NAME "__$type_info_names_data" @@ -709,4 +746,6 @@ 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") +#define ODIN_METADATA_MIN_ALIGN str_lit("odin-min-align") +#define ODIN_METADATA_MAX_ALIGN str_lit("odin-max-align") diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 7584df3ee..754bbfca2 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)); @@ -157,7 +154,7 @@ gb_internal LLVMValueRef llvm_const_named_struct(lbModule *m, Type *t, LLVMValue GB_ASSERT(value_count_ == bt->Struct.fields.count); auto field_remapping = lb_get_struct_remapping(m, t); - unsigned values_with_padding_count = LLVMCountStructElementTypes(struct_type); + unsigned values_with_padding_count = elem_count; LLVMValueRef *values_with_padding = gb_alloc_array(permanent_allocator(), LLVMValueRef, values_with_padding_count); for (unsigned i = 0; i < value_count; i++) { @@ -287,11 +284,26 @@ gb_internal lbValue lb_expr_untyped_const_to_typed(lbModule *m, Ast *expr, Type return lb_const_value(m, t, tv.value); } -gb_internal lbValue lb_const_source_code_location_const(lbModule *m, String const &procedure, TokenPos const &pos) { + +gb_internal lbValue lb_const_source_code_location_const(lbModule *m, String const &procedure_, TokenPos const &pos) { + String file = get_file_path_string(pos.file_id); + String procedure = procedure_; + + i32 line = pos.line; + i32 column = pos.column; + + if (build_context.obfuscate_source_code_locations) { + file = obfuscate_string(file, "F"); + procedure = obfuscate_string(procedure, "P"); + + line = obfuscate_i32(line); + column = obfuscate_i32(column); + } + LLVMValueRef fields[4] = {}; - fields[0]/*file*/ = lb_find_or_add_entity_string(m, get_file_path_string(pos.file_id)).value; - fields[1]/*line*/ = lb_const_int(m, t_i32, pos.line).value; - fields[2]/*column*/ = lb_const_int(m, t_i32, pos.column).value; + fields[0]/*file*/ = lb_find_or_add_entity_string(m, file).value; + fields[1]/*line*/ = lb_const_int(m, t_i32, line).value; + fields[2]/*column*/ = lb_const_int(m, t_i32, column).value; fields[3]/*procedure*/ = lb_find_or_add_entity_string(m, procedure).value; lbValue res = {}; @@ -326,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); @@ -344,7 +365,11 @@ gb_internal lbValue lb_emit_source_code_location_as_global(lbProcedure *p, Ast * -gb_internal LLVMValueRef lb_build_constant_array_values(lbModule *m, Type *type, Type *elem_type, isize count, LLVMValueRef *values, bool allow_local) { +gb_internal LLVMValueRef lb_build_constant_array_values(lbModule *m, Type *type, Type *elem_type, isize count, LLVMValueRef *values, bool allow_local, bool is_rodata) { + if (allow_local) { + is_rodata = false; + } + bool is_local = allow_local && m->curr_procedure != nullptr; bool is_const = true; if (is_local) { @@ -413,6 +438,8 @@ gb_internal LLVMValueRef lb_big_int_to_llvm(lbModule *m, Type *original_type, Bi } } + GB_ASSERT(!is_type_array(original_type)); + LLVMValueRef value = LLVMConstIntOfArbitraryPrecision(lb_type(m, original_type), cast(unsigned)((sz+7)/8), cast(u64 *)rop); if (big_int_is_neg(a)) { value = LLVMConstNeg(value); @@ -447,7 +474,11 @@ gb_internal bool lb_is_nested_possibly_constant(Type *ft, Selection const &sel, return lb_is_elem_const(elem, ft); } -gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bool allow_local) { +gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bool allow_local, bool is_rodata) { + if (allow_local) { + is_rodata = false; + } + LLVMContextRef ctx = m->ctx; type = default_type(type); @@ -505,7 +536,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo count = gb_max(cast(isize)cl->max_count, count); Type *elem = base_type(type)->Slice.elem; Type *t = alloc_type_array(elem, count); - lbValue backing_array = lb_const_value(m, t, value, allow_local); + lbValue backing_array = lb_const_value(m, t, value, allow_local, is_rodata); LLVMValueRef array_data = nullptr; @@ -524,7 +555,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}); @@ -542,6 +573,10 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo array_data = LLVMAddGlobal(m->mod, lb_type(m, t), str); LLVMSetInitializer(array_data, backing_array.value); + if (is_rodata) { + LLVMSetGlobalConstant(array_data, true); + } + lbValue g = {}; g.value = array_data; g.type = t; @@ -589,7 +624,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } // NOTE(bill, 2021-10-07): Allow for array programming value constants Type *core_elem = core_array_type(type); - return lb_const_value(m, core_elem, value, allow_local); + return lb_const_value(m, core_elem, value, allow_local, is_rodata); } else if (is_type_u8_array(type) && value.kind == ExactValue_String) { GB_ASSERT(type->Array.count == value.value_string.len); LLVMValueRef data = LLVMConstStringInContext(ctx, @@ -607,7 +642,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo Type *elem = type->Array.elem; - lbValue single_elem = lb_const_value(m, elem, value, allow_local); + lbValue single_elem = lb_const_value(m, elem, value, allow_local, is_rodata); LLVMValueRef *elems = gb_alloc_array(permanent_allocator(), LLVMValueRef, cast(isize)count); for (i64 i = 0; i < count; i++) { @@ -625,7 +660,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo Type *elem = type->Matrix.elem; - lbValue single_elem = lb_const_value(m, elem, value, allow_local); + lbValue single_elem = lb_const_value(m, elem, value, allow_local, is_rodata); single_elem.value = llvm_const_cast(single_elem.value, lb_type(m, elem)); i64 total_elem_count = matrix_type_total_internal_elems(type); @@ -647,7 +682,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo i64 count = type->SimdVector.count; Type *elem = type->SimdVector.elem; - lbValue single_elem = lb_const_value(m, elem, value, allow_local); + lbValue single_elem = lb_const_value(m, elem, value, allow_local, is_rodata); single_elem.value = llvm_const_cast(single_elem.value, lb_type(m, elem)); LLVMValueRef *elems = gb_alloc_array(permanent_allocator(), LLVMValueRef, count); @@ -687,7 +722,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } case ExactValue_Integer: - if (is_type_pointer(type) || is_type_multi_pointer(type)) { + if (is_type_pointer(type) || is_type_multi_pointer(type) || is_type_proc(type)) { LLVMTypeRef t = lb_type(m, original_type); LLVMValueRef i = lb_big_int_to_llvm(m, t_uintptr, &value.value_integer); res.value = LLVMConstIntToPtr(i, t); @@ -697,9 +732,21 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo return res; case ExactValue_Float: if (is_type_different_to_arch_endianness(type)) { - u64 u = bit_cast(value.value_float); - u = gb_endian_swap64(u); - res.value = LLVMConstReal(lb_type(m, original_type), bit_cast(u)); + if (type->Basic.kind == Basic_f32le || type->Basic.kind == Basic_f32be) { + f32 f = static_cast(value.value_float); + u32 u = bit_cast(f); + u = gb_endian_swap32(u); + res.value = LLVMConstReal(lb_type(m, original_type), bit_cast(u)); + } else if (type->Basic.kind == Basic_f16le || type->Basic.kind == Basic_f16be) { + f32 f = static_cast(value.value_float); + u16 u = f32_to_f16(f); + u = gb_endian_swap16(u); + res.value = LLVMConstReal(lb_type(m, original_type), f16_to_f32(u)); + } else { + u64 u = bit_cast(value.value_float); + u = gb_endian_swap64(u); + res.value = LLVMConstReal(lb_type(m, original_type), bit_cast(u)); + } } else { res.value = LLVMConstReal(lb_type(m, original_type), value.value_float); } @@ -764,7 +811,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo case ExactValue_Compound: if (is_type_slice(type)) { - return lb_const_value(m, type, value, allow_local); + return lb_const_value(m, type, value, allow_local, is_rodata); } else if (is_type_array(type)) { ast_node(cl, CompoundLit, value.value_compound); Type *elem_type = type->Array.elem; @@ -798,7 +845,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } if (lo == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; for (i64 k = lo; k < hi; k++) { values[value_index++] = val; } @@ -813,7 +860,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo i64 index = exact_value_to_i64(index_tav.value); if (index == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; values[value_index++] = val; found = true; break; @@ -826,7 +873,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } } - res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->Array.count, values, allow_local); + res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->Array.count, values, allow_local, is_rodata); return res; } else { GB_ASSERT_MSG(elem_count == type->Array.count, "%td != %td", elem_count, type->Array.count); @@ -836,13 +883,13 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo for (isize i = 0; i < elem_count; i++) { TypeAndValue tav = cl->elems[i]->tav; GB_ASSERT(tav.mode != Addressing_Invalid); - values[i] = lb_const_value(m, elem_type, tav.value, allow_local).value; + values[i] = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; } for (isize i = elem_count; i < type->Array.count; i++) { values[i] = LLVMConstNull(lb_type(m, elem_type)); } - res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->Array.count, values, allow_local); + res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->Array.count, values, allow_local, is_rodata); return res; } } else if (is_type_enumerated_array(type)) { @@ -882,7 +929,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } if (lo == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; for (i64 k = lo; k < hi; k++) { values[value_index++] = val; } @@ -897,7 +944,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo i64 index = exact_value_to_i64(index_tav.value); if (index == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; values[value_index++] = val; found = true; break; @@ -910,7 +957,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } } - res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->EnumeratedArray.count, values, allow_local); + res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->EnumeratedArray.count, values, allow_local, is_rodata); return res; } else { GB_ASSERT_MSG(elem_count == type->EnumeratedArray.count, "%td != %td", elem_count, type->EnumeratedArray.count); @@ -920,13 +967,13 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo for (isize i = 0; i < elem_count; i++) { TypeAndValue tav = cl->elems[i]->tav; GB_ASSERT(tav.mode != Addressing_Invalid); - values[i] = lb_const_value(m, elem_type, tav.value, allow_local).value; + values[i] = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; } for (isize i = elem_count; i < type->EnumeratedArray.count; i++) { values[i] = LLVMConstNull(lb_type(m, elem_type)); } - res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->EnumeratedArray.count, values, allow_local); + res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->EnumeratedArray.count, values, allow_local, is_rodata); return res; } } else if (is_type_simd_vector(type)) { @@ -965,7 +1012,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } if (lo == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; for (i64 k = lo; k < hi; k++) { values[value_index++] = val; } @@ -980,7 +1027,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo i64 index = exact_value_to_i64(index_tav.value); if (index == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; values[value_index++] = val; found = true; break; @@ -999,7 +1046,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo for (isize i = 0; i < elem_count; i++) { TypeAndValue tav = cl->elems[i]->tav; GB_ASSERT(tav.mode != Addressing_Invalid); - values[i] = lb_const_value(m, elem_type, tav.value, allow_local).value; + values[i] = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; } LLVMTypeRef et = lb_type(m, elem_type); @@ -1048,7 +1095,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo i32 index = field_remapping[f->Variable.field_index]; if (elem_type_can_be_constant(f->type)) { if (sel.index.count == 1) { - values[index] = lb_const_value(m, f->type, tav.value, allow_local).value; + values[index] = lb_const_value(m, f->type, tav.value, allow_local, is_rodata).value; visited[index] = true; } else { if (!visited[index]) { @@ -1090,7 +1137,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } } if (is_constant) { - LLVMValueRef elem_value = lb_const_value(m, tav.type, tav.value, allow_local).value; + LLVMValueRef elem_value = lb_const_value(m, tav.type, tav.value, allow_local, is_rodata).value; if (LLVMIsConstant(elem_value)) { values[index] = llvm_const_insert_value(m, values[index], elem_value, idx_list, idx_list_len); } else { @@ -1112,7 +1159,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo i32 index = field_remapping[f->Variable.field_index]; if (elem_type_can_be_constant(f->type)) { - values[index] = lb_const_value(m, f->type, val, allow_local).value; + values[index] = lb_const_value(m, f->type, val, allow_local, is_rodata).value; visited[index] = true; } } @@ -1238,7 +1285,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; for (i64 k = lo; k < hi; k++) { i64 offset = matrix_row_major_index_to_offset(type, k); GB_ASSERT(values[offset] == nullptr); @@ -1250,7 +1297,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo i64 index = exact_value_to_i64(index_tav.value); GB_ASSERT(index < max_count); TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; i64 offset = matrix_row_major_index_to_offset(type, index); GB_ASSERT(values[offset] == nullptr); values[offset] = val; @@ -1263,18 +1310,18 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } } - res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)total_count, values, allow_local); + res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)total_count, values, allow_local, is_rodata); return res; } else { GB_ASSERT_MSG(elem_count == max_count, "%td != %td", elem_count, max_count); LLVMValueRef *values = gb_alloc_array(temporary_allocator(), LLVMValueRef, cast(isize)total_count); - for_array(i, cl->elems) { TypeAndValue tav = cl->elems[i]->tav; GB_ASSERT(tav.mode != Addressing_Invalid); - i64 offset = matrix_row_major_index_to_offset(type, i); - values[offset] = lb_const_value(m, elem_type, tav.value, allow_local).value; + i64 offset = 0; + offset = matrix_row_major_index_to_offset(type, i); + values[offset] = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; } for (isize i = 0; i < total_count; i++) { if (values[i] == nullptr) { @@ -1282,7 +1329,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } } - res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)total_count, values, allow_local); + res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)total_count, values, allow_local, is_rodata); return res; } } else { diff --git a/src/llvm_backend_debug.cpp b/src/llvm_backend_debug.cpp index e053c5b40..464f7065c 100644 --- a/src/llvm_backend_debug.cpp +++ b/src/llvm_backend_debug.cpp @@ -46,6 +46,25 @@ 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_procedure_parameters(lbModule *m, Type *type) { + if (is_type_proc(type)) { + return lb_debug_type(m, t_rawptr); + } + if (type->kind == Type_Tuple && type->Tuple.variables.count == 1) { + return lb_debug_procedure_parameters(m, type->Tuple.variables[0]->type); + } + return lb_debug_type(m, type); +} + gb_internal LLVMMetadataRef lb_debug_type_internal_proc(lbModule *m, Type *type) { i64 size = type_size_of(type); // Check size gb_unused(size); @@ -63,13 +82,36 @@ gb_internal LLVMMetadataRef lb_debug_type_internal_proc(lbModule *m, Type *type) parameter_count += 1; } } - LLVMMetadataRef *parameters = gb_alloc_array(permanent_allocator(), LLVMMetadataRef, parameter_count); - unsigned param_index = 0; - if (type->Proc.result_count == 0) { - parameters[param_index++] = nullptr; - } else { - parameters[param_index++] = lb_debug_type(m, type->Proc.results); + auto parameters = array_make(permanent_allocator(), 0, type->Proc.param_count+type->Proc.result_count+2); + + array_add(¶meters, cast(LLVMMetadataRef)nullptr); + + bool return_is_tuple = false; + if (type->Proc.result_count != 0) { + Type *single_ret = reduce_tuple_to_single_type(type->Proc.results); + if (is_type_proc(single_ret)) { + single_ret = t_rawptr; + } + if (is_type_tuple(single_ret) && is_calling_convention_odin(type->Proc.calling_convention)) { + LLVMTypeRef actual = lb_type_internal_for_procedures_raw(m, type); + actual = LLVMGetReturnType(actual); + if (actual == nullptr) { + // results were passed as a single pointer + parameters[0] = lb_debug_procedure_parameters(m, single_ret); + } else { + LLVMTypeRef possible = lb_type(m, type->Proc.results); + if (possible == actual) { + // results were returned directly + parameters[0] = lb_debug_procedure_parameters(m, single_ret); + } else { + // resulsts were returned separately + return_is_tuple = true; + } + } + } else { + parameters[0] = lb_debug_procedure_parameters(m, single_ret); + } } LLVMMetadataRef file = nullptr; @@ -79,8 +121,22 @@ gb_internal LLVMMetadataRef lb_debug_type_internal_proc(lbModule *m, Type *type) if (e->kind != Entity_Variable) { continue; } - parameters[param_index] = lb_debug_type(m, e->type); - param_index += 1; + array_add(¶meters, lb_debug_procedure_parameters(m, e->type)); + } + + + if (return_is_tuple) { + Type *results = type->Proc.results; + GB_ASSERT(results != nullptr && results->kind == Type_Tuple); + isize count = results->Tuple.variables.count; + parameters[0] = lb_debug_procedure_parameters(m, results->Tuple.variables[count-1]->type); + for (isize i = 0; i < count-1; i++) { + array_add(¶meters, lb_debug_procedure_parameters(m, results->Tuple.variables[i]->type)); + } + } + + if (type->Proc.calling_convention == ProcCC_Odin) { + array_add(¶meters, lb_debug_type(m, t_context_ptr)); } LLVMDIFlags flags = LLVMDIFlagZero; @@ -88,7 +144,7 @@ gb_internal LLVMMetadataRef lb_debug_type_internal_proc(lbModule *m, Type *type) flags = LLVMDIFlagNoReturn; } - return LLVMDIBuilderCreateSubroutineType(m->debug_builder, file, parameters, parameter_count, flags); + return LLVMDIBuilderCreateSubroutineType(m->debug_builder, file, parameters.data, cast(unsigned)parameters.count, flags); } gb_internal LLVMMetadataRef lb_debug_struct_field(lbModule *m, String const &name, Type *type, u64 offset_in_bits) { @@ -114,6 +170,465 @@ gb_internal LLVMMetadataRef lb_debug_basic_struct(lbModule *m, String const &nam return LLVMDIBuilderCreateStructType(m->debug_builder, scope, cast(char const *)name.text, name.len, file, 1, size_in_bits, align_in_bits, LLVMDIFlagZero, nullptr, elements, element_count, 0, nullptr, "", 0); } +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; + } + + u64 size_in_bits = 8*type_size_of(bt); + u32 align_in_bits = 8*cast(u32)type_align_of(bt); + + LLVMMetadataRef temp_forward_decl = LLVMDIBuilderCreateReplaceableCompositeType( + m->debug_builder, tag, + cast(char const *)name.text, cast(size_t)name.len, + scope, file, line, 0, size_in_bits, align_in_bits, LLVMDIFlagZero, "", 0 + ); + + lb_set_llvm_metadata(m, type, temp_forward_decl); + + type_set_offsets(bt); + + unsigned element_count = cast(unsigned)(bt->Struct.fields.count); + LLVMMetadataRef *elements = gb_alloc_array(temporary_allocator(), LLVMMetadataRef, element_count); + + LLVMMetadataRef member_scope = lb_get_llvm_metadata(m, bt->Struct.scope); + + for_array(j, bt->Struct.fields) { + Entity *f = bt->Struct.fields[j]; + String fname = f->token.string; + + unsigned field_line = 0; + LLVMDIFlags field_flags = LLVMDIFlagZero; + GB_ASSERT(bt->Struct.offsets != nullptr); + u64 offset_in_bits = 8*cast(u64)bt->Struct.offsets[j]; + + elements[j] = LLVMDIBuilderCreateMemberType( + m->debug_builder, + member_scope, + cast(char const *)fname.text, cast(size_t)fname.len, + file, field_line, + 8*cast(u64)type_size_of(f->type), 8*cast(u32)type_align_of(f->type), + offset_in_bits, + field_flags, + lb_debug_type(m, f->type) + ); + } + + LLVMMetadataRef final_decl = nullptr; + if (tag == DW_TAG_union_type) { + final_decl = LLVMDIBuilderCreateUnionType( + m->debug_builder, scope, + cast(char const*)name.text, cast(size_t)name.len, + file, line, + size_in_bits, align_in_bits, + LLVMDIFlagZero, + elements, element_count, + 0, + "", 0 + ); + } else { + final_decl = LLVMDIBuilderCreateStructType( + m->debug_builder, scope, + cast(char const *)name.text, cast(size_t)name.len, + file, line, + size_in_bits, align_in_bits, + LLVMDIFlagZero, + nullptr, + elements, element_count, + 0, + nullptr, + "", 0 + ); + } + + LLVMMetadataReplaceAllUsesWith(temp_forward_decl, final_decl); + lb_set_llvm_metadata(m, type, final_decl); + return final_decl; +} + +gb_internal LLVMMetadataRef lb_debug_slice(lbModule *m, Type *type, String name, LLVMMetadataRef scope, LLVMMetadataRef file, unsigned line) { + Type *bt = base_type(type); + GB_ASSERT(bt->kind == Type_Slice); + + unsigned const ptr_bits = cast(unsigned)(8*build_context.ptr_size); + + u64 size_in_bits = 8*type_size_of(bt); + u32 align_in_bits = 8*cast(u32)type_align_of(bt); + + LLVMMetadataRef temp_forward_decl = LLVMDIBuilderCreateReplaceableCompositeType( + m->debug_builder, DW_TAG_structure_type, + cast(char const *)name.text, cast(size_t)name.len, + scope, file, line, 0, size_in_bits, align_in_bits, LLVMDIFlagZero, "", 0 + ); + + lb_set_llvm_metadata(m, type, temp_forward_decl); + + unsigned element_count = 2; + LLVMMetadataRef elements[2]; + + // LLVMMetadataRef member_scope = lb_get_llvm_metadata(m, bt->Slice.scope); + LLVMMetadataRef member_scope = nullptr; + + Type *elem_type = alloc_type_pointer(bt->Slice.elem); + elements[0] = LLVMDIBuilderCreateMemberType( + m->debug_builder, member_scope, + "data", 4, + file, line, + 8*cast(u64)type_size_of(elem_type), 8*cast(u32)type_align_of(elem_type), + 0, + LLVMDIFlagZero, lb_debug_type(m, elem_type) + ); + + elements[1] = LLVMDIBuilderCreateMemberType( + m->debug_builder, member_scope, + "len", 3, + file, line, + 8*cast(u64)type_size_of(t_int), 8*cast(u32)type_align_of(t_int), + ptr_bits, + LLVMDIFlagZero, lb_debug_type(m, t_int) + ); + + LLVMMetadataRef final_decl = LLVMDIBuilderCreateStructType( + m->debug_builder, scope, + cast(char const *)name.text, cast(size_t)name.len, + file, line, + size_in_bits, align_in_bits, + LLVMDIFlagZero, + nullptr, + elements, element_count, + 0, + nullptr, + "", 0 + ); + + LLVMMetadataReplaceAllUsesWith(temp_forward_decl, final_decl); + lb_set_llvm_metadata(m, type, final_decl); + return final_decl; +} + +gb_internal LLVMMetadataRef lb_debug_dynamic_array(lbModule *m, Type *type, String name, LLVMMetadataRef scope, LLVMMetadataRef file, unsigned line) { + Type *bt = base_type(type); + GB_ASSERT(bt->kind == Type_DynamicArray); + + unsigned const ptr_bits = cast(unsigned)(8*build_context.ptr_size); + unsigned const int_bits = cast(unsigned)(8*build_context.int_size); + + u64 size_in_bits = 8*type_size_of(bt); + u32 align_in_bits = 8*cast(u32)type_align_of(bt); + + LLVMMetadataRef temp_forward_decl = LLVMDIBuilderCreateReplaceableCompositeType( + m->debug_builder, DW_TAG_structure_type, + cast(char const *)name.text, cast(size_t)name.len, + scope, file, line, 0, size_in_bits, align_in_bits, LLVMDIFlagZero, "", 0 + ); + + lb_set_llvm_metadata(m, type, temp_forward_decl); + + unsigned element_count = 4; + LLVMMetadataRef elements[4]; + + // LLVMMetadataRef member_scope = lb_get_llvm_metadata(m, bt->DynamicArray.scope); + LLVMMetadataRef member_scope = nullptr; + + Type *elem_type = alloc_type_pointer(bt->DynamicArray.elem); + elements[0] = LLVMDIBuilderCreateMemberType( + m->debug_builder, member_scope, + "data", 4, + file, line, + 8*cast(u64)type_size_of(elem_type), 8*cast(u32)type_align_of(elem_type), + 0, + LLVMDIFlagZero, lb_debug_type(m, elem_type) + ); + + elements[1] = LLVMDIBuilderCreateMemberType( + m->debug_builder, member_scope, + "len", 3, + file, line, + 8*cast(u64)type_size_of(t_int), 8*cast(u32)type_align_of(t_int), + ptr_bits, + LLVMDIFlagZero, lb_debug_type(m, t_int) + ); + + elements[2] = LLVMDIBuilderCreateMemberType( + m->debug_builder, member_scope, + "cap", 3, + file, line, + 8*cast(u64)type_size_of(t_int), 8*cast(u32)type_align_of(t_int), + ptr_bits+int_bits, + LLVMDIFlagZero, lb_debug_type(m, t_int) + ); + + elements[3] = LLVMDIBuilderCreateMemberType( + m->debug_builder, member_scope, + "allocator", 9, + file, line, + 8*cast(u64)type_size_of(t_allocator), 8*cast(u32)type_align_of(t_allocator), + ptr_bits+int_bits+int_bits, + LLVMDIFlagZero, lb_debug_type(m, t_allocator) + ); + + LLVMMetadataRef final_decl = LLVMDIBuilderCreateStructType( + m->debug_builder, scope, + cast(char const *)name.text, cast(size_t)name.len, + file, line, + size_in_bits, align_in_bits, + LLVMDIFlagZero, + nullptr, + elements, element_count, + 0, + nullptr, + "", 0 + ); + + LLVMMetadataReplaceAllUsesWith(temp_forward_decl, final_decl); + lb_set_llvm_metadata(m, type, final_decl); + return final_decl; +} + +gb_internal LLVMMetadataRef lb_debug_union(lbModule *m, Type *type, String name, LLVMMetadataRef scope, LLVMMetadataRef file, unsigned line) { + 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); + + LLVMMetadataRef temp_forward_decl = LLVMDIBuilderCreateReplaceableCompositeType( + m->debug_builder, DW_TAG_union_type, + cast(char const *)name.text, cast(size_t)name.len, + scope, file, line, 0, size_in_bits, align_in_bits, LLVMDIFlagZero, "", 0 + ); + + lb_set_llvm_metadata(m, type, temp_forward_decl); + + isize index_offset = 1; + if (is_type_union_maybe_pointer(bt)) { + index_offset = 0; + } + + LLVMMetadataRef member_scope = lb_get_llvm_metadata(m, bt->Union.scope); + unsigned element_count = cast(unsigned)bt->Union.variants.count; + if (index_offset > 0) { + element_count += 1; + } + + LLVMMetadataRef *elements = gb_alloc_array(temporary_allocator(), LLVMMetadataRef, element_count); + + if (index_offset > 0) { + Type *tag_type = union_tag_type(bt); + u64 offset_in_bits = 8*cast(u64)bt->Union.variant_block_size; + + elements[0] = LLVMDIBuilderCreateMemberType( + m->debug_builder, member_scope, + "tag", 3, + file, line, + 8*cast(u64)type_size_of(tag_type), 8*cast(u32)type_align_of(tag_type), + offset_in_bits, + LLVMDIFlagZero, lb_debug_type(m, tag_type) + ); + } + + for_array(j, bt->Union.variants) { + Type *variant = bt->Union.variants[j]; + + unsigned field_index = cast(unsigned)(index_offset+j); + + char name[16] = {}; + gb_snprintf(name, gb_size_of(name), "v%u", field_index); + isize name_len = gb_strlen(name); + + elements[field_index] = LLVMDIBuilderCreateMemberType( + m->debug_builder, member_scope, + name, name_len, + file, line, + 8*cast(u64)type_size_of(variant), 8*cast(u32)type_align_of(variant), + 0, + LLVMDIFlagZero, lb_debug_type(m, variant) + ); + } + + LLVMMetadataRef final_decl = LLVMDIBuilderCreateUnionType( + m->debug_builder, + scope, + cast(char const *)name.text, cast(size_t)name.len, + file, line, + size_in_bits, align_in_bits, + LLVMDIFlagZero, + elements, + element_count, + 0, + "", 0 + ); + + LLVMMetadataReplaceAllUsesWith(temp_forward_decl, final_decl); + lb_set_llvm_metadata(m, type, final_decl); + return final_decl; +} + +gb_internal LLVMMetadataRef lb_debug_bitset(lbModule *m, Type *type, String name, LLVMMetadataRef scope, LLVMMetadataRef file, unsigned line) { + 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); + + LLVMMetadataRef bit_set_field_type = lb_debug_type(m, t_bool); + + unsigned element_count = 0; + LLVMMetadataRef *elements = nullptr; + + Type *elem = base_type(bt->BitSet.elem); + if (elem->kind == Type_Enum) { + element_count = cast(unsigned)elem->Enum.fields.count; + elements = gb_alloc_array(temporary_allocator(), LLVMMetadataRef, element_count); + + for_array(i, elem->Enum.fields) { + Entity *f = elem->Enum.fields[i]; + GB_ASSERT(f->kind == Entity_Constant); + i64 val = exact_value_to_i64(f->Constant.value); + String field_name = f->token.string; + u64 offset_in_bits = cast(u64)(val - bt->BitSet.lower); + elements[i] = LLVMDIBuilderCreateBitFieldMemberType( + m->debug_builder, + scope, + cast(char const *)field_name.text, field_name.len, + file, line, + 1, + offset_in_bits, + 0, + LLVMDIFlagZero, + bit_set_field_type + ); + } + } else { + char name[32] = {}; + + GB_ASSERT(is_type_integer(elem)); + i64 count = bt->BitSet.upper - bt->BitSet.lower + 1; + GB_ASSERT(0 <= count); + + element_count = cast(unsigned)count; + elements = gb_alloc_array(temporary_allocator(), LLVMMetadataRef, element_count); + + for (unsigned i = 0; i < element_count; i++) { + u64 offset_in_bits = i; + i64 val = bt->BitSet.lower + cast(i64)i; + gb_snprintf(name, gb_count_of(name), "%lld", cast(long long)val); + elements[i] = LLVMDIBuilderCreateBitFieldMemberType( + m->debug_builder, + scope, + name, gb_strlen(name), + file, line, + 1, + offset_in_bits, + 0, + LLVMDIFlagZero, + bit_set_field_type + ); + } + } + + LLVMMetadataRef final_decl = LLVMDIBuilderCreateUnionType( + m->debug_builder, + scope, + cast(char const *)name.text, cast(size_t)name.len, + file, line, + size_in_bits, align_in_bits, + LLVMDIFlagZero, + elements, + element_count, + 0, + "", 0 + ); + lb_set_llvm_metadata(m, type, final_decl); + return final_decl; +} + +gb_internal LLVMMetadataRef lb_debug_bitfield(lbModule *m, Type *type, String name, LLVMMetadataRef scope, LLVMMetadataRef file, unsigned line) { + Type *bt = base_type(type); + GB_ASSERT(bt->kind == Type_BitField); + + lb_debug_file_line(m, bt->BitField.node, &file, &line); + + u64 size_in_bits = 8*type_size_of(bt); + u32 align_in_bits = 8*cast(u32)type_align_of(bt); + + unsigned element_count = cast(unsigned)bt->BitField.fields.count; + LLVMMetadataRef *elements = gb_alloc_array(permanent_allocator(), LLVMMetadataRef, element_count); + + u64 offset_in_bits = 0; + for (unsigned i = 0; i < element_count; i++) { + Entity *f = bt->BitField.fields[i]; + u8 bit_size = bt->BitField.bit_sizes[i]; + GB_ASSERT(f->kind == Entity_Variable); + String name = f->token.string; + elements[i] = LLVMDIBuilderCreateBitFieldMemberType(m->debug_builder, scope, cast(char const *)name.text, name.len, file, line, + bit_size, offset_in_bits, 0, + LLVMDIFlagZero, lb_debug_type(m, f->type) + ); + + offset_in_bits += bit_size; + } + + LLVMMetadataRef final_decl = LLVMDIBuilderCreateStructType( + m->debug_builder, scope, + cast(char const *)name.text, cast(size_t)name.len, + file, line, + size_in_bits, align_in_bits, + LLVMDIFlagZero, + nullptr, + elements, element_count, + 0, + nullptr, + "", 0 + ); + lb_set_llvm_metadata(m, type, final_decl); + return final_decl; +} + +gb_internal LLVMMetadataRef lb_debug_enum(lbModule *m, Type *type, String name, LLVMMetadataRef scope, LLVMMetadataRef file, unsigned line) { + 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); + + unsigned element_count = cast(unsigned)bt->Enum.fields.count; + LLVMMetadataRef *elements = gb_alloc_array(temporary_allocator(), LLVMMetadataRef, element_count); + + Type *bt_enum = base_enum_type(bt); + LLVMBool is_unsigned = is_type_unsigned(bt_enum); + for (unsigned i = 0; i < element_count; i++) { + Entity *f = bt->Enum.fields[i]; + GB_ASSERT(f->kind == Entity_Constant); + String enum_name = f->token.string; + i64 value = exact_value_to_i64(f->Constant.value); + elements[i] = LLVMDIBuilderCreateEnumerator(m->debug_builder, cast(char const *)enum_name.text, cast(size_t)enum_name.len, value, is_unsigned); + } + + LLVMMetadataRef class_type = lb_debug_type(m, bt_enum); + LLVMMetadataRef final_decl = LLVMDIBuilderCreateEnumerationType( + m->debug_builder, + scope, + cast(char const *)name.text, cast(size_t)name.len, + file, line, + size_in_bits, align_in_bits, + elements, element_count, + class_type + ); + lb_set_llvm_metadata(m, type, final_decl); + return final_decl; +} gb_internal LLVMMetadataRef lb_debug_type_basic_type(lbModule *m, String const &name, u64 size_in_bits, LLVMDWARFTypeEncoding encoding, LLVMDIFlags flags = LLVMDIFlagZero) { LLVMMetadataRef basic_type = LLVMDIBuilderCreateBasicType(m->debug_builder, cast(char const *)name.text, name.len, size_in_bits, encoding, flags); @@ -200,50 +715,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)); } @@ -329,53 +844,21 @@ gb_internal LLVMMetadataRef lb_debug_type_internal(lbModule *m, Type *type) { return LLVMDIBuilderCreateTypedef(m->debug_builder, array_type, name, gb_string_length(name), nullptr, 0, nullptr, cast(u32)(8*type_align_of(type))); } + case Type_Map: { + init_map_internal_debug_types(type); + Type *bt = base_type(type->Map.debug_metadata_type); + GB_ASSERT(bt->kind == Type_Struct); - case Type_Struct: - case Type_Union: - case Type_Slice: - case Type_DynamicArray: - case Type_Map: - case Type_BitSet: - { - unsigned tag = DW_TAG_structure_type; - if (is_type_raw_union(type) || is_type_union(type)) { - tag = DW_TAG_union_type; - } - u64 size_in_bits = cast(u64)(8*type_size_of(type)); - u32 align_in_bits = cast(u32)(8*type_size_of(type)); - LLVMDIFlags flags = LLVMDIFlagZero; + return lb_debug_struct(m, type, bt, make_string_c(type_to_string(type, temporary_allocator())), nullptr, nullptr, 0); + } - LLVMMetadataRef temp_forward_decl = LLVMDIBuilderCreateReplaceableCompositeType( - m->debug_builder, tag, "", 0, nullptr, nullptr, 0, 0, size_in_bits, align_in_bits, flags, "", 0 - ); - lbIncompleteDebugType idt = {}; - idt.type = type; - idt.metadata = temp_forward_decl; - - array_add(&m->debug_incomplete_types, idt); - lb_set_llvm_metadata(m, type, temp_forward_decl); - return temp_forward_decl; - } - - case Type_Enum: - { - LLVMMetadataRef scope = nullptr; - LLVMMetadataRef file = nullptr; - unsigned line = 0; - unsigned element_count = cast(unsigned)type->Enum.fields.count; - LLVMMetadataRef *elements = gb_alloc_array(permanent_allocator(), LLVMMetadataRef, element_count); - Type *bt = base_enum_type(type); - LLVMBool is_unsigned = is_type_unsigned(bt); - for (unsigned i = 0; i < element_count; i++) { - Entity *f = type->Enum.fields[i]; - GB_ASSERT(f->kind == Entity_Constant); - String name = f->token.string; - i64 value = exact_value_to_i64(f->Constant.value); - elements[i] = LLVMDIBuilderCreateEnumerator(m->debug_builder, cast(char const *)name.text, cast(size_t)name.len, value, is_unsigned); - } - LLVMMetadataRef class_type = lb_debug_type(m, bt); - return LLVMDIBuilderCreateEnumerationType(m->debug_builder, scope, "", 0, file, line, 8*type_size_of(type), 8*cast(unsigned)type_align_of(type), elements, element_count, class_type); - } + case Type_Struct: return lb_debug_struct( m, type, type, make_string_c(type_to_string(type, temporary_allocator())), nullptr, nullptr, 0); + case Type_Slice: return lb_debug_slice( m, type, make_string_c(type_to_string(type, temporary_allocator())), nullptr, nullptr, 0); + case Type_DynamicArray: return lb_debug_dynamic_array(m, type, make_string_c(type_to_string(type, temporary_allocator())), nullptr, nullptr, 0); + case Type_Union: return lb_debug_union( m, type, make_string_c(type_to_string(type, temporary_allocator())), nullptr, nullptr, 0); + case Type_BitSet: return lb_debug_bitset( m, type, make_string_c(type_to_string(type, temporary_allocator())), nullptr, nullptr, 0); + case Type_Enum: return lb_debug_enum( m, type, make_string_c(type_to_string(type, temporary_allocator())), nullptr, nullptr, 0); + case Type_BitField: return lb_debug_bitfield( m, type, make_string_c(type_to_string(type, temporary_allocator())), nullptr, nullptr, 0); case Type_Tuple: if (type->Tuple.variables.count == 1) { @@ -437,17 +920,6 @@ gb_internal LLVMMetadataRef lb_debug_type_internal(lbModule *m, Type *type) { elem, subscripts, gb_count_of(subscripts)); } - case Type_RelativePointer: { - LLVMMetadataRef base_integer = lb_debug_type(m, type->RelativePointer.base_integer); - gbString name = type_to_string(type, temporary_allocator()); - return LLVMDIBuilderCreateTypedef(m->debug_builder, base_integer, name, gb_string_length(name), nullptr, 0, nullptr, cast(u32)(8*type_align_of(type))); - } - case Type_RelativeMultiPointer: { - LLVMMetadataRef base_integer = lb_debug_type(m, type->RelativeMultiPointer.base_integer); - gbString name = type_to_string(type, temporary_allocator()); - return LLVMDIBuilderCreateTypedef(m->debug_builder, base_integer, name, gb_string_length(name), nullptr, 0, nullptr, cast(u32)(8*type_align_of(type))); - } - case Type_Matrix: { LLVMMetadataRef subscripts[1] = {}; subscripts[0] = LLVMDIBuilderGetOrCreateSubrange(m->debug_builder, @@ -503,7 +975,6 @@ gb_internal LLVMMetadataRef lb_debug_type(lbModule *m, Type *type) { unsigned line = 0; LLVMMetadataRef scope = nullptr; - if (type->Named.type_name != nullptr) { Entity *e = type->Named.type_name; scope = lb_get_base_scope_metadata(m, e->scope); @@ -512,452 +983,50 @@ gb_internal LLVMMetadataRef lb_debug_type(lbModule *m, Type *type) { } line = cast(unsigned)e->token.pos.line; } - // TODO(bill): location data for Type_Named - u64 size_in_bits = 8*type_size_of(type); - u32 align_in_bits = 8*cast(u32)type_align_of(type); String name = type->Named.name; - char const *name_text = cast(char const *)name.text; - size_t name_len = cast(size_t)name.len; - unsigned tag = DW_TAG_structure_type; - if (is_type_raw_union(type) || is_type_union(type)) { - tag = DW_TAG_union_type; + if (type->Named.type_name && type->Named.type_name->pkg && type->Named.type_name->pkg->name.len != 0) { + name = concatenate3_strings(temporary_allocator(), type->Named.type_name->pkg->name, str_lit("."), type->Named.name); } - LLVMDIFlags flags = LLVMDIFlagZero; Type *bt = base_type(type->Named.base); - lbIncompleteDebugType idt = {}; - idt.type = type; - switch (bt->kind) { - case Type_Enum: - { - unsigned line = 0; - unsigned element_count = cast(unsigned)bt->Enum.fields.count; - LLVMMetadataRef *elements = gb_alloc_array(permanent_allocator(), LLVMMetadataRef, element_count); - Type *ct = base_enum_type(type); - LLVMBool is_unsigned = is_type_unsigned(ct); - for (unsigned i = 0; i < element_count; i++) { - Entity *f = bt->Enum.fields[i]; - GB_ASSERT(f->kind == Entity_Constant); - String name = f->token.string; - i64 value = exact_value_to_i64(f->Constant.value); - elements[i] = LLVMDIBuilderCreateEnumerator(m->debug_builder, cast(char const *)name.text, cast(size_t)name.len, value, is_unsigned); - } - LLVMMetadataRef class_type = lb_debug_type(m, ct); - return LLVMDIBuilderCreateEnumerationType(m->debug_builder, scope, name_text, name_len, file, line, 8*type_size_of(type), 8*cast(unsigned)type_align_of(type), elements, element_count, class_type); - } + default: { + u32 align_in_bits = 8*cast(u32)type_align_of(type); + LLVMMetadataRef debug_bt = lb_debug_type(m, bt); + LLVMMetadataRef final_decl = LLVMDIBuilderCreateTypedef( + m->debug_builder, + debug_bt, + cast(char const *)name.text, cast(size_t)name.len, + file, line, scope, align_in_bits + ); + lb_set_llvm_metadata(m, type, final_decl); + return final_decl; + } + case Type_Map: { + init_map_internal_debug_types(bt); + bt = base_type(bt->Map.debug_metadata_type); + GB_ASSERT(bt->kind == Type_Struct); + return lb_debug_struct(m, type, bt, name, scope, file, line); + } - default: - { - LLVMMetadataRef debug_bt = lb_debug_type(m, bt); - LLVMMetadataRef final_decl = LLVMDIBuilderCreateTypedef(m->debug_builder, debug_bt, name_text, name_len, file, line, scope, align_in_bits); - lb_set_llvm_metadata(m, type, final_decl); - return final_decl; - } - - case Type_Slice: - case Type_DynamicArray: - case Type_Map: - case Type_Struct: - case Type_Union: - case Type_BitSet: - { - LLVMMetadataRef temp_forward_decl = LLVMDIBuilderCreateReplaceableCompositeType( - m->debug_builder, tag, name_text, name_len, nullptr, nullptr, 0, 0, size_in_bits, align_in_bits, flags, "", 0 - ); - idt.metadata = temp_forward_decl; - - array_add(&m->debug_incomplete_types, idt); - lb_set_llvm_metadata(m, type, temp_forward_decl); - - LLVMMetadataRef dummy = nullptr; - switch (bt->kind) { - case Type_Slice: - dummy = lb_debug_type(m, bt->Slice.elem); - dummy = lb_debug_type(m, alloc_type_pointer(bt->Slice.elem)); - dummy = lb_debug_type(m, t_int); - break; - case Type_DynamicArray: - dummy = lb_debug_type(m, bt->DynamicArray.elem); - dummy = lb_debug_type(m, alloc_type_pointer(bt->DynamicArray.elem)); - dummy = lb_debug_type(m, t_int); - dummy = lb_debug_type(m, t_allocator); - break; - case Type_Map: - dummy = lb_debug_type(m, bt->Map.key); - dummy = lb_debug_type(m, bt->Map.value); - dummy = lb_debug_type(m, t_int); - dummy = lb_debug_type(m, t_allocator); - dummy = lb_debug_type(m, t_uintptr); - break; - case Type_BitSet: - if (bt->BitSet.elem) dummy = lb_debug_type(m, bt->BitSet.elem); - if (bt->BitSet.underlying) dummy = lb_debug_type(m, bt->BitSet.underlying); - break; - } - - return temp_forward_decl; - } + case Type_Struct: return lb_debug_struct(m, type, bt, name, scope, file, line); + case Type_Slice: return lb_debug_slice(m, type, name, scope, file, line); + case Type_DynamicArray: return lb_debug_dynamic_array(m, type, name, scope, file, line); + case Type_Union: return lb_debug_union(m, type, name, scope, file, line); + case Type_BitSet: return lb_debug_bitset(m, type, name, scope, file, line); + case Type_Enum: return lb_debug_enum(m, type, name, scope, file, line); + case Type_BitField: return lb_debug_bitfield(m, type, name, scope, file, line); } } - LLVMMetadataRef dt = lb_debug_type_internal(m, type); lb_set_llvm_metadata(m, type, dt); return dt; } -gb_internal void lb_debug_complete_types(lbModule *m) { - unsigned const int_bits = cast(unsigned)(8*build_context.int_size); - - for_array(debug_incomplete_type_index, m->debug_incomplete_types) { - TEMPORARY_ALLOCATOR_GUARD(); - - auto const &idt = m->debug_incomplete_types[debug_incomplete_type_index]; - GB_ASSERT(idt.type != nullptr); - GB_ASSERT(idt.metadata != nullptr); - - Type *t = idt.type; - Type *bt = base_type(t); - - LLVMMetadataRef parent_scope = nullptr; - LLVMMetadataRef file = nullptr; - unsigned line_number = 0; - u64 size_in_bits = 8*type_size_of(t); - u32 align_in_bits = cast(u32)(8*type_align_of(t)); - LLVMDIFlags flags = LLVMDIFlagZero; - - LLVMMetadataRef derived_from = nullptr; - - LLVMMetadataRef *elements = nullptr; - unsigned element_count = 0; - - - unsigned runtime_lang = 0; // Objective-C runtime version - char const *unique_id = ""; - LLVMMetadataRef vtable_holder = nullptr; - size_t unique_id_len = 0; - - - LLVMMetadataRef record_scope = nullptr; - - switch (bt->kind) { - case Type_Slice: - case Type_DynamicArray: - case Type_Map: - case Type_Struct: - case Type_Union: - case Type_BitSet: { - bool is_union = is_type_raw_union(bt) || is_type_union(bt); - - String name = str_lit(""); - if (t->kind == Type_Named) { - name = t->Named.name; - if (t->Named.type_name && t->Named.type_name->pkg && t->Named.type_name->pkg->name.len != 0) { - name = concatenate3_strings(temporary_allocator(), t->Named.type_name->pkg->name, str_lit("."), t->Named.name); - } - - LLVMMetadataRef file = nullptr; - unsigned line = 0; - LLVMMetadataRef file_scope = nullptr; - - if (t->Named.type_name != nullptr) { - Entity *e = t->Named.type_name; - file_scope = lb_get_llvm_metadata(m, e->scope); - if (file_scope != nullptr) { - file = LLVMDIScopeGetFile(file_scope); - } - line = cast(unsigned)e->token.pos.line; - } - // TODO(bill): location data for Type_Named - - } else { - name = make_string_c(type_to_string(t, temporary_allocator())); - } - - - - switch (bt->kind) { - case Type_Slice: - element_count = 2; - elements = gb_alloc_array(temporary_allocator(), LLVMMetadataRef, element_count); - #if defined(GB_SYSTEM_WINDOWS) - elements[0] = lb_debug_struct_field(m, str_lit("data"), alloc_type_pointer(bt->Slice.elem), 0*int_bits); - #else - // FIX HACK TODO(bill): For some reason this causes a crash in *nix systems due to the reference counting - // of the debug type information - elements[0] = lb_debug_struct_field(m, str_lit("data"), t_rawptr, 0*int_bits); - #endif - elements[1] = lb_debug_struct_field(m, str_lit("len"), t_int, 1*int_bits); - break; - case Type_DynamicArray: - element_count = 4; - elements = gb_alloc_array(temporary_allocator(), LLVMMetadataRef, element_count); - #if defined(GB_SYSTEM_WINDOWS) - elements[0] = lb_debug_struct_field(m, str_lit("data"), alloc_type_pointer(bt->DynamicArray.elem), 0*int_bits); - #else - // FIX HACK TODO(bill): For some reason this causes a crash in *nix systems due to the reference counting - // of the debug type information - elements[0] = lb_debug_struct_field(m, str_lit("data"), t_rawptr, 0*int_bits); - #endif - elements[1] = lb_debug_struct_field(m, str_lit("len"), t_int, 1*int_bits); - elements[2] = lb_debug_struct_field(m, str_lit("cap"), t_int, 2*int_bits); - elements[3] = lb_debug_struct_field(m, str_lit("allocator"), t_allocator, 3*int_bits); - break; - - case Type_Map: - GB_ASSERT(t_raw_map != nullptr); - bt = base_type(t_raw_map); - /*fallthrough*/ - case Type_Struct: - if (file == nullptr) { - if (bt->Struct.node) { - file = lb_get_llvm_metadata(m, bt->Struct.node->file()); - line_number = cast(unsigned)ast_token(bt->Struct.node).pos.line; - } - } - - type_set_offsets(bt); - { - isize element_offset = 0; - record_scope = lb_get_llvm_metadata(m, bt->Struct.scope); - switch (bt->Struct.soa_kind) { - case StructSoa_Slice: element_offset = 1; break; - case StructSoa_Dynamic: element_offset = 3; break; - } - element_count = cast(unsigned)(bt->Struct.fields.count + element_offset); - elements = gb_alloc_array(temporary_allocator(), LLVMMetadataRef, element_count); - - isize field_size_bits = 8*type_size_of(bt) - element_offset*int_bits; - - switch (bt->Struct.soa_kind) { - case StructSoa_Slice: - elements[0] = LLVMDIBuilderCreateMemberType( - m->debug_builder, record_scope, - ".len", 4, - file, 0, - 8*cast(u64)type_size_of(t_int), 8*cast(u32)type_align_of(t_int), - field_size_bits, - LLVMDIFlagZero, lb_debug_type(m, t_int) - ); - break; - case StructSoa_Dynamic: - elements[0] = LLVMDIBuilderCreateMemberType( - m->debug_builder, record_scope, - ".len", 4, - file, 0, - 8*cast(u64)type_size_of(t_int), 8*cast(u32)type_align_of(t_int), - field_size_bits + 0*int_bits, - LLVMDIFlagZero, lb_debug_type(m, t_int) - ); - elements[1] = LLVMDIBuilderCreateMemberType( - m->debug_builder, record_scope, - ".cap", 4, - file, 0, - 8*cast(u64)type_size_of(t_int), 8*cast(u32)type_align_of(t_int), - field_size_bits + 1*int_bits, - LLVMDIFlagZero, lb_debug_type(m, t_int) - ); - elements[2] = LLVMDIBuilderCreateMemberType( - m->debug_builder, record_scope, - ".allocator", 10, - file, 0, - 8*cast(u64)type_size_of(t_int), 8*cast(u32)type_align_of(t_int), - field_size_bits + 2*int_bits, - LLVMDIFlagZero, lb_debug_type(m, t_allocator) - ); - break; - } - - for_array(j, bt->Struct.fields) { - Entity *f = bt->Struct.fields[j]; - String fname = f->token.string; - - unsigned field_line = 0; - LLVMDIFlags field_flags = LLVMDIFlagZero; - GB_ASSERT(bt->Struct.offsets != nullptr); - u64 offset_in_bits = 8*cast(u64)bt->Struct.offsets[j]; - - elements[element_offset+j] = LLVMDIBuilderCreateMemberType( - m->debug_builder, record_scope, - cast(char const *)fname.text, cast(size_t)fname.len, - file, field_line, - 8*cast(u64)type_size_of(f->type), 8*cast(u32)type_align_of(f->type), - offset_in_bits, - field_flags, lb_debug_type(m, f->type) - ); - } - } - break; - case Type_Union: - { - if (file == nullptr) { - GB_ASSERT(bt->Union.node != nullptr); - file = lb_get_llvm_metadata(m, bt->Union.node->file()); - line_number = cast(unsigned)ast_token(bt->Union.node).pos.line; - } - - isize index_offset = 1; - if (is_type_union_maybe_pointer(bt)) { - index_offset = 0; - } - record_scope = lb_get_llvm_metadata(m, bt->Union.scope); - element_count = cast(unsigned)bt->Union.variants.count; - if (index_offset > 0) { - element_count += 1; - } - - elements = gb_alloc_array(temporary_allocator(), LLVMMetadataRef, element_count); - if (index_offset > 0) { - Type *tag_type = union_tag_type(bt); - unsigned field_line = 0; - u64 offset_in_bits = 8*cast(u64)bt->Union.variant_block_size; - LLVMDIFlags field_flags = LLVMDIFlagZero; - - elements[0] = LLVMDIBuilderCreateMemberType( - m->debug_builder, record_scope, - "tag", 3, - file, field_line, - 8*cast(u64)type_size_of(tag_type), 8*cast(u32)type_align_of(tag_type), - offset_in_bits, - field_flags, lb_debug_type(m, tag_type) - ); - } - - for_array(j, bt->Union.variants) { - Type *variant = bt->Union.variants[j]; - - unsigned field_index = cast(unsigned)(index_offset+j); - - char name[16] = {}; - gb_snprintf(name, gb_size_of(name), "v%u", field_index); - isize name_len = gb_strlen(name); - - unsigned field_line = 0; - LLVMDIFlags field_flags = LLVMDIFlagZero; - u64 offset_in_bits = 0; - - elements[field_index] = LLVMDIBuilderCreateMemberType( - m->debug_builder, record_scope, - name, name_len, - file, field_line, - 8*cast(u64)type_size_of(variant), 8*cast(u32)type_align_of(variant), - offset_in_bits, - field_flags, lb_debug_type(m, variant) - ); - } - } - break; - - case Type_BitSet: - { - if (file == nullptr) { - GB_ASSERT(bt->BitSet.node != nullptr); - file = lb_get_llvm_metadata(m, bt->BitSet.node->file()); - line_number = cast(unsigned)ast_token(bt->BitSet.node).pos.line; - } - - LLVMMetadataRef bit_set_field_type = lb_debug_type(m, t_bool); - LLVMMetadataRef scope = file; - - Type *elem = base_type(bt->BitSet.elem); - if (elem->kind == Type_Enum) { - element_count = cast(unsigned)elem->Enum.fields.count; - elements = gb_alloc_array(temporary_allocator(), LLVMMetadataRef, element_count); - for_array(i, elem->Enum.fields) { - Entity *f = elem->Enum.fields[i]; - GB_ASSERT(f->kind == Entity_Constant); - i64 val = exact_value_to_i64(f->Constant.value); - String name = f->token.string; - u64 offset_in_bits = cast(u64)(val - bt->BitSet.lower); - elements[i] = LLVMDIBuilderCreateBitFieldMemberType( - m->debug_builder, - scope, - cast(char const *)name.text, name.len, - file, line_number, - 1, - offset_in_bits, - 0, - LLVMDIFlagZero, - bit_set_field_type - ); - } - } else { - - char name[32] = {}; - - GB_ASSERT(is_type_integer(elem)); - i64 count = bt->BitSet.upper - bt->BitSet.lower + 1; - GB_ASSERT(0 <= count); - - element_count = cast(unsigned)count; - elements = gb_alloc_array(temporary_allocator(), LLVMMetadataRef, element_count); - for (unsigned i = 0; i < element_count; i++) { - u64 offset_in_bits = i; - i64 val = bt->BitSet.lower + cast(i64)i; - gb_snprintf(name, gb_count_of(name), "%lld", cast(long long)val); - elements[i] = LLVMDIBuilderCreateBitFieldMemberType( - m->debug_builder, - scope, - name, gb_strlen(name), - file, line_number, - 1, - offset_in_bits, - 0, - LLVMDIFlagZero, - bit_set_field_type - ); - } - } - } - } - - - LLVMMetadataRef final_metadata = nullptr; - if (is_union) { - final_metadata = LLVMDIBuilderCreateUnionType( - m->debug_builder, - parent_scope, - cast(char const *)name.text, cast(size_t)name.len, - file, line_number, - size_in_bits, align_in_bits, - flags, - elements, element_count, - runtime_lang, - unique_id, unique_id_len - ); - } else { - final_metadata = LLVMDIBuilderCreateStructType( - m->debug_builder, - parent_scope, - cast(char const *)name.text, cast(size_t)name.len, - file, line_number, - size_in_bits, align_in_bits, - flags, - derived_from, - elements, element_count, - runtime_lang, - vtable_holder, - unique_id, unique_id_len - ); - } - - LLVMMetadataReplaceAllUsesWith(idt.metadata, final_metadata); - lb_set_llvm_metadata(m, idt.type, final_metadata); - } break; - default: - GB_PANIC("invalid incomplete debug type"); - break; - } - } - array_clear(&m->debug_incomplete_types); -} - - - gb_internal void lb_add_debug_local_variable(lbProcedure *p, LLVMValueRef ptr, Type *type, Token const &token) { if (p->debug_info == nullptr) { return; @@ -1019,7 +1088,7 @@ gb_internal void lb_add_debug_local_variable(lbProcedure *p, LLVMValueRef ptr, T LLVMDIBuilderInsertDeclareAtEnd(m->debug_builder, storage, var_info, llvm_expr, llvm_debug_loc, block); } -gb_internal void lb_add_debug_param_variable(lbProcedure *p, LLVMValueRef ptr, Type *type, Token const &token, unsigned arg_number, lbBlock *block, lbArgKind arg_kind) { +gb_internal void lb_add_debug_param_variable(lbProcedure *p, LLVMValueRef ptr, Type *type, Token const &token, unsigned arg_number, lbBlock *block) { if (p->debug_info == nullptr) { return; } @@ -1080,15 +1149,7 @@ gb_internal void lb_add_debug_param_variable(lbProcedure *p, LLVMValueRef ptr, T // NOTE(bill, 2022-02-01): For parameter values, you must insert them at the end of the decl block // The reason is that if the parameter is at index 0 and a pointer, there is not such things as an // instruction "before" it. - switch (arg_kind) { - case lbArg_Direct: - LLVMDIBuilderInsertDbgValueAtEnd(m->debug_builder, storage, var_info, llvm_expr, llvm_debug_loc, block->block); - break; - case lbArg_Indirect: - LLVMDIBuilderInsertDeclareAtEnd(m->debug_builder, storage, var_info, llvm_expr, llvm_debug_loc, block->block); - break; - } - + LLVMDIBuilderInsertDeclareAtEnd(m->debug_builder, storage, var_info, llvm_expr, llvm_debug_loc, block->block); } @@ -1170,6 +1231,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; @@ -1227,4 +1289,4 @@ gb_internal void add_debug_info_for_global_constant_from_entity(lbGenerator *gen add_debug_info_for_global_constant_internal_i64(m, e, dtype, v); } } -} \ No newline at end of file +} diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 8678a125c..df9dca801 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -130,12 +130,17 @@ gb_internal lbValue lb_emit_unary_arith(lbProcedure *p, TokenKind op, lbValue x, LLVMTypeRef vector_type = nullptr; if (op != Token_Not && lb_try_vector_cast(p->module, val, &vector_type)) { LLVMValueRef vp = LLVMBuildPointerCast(p->builder, val.value, LLVMPointerType(vector_type, 0), ""); - LLVMValueRef v = LLVMBuildLoad2(p->builder, vector_type, vp, ""); + LLVMValueRef v = OdinLLVMBuildLoad(p, vector_type, vp); LLVMValueRef opv = nullptr; switch (op) { case Token_Xor: opv = LLVMBuildNot(p->builder, v, ""); + if (is_type_bit_set(elem_type)) { + ExactValue ev_mask = exact_bit_set_all_set_mask(elem_type); + lbValue mask = lb_const_value(p->module, elem_type, ev_mask); + opv = LLVMBuildAnd(p->builder, opv, mask.value, ""); + } break; case Token_Sub: if (is_type_float(elem_type)) { @@ -176,8 +181,14 @@ gb_internal lbValue lb_emit_unary_arith(lbProcedure *p, TokenKind op, lbValue x, if (op == Token_Xor) { lbValue cmp = {}; - cmp.value = LLVMBuildNot(p->builder, x.value, ""); cmp.type = x.type; + if (is_type_bit_set(x.type)) { + ExactValue ev_mask = exact_bit_set_all_set_mask(x.type); + lbValue mask = lb_const_value(p->module, x.type, ev_mask); + cmp.value = LLVMBuildXor(p->builder, x.value, mask.value, ""); + } else { + cmp.value = LLVMBuildNot(p->builder, x.value, ""); + } return lb_emit_conv(p, cmp, type); } @@ -296,12 +307,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 +316,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 = OdinLLVMBuildLoad(p, vector_type, lhs_vp); + LLVMValueRef y = OdinLLVMBuildLoad(p, vector_type, rhs_vp); + LLVMValueRef z = nullptr; + if (is_type_float(integral_type)) { switch (op) { case Token_Add: @@ -504,6 +519,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) { @@ -532,15 +551,14 @@ gb_internal LLVMValueRef lb_matrix_to_vector(lbProcedure *p, lbValue matrix) { Type *mt = base_type(matrix.type); GB_ASSERT(mt->kind == Type_Matrix); LLVMTypeRef elem_type = lb_type(p->module, mt->Matrix.elem); - + unsigned total_count = cast(unsigned)matrix_type_total_internal_elems(mt); LLVMTypeRef total_matrix_type = LLVMVectorType(elem_type, total_count); - + #if 1 LLVMValueRef ptr = lb_address_from_load_or_generate_local(p, matrix).value; LLVMValueRef matrix_vector_ptr = LLVMBuildPointerCast(p->builder, ptr, LLVMPointerType(total_matrix_type, 0), ""); - LLVMValueRef matrix_vector = LLVMBuildLoad2(p->builder, total_matrix_type, matrix_vector_ptr, ""); - LLVMSetAlignment(matrix_vector, cast(unsigned)type_align_of(mt)); + LLVMValueRef matrix_vector = OdinLLVMBuildLoadAligned(p, total_matrix_type, matrix_vector_ptr, type_align_of(mt)); return matrix_vector; #else LLVMValueRef matrix_vector = LLVMBuildBitCast(p->builder, matrix.value, total_matrix_type, ""); @@ -684,39 +702,39 @@ gb_internal lbValue lb_emit_matrix_flatten(lbProcedure *p, lbValue m, Type *type Type *mt = base_type(m.type); GB_ASSERT(mt->kind == Type_Matrix); - // TODO(bill): Determine why this fails on Windows sometimes - if (false && lb_is_matrix_simdable(mt)) { - LLVMValueRef vector = lb_matrix_to_trimmed_vector(p, m); - return lb_matrix_cast_vector_to_type(p, vector, type); - } - lbAddr res = lb_add_local_generated(p, type, true); - i64 row_count = mt->Matrix.row_count; - i64 column_count = mt->Matrix.column_count; - TEMPORARY_ALLOCATOR_GUARD(); + GB_ASSERT(type_size_of(type) == type_size_of(m.type)); - auto srcs = array_make(temporary_allocator(), 0, row_count*column_count); - auto dsts = array_make(temporary_allocator(), 0, row_count*column_count); + lbValue m_ptr = lb_address_from_load_or_generate_local(p, m); + lbValue n = lb_const_int(p->module, t_int, type_size_of(type)); + lb_mem_copy_non_overlapping(p, res.addr, m_ptr, n); - for (i64 j = 0; j < column_count; j++) { - for (i64 i = 0; i < row_count; i++) { - lbValue src = lb_emit_matrix_ev(p, m, i, j); - array_add(&srcs, src); - } - } + // i64 row_count = mt->Matrix.row_count; + // i64 column_count = mt->Matrix.column_count; + // TEMPORARY_ALLOCATOR_GUARD(); - for (i64 j = 0; j < column_count; j++) { - for (i64 i = 0; i < row_count; i++) { - lbValue dst = lb_emit_array_epi(p, res.addr, i + j*row_count); - array_add(&dsts, dst); - } - } + // auto srcs = array_make(temporary_allocator(), 0, row_count*column_count); + // auto dsts = array_make(temporary_allocator(), 0, row_count*column_count); - GB_ASSERT(srcs.count == dsts.count); - for_array(i, srcs) { - lb_emit_store(p, dsts[i], srcs[i]); - } + // for (i64 j = 0; j < column_count; j++) { + // for (i64 i = 0; i < row_count; i++) { + // lbValue src = lb_emit_matrix_ev(p, m, i, j); + // array_add(&srcs, src); + // } + // } + + // for (i64 j = 0; j < column_count; j++) { + // for (i64 i = 0; i < row_count; i++) { + // lbValue dst = lb_emit_array_epi(p, res.addr, i + j*row_count); + // array_add(&dsts, dst); + // } + // } + + // GB_ASSERT(srcs.count == dsts.count); + // for_array(i, srcs) { + // lb_emit_store(p, dsts[i], srcs[i]); + // } return lb_addr_load(p, res); } @@ -763,6 +781,7 @@ gb_internal lbValue lb_emit_matrix_mul(lbProcedure *p, lbValue lhs, lbValue rhs, GB_ASSERT(is_type_matrix(yt)); GB_ASSERT(xt->Matrix.column_count == yt->Matrix.row_count); GB_ASSERT(are_types_identical(xt->Matrix.elem, yt->Matrix.elem)); + GB_ASSERT(xt->Matrix.is_row_major == yt->Matrix.is_row_major); Type *elem = xt->Matrix.elem; @@ -770,7 +789,7 @@ gb_internal lbValue lb_emit_matrix_mul(lbProcedure *p, lbValue lhs, lbValue rhs, unsigned inner = cast(unsigned)xt->Matrix.column_count; unsigned outer_columns = cast(unsigned)yt->Matrix.column_count; - if (lb_is_matrix_simdable(xt)) { + if (!xt->Matrix.is_row_major && lb_is_matrix_simdable(xt)) { unsigned x_stride = cast(unsigned)matrix_type_stride_in_elems(xt); unsigned y_stride = cast(unsigned)matrix_type_stride_in_elems(yt); @@ -812,7 +831,7 @@ gb_internal lbValue lb_emit_matrix_mul(lbProcedure *p, lbValue lhs, lbValue rhs, return lb_addr_load(p, res); } - { + if (!xt->Matrix.is_row_major) { lbAddr res = lb_add_local_generated(p, type, true); auto inners = slice_make(permanent_allocator(), inner); @@ -835,6 +854,30 @@ gb_internal lbValue lb_emit_matrix_mul(lbProcedure *p, lbValue lhs, lbValue rhs, } } + return lb_addr_load(p, res); + } else { + lbAddr res = lb_add_local_generated(p, type, true); + + auto inners = slice_make(permanent_allocator(), inner); + + for (unsigned i = 0; i < outer_rows; i++) { + for (unsigned j = 0; j < outer_columns; j++) { + lbValue dst = lb_emit_matrix_epi(p, res.addr, i, j); + for (unsigned k = 0; k < inner; k++) { + inners[k][0] = lb_emit_matrix_ev(p, lhs, i, k); + inners[k][1] = lb_emit_matrix_ev(p, rhs, k, j); + } + + lbValue sum = lb_const_nil(p->module, elem); + for (unsigned k = 0; k < inner; k++) { + lbValue a = inners[k][0]; + lbValue b = inners[k][1]; + sum = lb_emit_mul_add(p, a, b, sum, elem); + } + lb_emit_store(p, dst, sum); + } + } + return lb_addr_load(p, res); } } @@ -855,7 +898,7 @@ gb_internal lbValue lb_emit_matrix_mul_vector(lbProcedure *p, lbValue lhs, lbVal Type *elem = mt->Matrix.elem; - if (lb_is_matrix_simdable(mt)) { + if (!mt->Matrix.is_row_major && lb_is_matrix_simdable(mt)) { unsigned stride = cast(unsigned)matrix_type_stride_in_elems(mt); unsigned row_count = cast(unsigned)mt->Matrix.row_count; @@ -924,7 +967,7 @@ gb_internal lbValue lb_emit_vector_mul_matrix(lbProcedure *p, lbValue lhs, lbVal Type *elem = mt->Matrix.elem; - if (lb_is_matrix_simdable(mt)) { + if (!mt->Matrix.is_row_major && lb_is_matrix_simdable(mt)) { unsigned stride = cast(unsigned)matrix_type_stride_in_elems(mt); unsigned row_count = cast(unsigned)mt->Matrix.row_count; @@ -1104,12 +1147,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); @@ -1133,6 +1185,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); @@ -1144,15 +1201,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) { + z0 = lb_emit_conv(p, z0, ft); + z1 = lb_emit_conv(p, z1, ft); + z2 = lb_emit_conv(p, z2, ft); + z3 = lb_emit_conv(p, z3, 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) { @@ -1225,6 +1306,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; @@ -1354,6 +1443,58 @@ gb_internal bool lb_is_empty_string_constant(Ast *expr) { return false; } +gb_internal lbValue lb_build_binary_in(lbProcedure *p, lbValue left, lbValue right, TokenKind op) { + Type *rt = base_type(right.type); + if (is_type_pointer(rt)) { + right = lb_emit_load(p, right); + rt = base_type(type_deref(rt)); + } + + switch (rt->kind) { + case Type_Map: + { + lbValue map_ptr = lb_address_from_load_or_generate_local(p, right); + lbValue key = left; + lbValue ptr = lb_internal_dynamic_map_get_ptr(p, map_ptr, key); + if (op == Token_in) { + return lb_emit_conv(p, lb_emit_comp_against_nil(p, Token_NotEq, ptr), t_bool); + } else { + return lb_emit_conv(p, lb_emit_comp_against_nil(p, Token_CmpEq, ptr), t_bool); + } + } + break; + case Type_BitSet: + { + Type *key_type = rt->BitSet.elem; + 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)); + } + + lbValue lower = lb_const_value(p->module, left.type, exact_value_i64(rt->BitSet.lower)); + lbValue key = lb_emit_arith(p, Token_Sub, left, lower, left.type); + lbValue bit = lb_emit_arith(p, Token_Shl, lb_const_int(p->module, left.type, 1), key, left.type); + bit = lb_emit_conv(p, bit, it); + + lbValue old_value = lb_emit_transmute(p, right, it); + lbValue new_value = lb_emit_arith(p, Token_And, old_value, bit, it); + + if (op == Token_in) { + return lb_emit_conv(p, lb_emit_comp(p, Token_NotEq, new_value, lb_const_int(p->module, new_value.type, 0)), t_bool); + } else { + return lb_emit_conv(p, lb_emit_comp(p, Token_CmpEq, new_value, lb_const_int(p->module, new_value.type, 0)), t_bool); + } + } + break; + } + GB_PANIC("Invalid 'in' type"); + return {}; +} + gb_internal lbValue lb_build_binary_expr(lbProcedure *p, Ast *expr) { ast_node(be, BinaryExpr, expr); @@ -1461,57 +1602,8 @@ gb_internal lbValue lb_build_binary_expr(lbProcedure *p, Ast *expr) { { lbValue left = lb_build_expr(p, be->left); lbValue right = lb_build_expr(p, be->right); - Type *rt = base_type(right.type); - if (is_type_pointer(rt)) { - right = lb_emit_load(p, right); - rt = base_type(type_deref(rt)); - } - - switch (rt->kind) { - case Type_Map: - { - lbValue map_ptr = lb_address_from_load_or_generate_local(p, right); - lbValue key = left; - lbValue ptr = lb_internal_dynamic_map_get_ptr(p, map_ptr, key); - if (be->op.kind == Token_in) { - return lb_emit_conv(p, lb_emit_comp_against_nil(p, Token_NotEq, ptr), t_bool); - } else { - return lb_emit_conv(p, lb_emit_comp_against_nil(p, Token_CmpEq, ptr), t_bool); - } - } - break; - case Type_BitSet: - { - Type *key_type = rt->BitSet.elem; - 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)); - } - - lbValue lower = lb_const_value(p->module, left.type, exact_value_i64(rt->BitSet.lower)); - lbValue key = lb_emit_arith(p, Token_Sub, left, lower, left.type); - lbValue bit = lb_emit_arith(p, Token_Shl, lb_const_int(p->module, left.type, 1), key, left.type); - bit = lb_emit_conv(p, bit, it); - - lbValue old_value = lb_emit_transmute(p, right, it); - lbValue new_value = lb_emit_arith(p, Token_And, old_value, bit, it); - - if (be->op.kind == Token_in) { - return lb_emit_conv(p, lb_emit_comp(p, Token_NotEq, new_value, lb_const_int(p->module, new_value.type, 0)), t_bool); - } else { - return lb_emit_conv(p, lb_emit_comp(p, Token_CmpEq, new_value, lb_const_int(p->module, new_value.type, 0)), t_bool); - } - } - break; - default: - GB_PANIC("Invalid 'in' type"); - } - break; + return lb_build_binary_in(p, left, right, be->op.kind); } - break; default: GB_PANIC("Invalid binary expression"); break; @@ -1555,7 +1647,7 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { lb_emit_store(p, a1, id); return lb_addr_load(p, res); } else if (dst->kind == Type_Basic) { - if (src->Basic.kind == Basic_string && dst->Basic.kind == Basic_cstring) { + if (src->kind == Type_Basic && src->Basic.kind == Basic_string && dst->Basic.kind == Basic_cstring) { String str = lb_get_const_string(m, value); lbValue res = {}; res.type = t; @@ -1848,13 +1940,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; } @@ -1925,7 +2044,11 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { } else if (is_type_integer(src_elem) && is_type_boolean(dst_elem)) { LLVMValueRef i1vector = LLVMBuildICmp(p->builder, LLVMIntNE, value.value, LLVMConstNull(LLVMTypeOf(value.value)), ""); res.value = LLVMBuildIntCast2(p->builder, i1vector, lb_type(m, t), !is_type_unsigned(src_elem), ""); - } else { + } else if (is_type_pointer(src_elem) && is_type_integer(dst_elem)) { + res.value = LLVMBuildPtrToInt(p->builder, value.value, lb_type(m, t), ""); + } else if (is_type_integer(src_elem) && is_type_pointer(dst_elem)) { + res.value = LLVMBuildIntToPtr(p->builder, value.value, lb_type(m, t), ""); + }else { GB_PANIC("Unhandled simd vector conversion: %s -> %s", type_to_string(src), type_to_string(dst)); } return res; @@ -1946,6 +2069,44 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { } } + // bit_field <-> backing type + if (is_type_bit_field(src)) { + if (are_types_identical(src->BitField.backing_type, dst)) { + lbValue res = {}; + res.type = t; + res.value = value.value; + return res; + } + } + if (is_type_bit_field(dst)) { + if (are_types_identical(src, dst->BitField.backing_type)) { + lbValue res = {}; + res.type = t; + res.value = value.value; + return res; + } + } + + // 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)) { @@ -1974,13 +2135,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)) { @@ -1990,6 +2144,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 @@ -2099,14 +2297,45 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { if (is_type_array_like(dst)) { Type *elem = base_array_type(dst); - lbValue e = lb_emit_conv(p, value, elem); - // NOTE(bill): Doesn't need to be zero because it will be initialized in the loops - lbAddr v = lb_add_local_generated(p, t, false); isize index_count = cast(isize)get_array_type_count(dst); - for (isize i = 0; i < index_count; i++) { - lbValue elem = lb_emit_array_epi(p, v.addr, i); + isize inlineable = type_size_of(dst) <= build_context.max_simd_align; + lbValue e = lb_emit_conv(p, value, elem); + if (inlineable && lb_is_const(e)) { + lbAddr v = {}; + if (e.value) { + TEMPORARY_ALLOCATOR_GUARD(); + LLVMValueRef *values = gb_alloc_array(temporary_allocator(), LLVMValueRef, index_count); + for (isize i = 0; i < index_count; i++) { + values[i] = e.value; + } + lbValue array_const_value = {}; + array_const_value.type = t; + array_const_value.value = LLVMConstArray(lb_type(m, elem), values, cast(unsigned)index_count); + v = lb_add_global_generated(m, t, array_const_value); + } else { + v = lb_add_global_generated(m, t); + } + + lb_make_global_private_const(v); + return lb_addr_load(p, v); + } + + // NOTE(bill): Doesn't need to be zero because it will be initialized in the loops + lbAddr v = lb_add_local_generated(p, t, false); + + if (!inlineable) { + auto loop_data = lb_loop_start(p, index_count, t_int); + + lbValue elem = lb_emit_array_ep(p, v.addr, loop_data.idx); lb_emit_store(p, elem, e); + + lb_loop_end(p, loop_data); + } else { + for (isize i = 0; i < index_count; i++) { + lbValue elem = lb_emit_array_epi(p, v.addr, i); + lb_emit_store(p, elem, e); + } } return lb_addr_load(p, v); } @@ -2134,12 +2363,23 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { GB_ASSERT(src->kind == Type_Matrix); lbAddr v = lb_add_local_generated(p, t, true); - if (is_matrix_square(dst) && is_matrix_square(dst)) { + if (dst->Matrix.row_count == src->Matrix.row_count && + dst->Matrix.column_count == src->Matrix.column_count) { + for (i64 j = 0; j < dst->Matrix.column_count; j++) { + for (i64 i = 0; i < dst->Matrix.row_count; i++) { + lbValue d = lb_emit_matrix_epi(p, v.addr, i, j); + lbValue s = lb_emit_matrix_ev(p, value, i, j); + s = lb_emit_conv(p, s, dst->Matrix.elem); + lb_emit_store(p, d, s); + } + } + } else if (is_matrix_square(dst) && is_matrix_square(dst)) { for (i64 j = 0; j < dst->Matrix.column_count; j++) { for (i64 i = 0; i < dst->Matrix.row_count; i++) { if (i < src->Matrix.row_count && j < src->Matrix.column_count) { lbValue d = lb_emit_matrix_epi(p, v.addr, i, j); lbValue s = lb_emit_matrix_ev(p, value, i, j); + s = lb_emit_conv(p, s, dst->Matrix.elem); lb_emit_store(p, d, s); } else if (i == j) { lbValue d = lb_emit_matrix_epi(p, v.addr, i, j); @@ -2256,6 +2496,17 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { return {}; } +gb_internal lbValue lb_emit_c_vararg(lbProcedure *p, lbValue arg, Type *type) { + Type *core = core_type(type); + if (core->kind == Type_BitSet) { + core = core_type(bit_set_to_int(core)); + arg = lb_emit_transmute(p, arg, core); + } + + Type *promoted = c_vararg_promote_type(core); + return lb_emit_conv(p, arg, promoted); +} + gb_internal lbValue lb_compare_records(lbProcedure *p, TokenKind op_kind, lbValue left, lbValue right, Type *type) { GB_ASSERT((is_type_struct(type) || is_type_union(type)) && is_type_comparable(type)); lbValue left_ptr = lb_address_from_load_or_generate_local(p, left); @@ -2314,9 +2565,26 @@ gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left if (are_types_identical(a, b)) { // NOTE(bill): No need for a conversion - } else if (lb_is_const(left) || lb_is_const_nil(left)) { + } else if ((lb_is_const(left) && !is_type_array(left.type)) || lb_is_const_nil(left)) { + // NOTE(karl): !is_type_array(left.type) is there to avoid lb_emit_conv + // trying to convert a constant array into a non-array. In that case we + // want the `else` branch to happen, so it can try to convert the + // non-array into an array instead. + + if (lb_is_const_nil(left)) { + if (internal_check_is_assignable_to(right.type, left.type)) { + right = lb_emit_conv(p, right, left.type); + } + return lb_emit_comp_against_nil(p, op_kind, right); + } left = lb_emit_conv(p, left, right.type); - } else if (lb_is_const(right) || lb_is_const_nil(right)) { + } else if ((lb_is_const(right) && !is_type_array(right.type)) || lb_is_const_nil(right)) { + if (lb_is_const_nil(right)) { + if (internal_check_is_assignable_to(left.type, right.type)) { + left = lb_emit_conv(p, left, right.type); + } + return lb_emit_comp_against_nil(p, op_kind, left); + } right = lb_emit_conv(p, right, left.type); } else { Type *lt = left.type; @@ -2444,7 +2712,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); @@ -2467,7 +2735,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); @@ -2549,6 +2817,12 @@ gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left Type *it = bit_set_to_int(a); lbValue lhs = lb_emit_transmute(p, left, it); lbValue rhs = lb_emit_transmute(p, right, it); + if (is_type_different_to_arch_endianness(it)) { + it = integer_endian_type_to_platform_type(it); + lhs = lb_emit_byte_swap(p, lhs, it); + rhs = lb_emit_byte_swap(p, rhs, it); + } + lbValue res = lb_emit_arith(p, Token_And, lhs, rhs, it); if (op_kind == Token_Lt || op_kind == Token_LtEq) { @@ -2646,6 +2920,12 @@ gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left } } + if (is_type_different_to_arch_endianness(left.type)) { + Type *pt = integer_endian_type_to_platform_type(left.type); + lhs = lb_emit_byte_swap(p, {lhs, pt}, pt).value; + rhs = lb_emit_byte_swap(p, {rhs, pt}, pt).value; + } + res.value = LLVMBuildICmp(p->builder, pred, lhs, rhs, ""); } else if (is_type_float(a)) { LLVMRealPredicate pred = {}; @@ -2657,6 +2937,13 @@ gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left case Token_LtEq: pred = LLVMRealOLE; break; case Token_NotEq: pred = LLVMRealONE; break; } + + if (is_type_different_to_arch_endianness(left.type)) { + Type *pt = integer_endian_type_to_platform_type(left.type); + left = lb_emit_byte_swap(p, left, pt); + right = lb_emit_byte_swap(p, right, pt); + } + res.value = LLVMBuildFCmp(p->builder, pred, left.value, right.value, ""); } else if (is_type_typeid(a)) { LLVMIntPredicate pred = {}; @@ -2775,13 +3062,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: { @@ -2890,15 +3196,6 @@ gb_internal lbValue lb_emit_comp_against_nil(lbProcedure *p, TokenKind op_kind, return {}; } -gb_internal lbValue lb_make_soa_pointer(lbProcedure *p, Type *type, lbValue const &addr, lbValue const &index) { - lbAddr v = lb_add_local_generated(p, type, false); - lbValue ptr = lb_emit_struct_ep(p, v.addr, 0); - lbValue idx = lb_emit_struct_ep(p, v.addr, 1); - lb_emit_store(p, ptr, addr); - lb_emit_store(p, idx, lb_emit_conv(p, index, t_int)); - - return lb_addr_load(p, v); -} gb_internal lbValue lb_build_unary_and(lbProcedure *p, Ast *expr) { ast_node(ue, UnaryExpr, expr); @@ -2939,6 +3236,12 @@ gb_internal lbValue lb_build_unary_and(lbProcedure *p, Ast *expr) { } else if (is_type_soa_pointer(tv.type)) { ast_node(ie, IndexExpr, ue_expr); lbValue addr = lb_build_addr_ptr(p, ie->expr); + + if (is_type_pointer(type_deref(addr.type))) { + addr = lb_emit_load(p, addr); + } + GB_ASSERT(is_type_pointer(addr.type)); + lbValue index = lb_build_expr(p, ie->index); if (!build_context.no_bounds_check) { @@ -3040,7 +3343,7 @@ gb_internal lbValue lb_build_unary_and(lbProcedure *p, Ast *expr) { Type *dst_type = type; - if ((p->state_flags & StateFlag_no_type_assert) == 0) { + if (!build_context.no_type_assert && (p->state_flags & StateFlag_no_type_assert) == 0) { lbValue src_tag = {}; lbValue dst_tag = {}; if (is_type_union_maybe_pointer(src_type)) { @@ -3080,7 +3383,7 @@ gb_internal lbValue lb_build_unary_and(lbProcedure *p, Ast *expr) { v = lb_emit_load(p, v); } lbValue data_ptr = lb_emit_struct_ev(p, v, 0); - if ((p->state_flags & StateFlag_no_type_assert) == 0) { + if (!build_context.no_type_assert && (p->state_flags & StateFlag_no_type_assert) == 0) { GB_ASSERT(!build_context.no_rtti); lbValue any_id = lb_emit_struct_ev(p, v, 1); @@ -3176,13 +3479,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) { @@ -3192,13 +3490,25 @@ gb_internal lbValue lb_build_expr_internal(lbProcedure *p, Ast *expr) { switch (expr->kind) { case_ast_node(bl, BasicLit, expr); + if (type != nullptr && type->Named.name == "Error") { + Entity *e = type->Named.type_name; + if (e->pkg && e->pkg->name == "os") { + return lb_const_nil(p->module, type); + } + } TokenPos pos = bl->token.pos; - GB_PANIC("Non-constant basic literal %s - %.*s", token_pos_to_string(pos), LIT(token_strings[bl->token.kind])); + GB_PANIC("Non-constant basic literal %s - %.*s (%s)", token_pos_to_string(pos), LIT(token_strings[bl->token.kind]), type_to_string(type)); case_end; case_ast_node(bd, BasicDirective, expr); TokenPos pos = bd->token.pos; - GB_PANIC("Non-constant basic literal %s - %.*s", token_pos_to_string(pos), LIT(bd->name.string)); + String name = bd->name.string; + if (name == "branch_location") { + GB_ASSERT(p->uses_branch_location); + String proc_name = p->entity->token.string; + return lb_emit_source_code_location_as_global(p, proc_name, p->branch_location_pos); + } + GB_PANIC("Non-constant basic literal %s - %.*s", token_pos_to_string(pos), LIT(name)); case_end; case_ast_node(i, Implicit, expr); @@ -3364,7 +3674,7 @@ gb_internal lbValue lb_build_expr_internal(lbProcedure *p, Ast *expr) { 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_defer_stmts(p, lbDeferExit_Branch, block, expr); lb_emit_jump(p, block); lb_start_block(p, then); @@ -3503,7 +3813,9 @@ gb_internal lbValue lb_get_using_variable(lbProcedure *p, Entity *e) { lbValue v = {}; + bool is_soa = false; if (pv == nullptr && parent->flags & EntityFlag_SoaPtrField) { + is_soa = true; // NOTE(bill): using SOA value (probably from for-in statement) lbAddr parent_addr = lb_get_soa_variable_addr(p, parent); v = lb_addr_get_ptr(p, parent_addr); @@ -3514,7 +3826,7 @@ gb_internal lbValue lb_get_using_variable(lbProcedure *p, Entity *e) { v = lb_build_addr_ptr(p, e->using_expr); } GB_ASSERT(v.value != nullptr); - GB_ASSERT_MSG(parent->type == type_deref(v.type), "%s %s", type_to_string(parent->type), type_to_string(v.type)); + GB_ASSERT_MSG(is_soa || parent->type == type_deref(v.type), "%s %s", type_to_string(parent->type), type_to_string(v.type)); lbValue ptr = lb_emit_deep_field_gep(p, v, sel); if (parent->scope) { if ((parent->scope->flags & (ScopeFlag_File|ScopeFlag_Pkg)) == 0) { @@ -3578,7 +3890,7 @@ gb_internal lbAddr lb_build_array_swizzle_addr(lbProcedure *p, AstCallExpr *ce, Type *type = base_type(lb_addr_type(addr)); GB_ASSERT(type->kind == Type_Array); i64 count = type->Array.count; - if (count <= 4) { + if (count <= 4 && index_count <= 4) { u8 indices[4] = {}; u8 index_count = 0; for (i32 i = 1; i < ce->args.count; i++) { @@ -3651,7 +3963,7 @@ gb_internal void lb_build_addr_compound_lit_populate(lbProcedure *p, Slicekind == Ast_FieldValue) { ast_node(fv, FieldValue, elem); - if (lb_is_elem_const(fv->value, et)) { + if (bt->kind != Type_DynamicArray && lb_is_elem_const(fv->value, et)) { continue; } if (is_ast_range(fv->field)) { @@ -3775,27 +4087,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); @@ -3901,30 +4225,6 @@ gb_internal lbAddr lb_build_addr_index_expr(lbProcedure *p, Ast *expr) { return lb_addr(v); } - case Type_RelativeMultiPointer: { - lbAddr rel_ptr_addr = {}; - if (deref) { - lbValue rel_ptr_ptr = lb_build_expr(p, ie->expr); - rel_ptr_addr = lb_addr(rel_ptr_ptr); - } else { - rel_ptr_addr = lb_build_addr(p, ie->expr); - } - lbValue rel_ptr = lb_relative_pointer_to_pointer(p, rel_ptr_addr); - - lbValue index = lb_build_expr(p, ie->index); - index = lb_emit_conv(p, index, t_int); - lbValue v = {}; - - Type *pointer_type = base_type(t->RelativeMultiPointer.pointer_type); - GB_ASSERT(pointer_type->kind == Type_MultiPointer); - Type *elem = pointer_type->MultiPointer.elem; - - LLVMValueRef indices[1] = {index.value}; - v.value = LLVMBuildGEP2(p->builder, lb_type(p->module, elem), rel_ptr.value, indices, 1, ""); - v.type = alloc_type_pointer(elem); - return lb_addr(v); - } - case Type_DynamicArray: { lbValue dynamic_array = {}; dynamic_array = lb_build_expr(p, ie->expr); @@ -3947,12 +4247,21 @@ gb_internal lbAddr lb_build_addr_index_expr(lbProcedure *p, Ast *expr) { } lbValue index = lb_build_expr(p, ie->index); index = lb_emit_conv(p, index, t_int); - lbValue elem = lb_emit_matrix_ep(p, matrix, lb_const_int(p->module, t_int, 0), index); + + isize bounds_len = 0; + lbValue elem = {}; + if (t->Matrix.is_row_major) { + bounds_len = t->Matrix.row_count; + elem = lb_emit_matrix_ep(p, matrix, index, lb_const_int(p->module, t_int, 0)); + } else { + bounds_len = t->Matrix.column_count; + elem = lb_emit_matrix_ep(p, matrix, lb_const_int(p->module, t_int, 0), index); + } elem = lb_emit_conv(p, elem, alloc_type_pointer(type_of_expr(expr))); auto index_tv = type_and_value_of_expr(ie->index); if (index_tv.mode != Addressing_Constant) { - lbValue len = lb_const_int(p->module, t_int, t->Matrix.column_count); + lbValue len = lb_const_int(p->module, t_int, bounds_len); lb_emit_bounds_check(p, ast_token(ie->index), index, len); } return lb_addr(elem); @@ -4025,13 +4334,6 @@ gb_internal lbAddr lb_build_addr_slice_expr(lbProcedure *p, Ast *expr) { return slice; } - case Type_RelativePointer: - GB_PANIC("TODO(bill): Type_RelativePointer should be handled above already on the lb_addr_load"); - break; - case Type_RelativeMultiPointer: - GB_PANIC("TODO(bill): Type_RelativeMultiPointer should be handled above already on the lb_addr_load"); - break; - case Type_DynamicArray: { Type *elem_type = type->DynamicArray.elem; Type *slice_type = alloc_type_slice(elem_type); @@ -4056,7 +4358,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); @@ -4065,7 +4367,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; @@ -4099,7 +4401,7 @@ gb_internal lbAddr lb_build_addr_slice_expr(lbProcedure *p, Ast *expr) { } case Type_Basic: { - GB_ASSERT_MSG(type == t_string, "got %s", type_to_string(type)); + GB_ASSERT_MSG(are_types_identical(type, t_string), "got %s", type_to_string(type)); lbValue len = lb_string_len(p, base); if (high.value == nullptr) high = len; @@ -4133,6 +4435,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); } @@ -4148,6 +4451,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); } @@ -4162,6 +4466,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); } @@ -4211,6 +4516,208 @@ 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: { + 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; + Selection sel = lookup_field(bt, name, false); + GB_ASSERT(sel.is_bit_field); + GB_ASSERT(!sel.indirect); + GB_ASSERT(sel.index.count == 1); + GB_ASSERT(sel.entity != nullptr); + + i64 index = sel.index[0]; + 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); + 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); + #if LLVM_VERSION_MAJOR >= 19 + mask = LLVMBuildShl(p->builder, mask, LLVMConstInt(lit, f.bit_size, false), ""); + #else + mask = LLVMConstShl(mask, LLVMConstInt(lit, f.bit_size, false)); + #endif + 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); + #if LLVM_VERSION_MAJOR >= 19 + mask = LLVMBuildShl(p->builder, mask, LLVMConstInt(vt, mask_width, false), ""); + #else + mask = LLVMConstShl(mask, LLVMConstInt(vt, mask_width, false)); + #endif + 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. // NOTE(bill): This is due to the layout of the unions when printed to LLVM-IR @@ -4239,10 +4746,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; } @@ -4290,7 +4813,7 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { if (cl->elems.count == 0) { break; } - GB_ASSERT(!build_context.no_dynamic_literals); + GB_ASSERT(expr->file()->feature_flags & OptInFeatureFlag_DynamicLiterals); lbValue err = lb_dynamic_map_reserve(p, v.addr, 2*cl->elems.count, pos); gb_unused(err); @@ -4379,7 +4902,7 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { if (cl->elems.count == 0) { break; } - GB_ASSERT(!build_context.no_dynamic_literals); + GB_ASSERT(expr->file()->feature_flags & OptInFeatureFlag_DynamicLiterals); Type *et = bt->DynamicArray.elem; lbValue size = lb_const_int(p->module, t_int, type_size_of(et)); @@ -4467,29 +4990,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; @@ -4591,15 +5128,17 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { if (tav.mode == Addressing_Type) { // Addressing_Type Selection sel = lookup_field(tav.type, selector, true); if (sel.pseudo_field) { - GB_ASSERT(sel.entity->kind == Entity_Procedure); - return lb_addr(lb_find_value_from_entity(p->module, sel.entity)); + GB_ASSERT(sel.entity->kind == Entity_Procedure || sel.entity->kind == Entity_ProcGroup); + Entity *e = entity_of_node(sel_node); + GB_ASSERT(e->kind == Entity_Procedure); + return lb_addr(lb_find_value_from_entity(p->module, e)); } GB_PANIC("Unreachable %.*s", LIT(selector)); } if (se->swizzle_count > 0) { Type *array_type = base_type(type_deref(tav.type)); - GB_ASSERT(array_type->kind == Type_Array); + GB_ASSERT(array_type->kind == Type_Array || array_type->kind == Type_SimdVector); u8 swizzle_count = se->swizzle_count; u8 swizzle_indices_raw = se->swizzle_indices; u8 swizzle_indices[4] = {}; @@ -4615,8 +5154,9 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { a = lb_addr_get_ptr(p, addr); } - GB_ASSERT(is_type_array(expr->tav.type)); - return lb_addr_swizzle(a, expr->tav.type, swizzle_count, swizzle_indices); + Type *type = type_deref(expr->tav.type); + GB_ASSERT(is_type_array(type) || is_type_simd_vector(type)); + return lb_addr_swizzle(a, type, swizzle_count, swizzle_indices); } Selection sel = lookup_field(type, selector, false); @@ -4628,6 +5168,33 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { return lb_addr(lb_find_value_from_entity(p->module, e)); } + if (sel.is_bit_field) { + lbAddr addr = lb_build_addr(p, se->expr); + + Selection sub_sel = sel; + sub_sel.index.count -= 1; + + lbValue ptr = lb_addr_get_ptr(p, addr); + if (sub_sel.index.count > 0) { + ptr = lb_emit_deep_field_gep(p, ptr, sub_sel); + } + if (is_type_pointer(type_deref(ptr.type))) { + ptr = lb_emit_load(p, ptr); + } + + Type *bf_type = type_deref(ptr.type); + bf_type = base_type(bf_type); + GB_ASSERT(bf_type->kind == Type_BitField); + + i32 index = sel.index[sel.index.count-1]; + + Entity *f = bf_type->BitField.fields[index]; + 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, bit_offset, bit_size); + } + { lbAddr addr = lb_build_addr(p, se->expr); if (addr.kind == lbAddr_Map) { @@ -4670,6 +5237,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); @@ -4681,6 +5251,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); @@ -4770,11 +5345,7 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) { case_ast_node(de, DerefExpr, expr); Type *t = type_of_expr(de->expr); - if (is_type_relative_pointer(t)) { - lbAddr addr = lb_build_addr(p, de->expr); - addr.relative.deref = true; - return addr; - } else if (is_type_soa_pointer(t)) { + if (is_type_soa_pointer(t)) { lbValue value = lb_build_expr(p, de->expr); lbValue ptr = lb_emit_struct_ev(p, value, 0); lbValue idx = lb_emit_struct_ev(p, value, 1); @@ -4886,6 +5457,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, expr); + 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 fdcf94f29..762256258 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,17 +72,17 @@ 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); - array_init(&m->debug_incomplete_types, a, 0, 1024); string_map_init(&m->objc_classes); string_map_init(&m->objc_selectors); @@ -90,6 +91,9 @@ gb_internal void lb_init_module(lbModule *m, Checker *c) { map_init(&m->map_cell_info_map, 0); map_init(&m->exact_value_compound_literal_addr_map, 1024); + array_init(&m->pad_types, heap_allocator()); + + m->const_dummy_builder = LLVMCreateBuilderInContext(m->ctx); } @@ -107,6 +111,10 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { String init_fullpath = c->parser->init_fullpath; linker_data_init(gen, &c->info, init_fullpath); + #if defined(GB_SYSTEM_OSX) && (LLVM_VERSION_MAJOR < 14) + linker_enable_system_library_linking(gen); + #endif + gen->info = &c->info; map_init(&gen->modules, gen->info->packages.count*2); @@ -114,15 +122,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); @@ -132,21 +142,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; } @@ -208,7 +218,7 @@ gb_internal void lb_loop_end(lbProcedure *p, lbLoopData const &data) { gb_internal void lb_make_global_private_const(LLVMValueRef global_data) { - LLVMSetLinkage(global_data, LLVMPrivateLinkage); + LLVMSetLinkage(global_data, LLVMLinkerPrivateLinkage); LLVMSetUnnamedAddress(global_data, LLVMGlobalUnnamedAddr); LLVMSetGlobalConstant(global_data, true); } @@ -383,12 +393,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; } } @@ -397,14 +409,6 @@ gb_internal lbModule *lb_module_of_entity(lbGenerator *gen, Entity *e) { gb_internal lbAddr lb_addr(lbValue addr) { lbAddr v = {lbAddr_Default, addr}; - if (addr.type != nullptr && is_type_relative_pointer(type_deref(addr.type))) { - GB_ASSERT(is_type_pointer(addr.type)); - v.kind = lbAddr_RelativePointer; - } else if (addr.type != nullptr && is_type_relative_multi_pointer(type_deref(addr.type))) { - GB_ASSERT(is_type_pointer(addr.type) || - is_type_multi_pointer(addr.type)); - v.kind = lbAddr_RelativePointer; - } return v; } @@ -430,7 +434,7 @@ gb_internal lbAddr lb_addr_soa_variable(lbValue addr, lbValue index, Ast *index_ } gb_internal lbAddr lb_addr_swizzle(lbValue addr, Type *array_type, u8 swizzle_count, u8 swizzle_indices[4]) { - GB_ASSERT(is_type_array(array_type)); + GB_ASSERT(is_type_array(array_type) || is_type_simd_vector(array_type)); GB_ASSERT(1 < swizzle_count && swizzle_count <= 4); lbAddr v = {lbAddr_Swizzle, addr}; v.swizzle.type = array_type; @@ -447,6 +451,19 @@ gb_internal lbAddr lb_addr_swizzle_large(lbValue addr, Type *array_type, Slicekind == Type_RelativePointer) { - pointer_type = t->RelativePointer.pointer_type; - base_integer = t->RelativePointer.base_integer; - } else if (t->kind == Type_RelativeMultiPointer) { - pointer_type = t->RelativeMultiPointer.pointer_type; - base_integer = t->RelativeMultiPointer.base_integer; - } - - lbValue ptr = lb_emit_conv(p, addr.addr, t_uintptr); - lbValue offset = lb_emit_conv(p, ptr, alloc_type_pointer(base_integer)); - offset = lb_emit_load(p, offset); - - if (!is_type_unsigned(base_integer)) { - offset = lb_emit_conv(p, offset, t_i64); - } - offset = lb_emit_conv(p, offset, t_uintptr); - lbValue absolute_ptr = lb_emit_arith(p, Token_Add, ptr, offset, t_uintptr); - absolute_ptr = lb_emit_conv(p, absolute_ptr, pointer_type); - - lbValue cond = lb_emit_comp(p, Token_CmpEq, offset, lb_const_nil(p->module, base_integer)); - - // NOTE(bill): nil check - lbValue nil_ptr = lb_const_nil(p->module, pointer_type); - lbValue final_ptr = lb_emit_select(p, cond, nil_ptr, absolute_ptr); - return final_ptr; + return lb_addr_load(p, v); } - gb_internal lbValue lb_addr_get_ptr(lbProcedure *p, lbAddr const &addr) { if (addr.addr.value == nullptr) { GB_PANIC("Illegal addr -> nullptr"); @@ -523,12 +513,13 @@ gb_internal lbValue lb_addr_get_ptr(lbProcedure *p, lbAddr const &addr) { case lbAddr_Map: return lb_internal_dynamic_map_get_ptr(p, addr.addr, addr.map.key); - case lbAddr_RelativePointer: - return lb_relative_pointer_to_pointer(p, addr); - case lbAddr_SoaVariable: - // TODO(bill): FIX THIS HACK - return lb_address_from_load(p, lb_addr_load(p, addr)); + { + Type *soa_ptr_type = alloc_type_soa_pointer(lb_addr_type(addr)); + return lb_address_from_load_or_generate_local(p, lb_make_soa_pointer(p, soa_ptr_type, addr.addr, addr.soa.index)); + // TODO(bill): FIX THIS HACK + // return lb_address_from_load(p, lb_addr_load(p, addr)); + } case lbAddr_Context: GB_PANIC("lbAddr_Context should be handled elsewhere"); @@ -546,9 +537,6 @@ gb_internal lbValue lb_addr_get_ptr(lbProcedure *p, lbAddr const &addr) { gb_internal lbValue lb_build_addr_ptr(lbProcedure *p, Ast *expr) { lbAddr addr = lb_build_addr(p, expr); - if (addr.kind == lbAddr_RelativePointer) { - return addr.addr; - } return lb_addr_get_ptr(p, addr); } @@ -684,7 +672,10 @@ gb_internal unsigned lb_try_get_alignment(LLVMValueRef addr_ptr, unsigned defaul gb_internal bool lb_try_update_alignment(LLVMValueRef addr_ptr, unsigned alignment) { if (LLVMIsAGlobalValue(addr_ptr) || LLVMIsAAllocaInst(addr_ptr) || LLVMIsALoadInst(addr_ptr)) { if (LLVMGetAlignment(addr_ptr) < alignment) { - if (LLVMIsAAllocaInst(addr_ptr) || LLVMIsAGlobalValue(addr_ptr)) { + if (LLVMIsAAllocaInst(addr_ptr)) { + LLVMSetAlignment(addr_ptr, alignment); + } else if (LLVMIsAGlobalValue(addr_ptr) && LLVMGetLinkage(addr_ptr) != LLVMExternalLinkage) { + // NOTE(laytan): setting alignment of an external global just changes the alignment we expect it to be. LLVMSetAlignment(addr_ptr, alignment); } } @@ -717,10 +708,7 @@ gb_internal bool lb_try_vector_cast(lbModule *m, lbValue ptr, LLVMTypeRef *vecto LLVMValueRef addr_ptr = ptr.value; if (LLVMIsAAllocaInst(addr_ptr) || LLVMIsAGlobalValue(addr_ptr)) { - unsigned alignment = LLVMGetAlignment(addr_ptr); - alignment = gb_max(alignment, vector_alignment); - possible = true; - LLVMSetAlignment(addr_ptr, alignment); + possible = lb_try_update_alignment(addr_ptr, vector_alignment); } else if (LLVMIsALoadInst(addr_ptr)) { unsigned alignment = LLVMGetAlignment(addr_ptr); possible = alignment >= vector_alignment; @@ -736,6 +724,47 @@ gb_internal bool lb_try_vector_cast(lbModule *m, lbValue ptr, LLVMTypeRef *vecto return false; } +gb_internal LLVMValueRef OdinLLVMBuildLoad(lbProcedure *p, LLVMTypeRef type, LLVMValueRef value) { + LLVMValueRef result = LLVMBuildLoad2(p->builder, type, 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)) { + u64 is_packed = lb_get_metadata_custom_u64(p->module, value, ODIN_METADATA_IS_PACKED); + if (is_packed != 0) { + LLVMSetAlignment(result, 1); + } + u64 align = LLVMGetAlignment(result); + u64 align_min = lb_get_metadata_custom_u64(p->module, value, ODIN_METADATA_MIN_ALIGN); + u64 align_max = lb_get_metadata_custom_u64(p->module, value, ODIN_METADATA_MAX_ALIGN); + if (align_min != 0 && align < align_min) { + align = align_min; + } + if (align_max != 0 && align > align_max) { + align = align_max; + } + GB_ASSERT(align <= UINT_MAX); + LLVMSetAlignment(result, (unsigned int)align); + } + + return result; +} + +gb_internal LLVMValueRef OdinLLVMBuildLoadAligned(lbProcedure *p, LLVMTypeRef type, LLVMValueRef value, i64 alignment) { + LLVMValueRef result = LLVMBuildLoad2(p->builder, type, value, ""); + + LLVMSetAlignment(result, cast(unsigned)alignment); + + if (LLVMIsAInstruction(value)) { + u64 is_packed = lb_get_metadata_custom_u64(p->module, value, ODIN_METADATA_IS_PACKED); + if (is_packed != 0) { + LLVMSetAlignment(result, 1); + } + } + + return result; +} + gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value) { if (addr.addr.value == nullptr) { return; @@ -751,48 +780,43 @@ gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value) { value.value = LLVMConstNull(lb_type(p->module, t)); } - if (addr.kind == lbAddr_RelativePointer && addr.relative.deref) { - addr = lb_addr(lb_address_from_load(p, lb_addr_load(p, addr))); - } + if (addr.kind == lbAddr_BitField) { + lbValue dst = addr.addr; + if (is_type_endian_big(addr.bitfield.type)) { + i64 shift_amount = 8*type_size_of(value.type) - addr.bitfield.bit_size; + lbValue shifted_value = value; + shifted_value.value = LLVMBuildLShr(p->builder, + shifted_value.value, + LLVMConstInt(LLVMTypeOf(shifted_value.value), shift_amount, false), ""); - if (addr.kind == lbAddr_RelativePointer) { - Type *rel_ptr = base_type(lb_addr_type(addr)); - GB_ASSERT(rel_ptr->kind == Type_RelativePointer || - rel_ptr->kind == Type_RelativeMultiPointer); - Type *pointer_type = nullptr; - Type *base_integer = nullptr; + lbValue src = lb_address_from_load_or_generate_local(p, shifted_value); - if (rel_ptr->kind == Type_RelativePointer) { - pointer_type = rel_ptr->RelativePointer.pointer_type; - base_integer = rel_ptr->RelativePointer.base_integer; - } else if (rel_ptr->kind == Type_RelativeMultiPointer) { - pointer_type = rel_ptr->RelativeMultiPointer.pointer_type; - base_integer = rel_ptr->RelativeMultiPointer.base_integer; + 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); } - - value = lb_emit_conv(p, value, pointer_type); - - GB_ASSERT(is_type_pointer(addr.addr.type)); - lbValue ptr = lb_emit_conv(p, addr.addr, t_uintptr); - lbValue val_ptr = lb_emit_conv(p, value, t_uintptr); - lbValue offset = {}; - offset.value = LLVMBuildSub(p->builder, val_ptr.value, ptr.value, ""); - offset.type = t_uintptr; - - if (!is_type_unsigned(base_integer)) { - offset = lb_emit_conv(p, offset, t_i64); - } - offset = lb_emit_conv(p, offset, base_integer); - - lbValue offset_ptr = lb_emit_conv(p, addr.addr, alloc_type_pointer(base_integer)); - offset = lb_emit_select(p, - lb_emit_comp(p, Token_CmpEq, val_ptr, lb_const_nil(p->module, t_uintptr)), - lb_const_nil(p->module, base_integer), - offset - ); - LLVMBuildStore(p->builder, offset.value, offset_ptr.value); return; - } else if (addr.kind == lbAddr_Map) { lb_internal_dynamic_map_set(p, addr.addr, addr.map.type, addr.map.key, value, p->curr_stmt); return; @@ -927,16 +951,6 @@ gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value) { GB_ASSERT(value.value != nullptr); value = lb_emit_conv(p, value, lb_addr_type(addr)); - // if (lb_is_const_or_global(value)) { - // // NOTE(bill): Just bypass the actual storage and set the initializer - // if (LLVMGetValueKind(addr.addr.value) == LLVMGlobalVariableValueKind) { - // LLVMValueRef dst = addr.addr.value; - // LLVMValueRef src = value.value; - // LLVMSetInitializer(dst, src); - // return; - // } - // } - lb_emit_store(p, addr.addr, value); } @@ -967,13 +981,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 { @@ -1052,7 +1068,7 @@ gb_internal lbValue lb_emit_load(lbProcedure *p, lbValue value) { Type *vt = base_type(value.type); GB_ASSERT(vt->kind == Type_MultiPointer); Type *t = vt->MultiPointer.elem; - LLVMValueRef v = LLVMBuildLoad2(p->builder, lb_type(p->module, t), value.value, ""); + LLVMValueRef v = OdinLLVMBuildLoad(p, lb_type(p->module, t), value.value); return lbValue{v, t}; } else if (is_type_soa_pointer(value.type)) { lbValue ptr = lb_emit_struct_ev(p, value, 0); @@ -1061,56 +1077,94 @@ 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, ""); + LLVMValueRef v = OdinLLVMBuildLoad(p, lb_type(p->module, t), value.value); + return lbValue{v, t}; } gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) { GB_ASSERT(addr.addr.value != nullptr); - - if (addr.kind == lbAddr_RelativePointer) { - Type *rel_ptr = base_type(lb_addr_type(addr)); - Type *base_integer = nullptr; - Type *pointer_type = nullptr; - GB_ASSERT(rel_ptr->kind == Type_RelativePointer || - rel_ptr->kind == Type_RelativeMultiPointer); - - if (rel_ptr->kind == Type_RelativePointer) { - base_integer = rel_ptr->RelativePointer.base_integer; - pointer_type = rel_ptr->RelativePointer.pointer_type; - } else if (rel_ptr->kind == Type_RelativeMultiPointer) { - base_integer = rel_ptr->RelativeMultiPointer.base_integer; - pointer_type = rel_ptr->RelativeMultiPointer.pointer_type; + 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; + } } - lbValue ptr = lb_emit_conv(p, addr.addr, t_uintptr); - lbValue offset = lb_emit_conv(p, ptr, alloc_type_pointer(base_integer)); - offset = lb_emit_load(p, offset); + 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; + 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); - if (!is_type_unsigned(base_integer)) { - offset = lb_emit_conv(p, offset, t_i64); + GB_ASSERT(type_size_of(addr.bitfield.type) >= ((addr.bitfield.bit_size+7)/8)); + + 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); } - offset = lb_emit_conv(p, offset, t_uintptr); - lbValue absolute_ptr = lb_emit_arith(p, Token_Add, ptr, offset, t_uintptr); - absolute_ptr = lb_emit_conv(p, absolute_ptr, pointer_type); - lbValue cond = lb_emit_comp(p, Token_CmpEq, offset, lb_const_nil(p->module, base_integer)); + Type *t = addr.bitfield.type; - // NOTE(bill): nil check - lbValue nil_ptr = lb_const_nil(p->module, pointer_type); - lbValue final_ptr = {}; - final_ptr.type = absolute_ptr.type; - final_ptr.value = LLVMBuildSelect(p->builder, cond.value, nil_ptr.value, absolute_ptr.value, ""); + if (do_mask) { + GB_ASSERT(addr.bitfield.bit_size <= 8*type_size_of(ct)); - if (rel_ptr->kind == Type_RelativeMultiPointer) { - return final_ptr; + 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); + } + + return r; } else if (addr.kind == lbAddr_Map) { Type *map_type = base_type(type_deref(addr.addr.type)); GB_ASSERT(map_type->kind == Type_Map); @@ -1199,7 +1253,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); @@ -1213,6 +1267,30 @@ gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) { return lb_addr_load(p, res); } else if (addr.kind == lbAddr_Swizzle) { Type *array_type = base_type(addr.swizzle.type); + if (array_type->kind == Type_SimdVector) { + lbValue vec = lb_emit_load(p, addr.addr); + u8 index_count = addr.swizzle.count; + if (index_count == 0) { + return vec; + } + + unsigned mask_len = cast(unsigned)index_count; + LLVMValueRef *mask_elems = gb_alloc_array(permanent_allocator(), LLVMValueRef, index_count); + for (isize i = 0; i < index_count; i++) { + mask_elems[i] = LLVMConstInt(lb_type(p->module, t_u32), addr.swizzle.indices[i], false); + } + + LLVMValueRef mask = LLVMConstVector(mask_elems, mask_len); + + LLVMValueRef v1 = vec.value; + LLVMValueRef v2 = vec.value; + + lbValue res = {}; + res.type = addr.swizzle.type; + res.value = LLVMBuildShuffleVector(p->builder, v1, v2, mask, ""); + return res; + } + GB_ASSERT(array_type->kind == Type_Array); unsigned res_align = cast(unsigned)type_align_of(addr.swizzle.type); @@ -1234,10 +1312,8 @@ 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 v = OdinLLVMBuildLoad(p, vector_type, vp); LLVMValueRef scalars[4] = {}; for (u8 i = 0; i < addr.swizzle.count; i++) { scalars[i] = LLVMConstInt(lb_type(p->module, t_u32), addr.swizzle.indices[i], false); @@ -1245,6 +1321,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 { @@ -1332,6 +1410,8 @@ gb_internal void lb_emit_store_union_variant(lbProcedure *p, lbValue parent, lbV Type *pt = base_type(type_deref(parent.type)); GB_ASSERT(pt->kind == Type_Union); if (pt->Union.kind == UnionType_shared_nil) { + GB_ASSERT(type_size_of(variant_type)); + lbBlock *if_nil = lb_create_block(p, "shared_nil.if_nil"); lbBlock *if_not_nil = lb_create_block(p, "shared_nil.if_not_nil"); lbBlock *done = lb_create_block(p, "shared_nil.done"); @@ -1353,9 +1433,13 @@ gb_internal void lb_emit_store_union_variant(lbProcedure *p, lbValue parent, lbV } else { - lbValue underlying = lb_emit_conv(p, parent, alloc_type_pointer(variant_type)); - - lb_emit_store(p, underlying, variant); + if (type_size_of(variant_type) == 0) { + unsigned alignment = 1; + lb_mem_zero_ptr_internal(p, parent.value, pt->Union.variant_block_size, alignment, false); + } else { + lbValue underlying = lb_emit_conv(p, parent, alloc_type_pointer(variant_type)); + lb_emit_store(p, underlying, variant); + } lb_emit_store_union_variant_tag(p, parent, variant_type); } } @@ -1369,7 +1453,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; @@ -1434,9 +1518,11 @@ gb_internal String lb_set_nested_type_name_ir_mangled_name(Entity *e, lbProcedur GB_ASSERT(scope->flags & ScopeFlag_Proc); proc = scope->procedure_entity; } - GB_ASSERT(proc->kind == Entity_Procedure); - if (proc->code_gen_procedure != nullptr) { - p = proc->code_gen_procedure; + if (proc != nullptr) { + GB_ASSERT(proc->kind == Entity_Procedure); + if (proc->code_gen_procedure != nullptr) { + p = proc->code_gen_procedure; + } } } @@ -1467,6 +1553,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; } @@ -1498,7 +1585,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; @@ -1600,7 +1687,7 @@ gb_internal LLVMTypeRef lb_type_internal_for_procedures_raw(lbModule *m, Type *t } } GB_ASSERT(param_index == param_count); - lbFunctionType *ft = lb_get_abi_info(m->ctx, params, param_count, ret, ret != nullptr, return_is_tuple, type->Proc.calling_convention, type); + lbFunctionType *ft = lb_get_abi_info(m, params, param_count, ret, ret != nullptr, return_is_tuple, type->Proc.calling_convention, type); { for_array(j, ft->args) { auto arg = ft->args[j]; @@ -1927,6 +2014,12 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { llvm_type = LLVMStructCreateNamed(ctx, name); map_set(&m->types, type, llvm_type); lb_clone_struct_type(llvm_type, lb_type(m, base)); + + if (base->kind == Type_Struct) { + map_set(&m->struct_field_remapping, cast(void *)llvm_type, lb_get_struct_remapping(m, base)); + map_set(&m->struct_field_remapping, cast(void *)type, lb_get_struct_remapping(m, base)); + } + return llvm_type; } } @@ -1998,7 +2091,7 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { break; case Type_Map: - init_map_internal_types(type); + init_map_internal_debug_types(type); GB_ASSERT(t_raw_map != nullptr); return lb_type_internal(m, t_raw_map); @@ -2038,16 +2131,19 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { array_add(&fields, padding_type); } - i64 padding_offset = 0; + i64 prev_offset = 0; + bool requires_packing = type->Struct.is_packed; for (i32 field_index : struct_fields_index_by_increasing_offset(temporary_allocator(), type)) { Entity *field = type->Struct.fields[field_index]; - i64 padding = type->Struct.offsets[field_index] - padding_offset; + i64 offset = type->Struct.offsets[field_index]; + GB_ASSERT(offset >= prev_offset); + i64 padding = offset - prev_offset; if (padding != 0) { LLVMTypeRef padding_type = lb_type_padding_filler(m, padding, type_align_of(field->type)); array_add(&fields, padding_type); } - + field_remapping[field_index] = cast(i32)fields.count; Type *field_type = field->type; @@ -2057,15 +2153,16 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { field_type = t_rawptr; } + // max_field_align might misalign items in a way that requires packing + // so check the alignment of all fields to see if packing is required. + requires_packing = requires_packing || ((offset % type_align_of(field_type)) != 0); + array_add(&fields, lb_type(m, field_type)); - - if (!type->Struct.is_packed) { - padding_offset = align_formula(padding_offset, type_align_of(field->type)); - } - padding_offset += type_size_of(field->type); + + prev_offset = offset + type_size_of(field->type); } - i64 end_padding = full_type_size-padding_offset; + i64 end_padding = full_type_size-prev_offset; if (end_padding > 0) { array_add(&fields, lb_type_padding_filler(m, end_padding, 1)); } @@ -2074,7 +2171,7 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { GB_ASSERT(fields[i] != nullptr); } - LLVMTypeRef struct_type = LLVMStructTypeInContext(ctx, fields.data, cast(unsigned)fields.count, type->Struct.is_packed); + LLVMTypeRef struct_type = LLVMStructTypeInContext(ctx, fields.data, cast(unsigned)fields.count, requires_packing); map_set(&m->struct_field_remapping, cast(void *)struct_type, field_remapping); map_set(&m->struct_field_remapping, cast(void *)type, field_remapping); #if 0 @@ -2165,13 +2262,6 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { case Type_SimdVector: return LLVMVectorType(lb_type(m, type->SimdVector.elem), cast(unsigned)type->SimdVector.count); - - case Type_RelativePointer: - return lb_type_internal(m, type->RelativePointer.base_integer); - case Type_RelativeMultiPointer: - return lb_type_internal(m, type->RelativeMultiPointer.base_integer); - - case Type_Matrix: { @@ -2206,7 +2296,9 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { } return LLVMStructTypeInContext(ctx, fields, field_count, false); } - + + case Type_BitField: + return lb_type_internal(m, type->BitField.backing_type); } GB_PANIC("Invalid type %s", type_to_string(type)); @@ -2338,6 +2430,15 @@ gb_internal LLVMAttributeRef lb_create_enum_attribute(LLVMContextRef ctx, char c return LLVMCreateEnumAttribute(ctx, kind, value); } +gb_internal LLVMAttributeRef lb_create_string_attribute(LLVMContextRef ctx, String const &key, String const &value) { + LLVMAttributeRef attr = LLVMCreateStringAttribute( + ctx, + cast(char const *)key.text, cast(unsigned)key.len, + cast(char const *)value.text, cast(unsigned)value.len); + return attr; +} + + gb_internal void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *name, u64 value) { LLVMAttributeRef attr = lb_create_enum_attribute(p->module->ctx, name, value); GB_ASSERT(attr != nullptr); @@ -2352,6 +2453,16 @@ gb_internal void lb_add_attribute_to_proc(lbModule *m, LLVMValueRef proc_value, 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); +} + gb_internal void lb_add_edge(lbBlock *from, lbBlock *to) { @@ -2497,7 +2608,7 @@ general_end:; if (LLVMIsALoadInst(val) && (src_size >= dst_size && src_align >= dst_align)) { LLVMValueRef val_ptr = LLVMGetOperand(val, 0); val_ptr = LLVMBuildPointerCast(p->builder, val_ptr, LLVMPointerType(dst_type, 0), ""); - LLVMValueRef loaded_val = LLVMBuildLoad2(p->builder, dst_type, val_ptr, ""); + LLVMValueRef loaded_val = OdinLLVMBuildLoad(p, dst_type, val_ptr); // LLVMSetAlignment(loaded_val, gb_min(src_align, dst_align)); @@ -2506,14 +2617,14 @@ general_end:; GB_ASSERT(p->decl_block != p->curr_block); i64 max_align = gb_max(lb_alignof(src_type), lb_alignof(dst_type)); - max_align = gb_max(max_align, 4); + max_align = gb_max(max_align, 16); LLVMValueRef ptr = llvm_alloca(p, dst_type, max_align); LLVMValueRef nptr = LLVMBuildPointerCast(p->builder, ptr, LLVMPointerType(src_type, 0), ""); LLVMBuildStore(p->builder, val, nptr); - return LLVMBuildLoad2(p->builder, dst_type, ptr, ""); + return OdinLLVMBuildLoad(p, dst_type, ptr); } } @@ -2862,7 +2973,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; } @@ -2880,8 +2991,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); @@ -2905,7 +3014,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 2e03b7974..7fe1359b4 100644 --- a/src/llvm_backend_opt.cpp +++ b/src/llvm_backend_opt.cpp @@ -380,6 +380,100 @@ gb_internal void lb_run_remove_dead_instruction_pass(lbProcedure *p) { } } +gb_internal LLVMValueRef lb_run_instrumentation_pass_insert_call(lbProcedure *p, Entity *entity, LLVMBuilderRef dummy_builder, bool is_enter) { + lbModule *m = p->module; + + if (p->debug_info != nullptr) { + TokenPos pos = {}; + if (is_enter) { + pos = ast_token(p->body).pos; + } else { + pos = ast_end_token(p->body).pos; + } + LLVMSetCurrentDebugLocation2(dummy_builder, lb_debug_location_from_token_pos(p, pos)); + } + + lbValue cc = lb_find_procedure_value_from_entity(m, entity); + + LLVMValueRef args[3] = {}; + args[0] = LLVMConstPointerCast(p->value, lb_type(m, t_rawptr)); + + 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); + + 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) { + name = p->entity->token; + } + args[2] = lb_emit_source_code_location_as_global_ptr(p, name.string, name.pos).value; + + LLVMTypeRef fnp = lb_type_internal_for_procedures_raw(p->module, entity->type); + return LLVMBuildCall2(dummy_builder, fnp, cc.value, args, gb_count_of(args), ""); +} + + +gb_internal void lb_run_instrumentation_pass(lbProcedure *p) { + lbModule *m = p->module; + Entity *enter = m->info->instrumentation_enter_entity; + Entity *exit = m->info->instrumentation_exit_entity; + if (enter == nullptr || exit == nullptr) { + return; + } + if (!(p->entity && + p->entity->kind == Entity_Procedure && + p->entity->Procedure.has_instrumentation)) { + return; + } + +#define LLVM_V_NAME(x) x, cast(unsigned)(gb_count_of(x)-1) + + LLVMBuilderRef dummy_builder = LLVMCreateBuilderInContext(m->ctx); + defer (LLVMDisposeBuilder(dummy_builder)); + + LLVMBasicBlockRef entry_bb = p->entry_block->block; + LLVMPositionBuilder(dummy_builder, entry_bb, LLVMGetFirstInstruction(entry_bb)); + lb_run_instrumentation_pass_insert_call(p, enter, dummy_builder, true); + LLVMRemoveStringAttributeAtIndex(p->value, LLVMAttributeIndex_FunctionIndex, LLVM_V_NAME("instrument-function-entry")); + + unsigned bb_count = LLVMCountBasicBlocks(p->value); + LLVMBasicBlockRef *bbs = gb_alloc_array(temporary_allocator(), LLVMBasicBlockRef, bb_count); + LLVMGetBasicBlocks(p->value, bbs); + for (unsigned i = 0; i < bb_count; i++) { + LLVMBasicBlockRef bb = bbs[i]; + LLVMValueRef terminator = LLVMGetBasicBlockTerminator(bb); + if (terminator == nullptr || + !LLVMIsAReturnInst(terminator)) { + continue; + } + + // TODO(bill): getTerminatingMustTailCall() + // If T is preceded by a musttail call, that's the real terminator. + // if (CallInst *CI = BB.getTerminatingMustTailCall()) + // T = CI; + + + LLVMPositionBuilderBefore(dummy_builder, terminator); + lb_run_instrumentation_pass_insert_call(p, exit, dummy_builder, false); + } + + LLVMRemoveStringAttributeAtIndex(p->value, LLVMAttributeIndex_FunctionIndex, LLVM_V_NAME("instrument-function-exit")); + +#undef LLVM_V_NAME +} + + gb_internal void lb_run_function_pass_manager(LLVMPassManagerRef fpm, lbProcedure *p, lbFunctionPassManagerKind pass_manager_kind) { if (p == nullptr) { @@ -391,6 +485,8 @@ gb_internal void lb_run_function_pass_manager(LLVMPassManagerRef fpm, lbProcedur // are not removed lb_run_remove_dead_instruction_pass(p); + lb_run_instrumentation_pass(p); + switch (pass_manager_kind) { case lbFunctionPassManager_none: return; @@ -552,3 +648,5 @@ gb_internal void lb_run_remove_unused_globals_pass(lbModule *m) { } } } + + diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index f64cbd52a..7e44a0046 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -125,6 +125,10 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i // map_init(&p->selector_addr, 0); // map_init(&p->tuple_fix_map, 0); + if (p->entity != nullptr && p->entity->Procedure.uses_branch_location) { + p->uses_branch_location = true; + } + if (p->is_foreign) { lb_add_foreign_library_path(p->module, entity->Procedure.foreign_library); } @@ -159,35 +163,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 +236,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 +262,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; } } @@ -329,6 +344,18 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i } } + if (p->body && entity->Procedure.has_instrumentation) { + Entity *instrumentation_enter = m->info->instrumentation_enter_entity; + Entity *instrumentation_exit = m->info->instrumentation_exit_entity; + if (instrumentation_enter && instrumentation_exit) { + String enter = lb_get_entity_name(m, instrumentation_enter); + String exit = lb_get_entity_name(m, instrumentation_exit); + + lb_add_attribute_to_proc_with_string(m, p->value, make_string_c("instrument-function-entry"), enter); + lb_add_attribute_to_proc_with_string(m, p->value, make_string_c("instrument-function-exit"), exit); + } + } + lbValue proc_value = {p->value, p->type}; lb_add_entity(m, entity, proc_value); lb_add_member(m, p->name, proc_value); @@ -504,6 +531,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); @@ -555,6 +583,8 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) { p->raw_input_parameters = array_make(permanent_allocator(), raw_input_parameters_count); LLVMGetParams(p->value, p->raw_input_parameters.data); + bool is_odin_cc = is_calling_convention_odin(ft->calling_convention); + unsigned param_index = 0; for_array(i, params->variables) { Entity *e = params->variables[i]; @@ -566,7 +596,10 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) { defer (param_index += 1); if (arg_type->kind == lbArg_Ignore) { - continue; + // Even though it is an ignored argument, it might still be referenced in the + // body. + lbValue dummy = lb_add_local_generated(p, e->type, false).addr; + lb_add_entity(p->module, e, dummy); } else if (arg_type->kind == lbArg_Direct) { if (e->token.string.len != 0 && !is_blank_ident(e->token.string)) { LLVMTypeRef param_type = lb_type(p->module, e->type); @@ -582,24 +615,32 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) { lbValue ptr = lb_address_from_load_or_generate_local(p, param); GB_ASSERT(LLVMIsAAllocaInst(ptr.value)); lb_add_entity(p->module, e, ptr); - - lbBlock *block = p->decl_block; - if (original_value != value) { - block = p->curr_block; - } - LLVMValueRef debug_storage_value = value; - if (original_value != value && LLVMIsALoadInst(value)) { - debug_storage_value = LLVMGetOperand(value, 0); - } - lb_add_debug_param_variable(p, debug_storage_value, e->type, e->token, param_index+1, block, arg_type->kind); + lb_add_debug_param_variable(p, ptr.value, e->type, e->token, param_index+1, p->curr_block); } } else if (arg_type->kind == lbArg_Indirect) { if (e->token.string.len != 0 && !is_blank_ident(e->token.string)) { + i64 sz = type_size_of(e->type); + bool do_callee_copy = false; + + if (is_odin_cc) { + do_callee_copy = sz <= 16; + if (build_context.internal_by_value) { + do_callee_copy = true; + } + } + lbValue ptr = {}; ptr.value = LLVMGetParam(p->value, param_offset+param_index); ptr.type = alloc_type_pointer(e->type); + + if (do_callee_copy) { + lbValue new_ptr = lb_add_local_generated(p, e->type, false).addr; + lb_mem_copy_non_overlapping(p, new_ptr, ptr, lb_const_int(p->module, t_uint, sz)); + ptr = new_ptr; + } + lb_add_entity(p->module, e, ptr); - lb_add_debug_param_variable(p, ptr.value, e->type, e->token, param_index+1, p->decl_block, arg_type->kind); + lb_add_debug_param_variable(p, ptr.value, e->type, e->token, param_index+1, p->decl_block); } } } @@ -681,7 +722,9 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) { } if (e->Variable.param_value.kind != ParameterValue_Invalid) { - lbValue c = lb_handle_param_value(p, e->type, e->Variable.param_value, e->token.pos); + GB_ASSERT(e->Variable.param_value.kind != ParameterValue_Location); + GB_ASSERT(e->Variable.param_value.kind != ParameterValue_Expression); + lbValue c = lb_handle_param_value(p, e->type, e->Variable.param_value, nullptr, nullptr); lb_addr_store(p, res, c); } @@ -697,13 +740,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) { @@ -719,7 +761,7 @@ gb_internal void lb_end_procedure_body(lbProcedure *p) { if (p->type->Proc.result_count == 0) { instr = LLVMGetLastInstruction(p->curr_block->block); if (!lb_is_instr_terminating(instr)) { - lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr); + lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr, p->body); lb_set_debug_position_to_procedure_end(p); LLVMBuildRetVoid(p->builder); } @@ -1039,6 +1081,7 @@ gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array c Type *original_type = e->type; lbArgType *arg = &ft->args[param_index]; if (arg->kind == lbArg_Ignore) { + param_index += 1; continue; } @@ -1083,15 +1126,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); } @@ -1105,10 +1140,6 @@ gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array c } } - if (inlining == ProcInlining_none) { - inlining = p->inlining; - } - Type *rt = reduce_tuple_to_single_type(results); Type *original_rt = rt; if (split_returns) { @@ -1387,8 +1418,7 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn return res; case BuiltinProc_simd_min: if (is_float) { - LLVMValueRef cond = LLVMBuildFCmp(p->builder, LLVMRealOLT, arg0.value, arg1.value, ""); - res.value = LLVMBuildSelect(p->builder, cond, arg0.value, arg1.value, ""); + return lb_emit_min(p, res.type, arg0, arg1); } else { LLVMValueRef cond = LLVMBuildICmp(p->builder, is_signed ? LLVMIntSLT : LLVMIntULT, arg0.value, arg1.value, ""); res.value = LLVMBuildSelect(p->builder, cond, arg0.value, arg1.value, ""); @@ -1396,8 +1426,7 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn return res; case BuiltinProc_simd_max: if (is_float) { - LLVMValueRef cond = LLVMBuildFCmp(p->builder, LLVMRealOGT, arg0.value, arg1.value, ""); - res.value = LLVMBuildSelect(p->builder, cond, arg0.value, arg1.value, ""); + return lb_emit_max(p, res.type, arg0, arg1); } else { LLVMValueRef cond = LLVMBuildICmp(p->builder, is_signed ? LLVMIntSGT : LLVMIntUGT, arg0.value, arg1.value, ""); res.value = LLVMBuildSelect(p->builder, cond, arg0.value, arg1.value, ""); @@ -1519,6 +1548,23 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn return res; } + case BuiltinProc_simd_reduce_any: + case BuiltinProc_simd_reduce_all: + { + char const *name = nullptr; + switch (builtin_id) { + case BuiltinProc_simd_reduce_any: name = "llvm.vector.reduce.or"; break; + case BuiltinProc_simd_reduce_all: name = "llvm.vector.reduce.and"; break; + } + + LLVMTypeRef types[1] = { lb_type(p->module, arg0.type) }; + LLVMValueRef args[1] = { arg0.value }; + + res.value = lb_call_intrinsic(p, name, args, gb_count_of(args), types, gb_count_of(types)); + return res; + } + + case BuiltinProc_simd_shuffle: { Type *vt = arg0.type; @@ -1621,13 +1667,13 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn } - case BuiltinProc_simd_add_sat: - case BuiltinProc_simd_sub_sat: + case BuiltinProc_simd_saturating_add: + case BuiltinProc_simd_saturating_sub: { char const *name = nullptr; switch (builtin_id) { - case BuiltinProc_simd_add_sat: name = is_signed ? "llvm.sadd.sat" : "llvm.uadd.sat"; break; - case BuiltinProc_simd_sub_sat: name = is_signed ? "llvm.ssub.sat" : "llvm.usub.sat"; break; + case BuiltinProc_simd_saturating_add: name = is_signed ? "llvm.sadd.sat" : "llvm.uadd.sat"; break; + case BuiltinProc_simd_saturating_sub: name = is_signed ? "llvm.ssub.sat" : "llvm.usub.sat"; break; } LLVMTypeRef types[1] = {lb_type(p->module, arg0.type)}; @@ -1663,6 +1709,85 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn return res; } + + case BuiltinProc_simd_gather: + case BuiltinProc_simd_scatter: + case BuiltinProc_simd_masked_load: + case BuiltinProc_simd_masked_store: + case BuiltinProc_simd_masked_expand_load: + case BuiltinProc_simd_masked_compress_store: + { + LLVMValueRef ptr = arg0.value; + LLVMValueRef val = arg1.value; + LLVMValueRef mask = arg2.value; + + unsigned count = cast(unsigned)get_array_type_count(arg1.type); + + LLVMTypeRef mask_type = LLVMVectorType(LLVMInt1TypeInContext(p->module->ctx), count); + mask = LLVMBuildTrunc(p->builder, mask, mask_type, ""); + + char const *name = nullptr; + switch (builtin_id) { + case BuiltinProc_simd_gather: name = "llvm.masked.gather"; break; + case BuiltinProc_simd_scatter: name = "llvm.masked.scatter"; break; + case BuiltinProc_simd_masked_load: name = "llvm.masked.load"; break; + case BuiltinProc_simd_masked_store: name = "llvm.masked.store"; break; + case BuiltinProc_simd_masked_expand_load: name = "llvm.masked.expandload"; break; + case BuiltinProc_simd_masked_compress_store: name = "llvm.masked.compressstore"; break; + } + unsigned type_count = 2; + LLVMTypeRef types[2] = { + lb_type(p->module, arg1.type), + lb_type(p->module, arg0.type) + }; + + auto alignment = cast(unsigned long long)type_align_of(base_array_type(arg1.type)); + LLVMValueRef align = LLVMConstInt(LLVMInt32TypeInContext(p->module->ctx), alignment, false); + + unsigned arg_count = 4; + LLVMValueRef args[4] = {}; + switch (builtin_id) { + case BuiltinProc_simd_masked_load: + types[1] = lb_type(p->module, t_rawptr); + /*fallthrough*/ + case BuiltinProc_simd_gather: + args[0] = ptr; + args[1] = align; + args[2] = mask; + args[3] = val; + break; + + case BuiltinProc_simd_masked_store: + types[1] = lb_type(p->module, t_rawptr); + /*fallthrough*/ + case BuiltinProc_simd_scatter: + args[0] = val; + args[1] = ptr; + args[2] = align; + args[3] = mask; + break; + + case BuiltinProc_simd_masked_expand_load: + arg_count = 3; + type_count = 1; + args[0] = ptr; + args[1] = mask; + args[2] = val; + break; + + case BuiltinProc_simd_masked_compress_store: + arg_count = 3; + type_count = 1; + args[0] = val; + args[1] = ptr; + args[2] = mask; + break; + } + + res.value = lb_call_intrinsic(p, name, args, arg_count, types, type_count); + return res; + + } } GB_PANIC("Unhandled simd intrinsic: '%.*s'", LIT(builtin_procs[builtin_id].name)); @@ -1681,24 +1806,61 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu case BuiltinProc_DIRECTIVE: { ast_node(bd, BasicDirective, ce->proc); String name = bd->name.string; - GB_ASSERT(name == "location"); - String procedure = p->entity->token.string; - TokenPos pos = ast_token(ce->proc).pos; - if (ce->args.count > 0) { - Ast *ident = unselector_expr(ce->args[0]); - GB_ASSERT(ident->kind == Ast_Ident); - Entity *e = entity_of_node(ident); - GB_ASSERT(e != nullptr); + if (name == "location") { + String procedure = p->entity->token.string; + TokenPos pos = ast_token(ce->proc).pos; + if (ce->args.count > 0) { + Ast *ident = unselector_expr(ce->args[0]); + GB_ASSERT(ident->kind == Ast_Ident); + Entity *e = entity_of_node(ident); + GB_ASSERT(e != nullptr); + + if (e->parent_proc_decl != nullptr && e->parent_proc_decl->entity != nullptr) { + procedure = e->parent_proc_decl->entity->token.string; + } else { + procedure = str_lit(""); + } + pos = e->token.pos; - if (e->parent_proc_decl != nullptr && e->parent_proc_decl->entity != nullptr) { - procedure = e->parent_proc_decl->entity->token.string; - } else { - procedure = str_lit(""); } - pos = e->token.pos; + return lb_emit_source_code_location_as_global(p, procedure, pos); + } else if (name == "load_directory") { + lbModule *m = p->module; + TEMPORARY_ALLOCATOR_GUARD(); + LoadDirectoryCache *cache = map_must_get(&m->info->load_directory_map, expr); + isize count = cache->files.count; + LLVMValueRef *elements = gb_alloc_array(temporary_allocator(), LLVMValueRef, count); + for_array(i, cache->files) { + LoadFileCache *file = cache->files[i]; + + String file_name = filename_without_directory(file->path); + + LLVMValueRef values[2] = {}; + values[0] = lb_const_string(m, file_name).value; + values[1] = lb_const_string(m, file->data).value; + LLVMValueRef element = llvm_const_named_struct(m, t_load_directory_file, values, gb_count_of(values)); + elements[i] = element; + } + + LLVMValueRef backing_array = llvm_const_array(lb_type(m, t_load_directory_file), elements, count); + + Type *array_type = alloc_type_array(t_load_directory_file, count); + lbAddr backing_array_addr = lb_add_global_generated(m, array_type, {backing_array, array_type}, nullptr); + lb_make_global_private_const(backing_array_addr); + + LLVMValueRef backing_array_ptr = backing_array_addr.addr.value; + backing_array_ptr = LLVMConstPointerCast(backing_array_ptr, lb_type(m, t_load_directory_file_ptr)); + + LLVMValueRef const_slice = llvm_const_slice_internal(m, backing_array_ptr, LLVMConstInt(lb_type(m, t_int), count, false)); + + lbAddr addr = lb_add_global_generated(p->module, tv.type, {const_slice, t_load_directory_file_slice}, nullptr); + lb_make_global_private_const(addr); + + return lb_addr_load(p, addr); + } else { + GB_PANIC("UNKNOWN DIRECTIVE: %.*s", LIT(name)); } - return lb_emit_source_code_location_as_global(p, procedure, pos); } case BuiltinProc_type_info_of: { @@ -1706,7 +1868,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu TypeAndValue tav = type_and_value_of_expr(arg); if (tav.mode == Addressing_Type) { Type *t = default_type(type_of_expr(arg)); - return lb_type_info(p->module, t); + return lb_type_info(p, t); } GB_ASSERT(is_type_typeid(tav.type)); @@ -1826,24 +1988,41 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu } case BuiltinProc_quaternion: { - lbValue real = lb_build_expr(p, ce->args[0]); - lbValue imag = lb_build_expr(p, ce->args[1]); - lbValue jmag = lb_build_expr(p, ce->args[2]); - lbValue kmag = lb_build_expr(p, ce->args[3]); + lbValue xyzw[4] = {}; + for (i32 i = 0; i < 4; i++) { + ast_node(f, FieldValue, ce->args[i]); + GB_ASSERT(f->field->kind == Ast_Ident); + String name = f->field->Ident.token.string; + i32 index = -1; + + // @QuaternionLayout + if (name == "x" || name == "imag") { + index = 0; + } else if (name == "y" || name == "jmag") { + index = 1; + } else if (name == "z" || name == "kmag") { + index = 2; + } else if (name == "w" || name == "real") { + index = 3; + } + GB_ASSERT(index >= 0); + + xyzw[index] = lb_build_expr(p, f->value); + } + - // @QuaternionLayout lbAddr dst_addr = lb_add_local_generated(p, tv.type, false); lbValue dst = lb_addr_get_ptr(p, dst_addr); Type *ft = base_complex_elem_type(tv.type); - real = lb_emit_conv(p, real, ft); - imag = lb_emit_conv(p, imag, ft); - jmag = lb_emit_conv(p, jmag, ft); - kmag = lb_emit_conv(p, kmag, ft); - lb_emit_store(p, lb_emit_struct_ep(p, dst, 3), real); - lb_emit_store(p, lb_emit_struct_ep(p, dst, 0), imag); - lb_emit_store(p, lb_emit_struct_ep(p, dst, 1), jmag); - lb_emit_store(p, lb_emit_struct_ep(p, dst, 2), kmag); + xyzw[0] = lb_emit_conv(p, xyzw[0], ft); + xyzw[1] = lb_emit_conv(p, xyzw[1], ft); + xyzw[2] = lb_emit_conv(p, xyzw[2], ft); + xyzw[3] = lb_emit_conv(p, xyzw[3], ft); + lb_emit_store(p, lb_emit_struct_ep(p, dst, 0), xyzw[0]); + lb_emit_store(p, lb_emit_struct_ep(p, dst, 1), xyzw[1]); + lb_emit_store(p, lb_emit_struct_ep(p, dst, 2), xyzw[2]); + lb_emit_store(p, lb_emit_struct_ep(p, dst, 3), xyzw[3]); return lb_emit_load(p, dst); } @@ -2004,9 +2183,9 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu case BuiltinProc_clamp: return lb_emit_clamp(p, type_of_expr(expr), - lb_build_expr(p, ce->args[0]), - lb_build_expr(p, ce->args[1]), - lb_build_expr(p, ce->args[2])); + lb_build_expr(p, ce->args[0]), + lb_build_expr(p, ce->args[1]), + lb_build_expr(p, ce->args[2])); case BuiltinProc_soa_zip: @@ -2223,6 +2402,39 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu return res; } + case BuiltinProc_saturating_add: + case BuiltinProc_saturating_sub: + { + 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_saturating_add: name = "llvm.uadd.sat"; break; + case BuiltinProc_saturating_sub: name = "llvm.usub.sat"; break; + } + } else { + switch (id) { + case BuiltinProc_saturating_add: name = "llvm.sadd.sat"; break; + case BuiltinProc_saturating_sub: name = "llvm.ssub.sat"; break; + } + } + LLVMTypeRef types[1] = {lb_type(p->module, type)}; + + 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; @@ -2318,9 +2530,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); } @@ -2374,7 +2587,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu case BuiltinProc_atomic_load_explicit: { lbValue dst = lb_build_expr(p, ce->args[0]); - LLVMValueRef instr = LLVMBuildLoad2(p->builder, lb_type(p->module, type_deref(dst.type)), dst.value, ""); + LLVMValueRef instr = OdinLLVMBuildLoad(p, lb_type(p->module, type_deref(dst.type)), dst.value); switch (id) { case BuiltinProc_non_temporal_load: { @@ -2427,8 +2640,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu if (is_type_simd_vector(t)) { lbValue res = {}; res.type = t; - res.value = LLVMBuildLoad2(p->builder, lb_type(p->module, t), src.value, ""); - LLVMSetAlignment(res.value, 1); + res.value = OdinLLVMBuildLoadAligned(p, lb_type(p->module, t), src.value, 1); return res; } else { lbAddr dst = lb_add_local_generated(p, t, false); @@ -2685,30 +2897,39 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu LLVMValueRef inline_asm = nullptr; switch (build_context.metrics.arch) { + case TargetArch_riscv64: + { + GB_ASSERT(arg_count <= 7); + + char asm_string[] = "ecall"; + gbString constraints = gb_string_make(heap_allocator(), "={a0}"); + for (unsigned i = 0; i < arg_count; i++) { + constraints = gb_string_appendc(constraints, ",{"); + static char const *regs[] = { + "a7", + "a0", + "a1", + "a2", + "a3", + "a4", + "a5", + "a6" + }; + constraints = gb_string_appendc(constraints, regs[i]); + constraints = gb_string_appendc(constraints, "}"); + } + + inline_asm = llvm_get_inline_asm(func_type, make_string_c(asm_string), make_string_c(constraints)); + } + break; case TargetArch_amd64: { GB_ASSERT(arg_count <= 7); - // 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", @@ -2732,36 +2953,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; @@ -2769,8 +2963,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++) { @@ -2782,16 +2975,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)); } @@ -2843,7 +3031,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"; @@ -2851,13 +3038,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, "}"); @@ -2875,6 +3063,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); @@ -2991,9 +3312,6 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu case BuiltinProc_wasm_memory_atomic_wait32: { char const *name = "llvm.wasm.memory.atomic.wait32"; - LLVMTypeRef types[1] = { - lb_type(p->module, t_u32), - }; Type *t_u32_ptr = alloc_type_pointer(t_u32); @@ -3004,26 +3322,24 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu lbValue res = {}; res.type = tv.type; - res.value = lb_call_intrinsic(p, name, args, gb_count_of(args), types, gb_count_of(types)); + res.value = lb_call_intrinsic(p, name, args, gb_count_of(args), nullptr, 0); return res; } case BuiltinProc_wasm_memory_atomic_notify32: { char const *name = "llvm.wasm.memory.atomic.notify"; - LLVMTypeRef types[1] = { - lb_type(p->module, t_u32), - }; Type *t_u32_ptr = alloc_type_pointer(t_u32); LLVMValueRef args[2] = { - lb_emit_conv(p, lb_build_expr(p, ce->args[0]), t_u32_ptr).value, - lb_emit_conv(p, lb_build_expr(p, ce->args[1]), t_u32).value }; + lb_emit_conv(p, lb_build_expr(p, ce->args[0]), t_u32_ptr).value, + lb_emit_conv(p, lb_build_expr(p, ce->args[1]), t_u32).value + }; lbValue res = {}; res.type = tv.type; - res.value = lb_call_intrinsic(p, name, args, gb_count_of(args), types, gb_count_of(types)); + res.value = lb_call_intrinsic(p, name, args, gb_count_of(args), nullptr, 0); return res; } @@ -3124,7 +3440,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu } -gb_internal lbValue lb_handle_param_value(lbProcedure *p, Type *parameter_type, ParameterValue const ¶m_value, TokenPos const &pos) { +gb_internal lbValue lb_handle_param_value(lbProcedure *p, Type *parameter_type, ParameterValue const ¶m_value, TypeProc *procedure_type, Ast* call_expression) { switch (param_value.kind) { case ParameterValue_Constant: if (is_type_constant_type(parameter_type)) { @@ -3150,8 +3466,60 @@ gb_internal lbValue lb_handle_param_value(lbProcedure *p, Type *parameter_type, if (p->entity != nullptr) { proc_name = p->entity->token.string; } + + ast_node(ce, CallExpr, call_expression); + TokenPos pos = ast_token(ce->proc).pos; + return lb_emit_source_code_location_as_global(p, proc_name, pos); } + case ParameterValue_Expression: + { + Ast *orig = param_value.original_ast_expr; + if (orig->kind == Ast_BasicDirective) { + gbString expr = expr_to_string(call_expression, temporary_allocator()); + return lb_const_string(p->module, make_string_c(expr)); + } + + isize param_idx = -1; + String param_str = {0}; + { + Ast *call = unparen_expr(orig); + GB_ASSERT(call->kind == Ast_CallExpr); + ast_node(ce, CallExpr, call); + GB_ASSERT(ce->proc->kind == Ast_BasicDirective); + GB_ASSERT(ce->args.count == 1); + Ast *target = ce->args[0]; + GB_ASSERT(target->kind == Ast_Ident); + String target_str = target->Ident.token.string; + + param_idx = lookup_procedure_parameter(procedure_type, target_str); + param_str = target_str; + } + GB_ASSERT(param_idx >= 0); + + + Ast *target_expr = nullptr; + ast_node(ce, CallExpr, call_expression); + + if (ce->split_args->positional.count > param_idx) { + target_expr = ce->split_args->positional[param_idx]; + } + + for_array(i, ce->split_args->named) { + Ast *arg = ce->split_args->named[i]; + ast_node(fv, FieldValue, arg); + GB_ASSERT(fv->field->kind == Ast_Ident); + String name = fv->field->Ident.token.string; + if (name == param_str) { + target_expr = fv->value; + break; + } + } + + gbString expr = expr_to_string(target_expr, temporary_allocator()); + return lb_const_string(p->module, make_string_c(expr)); + } + case ParameterValue_Value: return lb_build_expr(p, param_value.ast_value); } @@ -3295,9 +3663,12 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { for (Ast *var_arg : variadic) { lbValue arg = lb_build_expr(p, var_arg); if (is_type_any(elem_type)) { - array_add(&args, lb_emit_conv(p, arg, default_type(arg.type))); + if (is_type_untyped_nil(arg.type)) { + arg = lb_const_nil(p->module, t_rawptr); + } + array_add(&args, lb_emit_c_vararg(p, arg, arg.type)); } else { - array_add(&args, lb_emit_conv(p, arg, elem_type)); + array_add(&args, lb_emit_c_vararg(p, arg, elem_type)); } } break; @@ -3316,17 +3687,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); @@ -3359,6 +3780,30 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { if (e->kind == Entity_TypeName) { lbValue value = lb_const_nil(p->module, e->type); args[param_index] = value; + } else if (is_c_vararg && pt->variadic && pt->variadic_index == param_index) { + GB_ASSERT(param_index == pt->param_count-1); + Type *slice_type = e->type; + GB_ASSERT(slice_type->kind == Type_Slice); + Type *elem_type = slice_type->Slice.elem; + + if (fv->value->kind == Ast_CompoundLit) { + ast_node(literal, CompoundLit, fv->value); + for (Ast *var_arg : literal->elems) { + lbValue arg = lb_build_expr(p, var_arg); + if (is_type_any(elem_type)) { + if (is_type_untyped_nil(arg.type)) { + arg = lb_const_nil(p->module, t_rawptr); + } + array_add(&args, lb_emit_c_vararg(p, arg, arg.type)); + } else { + array_add(&args, lb_emit_c_vararg(p, arg, elem_type)); + } + } + } else { + lbValue value = lb_build_expr(p, fv->value); + GB_ASSERT(!is_type_tuple(value.type)); + array_add(&args, lb_emit_c_vararg(p, value, value.type)); + } } else { lbValue value = lb_build_expr(p, fv->value); GB_ASSERT(!is_type_tuple(value.type)); @@ -3366,8 +3811,6 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { } } - TokenPos pos = ast_token(ce->proc).pos; - if (pt->params != nullptr) { isize min_count = pt->params->Tuple.variables.count; @@ -3385,13 +3828,13 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { } lbValue arg = args[arg_index]; - if (arg.value == nullptr) { + if (arg.value == nullptr && arg.type == nullptr) { switch (e->kind) { case Entity_TypeName: args[arg_index] = lb_const_nil(p->module, e->type); break; case Entity_Variable: - args[arg_index] = lb_handle_param_value(p, e->type, e->Variable.param_value, pos); + args[arg_index] = lb_handle_param_value(p, e->type, e->Variable.param_value, pt, expr); break; case Entity_Constant: diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index 9d688be6a..a2f0d2f4a 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -201,13 +201,15 @@ gb_internal void lb_open_scope(lbProcedure *p, Scope *s) { } } + GB_ASSERT(s != nullptr); + p->curr_scope = s; p->scope_index += 1; array_add(&p->scope_stack, s); } -gb_internal void lb_close_scope(lbProcedure *p, lbDeferExitKind kind, lbBlock *block, bool pop_stack=true) { - lb_emit_defer_stmts(p, kind, block); +gb_internal void lb_close_scope(lbProcedure *p, lbDeferExitKind kind, lbBlock *block, Ast *node, bool pop_stack=true) { + lb_emit_defer_stmts(p, kind, block, node); GB_ASSERT(p->scope_index > 0); // NOTE(bill): Remove `context`s made in that scope @@ -221,6 +223,10 @@ gb_internal void lb_close_scope(lbProcedure *p, lbDeferExitKind kind, lbBlock *b } + if (p->curr_scope) { + p->curr_scope = p->curr_scope->parent; + } + p->scope_index -= 1; array_pop(&p->scope_stack); } @@ -715,7 +721,7 @@ gb_internal void lb_build_range_interval(lbProcedure *p, AstBinaryExpr *node, lb_build_stmt(p, rs->body); - lb_close_scope(p, lbDeferExit_Default, nullptr); + lb_close_scope(p, lbDeferExit_Default, nullptr, node->left); lb_pop_target_list(p); if (check != nullptr) { @@ -737,6 +743,22 @@ gb_internal void lb_build_range_interval(lbProcedure *p, AstBinaryExpr *node, lb_start_block(p, done); } +gb_internal lbValue lb_enum_values_slice(lbProcedure *p, Type *enum_type, i64 *enum_count_) { + Type *t = enum_type; + GB_ASSERT(is_type_enum(t)); + t = base_type(t); + GB_ASSERT(t->kind == Type_Enum); + i64 enum_count = t->Enum.fields.count; + + if (enum_count_) *enum_count_ = enum_count; + + lbValue ti = lb_type_info(p, t); + lbValue variant = lb_emit_struct_ep(p, ti, 4); + lbValue eti_ptr = lb_emit_conv(p, variant, t_type_info_enum_ptr); + lbValue values = lb_emit_load(p, lb_emit_struct_ep(p, eti_ptr, 2)); + return values; +} + gb_internal void lb_build_range_enum(lbProcedure *p, Type *enum_type, Type *val_type, lbValue *val_, lbValue *idx_, lbBlock **loop_, lbBlock **done_) { lbModule *m = p->module; @@ -744,15 +766,11 @@ gb_internal void lb_build_range_enum(lbProcedure *p, Type *enum_type, Type *val_ GB_ASSERT(is_type_enum(t)); t = base_type(t); Type *core_elem = core_type(t); - GB_ASSERT(t->kind == Type_Enum); - i64 enum_count = t->Enum.fields.count; - lbValue max_count = lb_const_int(m, t_int, enum_count); + i64 enum_count = 0; - lbValue ti = lb_type_info(m, t); - lbValue variant = lb_emit_struct_ep(p, ti, 4); - lbValue eti_ptr = lb_emit_conv(p, variant, t_type_info_enum_ptr); - lbValue values = lb_emit_load(p, lb_emit_struct_ep(p, eti_ptr, 2)); + lbValue values = lb_enum_values_slice(p, enum_type, &enum_count); lbValue values_data = lb_slice_elem(p, values); + lbValue max_count = lb_const_int(m, t_int, enum_count); lbAddr offset_ = lb_add_local_generated(p, t_int, false); lb_addr_store(p, offset_, lb_const_int(m, t_int, 0)); @@ -790,8 +808,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); @@ -809,11 +838,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, rs->body); + 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) { @@ -932,7 +976,7 @@ gb_internal void lb_build_range_stmt_struct_soa(lbProcedure *p, AstRangeStmt *rs lb_build_stmt(p, rs->body); - lb_close_scope(p, lbDeferExit_Default, nullptr); + lb_close_scope(p, lbDeferExit_Default, nullptr, rs->body); lb_pop_target_list(p); lb_emit_jump(p, loop); lb_start_block(p, done); @@ -956,6 +1000,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; @@ -974,7 +1029,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); @@ -1050,8 +1104,75 @@ 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); + GB_PANIC("Should be handled already"); + + case Type_BitSet: { + lbModule *m = p->module; + + lbValue the_set = lb_build_expr(p, expr); + if (is_type_pointer(type_deref(the_set.type))) { + the_set = lb_emit_load(p, the_set); + } + + Type *elem = et->BitSet.elem; + if (is_type_enum(elem)) { + i64 enum_count = 0; + lbValue values = lb_enum_values_slice(p, elem, &enum_count); + lbValue values_data = lb_slice_elem(p, values); + lbValue max_count = lb_const_int(m, t_int, enum_count); + + lbAddr offset_ = lb_add_local_generated(p, t_int, false); + lb_addr_store(p, offset_, lb_const_int(m, t_int, 0)); + + loop = lb_create_block(p, "for.bit_set.enum.loop"); + lb_emit_jump(p, loop); + lb_start_block(p, loop); + + lbBlock *body_check = lb_create_block(p, "for.bit_set.enum.body-check"); + lbBlock *body = lb_create_block(p, "for.bit_set.enum.body"); + done = lb_create_block(p, "for.bit_set.enum.done"); + + lbValue offset = lb_addr_load(p, offset_); + lbValue cond = lb_emit_comp(p, Token_Lt, offset, max_count); + lb_emit_if(p, cond, body_check, done); + lb_start_block(p, body_check); + + lbValue val_ptr = lb_emit_ptr_offset(p, values_data, offset); + lb_emit_increment(p, offset_.addr); + val = lb_emit_load(p, val_ptr); + val = lb_emit_conv(p, val, elem); + + lbValue check = lb_build_binary_in(p, val, the_set, Token_in); + lb_emit_if(p, check, body, loop); + lb_start_block(p, body); + } else { + lbAddr offset_ = lb_add_local_generated(p, t_int, false); + lb_addr_store(p, offset_, lb_const_int(m, t_int, et->BitSet.lower)); + + lbValue max_count = lb_const_int(m, t_int, et->BitSet.upper); + + loop = lb_create_block(p, "for.bit_set.range.loop"); + lb_emit_jump(p, loop); + lb_start_block(p, loop); + + lbBlock *body_check = lb_create_block(p, "for.bit_set.range.body-check"); + lbBlock *body = lb_create_block(p, "for.bit_set.range.body"); + done = lb_create_block(p, "for.bit_set.range.done"); + + lbValue offset = lb_addr_load(p, offset_); + lbValue cond = lb_emit_comp(p, Token_LtEq, offset, max_count); + lb_emit_if(p, cond, body_check, done); + lb_start_block(p, body_check); + + val = lb_emit_conv(p, offset, elem); + lb_emit_increment(p, offset_.addr); + + lbValue check = lb_build_binary_in(p, val, the_set, Token_in); + lb_emit_if(p, check, body, loop); + lb_start_block(p, body); + } break; + } default: GB_PANIC("Cannot range over %s", type_to_string(expr_type)); break; @@ -1071,7 +1192,7 @@ gb_internal void lb_build_range_stmt(lbProcedure *p, AstRangeStmt *rs, Scope *sc lb_build_stmt(p, rs->body); - lb_close_scope(p, lbDeferExit_Default, nullptr); + lb_close_scope(p, lbDeferExit_Default, nullptr, rs->body); lb_pop_target_list(p); lb_emit_jump(p, loop); lb_start_block(p, done); @@ -1242,7 +1363,7 @@ gb_internal void lb_build_unroll_range_stmt(lbProcedure *p, AstUnrollRangeStmt * } - lb_close_scope(p, lbDeferExit_Default, nullptr); + lb_close_scope(p, lbDeferExit_Default, nullptr, rs->body); } gb_internal bool lb_switch_stmt_can_be_trivial_jump_table(AstSwitchStmt *ss, bool *default_found_) { @@ -1289,6 +1410,10 @@ gb_internal bool lb_switch_stmt_can_be_trivial_jump_table(AstSwitchStmt *ss, boo } + if (is_typeid) { + return false; + } + return true; } @@ -1308,6 +1433,7 @@ gb_internal void lb_build_switch_stmt(lbProcedure *p, AstSwitchStmt *ss, Scope * ast_node(body, BlockStmt, ss->body); isize case_count = body->stmts.count; + Ast *default_clause = nullptr; Slice default_stmts = {}; lbBlock *default_fall = nullptr; lbBlock *default_block = nullptr; @@ -1357,6 +1483,7 @@ gb_internal void lb_build_switch_stmt(lbProcedure *p, AstSwitchStmt *ss, Scope * if (cc->list.count == 0) { // default case + default_clause = clause; default_stmts = cc->stmts; default_fall = fall; if (switch_instr == nullptr) { @@ -1427,7 +1554,7 @@ gb_internal void lb_build_switch_stmt(lbProcedure *p, AstSwitchStmt *ss, Scope * lb_push_target_list(p, ss->label, done, nullptr, fall); lb_open_scope(p, body->scope); lb_build_stmt_list(p, cc->stmts); - lb_close_scope(p, lbDeferExit_Default, body); + lb_close_scope(p, lbDeferExit_Default, body, clause); lb_pop_target_list(p); lb_emit_jump(p, done); @@ -1445,26 +1572,33 @@ gb_internal void lb_build_switch_stmt(lbProcedure *p, AstSwitchStmt *ss, Scope * lb_push_target_list(p, ss->label, done, nullptr, default_fall); lb_open_scope(p, default_block->scope); lb_build_stmt_list(p, default_stmts); - lb_close_scope(p, lbDeferExit_Default, default_block); + lb_close_scope(p, lbDeferExit_Default, default_block, default_clause); lb_pop_target_list(p); } lb_emit_jump(p, done); lb_start_block(p, done); - lb_close_scope(p, lbDeferExit_Default, done); + lb_close_scope(p, lbDeferExit_Default, done, ss->body); } -gb_internal void lb_store_type_case_implicit(lbProcedure *p, Ast *clause, lbValue value) { +gb_internal void lb_store_type_case_implicit(lbProcedure *p, Ast *clause, lbValue value, bool is_default_case) { Entity *e = implicit_entity_of_node(clause); GB_ASSERT(e != nullptr); if (e->flags & EntityFlag_Value) { // by value - GB_ASSERT(are_types_identical(e->type, value.type)); - lbAddr x = lb_add_local(p, e->type, e, false); - lb_addr_store(p, x, value); + if (are_types_identical(e->type, value.type)) { + lbAddr x = lb_add_local(p, e->type, e, false); + lb_addr_store(p, x, value); + } else { + GB_ASSERT_MSG(are_types_identical(e->type, type_deref(value.type)), "%s", type_to_string(value.type)); + lbAddr x = lb_add_local(p, e->type, e, false); + lb_addr_store(p, x, lb_emit_load(p, value)); + } } else { - // by reference - GB_ASSERT(are_types_identical(e->type, type_deref(value.type))); + if (!is_default_case) { + 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); } } @@ -1495,7 +1629,7 @@ gb_internal void lb_type_case_body(lbProcedure *p, Ast *label, Ast *clause, lbBl lb_push_target_list(p, label, done, nullptr, nullptr); lb_build_stmt_list(p, cc->stmts); - lb_close_scope(p, lbDeferExit_Default, body); + lb_close_scope(p, lbDeferExit_Default, body, clause); lb_pop_target_list(p); lb_emit_jump(p, done); @@ -1529,7 +1663,7 @@ gb_internal void lb_build_type_switch_stmt(lbProcedure *p, AstTypeSwitchStmt *ss union_data = lb_emit_conv(p, parent_ptr, t_rawptr); Type *union_type = type_deref(parent_ptr.type); if (is_type_union_maybe_pointer(union_type)) { - tag = lb_emit_conv(p, lb_emit_comp_against_nil(p, Token_NotEq, union_data), t_int); + tag = lb_emit_conv(p, lb_emit_comp_against_nil(p, Token_NotEq, parent_value), t_int); } else if (union_tag_size(union_type) == 0) { tag = {}; // there is no tag for a zero sized union } else { @@ -1619,10 +1753,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); + 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; } @@ -1652,7 +1793,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); @@ -1665,6 +1805,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); @@ -1688,7 +1829,7 @@ gb_internal void lb_build_type_switch_stmt(lbProcedure *p, AstTypeSwitchStmt *ss lb_add_entity(p->module, case_entity, ptr); lb_add_debug_local_variable(p, ptr.value, case_entity->type, case_entity->token); } else { - lb_store_type_case_implicit(p, clause, parent_value); + lb_store_type_case_implicit(p, clause, parent_value, false); } lb_type_case_body(p, ss->label, clause, body, done); @@ -1696,7 +1837,7 @@ gb_internal void lb_build_type_switch_stmt(lbProcedure *p, AstTypeSwitchStmt *ss lb_emit_jump(p, done); lb_start_block(p, done); - lb_close_scope(p, lbDeferExit_Default, done); + lb_close_scope(p, lbDeferExit_Default, done, ss->body); } @@ -1734,7 +1875,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); @@ -1818,7 +1961,7 @@ gb_internal void lb_build_assignment(lbProcedure *p, Array &lvals, Slice p->in_multi_assignment = prev_in_assignment; } -gb_internal void lb_build_return_stmt_internal(lbProcedure *p, lbValue res) { +gb_internal void lb_build_return_stmt_internal(lbProcedure *p, lbValue res, TokenPos pos) { lbFunctionType *ft = lb_get_function_type(p->module, p->type); bool return_by_pointer = ft->ret.kind == lbArg_Indirect; bool split_returns = ft->multiple_return_original_type != nullptr; @@ -1841,40 +1984,72 @@ gb_internal void lb_build_return_stmt_internal(lbProcedure *p, lbValue res) { LLVMBuildStore(p->builder, LLVMConstNull(p->abi_function_type->ret.type), p->return_ptr.addr.value); } - lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr); + lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr, pos); - LLVMBuildRetVoid(p->builder); + // Check for terminator in the defer stmts + LLVMValueRef instr = LLVMGetLastInstruction(p->curr_block->block); + if (!lb_is_instr_terminating(instr)) { + LLVMBuildRetVoid(p->builder); + } } else { LLVMValueRef ret_val = res.value; - ret_val = OdinLLVMBuildTransmute(p, ret_val, p->abi_function_type->ret.type); - if (p->abi_function_type->ret.cast_type != nullptr) { - ret_val = OdinLLVMBuildTransmute(p, ret_val, p->abi_function_type->ret.cast_type); + LLVMTypeRef ret_type = p->abi_function_type->ret.type; + if (LLVMTypeRef cast_type = p->abi_function_type->ret.cast_type) { + ret_type = cast_type; } - lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr); - LLVMBuildRet(p->builder, ret_val); + if (LLVMGetTypeKind(ret_type) == LLVMStructTypeKind) { + LLVMTypeRef src_type = LLVMTypeOf(ret_val); + + if (p->temp_callee_return_struct_memory == nullptr) { + i64 max_align = gb_max(lb_alignof(ret_type), lb_alignof(src_type)); + p->temp_callee_return_struct_memory = llvm_alloca(p, ret_type, max_align); + } + // reuse the temp return value memory where possible + LLVMValueRef ptr = p->temp_callee_return_struct_memory; + LLVMValueRef nptr = LLVMBuildPointerCast(p->builder, ptr, LLVMPointerType(src_type, 0), ""); + LLVMBuildStore(p->builder, ret_val, nptr); + ret_val = OdinLLVMBuildLoad(p, ret_type, ptr); + } else { + ret_val = OdinLLVMBuildTransmute(p, ret_val, ret_type); + } + + lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr, pos); + + // Check for terminator in the defer stmts + LLVMValueRef instr = LLVMGetLastInstruction(p->curr_block->block); + if (!lb_is_instr_terminating(instr)) { + LLVMBuildRet(p->builder, ret_val); + } } } -gb_internal void lb_build_return_stmt(lbProcedure *p, Slice const &return_results) { +gb_internal void lb_build_return_stmt(lbProcedure *p, Slice const &return_results, TokenPos pos) { lb_ensure_abi_function_type(p->module, p); + isize return_count = p->type->Proc.result_count; + + if (return_count == 0) { + // No return values + + lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr, pos); + + // Check for terminator in the defer stmts + LLVMValueRef instr = LLVMGetLastInstruction(p->curr_block->block); + if (!lb_is_instr_terminating(instr)) { + LLVMBuildRetVoid(p->builder); + } + return; + } + lbValue res = {}; - TypeTuple *tuple = &p->type->Proc.results->Tuple; - isize return_count = p->type->Proc.result_count; + TypeTuple *tuple = &p->type->Proc.results->Tuple; isize res_count = return_results.count; lbFunctionType *ft = lb_get_function_type(p->module, p->type); bool return_by_pointer = ft->ret.kind == lbArg_Indirect; - if (return_count == 0) { - // No return values - - lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr); - - LLVMBuildRetVoid(p->builder); - return; - } else if (return_count == 1) { + if (return_count == 1) { Entity *e = tuple->variables[0]; if (res_count == 0) { rw_mutex_shared_lock(&p->module->values_mutex); @@ -1965,11 +2140,11 @@ gb_internal void lb_build_return_stmt(lbProcedure *p, Slice const &return GB_ASSERT(result_values.count-1 == result_eps.count); lb_addr_store(p, p->return_ptr, result_values[result_values.count-1]); - lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr); + lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr, pos); LLVMBuildRetVoid(p->builder); return; } else { - return lb_build_return_stmt_internal(p, result_values[result_values.count-1]); + return lb_build_return_stmt_internal(p, result_values[result_values.count-1], pos); } } else { @@ -1996,7 +2171,7 @@ gb_internal void lb_build_return_stmt(lbProcedure *p, Slice const &return } if (return_by_pointer) { - lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr); + lb_emit_defer_stmts(p, lbDeferExit_Return, nullptr, pos); LLVMBuildRetVoid(p->builder); return; } @@ -2004,14 +2179,24 @@ gb_internal void lb_build_return_stmt(lbProcedure *p, Slice const &return res = lb_emit_load(p, res); } } - lb_build_return_stmt_internal(p, res); + lb_build_return_stmt_internal(p, res, pos); } gb_internal void lb_build_if_stmt(lbProcedure *p, Ast *node) { ast_node(is, IfStmt, node); lb_open_scope(p, is->scope); // Scope #1 - defer (lb_close_scope(p, lbDeferExit_Default, nullptr)); + defer (lb_close_scope(p, lbDeferExit_Default, nullptr, node)); + 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); @@ -2019,24 +2204,13 @@ 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)) { + if (cond.value && LLVMIsAConstantInt(cond.value)) { // NOTE(bill): Do a compile time short circuit for when the condition is constantly known. // This done manually rather than relying on the SSA passes because sometimes the SSA passes // miss some even if they are constantly known, especially with few optimization passes. @@ -2062,7 +2236,7 @@ gb_internal void lb_build_if_stmt(lbProcedure *p, Ast *node) { lb_open_scope(p, scope_of_node(is->else_stmt)); lb_build_stmt(p, is->else_stmt); - lb_close_scope(p, lbDeferExit_Default, nullptr); + lb_close_scope(p, lbDeferExit_Default, nullptr, is->else_stmt); } lb_emit_jump(p, done); @@ -2079,7 +2253,7 @@ gb_internal void lb_build_if_stmt(lbProcedure *p, Ast *node) { lb_open_scope(p, scope_of_node(is->else_stmt)); lb_build_stmt(p, is->else_stmt); - lb_close_scope(p, lbDeferExit_Default, nullptr); + lb_close_scope(p, lbDeferExit_Default, nullptr, is->else_stmt); lb_emit_jump(p, done); } @@ -2099,15 +2273,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; @@ -2119,6 +2284,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); @@ -2131,7 +2307,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); @@ -2149,7 +2324,7 @@ gb_internal void lb_build_for_stmt(lbProcedure *p, Ast *node) { } lb_start_block(p, done); - lb_close_scope(p, lbDeferExit_Default, nullptr); + lb_close_scope(p, lbDeferExit_Default, nullptr, node); } gb_internal void lb_build_assign_stmt_array(lbProcedure *p, TokenKind op, lbAddr const &lhs, lbValue const &value) { @@ -2415,7 +2590,7 @@ gb_internal void lb_build_stmt(lbProcedure *p, Ast *node) { lb_open_scope(p, bs->scope); lb_build_stmt_list(p, bs->stmts); - lb_close_scope(p, lbDeferExit_Default, nullptr); + lb_close_scope(p, lbDeferExit_Default, nullptr, node); if (done != nullptr) { lb_emit_jump(p, done); @@ -2529,7 +2704,7 @@ gb_internal void lb_build_stmt(lbProcedure *p, Ast *node) { case_end; case_ast_node(rs, ReturnStmt, node); - lb_build_return_stmt(p, rs->results); + lb_build_return_stmt(p, rs->results, ast_token(node).pos); case_end; case_ast_node(is, IfStmt, node); @@ -2582,7 +2757,7 @@ gb_internal void lb_build_stmt(lbProcedure *p, Ast *node) { } } if (block != nullptr) { - lb_emit_defer_stmts(p, lbDeferExit_Branch, block); + lb_emit_defer_stmts(p, lbDeferExit_Branch, block, node); } lb_emit_jump(p, block); lb_start_block(p, lb_create_block(p, "unreachable")); @@ -2622,7 +2797,13 @@ gb_internal void lb_build_defer_stmt(lbProcedure *p, lbDefer const &d) { } } -gb_internal void lb_emit_defer_stmts(lbProcedure *p, lbDeferExitKind kind, lbBlock *block) { +gb_internal void lb_emit_defer_stmts(lbProcedure *p, lbDeferExitKind kind, lbBlock *block, TokenPos pos) { + TokenPos prev_token_pos = p->branch_location_pos; + if (p->uses_branch_location) { + p->branch_location_pos = pos; + } + defer (p->branch_location_pos = prev_token_pos); + isize count = p->defer_stmts.count; isize i = count; while (i --> 0) { @@ -2649,6 +2830,21 @@ gb_internal void lb_emit_defer_stmts(lbProcedure *p, lbDeferExitKind kind, lbBlo } } +gb_internal void lb_emit_defer_stmts(lbProcedure *p, lbDeferExitKind kind, lbBlock *block, Ast *node) { + TokenPos pos = {}; + if (node) { + if (node->kind == Ast_BlockStmt) { + pos = ast_end_token(node).pos; + } else if (node->kind == Ast_CaseClause) { + pos = ast_end_token(node).pos; + } else { + pos = ast_token(node).pos; + } + } + return lb_emit_defer_stmts(p, kind, block, pos); +} + + gb_internal void lb_add_defer_node(lbProcedure *p, isize scope_index, Ast *stmt) { Type *pt = base_type(p->type); GB_ASSERT(pt->kind == Type_Proc); diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index 02dad2a3a..6c12b37be 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -2,14 +2,19 @@ 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; } } if (err_on_not_found) { - GB_PANIC("NOT FOUND lb_type_info_index %s @ index %td", type_to_string(type), index); + gb_printf_err("NOT FOUND lb_type_info_index:\n\t%s\n\t@ index %td\n\tmax count: %u\nFound:\n", type_to_string(type), index, set->count); + for (auto const &entry : *set) { + isize type_info_index = entry.key; + gb_printf_err("\t%s\n", type_to_string(info->type_info_types[type_info_index])); + } + GB_PANIC("NOT FOUND"); } return -1; } @@ -38,6 +43,8 @@ gb_internal u64 lb_typeid_kind(lbModule *m, Type *type, u64 id=0) { if (flags & BasicFlag_Pointer) kind = Typeid_Pointer; if (flags & BasicFlag_String) kind = Typeid_String; if (flags & BasicFlag_Rune) kind = Typeid_Rune; + + if (bt->Basic.kind == Basic_typeid) kind = Typeid_Type_Id; } break; case Type_Pointer: kind = Typeid_Pointer; break; case Type_MultiPointer: kind = Typeid_Multi_Pointer; break; @@ -54,9 +61,8 @@ gb_internal u64 lb_typeid_kind(lbModule *m, Type *type, u64 id=0) { case Type_Proc: kind = Typeid_Procedure; break; case Type_BitSet: kind = Typeid_Bit_Set; break; case Type_SimdVector: kind = Typeid_Simd_Vector; break; - case Type_RelativePointer: kind = Typeid_Relative_Pointer; break; - case Type_RelativeMultiPointer: kind = Typeid_Relative_Multi_Pointer; break; case Type_SoaPointer: kind = Typeid_SoaPointer; break; + case Type_BitField: kind = Typeid_Bit_Field; break; } return kind; @@ -105,16 +111,19 @@ gb_internal lbValue lb_typeid(lbModule *m, Type *type) { return res; } -gb_internal lbValue lb_type_info(lbModule *m, Type *type) { +gb_internal lbValue lb_type_info(lbProcedure *p, Type *type) { GB_ASSERT(!build_context.no_rtti); type = default_type(type); + lbModule *m = p->module; isize index = lb_type_info_index(m->info, type); GB_ASSERT(index >= 0); - lbValue data = lb_global_type_info_data_ptr(m); - return lb_emit_array_epi(m, data, index); + lbValue global = lb_global_type_info_data_ptr(m); + + lbValue ptr = lb_emit_array_epi(p, global, index); + return lb_emit_load(p, ptr); } gb_internal LLVMTypeRef lb_get_procedure_raw_type(lbModule *m, Type *type) { @@ -175,16 +184,7 @@ gb_internal lbValue lb_type_info_member_tags_offset(lbModule *m, isize count, i6 return offset; } -// enum {LB_USE_GIANT_PACKED_STRUCT = LB_USE_NEW_PASS_SYSTEM}; -enum {LB_USE_GIANT_PACKED_STRUCT = 0}; - -gb_internal LLVMTypeRef lb_setup_type_info_data_internal_type(lbModule *m, isize max_type_info_count) { - if (!LB_USE_GIANT_PACKED_STRUCT) { - Type *t = alloc_type_array(t_type_info, max_type_info_count); - return lb_type(m, t); - } - CheckerInfo *info = m->gen->info; - +gb_internal LLVMTypeRef *lb_setup_modified_types_for_type_info(lbModule *m, isize max_type_info_count) { LLVMTypeRef *element_types = gb_alloc_array(heap_allocator(), LLVMTypeRef, max_type_info_count); defer (gb_free(heap_allocator(), element_types)); @@ -205,7 +205,7 @@ gb_internal LLVMTypeRef lb_setup_type_info_data_internal_type(lbModule *m, isize stypes[1] = lb_type(m, tibt->Struct.fields[1]->type); stypes[2] = lb_type(m, tibt->Struct.fields[2]->type); isize variant_index = 0; - if (build_context.int_size == 8) { + if (build_context.ptr_size == 8) { stypes[3] = lb_type(m, t_i32); // padding stypes[4] = lb_type(m, tibt->Struct.fields[3]->type); variant_index = 5; @@ -214,8 +214,8 @@ gb_internal LLVMTypeRef lb_setup_type_info_data_internal_type(lbModule *m, isize variant_index = 4; } - LLVMTypeRef modified_types[32] = {}; - GB_ASSERT(gb_count_of(modified_types) >= ut->Union.variants.count); + LLVMTypeRef *modified_types = gb_alloc_array(heap_allocator(), LLVMTypeRef, Typeid__COUNT); + GB_ASSERT(Typeid__COUNT == ut->Union.variants.count); modified_types[0] = element_types[0]; i64 tag_offset = ut->Union.variant_block_size; @@ -237,40 +237,22 @@ gb_internal LLVMTypeRef lb_setup_type_info_data_internal_type(lbModule *m, isize modified_types[i] = modified_type; } - for_array(type_info_type_index, info->type_info_types) { - Type *t = info->type_info_types[type_info_type_index]; - if (t == nullptr || t == t_invalid) { - continue; - } - isize entry_index = lb_type_info_index(info, t, false); - if (entry_index <= 0) { - continue; - } - - if (entries_handled[entry_index]) { - continue; - } - entries_handled[entry_index] = true; - - - if (t->kind == Type_Named) { - element_types[entry_index] = modified_types[0]; - } else { - i64 variant_index = lb_typeid_kind(m, t); - element_types[entry_index] = modified_types[variant_index]; - } - - GB_ASSERT(element_types[entry_index] != nullptr); + for (isize i = 0; i < Typeid__COUNT; i++) { + GB_ASSERT_MSG(modified_types[i] != nullptr, "%td", ut->Union.variants.count); } - for_array(i, entries_handled) { - GB_ASSERT(entries_handled[i]); - } - - return LLVMStructType(element_types, cast(unsigned)max_type_info_count, true); + return modified_types; } -gb_internal void lb_setup_type_info_data_giant_packed_struct(lbModule *m, i64 global_type_info_data_entity_count, lbProcedure *p) { // NOTE(bill): Setup type_info data +gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_type_info_data_entity_count) { // NOTE(bill): Setup type_info data + auto const &ADD_GLOBAL_TYPE_INFO_ENTRY = [](lbModule *m, LLVMTypeRef type, isize index) -> LLVMValueRef { + char name[64] = {}; + gb_snprintf(name, 63, "__$ti-%lld", cast(long long)index); + LLVMValueRef g = LLVMAddGlobal(m->mod, type, name); + lb_make_global_private_const(g); + return g; + }; + CheckerInfo *info = m->info; // Useful types @@ -287,19 +269,49 @@ gb_internal void lb_setup_type_info_data_giant_packed_struct(lbModule *m, i64 gl defer (gb_free(heap_allocator(), entries_handled.data)); entries_handled[0] = true; - LLVMValueRef giant_struct = lb_global_type_info_data_ptr(m).value; - LLVMTypeRef giant_struct_type = LLVMGlobalGetValueType(giant_struct); - GB_ASSERT(LLVMGetTypeKind(giant_struct_type) == LLVMStructTypeKind); - LLVMValueRef *giant_const_values = gb_alloc_array(heap_allocator(), LLVMValueRef, global_type_info_data_entity_count); defer (gb_free(heap_allocator(), giant_const_values)); - giant_const_values[0] = LLVMConstNull(LLVMStructGetTypeAtIndex(giant_struct_type, 0)); + // zero value is just zero data + giant_const_values[0] = ADD_GLOBAL_TYPE_INFO_ENTRY(m, lb_type(m, t_type_info), 0); + LLVMSetInitializer(giant_const_values[0], LLVMConstNull(lb_type(m, t_type_info))); + + + LLVMTypeRef *modified_types = lb_setup_modified_types_for_type_info(m, global_type_info_data_entity_count); + defer (gb_free(heap_allocator(), modified_types)); + for_array(type_info_type_index, info->type_info_types) { + Type *t = info->type_info_types[type_info_type_index]; + if (t == nullptr || t == t_invalid) { + continue; + } + + isize entry_index = lb_type_info_index(info, t, false); + if (entry_index <= 0) { + continue; + } + + if (entries_handled[entry_index]) { + continue; + } + entries_handled[entry_index] = true; + + + LLVMTypeRef stype = nullptr; + if (t->kind == Type_Named) { + stype = modified_types[0]; + } else { + stype = modified_types[lb_typeid_kind(m, t)]; + } + giant_const_values[entry_index] = ADD_GLOBAL_TYPE_INFO_ENTRY(m, stype, entry_index); + } + for (isize i = 1; i < global_type_info_data_entity_count; i++) { + entries_handled[i] = false; + } + LLVMValueRef *small_const_values = gb_alloc_array(heap_allocator(), LLVMValueRef, 6); defer (gb_free(heap_allocator(), small_const_values)); - #define type_info_allocate_values(name) \ LLVMValueRef *name##_values = gb_alloc_array(heap_allocator(), LLVMValueRef, type_deref(name.addr.type)->Array.count); \ defer (gb_free(heap_allocator(), name##_values)); \ @@ -311,7 +323,7 @@ gb_internal void lb_setup_type_info_data_giant_packed_struct(lbModule *m, i64 gl (name##_values)[i] = LLVMConstNull(elem); \ } \ } \ - LLVMSetInitializer(name.addr.value, llvm_const_array(elem, name##_values, at->Array.count)); \ + LLVMSetInitializer(name.addr.value, llvm_const_array(elem, name##_values, at->Array.count)); \ }) type_info_allocate_values(lb_global_type_info_member_types); @@ -321,27 +333,13 @@ gb_internal void lb_setup_type_info_data_giant_packed_struct(lbModule *m, i64 gl type_info_allocate_values(lb_global_type_info_member_tags); - i64 const type_info_struct_size = type_size_of(t_type_info); - LLVMTypeRef llvm_u8 = lb_type(m, t_u8); - LLVMTypeRef llvm_int = lb_type(m, t_int); - // LLVMTypeRef llvm_type_info_ptr = lb_type(m, t_type_info_ptr); - auto const get_type_info_ptr = [&](lbModule *m, Type *type) -> LLVMValueRef { type = default_type(type); isize index = lb_type_info_index(m->info, type); GB_ASSERT(index >= 0); - u64 offset = cast(u64)(index * type_info_struct_size); - - LLVMValueRef indices[1] = { - LLVMConstInt(llvm_int, offset, false) - }; - - // LLVMValueRef ptr = LLVMConstInBoundsGEP2(llvm_u8, giant_struct, indices, gb_count_of(indices)); - LLVMValueRef ptr = LLVMConstGEP2(llvm_u8, giant_struct, indices, gb_count_of(indices)); - return ptr; - // return LLVMConstPointerCast(ptr, llvm_type_info_ptr); + return giant_const_values[index]; }; for_array(type_info_type_index, info->type_info_types) { @@ -361,7 +359,12 @@ gb_internal void lb_setup_type_info_data_giant_packed_struct(lbModule *m, i64 gl entries_handled[entry_index] = true; - LLVMTypeRef stype = LLVMStructGetTypeAtIndex(giant_struct_type, cast(unsigned)entry_index); + LLVMTypeRef stype = nullptr; + if (t->kind == Type_Named) { + stype = modified_types[0]; + } else { + stype = modified_types[lb_typeid_kind(m, t)]; + } i64 size = type_size_of(t); i64 align = type_align_of(t); @@ -371,12 +374,16 @@ gb_internal void lb_setup_type_info_data_giant_packed_struct(lbModule *m, i64 gl lbValue type_info_flags = lb_const_int(m, t_type_info_flags, flags); + for (isize i = 0; i < 6; i++) { + small_const_values[i] = nullptr; + } + small_const_values[0] = LLVMConstInt(lb_type(m, t_int), size, true); small_const_values[1] = LLVMConstInt(lb_type(m, t_int), align, true); small_const_values[2] = type_info_flags.value; unsigned variant_index = 0; - if (build_context.int_size == 8) { + if (build_context.ptr_size == 8) { small_const_values[3] = LLVMConstNull(LLVMStructGetTypeAtIndex(stype, 3)); small_const_values[4] = id.value; variant_index = 5; @@ -414,7 +421,7 @@ gb_internal void lb_setup_type_info_data_giant_packed_struct(lbModule *m, i64 gl } 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, @@ -803,32 +810,31 @@ gb_internal void lb_setup_type_info_data_giant_packed_struct(lbModule *m, i64 gl 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; } if (t->Struct.soa_kind != StructSoa_None) { - Type *kind_type = get_struct_field_type(tag_type, 10); + Type *kind_type = get_struct_field_type(tag_type, 7); 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; } } @@ -851,7 +857,7 @@ gb_internal void lb_setup_type_info_data_giant_packed_struct(lbModule *m, i64 gl Entity *f = t->Struct.fields[source_index]; i64 foffset = 0; if (!t->Struct.is_raw_union) { - GB_ASSERT(t->Struct.offsets != nullptr); + GB_ASSERT_MSG(t->Struct.offsets != nullptr, "%s", type_to_string(t)); GB_ASSERT(0 <= f->Variable.field_index && f->Variable.field_index < count); foffset = t->Struct.offsets[source_index]; } @@ -875,12 +881,13 @@ gb_internal void lb_setup_type_info_data_giant_packed_struct(lbModule *m, i64 gl } - 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) { @@ -894,7 +901,7 @@ gb_internal void lb_setup_type_info_data_giant_packed_struct(lbModule *m, i64 gl case Type_Map: { tag_type = t_type_info_map; - init_map_internal_types(t); + init_map_internal_debug_types(t); LLVMValueRef vals[3] = { get_type_info_ptr(m, t->Map.key), @@ -941,46 +948,87 @@ gb_internal void lb_setup_type_info_data_giant_packed_struct(lbModule *m, i64 gl } break; - case Type_RelativePointer: - { - tag_type = t_type_info_relative_pointer; - LLVMValueRef vals[2] = { - get_type_info_ptr(m, t->RelativePointer.pointer_type), - get_type_info_ptr(m, t->RelativePointer.base_integer), - }; - - variant_value = llvm_const_named_struct(m, tag_type, vals, gb_count_of(vals)); - } - break; - - case Type_RelativeMultiPointer: - { - tag_type = t_type_info_relative_multi_pointer; - LLVMValueRef vals[2] = { - get_type_info_ptr(m, t->RelativeMultiPointer.pointer_type), - get_type_info_ptr(m, t->RelativeMultiPointer.base_integer), - }; - - variant_value = llvm_const_named_struct(m, tag_type, vals, gb_count_of(vals)); - } - break; - case Type_Matrix: { tag_type = t_type_info_matrix; i64 ez = type_size_of(t->Matrix.elem); - LLVMValueRef vals[5] = { + LLVMValueRef vals[6] = { get_type_info_ptr(m, t->Matrix.elem), lb_const_int(m, t_int, ez).value, lb_const_int(m, t_int, matrix_type_stride_in_elems(t)).value, lb_const_int(m, t_int, t->Matrix.row_count).value, lb_const_int(m, t_int, t->Matrix.column_count).value, + lb_const_int(m, t_u8, cast(u8)t->Matrix.is_row_major).value, }; variant_value = llvm_const_named_struct(m, tag_type, vals, gb_count_of(vals)); } break; + + case Type_BitField: + { + tag_type = t_type_info_bit_field; + + LLVMValueRef vals[7] = {}; + vals[0] = get_type_info_ptr(m, t->BitField.backing_type); + isize count = t->BitField.fields.count; + if (count > 0) { + i64 names_offset = 0; + i64 types_offset = 0; + i64 bit_sizes_offset = 0; + i64 bit_offsets_offset = 0; + i64 tags_offset = 0; + lbValue memory_names = lb_type_info_member_names_offset (m, count, &names_offset); + lbValue memory_types = lb_type_info_member_types_offset (m, count, &types_offset); + lbValue memory_bit_sizes = lb_type_info_member_offsets_offset(m, count, &bit_sizes_offset); + lbValue memory_bit_offsets = lb_type_info_member_offsets_offset(m, count, &bit_offsets_offset); + lbValue memory_tags = lb_type_info_member_tags_offset (m, count, &tags_offset); + + u64 bit_offset = 0; + for (isize source_index = 0; source_index < count; source_index++) { + Entity *f = t->BitField.fields[source_index]; + u64 bit_size = cast(u64)t->BitField.bit_sizes[source_index]; + + lbValue index = lb_const_int(m, t_int, source_index); + if (f->token.string.len > 0) { + lb_global_type_info_member_names_values[names_offset+source_index] = lb_const_string(m, f->token.string).value; + } + + lb_global_type_info_member_types_values[types_offset+source_index] = get_type_info_ptr(m, f->type); + + lb_global_type_info_member_offsets_values[bit_sizes_offset+source_index] = lb_const_int(m, t_uintptr, bit_size).value; + lb_global_type_info_member_offsets_values[bit_offsets_offset+source_index] = lb_const_int(m, t_uintptr, bit_offset).value; + + if (t->BitField.tags) { + String tag = t->BitField.tags[source_index]; + if (tag.len > 0) { + lb_global_type_info_member_tags_values[tags_offset+source_index] = lb_const_string(m, tag).value; + } + } + + bit_offset += bit_size; + } + + lbValue cv = lb_const_int(m, t_int, count); + 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; + } + + + for (isize i = 0; i < gb_count_of(vals); i++) { + if (vals[i] == nullptr) { + vals[i] = LLVMConstNull(lb_type(m, get_struct_field_type(tag_type, i))); + } + } + + variant_value = llvm_const_named_struct(m, tag_type, vals, gb_count_of(vals)); + break; + } } @@ -989,6 +1037,7 @@ gb_internal void lb_setup_type_info_data_giant_packed_struct(lbModule *m, i64 gl if (tag_type != nullptr) { tag_index = union_variant_index(ut, tag_type); } + GB_ASSERT(tag_index <= Typeid__COUNT); LLVMValueRef full_variant_values[3] = {}; @@ -1019,788 +1068,47 @@ gb_internal void lb_setup_type_info_data_giant_packed_struct(lbModule *m, i64 gl small_const_values[variant_index] = full_variant_value; - giant_const_values[entry_index] = LLVMConstNamedStruct(stype, small_const_values, variant_index+1); + LLVMSetInitializer(giant_const_values[entry_index], LLVMConstNamedStruct(stype, small_const_values, variant_index+1)); + } + for (isize i = 0; i < global_type_info_data_entity_count; i++) { + giant_const_values[i] = LLVMConstPointerCast(giant_const_values[i], lb_type(m, t_type_info_ptr)); } - LLVMValueRef giant_const = LLVMConstNamedStruct(giant_struct_type, giant_const_values, cast(unsigned)global_type_info_data_entity_count); - LLVMSetInitializer(giant_struct, giant_const); + + LLVMValueRef giant_const = LLVMConstArray(lb_type(m, t_type_info_ptr), giant_const_values, cast(unsigned)global_type_info_data_entity_count); + LLVMValueRef giant_array = lb_global_type_info_data_ptr(m).value; + LLVMSetInitializer(giant_array, giant_const); + lb_make_global_private_const(giant_array); } -gb_internal void lb_setup_type_info_data(lbProcedure *p) { // NOTE(bill): Setup type_info data +gb_internal void lb_setup_type_info_data(lbModule *m) { // NOTE(bill): Setup type_info data if (build_context.no_rtti) { return; } - lbModule *m = p->module; - CheckerInfo *info = m->info; i64 global_type_info_data_entity_count = 0; - { - // NOTE(bill): Set the type_table slice with the global backing array - lbValue global_type_table = lb_find_runtime_value(m, str_lit("type_table")); - Type *type = base_type(lb_global_type_info_data_entity->type); - GB_ASSERT(type->kind == Type_Array); - global_type_info_data_entity_count = type->Array.count; - LLVMValueRef data = lb_global_type_info_data_ptr(m).value; - data = LLVMConstPointerCast(data, lb_type(m, alloc_type_pointer(type->Array.elem))); - LLVMValueRef len = LLVMConstInt(lb_type(m, t_int), type->Array.count, true); - Type *t = type_deref(global_type_table.type); - GB_ASSERT(is_type_slice(t)); - LLVMValueRef slice = llvm_const_slice_internal(m, data, len); + // NOTE(bill): Set the type_table slice with the global backing array + lbValue global_type_table = lb_find_runtime_value(m, str_lit("type_table")); + Type *type = base_type(lb_global_type_info_data_entity->type); + GB_ASSERT(type->kind == Type_Array); + global_type_info_data_entity_count = type->Array.count; - LLVMSetInitializer(global_type_table.value, slice); + if (true) { + lb_setup_type_info_data_giant_array(m, global_type_info_data_entity_count); } - if (LB_USE_GIANT_PACKED_STRUCT) { - lb_setup_type_info_data_giant_packed_struct(m, global_type_info_data_entity_count, p); - return; - } + LLVMValueRef data = lb_global_type_info_data_ptr(m).value; + data = LLVMConstPointerCast(data, lb_type(m, alloc_type_pointer(type->Array.elem))); + LLVMValueRef len = LLVMConstInt(lb_type(m, t_int), type->Array.count, true); + Type *t = type_deref(global_type_table.type); + GB_ASSERT(is_type_slice(t)); + LLVMValueRef slice = llvm_const_slice_internal(m, data, len); - // Useful types - Entity *type_info_flags_entity = find_core_entity(info->checker, str_lit("Type_Info_Flags")); - Type *t_type_info_flags = type_info_flags_entity->type; + LLVMSetInitializer(global_type_table.value, slice); - - auto entries_handled = slice_make(heap_allocator(), cast(isize)global_type_info_data_entity_count); - defer (gb_free(heap_allocator(), entries_handled.data)); - entries_handled[0] = true; - - for_array(type_info_type_index, info->type_info_types) { - Type *t = info->type_info_types[type_info_type_index]; - if (t == nullptr || t == t_invalid) { - continue; - } - - isize entry_index = lb_type_info_index(info, t, false); - if (entry_index <= 0) { - continue; - } - - if (entries_handled[entry_index]) { - continue; - } - entries_handled[entry_index] = true; - - lbValue global_data_ptr = lb_global_type_info_data_ptr(m); - lbValue tag = {}; - lbValue ti_ptr = lb_emit_array_epi(p, global_data_ptr, cast(i32)entry_index); - - i64 size = type_size_of(t); - i64 align = type_align_of(t); - u32 flags = type_info_flags_of_type(t); - lbValue id = lb_typeid(m, t); - GB_ASSERT_MSG(align != 0, "%lld %s", align, type_to_string(t)); - - lbValue type_info_flags = lb_const_int(p->module, t_type_info_flags, flags); - - lbValue size_ptr = lb_emit_struct_ep(p, ti_ptr, 0); - lbValue align_ptr = lb_emit_struct_ep(p, ti_ptr, 1); - lbValue flags_ptr = lb_emit_struct_ep(p, ti_ptr, 2); - lbValue id_ptr = lb_emit_struct_ep(p, ti_ptr, 3); - - lb_emit_store(p, size_ptr, lb_const_int(m, t_int, size)); - lb_emit_store(p, align_ptr, lb_const_int(m, t_int, align)); - lb_emit_store(p, flags_ptr, type_info_flags); - lb_emit_store(p, id_ptr, id); - - lbValue variant_ptr = lb_emit_struct_ep(p, ti_ptr, 4); - - switch (t->kind) { - case Type_Named: { - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_named_ptr); - - LLVMValueRef pkg_name = nullptr; - if (t->Named.type_name->pkg) { - pkg_name = lb_const_string(m, t->Named.type_name->pkg->name).value; - } else { - pkg_name = LLVMConstNull(lb_type(m, t_string)); - } - - String proc_name = {}; - if (t->Named.type_name->parent_proc_decl) { - DeclInfo *decl = t->Named.type_name->parent_proc_decl; - if (decl->entity && decl->entity->kind == Entity_Procedure) { - proc_name = decl->entity->token.string; - } - } - TokenPos pos = t->Named.type_name->token.pos; - - lbValue loc = lb_emit_source_code_location_const(p, proc_name, pos); - - LLVMValueRef vals[4] = { - lb_const_string(p->module, t->Named.type_name->token.string).value, - lb_type_info(m, t->Named.base).value, - pkg_name, - loc.value - }; - - lbValue res = {}; - res.type = type_deref(tag.type); - res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals)); - lb_emit_store(p, tag, res); - break; - } - - case Type_Basic: - switch (t->Basic.kind) { - case Basic_bool: - case Basic_b8: - case Basic_b16: - case Basic_b32: - case Basic_b64: - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_boolean_ptr); - break; - - case Basic_i8: - case Basic_u8: - case Basic_i16: - case Basic_u16: - case Basic_i32: - case Basic_u32: - case Basic_i64: - case Basic_u64: - case Basic_i128: - case Basic_u128: - - case Basic_i16le: - case Basic_u16le: - case Basic_i32le: - case Basic_u32le: - case Basic_i64le: - case Basic_u64le: - case Basic_i128le: - case Basic_u128le: - case Basic_i16be: - case Basic_u16be: - case Basic_i32be: - case Basic_u32be: - case Basic_i64be: - case Basic_u64be: - case Basic_i128be: - case Basic_u128be: - - case Basic_int: - case Basic_uint: - case Basic_uintptr: { - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_integer_ptr); - - lbValue is_signed = lb_const_bool(m, t_bool, (t->Basic.flags & BasicFlag_Unsigned) == 0); - // NOTE(bill): This is matches the runtime layout - u8 endianness_value = 0; - if (t->Basic.flags & BasicFlag_EndianLittle) { - endianness_value = 1; - } else if (t->Basic.flags & BasicFlag_EndianBig) { - endianness_value = 2; - } - lbValue endianness = lb_const_int(m, t_u8, endianness_value); - - LLVMValueRef vals[2] = { - is_signed.value, - endianness.value, - }; - - lbValue res = {}; - res.type = type_deref(tag.type); - res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals)); - lb_emit_store(p, tag, res); - break; - } - - case Basic_rune: - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_rune_ptr); - break; - - case Basic_f16: - case Basic_f32: - case Basic_f64: - case Basic_f16le: - case Basic_f32le: - case Basic_f64le: - case Basic_f16be: - case Basic_f32be: - case Basic_f64be: - { - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_float_ptr); - - // NOTE(bill): This is matches the runtime layout - u8 endianness_value = 0; - if (t->Basic.flags & BasicFlag_EndianLittle) { - endianness_value = 1; - } else if (t->Basic.flags & BasicFlag_EndianBig) { - endianness_value = 2; - } - lbValue endianness = lb_const_int(m, t_u8, endianness_value); - - LLVMValueRef vals[1] = { - endianness.value, - }; - - lbValue res = {}; - res.type = type_deref(tag.type); - res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals)); - lb_emit_store(p, tag, res); - } - break; - - case Basic_complex32: - case Basic_complex64: - case Basic_complex128: - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_complex_ptr); - break; - - case Basic_quaternion64: - case Basic_quaternion128: - case Basic_quaternion256: - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_quaternion_ptr); - break; - - case Basic_rawptr: - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_pointer_ptr); - break; - - case Basic_string: - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_string_ptr); - break; - - case Basic_cstring: - { - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_string_ptr); - LLVMValueRef vals[1] = { - lb_const_bool(m, t_bool, true).value, - }; - - lbValue res = {}; - res.type = type_deref(tag.type); - res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals)); - lb_emit_store(p, tag, res); - } - break; - - case Basic_any: - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_any_ptr); - break; - - case Basic_typeid: - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_typeid_ptr); - break; - } - break; - - case Type_Pointer: { - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_pointer_ptr); - lbValue gep = lb_type_info(m, t->Pointer.elem); - - LLVMValueRef vals[1] = { - gep.value, - }; - - lbValue res = {}; - res.type = type_deref(tag.type); - res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals)); - lb_emit_store(p, tag, res); - break; - } - case Type_MultiPointer: { - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_multi_pointer_ptr); - lbValue gep = lb_type_info(m, t->MultiPointer.elem); - - LLVMValueRef vals[1] = { - gep.value, - }; - - lbValue res = {}; - res.type = type_deref(tag.type); - res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals)); - lb_emit_store(p, tag, res); - break; - } - case Type_SoaPointer: { - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_soa_pointer_ptr); - lbValue gep = lb_type_info(m, t->SoaPointer.elem); - - LLVMValueRef vals[1] = { - gep.value, - }; - - lbValue res = {}; - res.type = type_deref(tag.type); - res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals)); - lb_emit_store(p, tag, res); - break; - } - case Type_Array: { - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_array_ptr); - i64 ez = type_size_of(t->Array.elem); - - LLVMValueRef vals[3] = { - lb_type_info(m, t->Array.elem).value, - lb_const_int(m, t_int, ez).value, - lb_const_int(m, t_int, t->Array.count).value, - }; - - lbValue res = {}; - res.type = type_deref(tag.type); - res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals)); - lb_emit_store(p, tag, res); - break; - } - case Type_EnumeratedArray: { - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_enumerated_array_ptr); - - LLVMValueRef vals[7] = { - lb_type_info(m, t->EnumeratedArray.elem).value, - lb_type_info(m, t->EnumeratedArray.index).value, - lb_const_int(m, t_int, type_size_of(t->EnumeratedArray.elem)).value, - lb_const_int(m, t_int, t->EnumeratedArray.count).value, - - // Unions - LLVMConstNull(lb_type(m, t_type_info_enum_value)), - LLVMConstNull(lb_type(m, t_type_info_enum_value)), - - lb_const_bool(m, t_bool, t->EnumeratedArray.is_sparse).value, - }; - - lbValue res = {}; - res.type = type_deref(tag.type); - res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals)); - lb_emit_store(p, tag, res); - - // NOTE(bill): Union assignment - lbValue min_value = lb_emit_struct_ep(p, tag, 4); - lbValue max_value = lb_emit_struct_ep(p, tag, 5); - - lbValue min_v = lb_const_value(m, t_i64, *t->EnumeratedArray.min_value); - lbValue max_v = lb_const_value(m, t_i64, *t->EnumeratedArray.max_value); - - lb_emit_store(p, min_value, min_v); - lb_emit_store(p, max_value, max_v); - break; - } - case Type_DynamicArray: { - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_dynamic_array_ptr); - - LLVMValueRef vals[2] = { - lb_type_info(m, t->DynamicArray.elem).value, - lb_const_int(m, t_int, type_size_of(t->DynamicArray.elem)).value, - }; - - lbValue res = {}; - res.type = type_deref(tag.type); - res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals)); - lb_emit_store(p, tag, res); - break; - } - case Type_Slice: { - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_slice_ptr); - - LLVMValueRef vals[2] = { - lb_type_info(m, t->Slice.elem).value, - lb_const_int(m, t_int, type_size_of(t->Slice.elem)).value, - }; - - lbValue res = {}; - res.type = type_deref(tag.type); - res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals)); - lb_emit_store(p, tag, res); - break; - } - case Type_Proc: { - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_procedure_ptr); - - LLVMValueRef params = LLVMConstNull(lb_type(m, t_type_info_ptr)); - LLVMValueRef results = LLVMConstNull(lb_type(m, t_type_info_ptr)); - if (t->Proc.params != nullptr) { - params = lb_type_info(m, t->Proc.params).value; - } - if (t->Proc.results != nullptr) { - results = lb_type_info(m, t->Proc.results).value; - } - - LLVMValueRef vals[4] = { - params, - results, - lb_const_bool(m, t_bool, t->Proc.variadic).value, - lb_const_int(m, t_u8, t->Proc.calling_convention).value, - }; - - lbValue res = {}; - res.type = type_deref(tag.type); - res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals)); - lb_emit_store(p, tag, res); - break; - } - case Type_Tuple: { - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_parameters_ptr); - - lbValue memory_types = lb_type_info_member_types_offset(m, t->Tuple.variables.count); - lbValue memory_names = lb_type_info_member_names_offset(m, t->Tuple.variables.count); - - - for_array(i, t->Tuple.variables) { - // NOTE(bill): offset is not used for tuples - Entity *f = t->Tuple.variables[i]; - - lbValue index = lb_const_int(m, t_int, i); - lbValue type_info = lb_emit_ptr_offset(p, memory_types, index); - - // TODO(bill): Make this constant if possible, 'lb_const_store' does not work - lb_emit_store(p, type_info, lb_type_info(m, f->type)); - if (f->token.string.len > 0) { - lbValue name = lb_emit_ptr_offset(p, memory_names, index); - lb_emit_store(p, name, lb_const_string(m, f->token.string)); - } - } - - lbValue count = lb_const_int(m, t_int, t->Tuple.variables.count); - - LLVMValueRef types_slice = llvm_const_slice(m, memory_types, count); - LLVMValueRef names_slice = llvm_const_slice(m, memory_names, count); - - LLVMValueRef vals[2] = { - types_slice, - names_slice, - }; - - lbValue res = {}; - res.type = type_deref(tag.type); - res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals)); - lb_emit_store(p, tag, res); - - break; - } - - case Type_Enum: - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_enum_ptr); - - { - GB_ASSERT(t->Enum.base_type != nullptr); - // GB_ASSERT_MSG(type_size_of(t_type_info_enum_value) == 16, "%lld == 16", cast(long long)type_size_of(t_type_info_enum_value)); - - - LLVMValueRef vals[3] = {}; - vals[0] = lb_type_info(m, t->Enum.base_type).value; - if (t->Enum.fields.count > 0) { - auto fields = t->Enum.fields; - lbValue name_array = lb_generate_global_array(m, t_string, fields.count, - str_lit("$enum_names"), cast(i64)entry_index); - lbValue value_array = lb_generate_global_array(m, t_type_info_enum_value, fields.count, - str_lit("$enum_values"), cast(i64)entry_index); - - - LLVMValueRef *name_values = gb_alloc_array(temporary_allocator(), LLVMValueRef, fields.count); - LLVMValueRef *value_values = gb_alloc_array(temporary_allocator(), LLVMValueRef, fields.count); - - GB_ASSERT(is_type_integer(t->Enum.base_type)); - - for_array(i, fields) { - name_values[i] = lb_const_string(m, fields[i]->token.string).value; - value_values[i] = lb_const_value(m, t_i64, fields[i]->Constant.value).value; - } - - LLVMValueRef name_init = llvm_const_array(lb_type(m, t_string), name_values, cast(unsigned)fields.count); - LLVMValueRef value_init = llvm_const_array(lb_type(m, t_type_info_enum_value), value_values, cast(unsigned)fields.count); - LLVMSetInitializer(name_array.value, name_init); - LLVMSetInitializer(value_array.value, value_init); - LLVMSetGlobalConstant(name_array.value, true); - LLVMSetGlobalConstant(value_array.value, true); - - lbValue v_count = lb_const_int(m, t_int, fields.count); - - vals[1] = llvm_const_slice(m, lb_array_elem(p, name_array), v_count); - vals[2] = llvm_const_slice(m, lb_array_elem(p, value_array), v_count); - } else { - vals[1] = LLVMConstNull(lb_type(m, base_type(t_type_info_enum)->Struct.fields[1]->type)); - vals[2] = LLVMConstNull(lb_type(m, base_type(t_type_info_enum)->Struct.fields[2]->type)); - } - - - lbValue res = {}; - res.type = type_deref(tag.type); - res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals)); - lb_emit_store(p, tag, res); - } - break; - - case Type_Union: { - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_union_ptr); - - { - LLVMValueRef vals[7] = {}; - - isize variant_count = gb_max(0, t->Union.variants.count); - lbValue memory_types = lb_type_info_member_types_offset(m, variant_count); - - // NOTE(bill): Zeroth is nil so ignore it - for (isize variant_index = 0; variant_index < variant_count; variant_index++) { - Type *vt = t->Union.variants[variant_index]; - lbValue tip = lb_type_info(m, vt); - - lbValue index = lb_const_int(m, t_int, variant_index); - lbValue type_info = lb_emit_ptr_offset(p, memory_types, index); - lb_emit_store(p, type_info, lb_type_info(m, vt)); - } - - lbValue count = lb_const_int(m, t_int, variant_count); - vals[0] = llvm_const_slice(m, memory_types, count); - - i64 tag_size = union_tag_size(t); - if (tag_size > 0) { - i64 tag_offset = align_formula(t->Union.variant_block_size, tag_size); - vals[1] = lb_const_int(m, t_uintptr, tag_offset).value; - vals[2] = lb_type_info(m, union_tag_type(t)).value; - } else { - vals[1] = lb_const_int(m, t_uintptr, 0).value; - vals[2] = LLVMConstNull(lb_type(m, t_type_info_ptr)); - } - - if (is_type_comparable(t) && !is_type_simple_compare(t)) { - vals[3] = lb_equal_proc_for_type(m, t).value; - } - - vals[4] = lb_const_bool(m, t_bool, t->Union.custom_align != 0).value; - vals[5] = lb_const_bool(m, t_bool, t->Union.kind == UnionType_no_nil).value; - vals[6] = lb_const_bool(m, t_bool, t->Union.kind == UnionType_shared_nil).value; - - for (isize i = 0; i < gb_count_of(vals); i++) { - if (vals[i] == nullptr) { - vals[i] = LLVMConstNull(lb_type(m, get_struct_field_type(tag.type, i))); - } - } - - lbValue res = {}; - res.type = type_deref(tag.type); - res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals)); - lb_emit_store(p, tag, res); - } - - break; - } - - case Type_Struct: { - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_struct_ptr); - - LLVMValueRef vals[13] = {}; - - - { - 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; - if (is_type_comparable(t) && !is_type_simple_compare(t)) { - vals[9] = lb_equal_proc_for_type(m, t).value; - } - - - if (t->Struct.soa_kind != StructSoa_None) { - lbValue kind = lb_emit_struct_ep(p, tag, 10); - Type *kind_type = type_deref(kind.type); - - lbValue soa_kind = lb_const_value(m, kind_type, exact_value_i64(t->Struct.soa_kind)); - lbValue soa_type = lb_type_info(m, t->Struct.soa_elem); - lbValue soa_len = lb_const_int(m, t_int, t->Struct.soa_count); - - vals[10] = soa_kind.value; - vals[11] = soa_type.value; - vals[12] = soa_len.value; - } - } - - isize count = t->Struct.fields.count; - if (count > 0) { - lbValue memory_types = lb_type_info_member_types_offset (m, count); - lbValue memory_names = lb_type_info_member_names_offset (m, count); - lbValue memory_offsets = lb_type_info_member_offsets_offset(m, count); - lbValue memory_usings = lb_type_info_member_usings_offset (m, count); - lbValue memory_tags = lb_type_info_member_tags_offset (m, count); - - type_set_offsets(t); // NOTE(bill): Just incase the offsets have not been set yet - for (isize source_index = 0; source_index < count; source_index++) { - Entity *f = t->Struct.fields[source_index]; - lbValue tip = lb_type_info(m, f->type); - i64 foffset = 0; - if (!t->Struct.is_raw_union) { - GB_ASSERT(t->Struct.offsets != nullptr); - GB_ASSERT(0 <= f->Variable.field_index && f->Variable.field_index < count); - foffset = t->Struct.offsets[source_index]; - } - GB_ASSERT(f->kind == Entity_Variable && f->flags & EntityFlag_Field); - - lbValue index = lb_const_int(m, t_int, source_index); - lbValue type_info = lb_emit_ptr_offset(p, memory_types, index); - lbValue offset = lb_emit_ptr_offset(p, memory_offsets, index); - lbValue is_using = lb_emit_ptr_offset(p, memory_usings, index); - - lb_emit_store(p, type_info, lb_type_info(m, f->type)); - if (f->token.string.len > 0) { - lbValue name = lb_emit_ptr_offset(p, memory_names, index); - lb_emit_store(p, name, lb_const_string(m, f->token.string)); - } - lb_emit_store(p, offset, lb_const_int(m, t_uintptr, foffset)); - lb_emit_store(p, is_using, lb_const_bool(m, t_bool, (f->flags&EntityFlag_Using) != 0)); - - if (t->Struct.tags != nullptr) { - String tag_string = t->Struct.tags[source_index]; - if (tag_string.len > 0) { - lbValue tag_ptr = lb_emit_ptr_offset(p, memory_tags, index); - lb_emit_store(p, tag_ptr, lb_const_string(m, tag_string)); - } - } - - } - - 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); - } - for (isize i = 0; i < gb_count_of(vals); i++) { - if (vals[i] == nullptr) { - vals[i] = LLVMConstNull(lb_type(m, get_struct_field_type(tag.type, i))); - } - } - - lbValue res = {}; - res.type = type_deref(tag.type); - res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals)); - lb_emit_store(p, tag, res); - - break; - } - - case Type_Map: { - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_map_ptr); - init_map_internal_types(t); - - LLVMValueRef vals[3] = { - lb_type_info(m, t->Map.key).value, - lb_type_info(m, t->Map.value).value, - lb_gen_map_info_ptr(p->module, t).value - }; - - lbValue res = {}; - res.type = type_deref(tag.type); - res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals)); - lb_emit_store(p, tag, res); - break; - } - - case Type_BitSet: - { - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_bit_set_ptr); - - GB_ASSERT(is_type_typed(t->BitSet.elem)); - - - LLVMValueRef vals[4] = { - lb_type_info(m, t->BitSet.elem).value, - LLVMConstNull(lb_type(m, t_type_info_ptr)), - lb_const_int(m, t_i64, t->BitSet.lower).value, - lb_const_int(m, t_i64, t->BitSet.upper).value, - }; - if (t->BitSet.underlying != nullptr) { - vals[1] =lb_type_info(m, t->BitSet.underlying).value; - } - - lbValue res = {}; - res.type = type_deref(tag.type); - res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals)); - lb_emit_store(p, tag, res); - } - break; - - case Type_SimdVector: - { - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_simd_vector_ptr); - - LLVMValueRef vals[3] = {}; - - vals[0] = lb_type_info(m, t->SimdVector.elem).value; - vals[1] = lb_const_int(m, t_int, type_size_of(t->SimdVector.elem)).value; - vals[2] = lb_const_int(m, t_int, t->SimdVector.count).value; - - lbValue res = {}; - res.type = type_deref(tag.type); - res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals)); - lb_emit_store(p, tag, res); - } - break; - - case Type_RelativePointer: - { - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_relative_pointer_ptr); - LLVMValueRef vals[2] = { - lb_type_info(m, t->RelativePointer.pointer_type).value, - lb_type_info(m, t->RelativePointer.base_integer).value, - }; - - lbValue res = {}; - res.type = type_deref(tag.type); - res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals)); - lb_emit_store(p, tag, res); - } - break; - - case Type_RelativeMultiPointer: - { - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_relative_multi_pointer_ptr); - LLVMValueRef vals[2] = { - lb_type_info(m, t->RelativeMultiPointer.pointer_type).value, - lb_type_info(m, t->RelativeMultiPointer.base_integer).value, - }; - - lbValue res = {}; - res.type = type_deref(tag.type); - res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals)); - lb_emit_store(p, tag, res); - } - break; - - case Type_Matrix: - { - tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_matrix_ptr); - i64 ez = type_size_of(t->Matrix.elem); - - LLVMValueRef vals[5] = { - lb_type_info(m, t->Matrix.elem).value, - lb_const_int(m, t_int, ez).value, - lb_const_int(m, t_int, matrix_type_stride_in_elems(t)).value, - lb_const_int(m, t_int, t->Matrix.row_count).value, - lb_const_int(m, t_int, t->Matrix.column_count).value, - }; - - lbValue res = {}; - res.type = type_deref(tag.type); - res.value = llvm_const_named_struct(m, res.type, vals, gb_count_of(vals)); - lb_emit_store(p, tag, res); - } - break; - } - - - if (tag.value != nullptr) { - Type *tag_type = type_deref(tag.type); - GB_ASSERT(is_type_named(tag_type)); - // lb_emit_store_union_variant(p, variant_ptr, lb_emit_load(p, tag), tag_type); - lb_emit_store_union_variant_tag(p, variant_ptr, tag_type); - } else { - if (t != t_llvm_bool) { - GB_PANIC("Unhandled Type_Info variant: %s", type_to_string(t)); - } - } - } - - for_array(i, entries_handled) { - if (!entries_handled[i]) { - GB_PANIC("UNHANDLED ENTRY %td (%td)", i, entries_handled.count); - } - } + // force it to be constant + LLVMSetGlobalConstant(global_type_table.value, true); } diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index d8dbfd736..7b7c9d6e9 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -57,6 +57,33 @@ 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); +} + gb_internal LLVMValueRef lb_mem_zero_ptr_internal(lbProcedure *p, LLVMValueRef ptr, LLVMValueRef len, unsigned alignment, bool is_volatile) { bool is_inlinable = false; @@ -79,27 +106,13 @@ gb_internal LLVMValueRef lb_mem_zero_ptr_internal(lbProcedure *p, LLVMValueRef p lb_type(p->module, t_rawptr), lb_type(p->module, t_int) }; - if (true || is_inlinable) { + LLVMValueRef args[4] = {}; + args[0] = LLVMBuildPointerCast(p->builder, ptr, types[0], ""); + args[1] = LLVMConstInt(LLVMInt8TypeInContext(p->module->ctx), 0, false); + args[2] = LLVMBuildIntCast2(p->builder, len, types[1], /*signed*/false, ""); + args[3] = LLVMConstInt(LLVMInt1TypeInContext(p->module->ctx), is_volatile, false); - LLVMValueRef args[4] = {}; - args[0] = LLVMBuildPointerCast(p->builder, ptr, types[0], ""); - args[1] = LLVMConstInt(LLVMInt8TypeInContext(p->module->ctx), 0, false); - args[2] = LLVMBuildIntCast2(p->builder, len, types[1], /*signed*/false, ""); - args[3] = LLVMConstInt(LLVMInt1TypeInContext(p->module->ctx), is_volatile, false); - - return lb_call_intrinsic(p, name, args, gb_count_of(args), types, gb_count_of(types)); - } else { - lbValue pr = lb_lookup_runtime_procedure(p->module, str_lit("memset")); - - LLVMValueRef args[3] = {}; - args[0] = LLVMBuildPointerCast(p->builder, ptr, types[0], ""); - args[1] = LLVMConstInt(LLVMInt32TypeInContext(p->module->ctx), 0, false); - args[2] = LLVMBuildIntCast2(p->builder, len, types[1], /*signed*/false, ""); - - // We always get the function pointer type rather than the function and there is apparently no way around that? - LLVMTypeRef type = lb_type_internal_for_procedures_raw(p->module, pr.type); - return LLVMBuildCall2(p->builder, type, pr.value, args, gb_count_of(args), ""); - } + return lb_call_intrinsic(p, name, args, gb_count_of(args), types, gb_count_of(types)); } @@ -107,13 +120,18 @@ 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: - { + if (is_type_tuple(type)) { + // NOTE(bill): even though this should be safe, to keep ASAN happy, do not zero the implicit padding at the end + GB_ASSERT(type->kind == Type_Tuple); + i64 n = type->Tuple.variables.count-1; + i64 end_offset = type->Tuple.offsets[n] + type_size_of(type->Tuple.variables[n]->type); + lb_mem_zero_ptr_internal(p, ptr, lb_const_int(p->module, t_int, end_offset).value, alignment, false); + } else { // 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); } break; @@ -134,11 +152,33 @@ gb_internal lbValue lb_emit_select(lbProcedure *p, lbValue cond, lbValue x, lbVa gb_internal lbValue lb_emit_min(lbProcedure *p, Type *t, lbValue x, lbValue y) { x = lb_emit_conv(p, x, t); y = lb_emit_conv(p, y, t); + bool use_llvm_intrinsic = !is_arch_wasm() && (is_type_float(t) || (is_type_simd_vector(t) && is_type_float(base_array_type(t)))); + if (use_llvm_intrinsic) { + LLVMValueRef args[2] = {x.value, y.value}; + LLVMTypeRef types[1] = {lb_type(p->module, t)}; + + // NOTE(bill): f either operand is a NaN, returns NaN. Otherwise returns the lesser of the two arguments. + // -0.0 is considered to be less than +0.0 for this intrinsic. + // These semantics are specified by IEEE 754-2008. + LLVMValueRef v = lb_call_intrinsic(p, "llvm.minnum", args, gb_count_of(args), types, gb_count_of(types)); + return {v, t}; + } return lb_emit_select(p, lb_emit_comp(p, Token_Lt, x, y), x, y); } gb_internal lbValue lb_emit_max(lbProcedure *p, Type *t, lbValue x, lbValue y) { x = lb_emit_conv(p, x, t); y = lb_emit_conv(p, y, t); + bool use_llvm_intrinsic = !is_arch_wasm() && (is_type_float(t) || (is_type_simd_vector(t) && is_type_float(base_array_type(t)))); + if (use_llvm_intrinsic) { + LLVMValueRef args[2] = {x.value, y.value}; + LLVMTypeRef types[1] = {lb_type(p->module, t)}; + + // NOTE(bill): If either operand is a NaN, returns NaN. Otherwise returns the greater of the two arguments. + // -0.0 is considered to be less than +0.0 for this intrinsic. + // These semantics are specified by IEEE 754-2008. + LLVMValueRef v = lb_call_intrinsic(p, "llvm.maxnum", args, gb_count_of(args), types, gb_count_of(types)); + return {v, t}; + } return lb_emit_select(p, lb_emit_comp(p, Token_Gt, x, y), x, y); } @@ -231,13 +271,13 @@ gb_internal lbValue lb_emit_transmute(lbProcedure *p, lbValue value, Type *t) { if (is_type_simd_vector(src) && is_type_simd_vector(dst)) { res.value = LLVMBuildBitCast(p->builder, value.value, lb_type(p->module, t), ""); return res; - } else if (is_type_array_like(src) && is_type_simd_vector(dst)) { + } else if (is_type_array_like(src) && (is_type_simd_vector(dst) || is_type_integer_128bit(dst))) { unsigned align = cast(unsigned)gb_max(type_align_of(src), type_align_of(dst)); lbValue ptr = lb_address_from_load_or_generate_local(p, value); if (lb_try_update_alignment(ptr, align)) { LLVMTypeRef result_type = lb_type(p->module, t); res.value = LLVMBuildPointerCast(p->builder, ptr.value, LLVMPointerType(result_type, 0), ""); - res.value = LLVMBuildLoad2(p->builder, result_type, res.value, ""); + res.value = OdinLLVMBuildLoad(p, result_type, res.value); return res; } lbAddr addr = lb_add_local_generated(p, t, false); @@ -296,6 +336,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); } @@ -435,8 +476,8 @@ gb_internal lbValue lb_emit_or_else(lbProcedure *p, Ast *arg, Ast *else_expr, Ty } } -gb_internal void lb_build_return_stmt(lbProcedure *p, Slice const &return_results); -gb_internal void lb_build_return_stmt_internal(lbProcedure *p, lbValue res); +gb_internal void lb_build_return_stmt(lbProcedure *p, Slice const &return_results, TokenPos pos); +gb_internal void lb_build_return_stmt_internal(lbProcedure *p, lbValue res, TokenPos pos); gb_internal lbValue lb_emit_or_return(lbProcedure *p, Ast *arg, TypeAndValue const &tv) { lbValue lhs = {}; @@ -465,10 +506,10 @@ gb_internal lbValue lb_emit_or_return(lbProcedure *p, Ast *arg, TypeAndValue con lbValue found = map_must_get(&p->module->values, end_entity); lb_emit_store(p, found, rhs); - lb_build_return_stmt(p, {}); + lb_build_return_stmt(p, {}, ast_token(arg).pos); } else { GB_ASSERT(tuple->variables.count == 1); - lb_build_return_stmt_internal(p, rhs); + lb_build_return_stmt_internal(p, rhs, ast_token(arg).pos); } } lb_start_block(p, continue_block); @@ -716,30 +757,32 @@ gb_internal lbValue lb_emit_union_cast(lbProcedure *p, lbValue value, Type *type lb_start_block(p, end_block); if (!is_tuple) { - GB_ASSERT((p->state_flags & StateFlag_no_type_assert) == 0); - // NOTE(bill): Panic on invalid conversion - Type *dst_type = tuple->Tuple.variables[0]->type; + if (!build_context.no_type_assert) { + GB_ASSERT((p->state_flags & StateFlag_no_type_assert) == 0); + // NOTE(bill): Panic on invalid conversion + Type *dst_type = tuple->Tuple.variables[0]->type; - isize arg_count = 7; - if (build_context.no_rtti) { - arg_count = 4; + isize arg_count = 7; + if (build_context.no_rtti) { + arg_count = 4; + } + + lbValue ok = lb_emit_load(p, lb_emit_struct_ep(p, v.addr, 1)); + auto args = array_make(permanent_allocator(), arg_count); + args[0] = ok; + + args[1] = lb_const_string(m, get_file_path_string(pos.file_id)); + args[2] = lb_const_int(m, t_i32, pos.line); + args[3] = lb_const_int(m, t_i32, pos.column); + + if (!build_context.no_rtti) { + args[4] = lb_typeid(m, src_type); + args[5] = lb_typeid(m, dst_type); + args[6] = lb_emit_conv(p, value_, t_rawptr); + } + lb_emit_runtime_call(p, "type_assertion_check2", args); } - lbValue ok = lb_emit_load(p, lb_emit_struct_ep(p, v.addr, 1)); - auto args = array_make(permanent_allocator(), arg_count); - args[0] = ok; - - args[1] = lb_const_string(m, get_file_path_string(pos.file_id)); - args[2] = lb_const_int(m, t_i32, pos.line); - args[3] = lb_const_int(m, t_i32, pos.column); - - if (!build_context.no_rtti) { - args[4] = lb_typeid(m, src_type); - args[5] = lb_typeid(m, dst_type); - args[6] = lb_emit_conv(p, value_, t_rawptr); - } - lb_emit_runtime_call(p, "type_assertion_check2", args); - return lb_emit_load(p, lb_emit_struct_ep(p, v.addr, 0)); } return lb_addr_load(p, v); @@ -794,25 +837,27 @@ gb_internal lbAddr lb_emit_any_cast_addr(lbProcedure *p, lbValue value, Type *ty if (!is_tuple) { // NOTE(bill): Panic on invalid conversion - lbValue ok = lb_emit_load(p, lb_emit_struct_ep(p, v.addr, 1)); + if (!build_context.no_type_assert) { + lbValue ok = lb_emit_load(p, lb_emit_struct_ep(p, v.addr, 1)); - isize arg_count = 7; - if (build_context.no_rtti) { - arg_count = 4; + isize arg_count = 7; + if (build_context.no_rtti) { + arg_count = 4; + } + auto args = array_make(permanent_allocator(), arg_count); + args[0] = ok; + + args[1] = lb_const_string(m, get_file_path_string(pos.file_id)); + args[2] = lb_const_int(m, t_i32, pos.line); + args[3] = lb_const_int(m, t_i32, pos.column); + + if (!build_context.no_rtti) { + args[4] = any_typeid; + args[5] = dst_typeid; + args[6] = lb_emit_struct_ev(p, value, 0); + } + lb_emit_runtime_call(p, "type_assertion_check2", args); } - auto args = array_make(permanent_allocator(), arg_count); - args[0] = ok; - - args[1] = lb_const_string(m, get_file_path_string(pos.file_id)); - args[2] = lb_const_int(m, t_i32, pos.line); - args[3] = lb_const_int(m, t_i32, pos.column); - - if (!build_context.no_rtti) { - args[4] = any_typeid; - args[5] = dst_typeid; - args[6] = lb_emit_struct_ev(p, value, 0); - } - lb_emit_runtime_call(p, "type_assertion_check2", args); return lb_addr(lb_emit_struct_ep(p, v.addr, 0)); } @@ -966,6 +1011,21 @@ gb_internal i32 lb_convert_struct_index(lbModule *m, Type *t, i32 index) { } gb_internal LLVMTypeRef lb_type_padding_filler(lbModule *m, i64 padding, i64 padding_align) { + MUTEX_GUARD(&m->pad_types_mutex); + if (padding % padding_align == 0) { + for (auto pd : m->pad_types) { + if (pd.padding == padding && pd.padding_align == padding_align) { + return pd.type; + } + } + } else { + for (auto pd : m->pad_types) { + if (pd.padding == padding && pd.padding_align == 1) { + return pd.type; + } + } + } + // NOTE(bill): limit to `[N x u64]` to prevent ABI issues padding_align = gb_clamp(padding_align, 1, 8); if (padding % padding_align == 0) { @@ -979,13 +1039,19 @@ gb_internal LLVMTypeRef lb_type_padding_filler(lbModule *m, i64 padding, i64 pad } GB_ASSERT_MSG(elem != nullptr, "Invalid lb_type_padding_filler padding and padding_align: %lld", padding_align); + + LLVMTypeRef type = nullptr; if (len != 1) { - return llvm_array_type(elem, len); + type = llvm_array_type(elem, len); } else { - return elem; + type = elem; } + array_add(&m->pad_types, lbPadType{padding, padding_align, type}); + return type; } else { - return llvm_array_type(lb_type(m, t_u8), padding); + LLVMTypeRef type = llvm_array_type(lb_type(m, t_u8), padding); + array_add(&m->pad_types, lbPadType{padding, 1, type}); + return type; } } @@ -1065,10 +1131,6 @@ gb_internal lbValue lb_emit_struct_ep(lbProcedure *p, lbValue s, i32 index) { Type *t = base_type(type_deref(s.type)); Type *result_type = nullptr; - if (is_type_relative_pointer(t)) { - s = lb_addr_get_ptr(p, lb_addr(s)); - } - if (is_type_struct(t)) { result_type = get_struct_field_type(t, index); } else if (is_type_union(t)) { @@ -1113,7 +1175,7 @@ gb_internal lbValue lb_emit_struct_ep(lbProcedure *p, lbValue s, i32 index) { case 3: result_type = t_allocator; break; } } else if (is_type_map(t)) { - init_map_internal_types(t); + init_map_internal_debug_types(t); Type *itp = alloc_type_pointer(t_raw_map); s = lb_emit_transmute(p, s, itp); @@ -1135,7 +1197,28 @@ 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) { + if (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); + } + u64 align_max = bt->Struct.custom_max_field_align; + u64 align_min = bt->Struct.custom_min_field_align; + GB_ASSERT(align_min == 0 || align_max == 0 || align_min <= align_max); + if (align_max) { + lb_set_metadata_custom_u64(p->module, gep.value, ODIN_METADATA_MAX_ALIGN, align_max); + GB_ASSERT(lb_get_metadata_custom_u64(p->module, gep.value, ODIN_METADATA_MAX_ALIGN) == align_max); + } + if (align_min) { + lb_set_metadata_custom_u64(p->module, gep.value, ODIN_METADATA_MIN_ALIGN, align_min); + GB_ASSERT(lb_get_metadata_custom_u64(p->module, gep.value, ODIN_METADATA_MIN_ALIGN) == align_min); + } + } + + return gep; } gb_internal lbValue lb_emit_tuple_ev(lbProcedure *p, lbValue value, i32 index) { @@ -1252,7 +1335,7 @@ gb_internal lbValue lb_emit_struct_ev(lbProcedure *p, lbValue s, i32 index) { case Type_Map: { - init_map_internal_types(t); + init_map_internal_debug_types(t); switch (index) { case 0: result_type = get_struct_field_type(t_raw_map, 0); break; case 1: result_type = get_struct_field_type(t_raw_map, 1); break; @@ -1320,6 +1403,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)) { @@ -1342,7 +1427,7 @@ gb_internal lbValue lb_emit_deep_field_gep(lbProcedure *p, lbValue e, Selection if (index == 0) { type = t_rawptr; } else if (index == 1) { - type = t_type_info_ptr; + type = t_typeid; } e = lb_emit_struct_ep(p, e, index); break; @@ -1364,8 +1449,6 @@ gb_internal lbValue lb_emit_deep_field_gep(lbProcedure *p, lbValue e, Selection e = lb_emit_array_epi(p, e, index); } else if (type->kind == Type_Map) { e = lb_emit_struct_ep(p, e, index); - } else if (type->kind == Type_RelativePointer) { - e = lb_emit_struct_ep(p, e, index); } else { GB_PANIC("un-gep-able type %s", type_to_string(type)); } @@ -1452,14 +1535,16 @@ gb_internal lbValue lb_emit_matrix_epi(lbProcedure *p, lbValue s, isize row, isi Type *t = s.type; GB_ASSERT(is_type_pointer(t)); Type *mt = base_type(type_deref(t)); - if (column == 0) { - GB_ASSERT_MSG(is_type_matrix(mt) || is_type_array_like(mt), "%s", type_to_string(mt)); - return lb_emit_epi(p, s, row); - } else if (row == 0 && is_type_array_like(mt)) { - return lb_emit_epi(p, s, column); + + if (!mt->Matrix.is_row_major) { + if (column == 0) { + GB_ASSERT_MSG(is_type_matrix(mt) || is_type_array_like(mt), "%s", type_to_string(mt)); + return lb_emit_epi(p, s, row); + } else if (row == 0 && is_type_array_like(mt)) { + return lb_emit_epi(p, s, column); + } } - GB_ASSERT_MSG(is_type_matrix(mt), "%s", type_to_string(mt)); isize offset = matrix_indices_to_offset(mt, row, column); @@ -1479,7 +1564,13 @@ gb_internal lbValue lb_emit_matrix_ep(lbProcedure *p, lbValue s, lbValue row, lb row = lb_emit_conv(p, row, t_int); column = lb_emit_conv(p, column, t_int); - LLVMValueRef index = LLVMBuildAdd(p->builder, row.value, LLVMBuildMul(p->builder, column.value, stride_elems, ""), ""); + LLVMValueRef index = nullptr; + + if (mt->Matrix.is_row_major) { + index = LLVMBuildAdd(p->builder, column.value, LLVMBuildMul(p->builder, row.value, stride_elems, ""), ""); + } else { + index = LLVMBuildAdd(p->builder, row.value, LLVMBuildMul(p->builder, column.value, stride_elems, ""), ""); + } LLVMValueRef indices[2] = { LLVMConstInt(lb_type(p->module, t_int), 0, false), @@ -1507,19 +1598,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); } @@ -1687,7 +1785,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; @@ -1955,7 +2054,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; } @@ -1966,7 +2065,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); } @@ -2162,8 +2265,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 79c2b3561..41c7170f6 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 > 19 + #error LLVM Version 11..=14 or 17..=19 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(); @@ -198,20 +270,26 @@ gb_internal void print_usage_line(i32 indent, char const *fmt, ...) { gb_printf("\n"); } -gb_internal void usage(String argv0) { - print_usage_line(0, "%.*s is a tool for managing Odin source code", LIT(argv0)); +gb_internal void usage(String argv0, String argv1 = {}) { + if (argv1 == "run.") { + print_usage_line(0, "Did you mean 'odin run .'?"); + } else if (argv1 == "build.") { + print_usage_line(0, "Did you mean 'odin build .'?"); + } + print_usage_line(0, "%.*s is a tool for managing Odin source code.", LIT(argv0)); print_usage_line(0, "Usage:"); print_usage_line(1, "%.*s command [arguments]", LIT(argv0)); print_usage_line(0, "Commands:"); - print_usage_line(1, "build compile directory of .odin files, as an executable."); - print_usage_line(1, " one must contain the program's entry point, all must be in the same package."); - print_usage_line(1, "run same as 'build', but also then runs the newly compiled executable."); - print_usage_line(1, "check parse, and type check a directory of .odin files"); - print_usage_line(1, "strip-semicolon parse, type check, and remove unneeded semicolons from the entire program"); - print_usage_line(1, "test build and runs procedures with the attribute @(test) in the initial package"); - print_usage_line(1, "doc generate documentation on a directory of .odin files"); - print_usage_line(1, "version print version"); - print_usage_line(1, "report print information useful to reporting a bug"); + print_usage_line(1, "build Compiles directory of .odin files, as an executable."); + print_usage_line(1, " One must contain the program's entry point, all must be in the same package."); + print_usage_line(1, "run Same as 'build', but also then runs the newly compiled executable."); + print_usage_line(1, "check Parses, and type checks a directory of .odin files."); + print_usage_line(1, "strip-semicolon Parses, type checks, and removes unneeded semicolons from the entire program."); + print_usage_line(1, "test Builds and runs procedures with the attribute @(test) in the initial package."); + print_usage_line(1, "doc Generates documentation on a directory of .odin files."); + print_usage_line(1, "version Prints version."); + print_usage_line(1, "report Prints information useful to reporting a bug."); + print_usage_line(1, "root Prints the root path where Odin looks for the builtin collections."); print_usage_line(0, ""); print_usage_line(0, "For further details on a command, invoke command help:"); print_usage_line(1, "e.g. `odin build -help` or `odin help build`"); @@ -231,6 +309,8 @@ enum BuildFlagKind { BuildFlag_ShowMoreTimings, BuildFlag_ExportTimings, BuildFlag_ExportTimingsFile, + BuildFlag_ExportDependencies, + BuildFlag_ExportDependenciesFile, BuildFlag_ShowSystemCalls, BuildFlag_ThreadCount, BuildFlag_KeepTempFiles, @@ -242,41 +322,56 @@ enum BuildFlagKind { BuildFlag_Debug, BuildFlag_DisableAssert, BuildFlag_NoBoundsCheck, + BuildFlag_NoTypeAssert, BuildFlag_NoDynamicLiterals, BuildFlag_NoCRT, + BuildFlag_NoRPath, BuildFlag_NoEntryPoint, BuildFlag_UseLLD, + BuildFlag_UseRADLink, + BuildFlag_Linker, BuildFlag_UseSeparateModules, BuildFlag_NoThreadedChecker, BuildFlag_ShowDebugMessages, + BuildFlag_ShowDefineables, + BuildFlag_ExportDefineables, + BuildFlag_Vet, BuildFlag_VetShadowing, BuildFlag_VetUnused, + BuildFlag_VetUnusedImports, + BuildFlag_VetUnusedVariables, + BuildFlag_VetUnusedProcedures, BuildFlag_VetUsingStmt, BuildFlag_VetUsingParam, BuildFlag_VetStyle, BuildFlag_VetSemicolon, + BuildFlag_VetCast, + BuildFlag_VetTabs, + BuildFlag_VetPackages, + 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, BuildFlag_StrictStyle, BuildFlag_ForeignErrorProcedures, BuildFlag_NoRTTI, BuildFlag_DynamicMapCalls, + BuildFlag_ObfuscateSourceCodeLocations, BuildFlag_Compact, BuildFlag_GlobalDefinitions, @@ -290,12 +385,23 @@ enum BuildFlagKind { BuildFlag_WarningsAsErrors, BuildFlag_TerseErrors, BuildFlag_VerboseErrors, + BuildFlag_JsonErrors, BuildFlag_ErrorPosStyle, BuildFlag_MaxErrorCount, + BuildFlag_MinLinkLibs, + + BuildFlag_PrintLinkerFlags, + // internal use only + BuildFlag_InternalFastISel, BuildFlag_InternalIgnoreLazy, BuildFlag_InternalIgnoreLLVMBuild, + BuildFlag_InternalIgnorePanic, + BuildFlag_InternalModulePerFile, + BuildFlag_InternalCached, + BuildFlag_InternalNoInline, + BuildFlag_InternalByValue, BuildFlag_Tilde, @@ -308,7 +414,6 @@ enum BuildFlagKind { BuildFlag_Subsystem, #endif - BuildFlag_COUNT, }; @@ -328,12 +433,12 @@ struct BuildFlag { String name; BuildFlagParamKind param_kind; u32 command_support; - bool allow_mulitple; + bool allow_multiple; }; -gb_internal void add_flag(Array *build_flags, BuildFlagKind kind, String name, BuildFlagParamKind param_kind, u32 command_support, bool allow_mulitple=false) { - BuildFlag flag = {kind, name, param_kind, command_support, allow_mulitple}; +gb_internal void add_flag(Array *build_flags, BuildFlagKind kind, String name, BuildFlagParamKind param_kind, u32 command_support, bool allow_multiple=false) { + BuildFlag flag = {kind, name, param_kind, command_support, allow_multiple}; array_add(build_flags, flag); } @@ -409,12 +514,14 @@ gb_internal bool parse_build_flags(Array args) { auto build_flags = array_make(heap_allocator(), 0, BuildFlag_COUNT); add_flag(&build_flags, BuildFlag_Help, str_lit("help"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_SingleFile, str_lit("file"), BuildFlagParam_None, Command__does_build | Command__does_check); - add_flag(&build_flags, BuildFlag_OutFile, str_lit("out"), BuildFlagParam_String, Command__does_build | Command_test); + add_flag(&build_flags, BuildFlag_OutFile, str_lit("out"), BuildFlagParam_String, Command__does_build | Command_test | Command_doc); add_flag(&build_flags, BuildFlag_OptimizationMode, str_lit("o"), BuildFlagParam_String, Command__does_build); add_flag(&build_flags, BuildFlag_ShowTimings, str_lit("show-timings"), BuildFlagParam_None, Command__does_check); 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); @@ -428,37 +535,51 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_Debug, str_lit("debug"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_DisableAssert, str_lit("disable-assert"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_NoBoundsCheck, str_lit("no-bounds-check"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_NoTypeAssert, str_lit("no-type-assert"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_NoThreadLocal, str_lit("no-thread-local"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_NoDynamicLiterals, str_lit("no-dynamic-literals"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_NoCRT, str_lit("no-crt"), BuildFlagParam_None, Command__does_build); + add_flag(&build_flags, BuildFlag_NoRPath, str_lit("no-rpath"), BuildFlagParam_None, Command__does_build); add_flag(&build_flags, BuildFlag_NoEntryPoint, str_lit("no-entry-point"), BuildFlagParam_None, Command__does_check &~ Command_test); add_flag(&build_flags, BuildFlag_UseLLD, str_lit("lld"), BuildFlagParam_None, Command__does_build); + add_flag(&build_flags, BuildFlag_UseRADLink, str_lit("radlink"), BuildFlagParam_None, Command__does_build); + add_flag(&build_flags, BuildFlag_Linker, str_lit("linker"), BuildFlagParam_String, Command__does_build); add_flag(&build_flags, BuildFlag_UseSeparateModules, str_lit("use-separate-modules"), BuildFlagParam_None, Command__does_build); 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); + add_flag(&build_flags, BuildFlag_VetUnusedProcedures, str_lit("vet-unused-procedures"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_VetUnusedImports, str_lit("vet-unused-imports"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetShadowing, str_lit("vet-shadowing"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_VetUsingStmt, str_lit("vet-using-stmt"), BuildFlagParam_None, Command__does_check); 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_VetPackages, str_lit("vet-packages"), BuildFlagParam_String, 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); add_flag(&build_flags, BuildFlag_StrictStyle, str_lit("strict-style"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_ForeignErrorProcedures, str_lit("foreign-error-procedures"), BuildFlagParam_None, Command__does_check); @@ -467,19 +588,32 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_DynamicMapCalls, str_lit("dynamic-map-calls"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_ObfuscateSourceCodeLocations, str_lit("obfuscate-source-code-locations"), BuildFlagParam_None, Command__does_build); + add_flag(&build_flags, BuildFlag_Short, str_lit("short"), BuildFlagParam_None, Command_doc); - add_flag(&build_flags, BuildFlag_AllPackages, str_lit("all-packages"), BuildFlagParam_None, Command_doc); + add_flag(&build_flags, BuildFlag_AllPackages, str_lit("all-packages"), BuildFlagParam_None, Command_doc | Command_test); add_flag(&build_flags, BuildFlag_DocFormat, str_lit("doc-format"), BuildFlagParam_None, Command_doc); add_flag(&build_flags, BuildFlag_IgnoreWarnings, str_lit("ignore-warnings"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_WarningsAsErrors, str_lit("warnings-as-errors"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_TerseErrors, str_lit("terse-errors"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_VerboseErrors, str_lit("verbose-errors"), BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_JsonErrors, str_lit("json-errors"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_ErrorPosStyle, str_lit("error-pos-style"), BuildFlagParam_String, Command_all); add_flag(&build_flags, BuildFlag_MaxErrorCount, str_lit("max-error-count"), BuildFlagParam_Integer, Command_all); + 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_InternalFastISel, str_lit("internal-fast-isel"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_InternalIgnoreLazy, str_lit("internal-ignore-lazy"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_InternalIgnoreLLVMBuild, str_lit("internal-ignore-llvm-build"),BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_InternalIgnorePanic, str_lit("internal-ignore-panic"), BuildFlagParam_None, Command_all); + 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); + add_flag(&build_flags, BuildFlag_InternalByValue, str_lit("internal-by-value"), BuildFlagParam_None, Command_all); #if ALLOW_TILDE add_flag(&build_flags, BuildFlag_Tilde, str_lit("tilde"), BuildFlagParam_None, Command__does_build); @@ -487,6 +621,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); @@ -731,6 +866,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; @@ -802,9 +984,10 @@ gb_internal bool parse_build_flags(Array args) { } gbAllocator a = heap_allocator(); - String fullpath = path_to_fullpath(a, path); - if (!path_is_directory(fullpath)) { - gb_printf_err("Library collection '%.*s' path must be a directory, got '%.*s'\n", LIT(name), LIT(fullpath)); + bool path_ok = false; + String fullpath = path_to_fullpath(a, path, &path_ok); + if (!path_ok || !path_is_directory(fullpath)) { + gb_printf_err("Library collection '%.*s' path must be a directory, got '%.*s'\n", LIT(name), LIT(path_ok ? fullpath : path)); gb_free(a, fullpath.text); bad_flags = true; break; @@ -883,27 +1066,29 @@ gb_internal bool parse_build_flags(Array args) { } if (!found) { - struct DistanceAndTargetIndex { - isize distance; - isize target_index; - }; + if (str != "?") { + struct DistanceAndTargetIndex { + isize distance; + isize target_index; + }; - DistanceAndTargetIndex distances[gb_count_of(named_targets)] = {}; - for (isize i = 0; i < gb_count_of(named_targets); i++) { - distances[i].target_index = i; - distances[i].distance = levenstein_distance_case_insensitive(str, named_targets[i].name); - } - gb_sort_array(distances, gb_count_of(distances), gb_isize_cmp(gb_offset_of(DistanceAndTargetIndex, distance))); - - gb_printf_err("Unknown target '%.*s'\n", LIT(str)); - - if (distances[0].distance <= MAX_SMALLEST_DID_YOU_MEAN_DISTANCE) { - gb_printf_err("Did you mean:\n"); + DistanceAndTargetIndex distances[gb_count_of(named_targets)] = {}; for (isize i = 0; i < gb_count_of(named_targets); i++) { - if (distances[i].distance > MAX_SMALLEST_DID_YOU_MEAN_DISTANCE) { - break; + distances[i].target_index = i; + distances[i].distance = levenstein_distance_case_insensitive(str, named_targets[i].name); + } + gb_sort_array(distances, gb_count_of(distances), gb_isize_cmp(gb_offset_of(DistanceAndTargetIndex, distance))); + + gb_printf_err("Unknown target '%.*s'\n", LIT(str)); + + if (distances[0].distance <= MAX_SMALLEST_DID_YOU_MEAN_DISTANCE) { + gb_printf_err("Did you mean:\n"); + for (isize i = 0; i < gb_count_of(named_targets); i++) { + if (distances[i].distance > MAX_SMALLEST_DID_YOU_MEAN_DISTANCE) { + break; + } + gb_printf_err("\t%.*s\n", LIT(named_targets[distances[i].target_index].name)); } - gb_printf_err("\t%.*s\n", LIT(named_targets[distances[i].target_index].name)); } } gb_printf_err("All supported targets:\n"); @@ -966,20 +1151,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; } @@ -996,12 +1188,18 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_NoBoundsCheck: build_context.no_bounds_check = true; break; + case BuildFlag_NoTypeAssert: + build_context.no_type_assert = true; + break; case BuildFlag_NoDynamicLiterals: - build_context.no_dynamic_literals = true; + gb_printf_err("Warning: Use of -no-dynamic-literals is now redundant\n"); break; case BuildFlag_NoCRT: build_context.no_crt = true; break; + case BuildFlag_NoRPath: + build_context.no_rpath = true; + break; case BuildFlag_NoEntryPoint: build_context.no_entry_point = true; break; @@ -1009,15 +1207,44 @@ gb_internal bool parse_build_flags(Array args) { build_context.no_thread_local = true; break; case BuildFlag_UseLLD: - build_context.use_lld = true; + gb_printf_err("Warning: Use of -lld has been deprecated in favour of -linker:lld\n"); + build_context.linker_choice = Linker_lld; break; + case BuildFlag_UseRADLink: + gb_printf_err("Warning: Use of -lld has been deprecated in favour of -linker:radlink\n"); + build_context.linker_choice = Linker_radlink; + break; + case BuildFlag_Linker: + { + GB_ASSERT(value.kind == ExactValue_String); + LinkerChoice linker_choice = Linker_Invalid; + + for (i32 i = 0; i < Linker_COUNT; i++) { + if (linker_choices[i] == value.value_string) { + linker_choice = cast(LinkerChoice)i; + break; + } + } + + if (linker_choice == Linker_Invalid) { + gb_printf_err("Invalid option for -linker:. Expected one of the following\n"); + for (i32 i = 0; i < Linker_COUNT; i++) { + gb_printf_err("\t%.*s\n", LIT(linker_choices[i])); + } + bad_flags = true; + } else { + build_context.linker_choice = linker_choice; + } + } + break; + + case BuildFlag_UseSeparateModules: build_context.use_separate_modules = true; break; - case BuildFlag_NoThreadedChecker: { + case BuildFlag_NoThreadedChecker: build_context.no_threaded_checker = true; break; - } case BuildFlag_ShowDebugMessages: build_context.show_debug_messages = true; break; @@ -1025,12 +1252,69 @@ gb_internal bool parse_build_flags(Array args) { build_context.vet_flags |= VetFlag_All; break; - case BuildFlag_VetUnused: build_context.vet_flags |= VetFlag_Unused; break; - case BuildFlag_VetShadowing: build_context.vet_flags |= VetFlag_Shadowing; break; - case BuildFlag_VetUsingStmt: build_context.vet_flags |= VetFlag_UsingStmt; break; - 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_VetUnusedVariables: build_context.vet_flags |= VetFlag_UnusedVariables; break; + case BuildFlag_VetUnusedImports: build_context.vet_flags |= VetFlag_UnusedImports; break; + case BuildFlag_VetUnused: build_context.vet_flags |= VetFlag_Unused; break; + case BuildFlag_VetShadowing: build_context.vet_flags |= VetFlag_Shadowing; break; + case BuildFlag_VetUsingStmt: build_context.vet_flags |= VetFlag_UsingStmt; break; + 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_VetUnusedProcedures: + build_context.vet_flags |= VetFlag_UnusedProcedures; + if (!set_flags[BuildFlag_VetPackages]) { + gb_printf_err("-%.*s must be used with -vet-packages\n", LIT(name)); + bad_flags = true; + } + break; + + case BuildFlag_VetPackages: + { + GB_ASSERT(value.kind == ExactValue_String); + String val = value.value_string; + String_Iterator it = {val, 0}; + for (;;) { + String pkg = string_split_iterator(&it, ','); + if (pkg.len == 0) { + break; + } + + pkg = string_trim_whitespace(pkg); + if (!string_is_valid_identifier(pkg)) { + gb_printf_err("-%.*s '%.*s' must be a valid identifier\n", LIT(name), LIT(pkg)); + bad_flags = true; + continue; + } + + string_set_add(&build_context.vet_packages, pkg); + } + } + 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("-%.*s '%.*s' must be a valid identifier\n", LIT(name), LIT(attr)); + bad_flags = true; + continue; + } + + string_set_add(&build_context.custom_attributes, attr); + } + } + break; case BuildFlag_IgnoreUnknownAttributes: build_context.ignore_unknown_attributes = true; @@ -1055,9 +1339,13 @@ 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; + build_context.minimum_os_version_string_given = true; break; } case BuildFlag_RelocMode: { @@ -1085,21 +1373,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; @@ -1113,9 +1386,26 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_DynamicMapCalls: build_context.dynamic_map_calls = true; break; + + case BuildFlag_ObfuscateSourceCodeLocations: + build_context.obfuscate_source_code_locations = true; + break; + case BuildFlag_DefaultToNilAllocator: + if (build_context.ODIN_DEFAULT_TO_PANIC_ALLOCATOR) { + gb_printf_err("'-default-to-panic-allocator' cannot be used with '-default-to-nil-allocator'\n"); + bad_flags = true; + } build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR = true; break; + case BuildFlag_DefaultToPanicAllocator: + if (build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR) { + gb_printf_err("'-default-to-nil-allocator' cannot be used with '-default-to-panic-allocator'\n"); + bad_flags = true; + } + build_context.ODIN_DEFAULT_TO_PANIC_ALLOCATOR = true; + break; + case BuildFlag_ForeignErrorProcedures: build_context.ODIN_FOREIGN_ERROR_PROCEDURES = true; break; @@ -1127,6 +1417,7 @@ gb_internal bool parse_build_flags(Array args) { break; case BuildFlag_AllPackages: build_context.cmd_doc_flags |= CmdDocFlag_AllPackages; + build_context.test_all_packages = true; break; case BuildFlag_DocFormat: build_context.cmd_doc_flags |= CmdDocFlag_DocFormat; @@ -1152,10 +1443,16 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_TerseErrors: build_context.hide_error_line = true; + build_context.terse_errors = true; break; case BuildFlag_VerboseErrors: - gb_printf_err("-verbose-errors is not the default, -terse-errors can now disable it\n"); + gb_printf_err("-verbose-errors is now the default, -terse-errors can disable it\n"); build_context.hide_error_line = false; + build_context.terse_errors = false; + break; + + case BuildFlag_JsonErrors: + build_context.json_errors = true; break; case BuildFlag_ErrorPosStyle: @@ -1182,12 +1479,41 @@ gb_internal bool parse_build_flags(Array args) { break; } + case BuildFlag_MinLinkLibs: + build_context.min_link_libs = true; + break; + + case BuildFlag_PrintLinkerFlags: + build_context.print_linker_flags = true; + break; + + case BuildFlag_InternalFastISel: + build_context.fast_isel = true; + break; case BuildFlag_InternalIgnoreLazy: build_context.ignore_lazy = true; break; 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_InternalByValue: + build_context.internal_by_value = true; + break; + case BuildFlag_Tilde: build_context.tilde_backend = true; break; @@ -1207,6 +1533,7 @@ gb_internal bool parse_build_flags(Array args) { } break; + #if defined(GB_SYSTEM_WINDOWS) case BuildFlag_IgnoreVsSearch: { GB_ASSERT(value.kind == ExactValue_Invalid); @@ -1218,8 +1545,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)) { @@ -1260,16 +1588,43 @@ gb_internal bool parse_build_flags(Array args) { } case BuildFlag_Subsystem: { + // TODO(Jeroen): Parse optional "[,major[.minor]]" + GB_ASSERT(value.kind == ExactValue_String); String subsystem = value.value_string; - if (str_eq_ignore_case(subsystem, str_lit("console"))) { - build_context.use_subsystem_windows = false; - } else if (str_eq_ignore_case(subsystem, str_lit("window"))) { - build_context.use_subsystem_windows = true; - } else if (str_eq_ignore_case(subsystem, str_lit("windows"))) { - build_context.use_subsystem_windows = true; - } else { - gb_printf_err("Invalid -subsystem string, got %.*s, expected either 'console' or 'windows'\n", LIT(subsystem)); + bool subsystem_found = false; + for (int i = 0; i < Windows_Subsystem_COUNT; i++) { + if (str_eq_ignore_case(subsystem, windows_subsystem_names[i])) { + build_context.ODIN_WINDOWS_SUBSYSTEM = windows_subsystem_names[i]; + subsystem_found = true; + break; + } + } + + // WINDOW is a hidden alias for WINDOWS. Check it. + String subsystem_windows_alias = str_lit("WINDOW"); + if (!subsystem_found && str_eq_ignore_case(subsystem, subsystem_windows_alias)) { + build_context.ODIN_WINDOWS_SUBSYSTEM = windows_subsystem_names[Windows_Subsystem_WINDOWS]; + subsystem_found = true; + break; + } + + if (!subsystem_found) { + gb_printf_err("Invalid -subsystem string, got %.*s. Expected one of:\n", LIT(subsystem)); + gb_printf_err("\t"); + for (int i = 0; i < Windows_Subsystem_COUNT; i++) { + if (i > 0) { + gb_printf_err(", "); + } + gb_printf_err("%.*s", LIT(windows_subsystem_names[i])); + if (i == Windows_Subsystem_CONSOLE) { + gb_printf_err(" (default)"); + } + if (i == Windows_Subsystem_WINDOWS) { + gb_printf_err(" (or WINDOW)"); + } + } + gb_printf_err("\n"); bad_flags = true; } break; @@ -1279,7 +1634,7 @@ gb_internal bool parse_build_flags(Array args) { } } - if (!bf.allow_mulitple) { + if (!bf.allow_multiple) { set_flags[bf.kind] = ok; } } @@ -1320,6 +1675,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; } @@ -1345,7 +1710,7 @@ gb_internal void timings_export_all(Timings *t, Checker *c, bool timings_are_fin gbFileError err = gb_file_open_mode(&f, gbFileMode_Write, fileName); if (err != gbFileError_None) { gb_printf_err("Failed to export timings to: %s\n", fileName); - gb_exit(1); + exit_with_errors(); return; } else { gb_printf("\nExporting timings to '%s'... ", fileName); @@ -1418,6 +1783,129 @@ 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_and_remove_duplicates(Checker *c) { + if (c->info.defineables.count == 0) { + return; + } + gb_sort_array(c->info.defineables.data, c->info.defineables.count, defineables_cmp); + + Defineable prev = c->info.defineables[0]; + for (isize i = 1; i < c->info.defineables.count; ) { + Defineable curr = c->info.defineables[i]; + if (prev.pos == curr.pos) { + array_ordered_remove(&c->info.defineables, i); + continue; + } + prev = curr; + i++; + } +} + +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; @@ -1546,11 +2034,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; @@ -1569,6 +2169,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); @@ -1579,46 +2180,53 @@ gb_internal void remove_temp_files(lbGenerator *gen) { } -gb_internal void print_show_help(String const arg0, String const &command) { - print_usage_line(0, "%.*s is a tool for managing Odin source code", LIT(arg0)); +gb_internal void print_show_help(String const arg0, String command, String optional_flag = {}) { + if (command == "help" && optional_flag.len != 0 && optional_flag[0] != '-') { + command = optional_flag; + optional_flag = {}; + } + + print_usage_line(0, "%.*s is a tool for managing Odin source code.", LIT(arg0)); print_usage_line(0, "Usage:"); print_usage_line(1, "%.*s %.*s [arguments]", LIT(arg0), LIT(command)); print_usage_line(0, ""); + defer (print_usage_line(0, "")); + if (command == "build") { - print_usage_line(1, "build Compile directory of .odin files as an executable."); + print_usage_line(1, "build Compiles directory of .odin files as an executable."); print_usage_line(2, "One must contain the program's entry point, all must be in the same package."); print_usage_line(2, "Use `-file` to build a single file instead."); print_usage_line(2, "Examples:"); - print_usage_line(3, "odin build . # Build package in current directory"); - print_usage_line(3, "odin build # Build package in "); - print_usage_line(3, "odin build filename.odin -file # Build single-file package, must contain entry point."); + print_usage_line(3, "odin build . Builds package in current directory."); + print_usage_line(3, "odin build Builds package in ."); + print_usage_line(3, "odin build filename.odin -file Builds single-file package, must contain entry point."); } else if (command == "run") { print_usage_line(1, "run Same as 'build', but also then runs the newly compiled executable."); print_usage_line(2, "Append an empty flag and then the args, '-- ', to specify args for the output."); print_usage_line(2, "Examples:"); - print_usage_line(3, "odin run . # Build and run package in current directory"); - print_usage_line(3, "odin run # Build and run package in "); - print_usage_line(3, "odin run filename.odin -file # Build and run single-file package, must contain entry point."); + print_usage_line(3, "odin run . Builds and runs package in current directory."); + print_usage_line(3, "odin run Builds and runs package in ."); + print_usage_line(3, "odin run filename.odin -file Builds and runs single-file package, must contain entry point."); } else if (command == "check") { - print_usage_line(1, "check Parse and type check directory of .odin files"); + print_usage_line(1, "check Parses and type checks directory of .odin files."); print_usage_line(2, "Examples:"); - print_usage_line(3, "odin check . # Type check package in current directory"); - print_usage_line(3, "odin check # Type check package in "); - print_usage_line(3, "odin check filename.odin -file # Type check single-file package, must contain entry point."); + print_usage_line(3, "odin check . Type checks package in current directory."); + print_usage_line(3, "odin check Type checks package in ."); + print_usage_line(3, "odin check filename.odin -file Type checks single-file package, must contain entry point."); } else if (command == "test") { - print_usage_line(1, "test Build and runs procedures with the attribute @(test) in the initial package"); + print_usage_line(1, "test Builds and runs procedures with the attribute @(test) in the initial package."); } else if (command == "doc") { - print_usage_line(1, "doc generate documentation from a directory of .odin files"); + print_usage_line(1, "doc Generates documentation from a directory of .odin files."); print_usage_line(2, "Examples:"); - print_usage_line(3, "odin doc . # Generate documentation on package in current directory"); - print_usage_line(3, "odin doc # Generate documentation on package in "); - print_usage_line(3, "odin doc filename.odin -file # Generate documentation on single-file package."); + print_usage_line(3, "odin doc . Generates documentation on package in current directory."); + print_usage_line(3, "odin doc Generates documentation on package in ."); + print_usage_line(3, "odin doc filename.odin -file Generates documentation on single-file package."); } else if (command == "version") { - print_usage_line(1, "version print version"); + print_usage_line(1, "version Prints version."); } else if (command == "strip-semicolon") { print_usage_line(1, "strip-semicolon"); - print_usage_line(2, "Parse and type check .odin file(s) and then remove unneeded semicolons from the entire project"); + print_usage_line(2, "Parses and type checks .odin file(s) and then removes unneeded semicolons from the entire project."); } bool doc = command == "doc"; @@ -1629,356 +2237,560 @@ gb_internal void print_show_help(String const arg0, String const &command) { bool check_only = command == "check" || strip_semicolon; bool check = run_or_build || check_only; + if (command == "help") { + doc = true; + build = true; + run_or_build = true; + test_only = true; + strip_semicolon = true; + check_only = true; + check = true; + } + print_usage_line(0, ""); print_usage_line(1, "Flags"); print_usage_line(0, ""); - if (check) { - print_usage_line(1, "-file"); - print_usage_line(2, "Tells `%.*s %.*s` to treat the given file as a self-contained package.", LIT(arg0), LIT(command)); - print_usage_line(2, "This means that `/a.odin` won't have access to `/b.odin`'s contents."); + + + auto const print_flag = [&optional_flag](char const *flag) -> bool { + if (optional_flag.len != 0) { + String f = make_string_c(flag); + isize i = string_index_byte(f, ':'); + if (i >= 0) { + f.len = i; + } + if (optional_flag != f) { + return false; + } + } print_usage_line(0, ""); - } + print_usage_line(1, flag); + return true; + }; + if (doc) { - print_usage_line(1, "-short"); - print_usage_line(2, "Show shortened documentation for the packages"); - print_usage_line(0, ""); - - print_usage_line(1, "-all-packages"); - print_usage_line(2, "Generates documentation for all packages used in the current project"); - print_usage_line(0, ""); - - print_usage_line(1, "-doc-format"); - print_usage_line(2, "Generates documentation as the .odin-doc format (useful for external tooling)"); - print_usage_line(0, ""); - } - - if (run_or_build) { - print_usage_line(1, "-out:"); - print_usage_line(2, "Set the file name of the outputted executable"); - print_usage_line(2, "Example: -out:foo.exe"); - print_usage_line(0, ""); - - print_usage_line(1, "-o:"); - print_usage_line(2, "Set the optimization mode for compilation"); - if (LB_USE_NEW_PASS_SYSTEM) { - print_usage_line(2, "Accepted values: none, minimal, size, speed, aggressive"); - } else { - print_usage_line(2, "Accepted values: none, minimal, size, speed"); + if (print_flag("-all-packages")) { + print_usage_line(2, "Generates documentation for all packages used in the current project."); } - print_usage_line(2, "Example: -o:speed"); - print_usage_line(2, "The default is -o:minimal"); - print_usage_line(0, ""); } - - if (check) { - print_usage_line(1, "-show-timings"); - print_usage_line(2, "Shows basic overview of the timings of different stages within the compiler in milliseconds"); - print_usage_line(0, ""); - - print_usage_line(1, "-show-more-timings"); - print_usage_line(2, "Shows an advanced overview of the timings of different stages within the compiler in milliseconds"); - print_usage_line(0, ""); - - print_usage_line(1, "-show-system-calls"); - print_usage_line(2, "Prints the whole command and arguments for calls to external tools like linker and assembler"); - print_usage_line(0, ""); - - print_usage_line(1, "-export-timings:"); - print_usage_line(2, "Export timings to one of a few formats. Requires `-show-timings` or `-show-more-timings`"); - print_usage_line(2, "Available options:"); - print_usage_line(3, "-export-timings:json Export compile time stats to JSON"); - print_usage_line(3, "-export-timings:csv Export compile time stats to CSV"); - print_usage_line(0, ""); - - print_usage_line(1, "-export-timings-file:"); - print_usage_line(2, "Specify the filename for `-export-timings`"); - print_usage_line(2, "Example: -export-timings-file:timings.json"); - print_usage_line(0, ""); - - print_usage_line(1, "-thread-count:"); - print_usage_line(2, "Override the number of threads the compiler will use to compile with"); - print_usage_line(2, "Example: -thread-count:2"); - print_usage_line(0, ""); - } - - if (check_only) { - print_usage_line(1, "-show-unused"); - print_usage_line(2, "Shows unused package declarations within the current project"); - print_usage_line(0, ""); - print_usage_line(1, "-show-unused-with-location"); - print_usage_line(2, "Shows unused package declarations within the current project with the declarations source location"); - print_usage_line(0, ""); - } - - if (run_or_build) { - print_usage_line(1, "-keep-temp-files"); - print_usage_line(2, "Keeps the temporary files generated during compilation"); - print_usage_line(0, ""); - } else if (strip_semicolon) { - print_usage_line(1, "-keep-temp-files"); - print_usage_line(2, "Keeps the temporary files generated during stripping the unneeded semicolons from files"); - print_usage_line(0, ""); - } - - if (check) { - print_usage_line(1, "-collection:="); - print_usage_line(2, "Defines a library collection used for imports"); - print_usage_line(2, "Example: -collection:shared=dir/to/shared"); - print_usage_line(2, "Usage in Code:"); - print_usage_line(3, "import \"shared:foo\""); - print_usage_line(0, ""); - - print_usage_line(1, "-define:="); - print_usage_line(2, "Defines a scalar boolean, integer or string as global constant"); - print_usage_line(2, "Example: -define:SPAM=123"); - print_usage_line(2, "To use: #config(SPAM, default_value)"); - print_usage_line(0, ""); + if (test_only) { + if (print_flag("-all-packages")) { + print_usage_line(2, "Tests all packages imported into the given initial package."); + } } if (build) { - print_usage_line(1, "-build-mode:"); - print_usage_line(2, "Sets the build mode"); - print_usage_line(2, "Available options:"); - print_usage_line(3, "-build-mode:exe Build as an executable"); - print_usage_line(3, "-build-mode:dll Build as a dynamically linked library"); - print_usage_line(3, "-build-mode:shared Build as a dynamically linked library"); - print_usage_line(3, "-build-mode:obj Build as an object file"); - print_usage_line(3, "-build-mode:object Build as an object file"); - print_usage_line(3, "-build-mode:assembly Build as an assembly file"); - print_usage_line(3, "-build-mode:assembler Build as an assembly file"); - print_usage_line(3, "-build-mode:asm Build as an assembly file"); - print_usage_line(3, "-build-mode:llvm-ir Build as an LLVM IR file"); - print_usage_line(3, "-build-mode:llvm Build as an LLVM IR file"); - print_usage_line(0, ""); - } - - if (check) { - print_usage_line(1, "-target:"); - print_usage_line(2, "Sets the target for the executable to be built in"); - print_usage_line(0, ""); - } - - if (run_or_build) { - print_usage_line(1, "-debug"); - print_usage_line(2, "Enabled debug information, and defines the global constant ODIN_DEBUG to be 'true'"); - print_usage_line(0, ""); - - print_usage_line(1, "-disable-assert"); - print_usage_line(2, "Disable the code generation of the built-in run-time 'assert' procedure, and defines the global constant ODIN_DISABLE_ASSERT to be 'true'"); - print_usage_line(0, ""); - - print_usage_line(1, "-no-bounds-check"); - print_usage_line(2, "Disables bounds checking program wide"); - print_usage_line(0, ""); - - print_usage_line(1, "-no-crt"); - print_usage_line(2, "Disables automatic linking with the C Run Time"); - print_usage_line(0, ""); - - print_usage_line(1, "-no-thread-local"); - print_usage_line(2, "Ignore @thread_local attribute, effectively treating the program as if it is single-threaded"); - print_usage_line(0, ""); - - print_usage_line(1, "-lld"); - print_usage_line(2, "Use the LLD linker rather than the default"); - 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(0, ""); - - } - - if (check) { - print_usage_line(1, "-no-threaded-checker"); - print_usage_line(2, "Disabled multithreading in the semantic checker stage"); - print_usage_line(0, ""); - } - - if (check) { - print_usage_line(1, "-vet"); - print_usage_line(2, "Do extra checks on the code"); - print_usage_line(2, "Extra checks include:"); - print_usage_line(2, "-vet-unused"); - print_usage_line(2, "-vet-shadowing"); - print_usage_line(2, "-vet-using-stmt"); - print_usage_line(0, ""); - - print_usage_line(1, "-vet-unused"); - print_usage_line(2, "Checks for unused declarations"); - print_usage_line(0, ""); - - print_usage_line(1, "-vet-shadowing"); - print_usage_line(2, "Checks for variable shadowing within procedures"); - print_usage_line(0, ""); - - print_usage_line(1, "-vet-using-stmt"); - print_usage_line(2, "Checks for the use of 'using' as a statement"); - print_usage_line(2, "'using' is considered bad practice outside of immediate refactoring"); - print_usage_line(0, ""); - - print_usage_line(1, "-vet-using-param"); - print_usage_line(2, "Checks for the use of 'using' on procedure parameters"); - print_usage_line(2, "'using' is considered bad practice outside of immediate refactoring"); - print_usage_line(0, ""); - - print_usage_line(1, "-vet-style"); - 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, "Does not err on unneeded tokens (unlike -strict-style)"); - print_usage_line(0, ""); - - print_usage_line(1, "-vet-semicolon"); - print_usage_line(2, "Errs on unneeded semicolons"); - print_usage_line(0, ""); - } - - if (check) { - 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"); - print_usage_line(0, ""); - - if (command != "test") { - print_usage_line(1, "-no-entry-point"); - print_usage_line(2, "Removes default requirement of an entry point (e.g. main procedure)"); - print_usage_line(0, ""); + if (print_flag("-build-mode:")) { + print_usage_line(2, "Sets the build mode."); + print_usage_line(2, "Available options:"); + print_usage_line(3, "-build-mode:exe Builds as an executable."); + print_usage_line(3, "-build-mode:test Builds as an executable that executes tests."); + print_usage_line(3, "-build-mode:dll Builds as a dynamically linked library."); + print_usage_line(3, "-build-mode:shared Builds as a dynamically linked library."); + print_usage_line(3, "-build-mode:dynamic 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."); + print_usage_line(3, "-build-mode:assembler Builds as an assembly file."); + print_usage_line(3, "-build-mode:asm Builds as an assembly file."); + print_usage_line(3, "-build-mode:llvm-ir Builds as an LLVM IR file."); + print_usage_line(3, "-build-mode:llvm Builds as an LLVM IR file."); } } - if (test_only) { - print_usage_line(1, "-test-name:"); - print_usage_line(2, "Run specific test only by name"); - print_usage_line(0, ""); + if (check) { + if (print_flag("-collection:=")) { + print_usage_line(2, "Defines a library collection used for imports."); + print_usage_line(2, "Example: -collection:shared=dir/to/shared"); + print_usage_line(2, "Usage in Code:"); + print_usage_line(3, "import \"shared:foo\""); + } + + if (print_flag("-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"); + } } if (run_or_build) { - print_usage_line(1, "-minimum-os-version:"); - print_usage_line(2, "Sets the minimum OS version targeted by the application"); - print_usage_line(2, "e.g. -minimum-os-version:12.0.0"); - print_usage_line(2, "(Only used when target is Darwin)"); - print_usage_line(0, ""); - - print_usage_line(1, "-extra-linker-flags:"); - print_usage_line(2, "Adds extra linker specific flags in a string"); - print_usage_line(0, ""); - - print_usage_line(1, "-extra-assembler-flags:"); - print_usage_line(2, "Adds extra assembler specific flags in a string"); - print_usage_line(0, ""); - - print_usage_line(1, "-microarch:"); - print_usage_line(2, "Specifies the specific micro-architecture for the build in a string"); - print_usage_line(2, "Examples:"); - print_usage_line(3, "-microarch:sandybridge"); - print_usage_line(3, "-microarch:native"); - print_usage_line(0, ""); - - print_usage_line(1, "-reloc-mode:"); - print_usage_line(2, "Specifies the reloc mode"); - print_usage_line(2, "Options:"); - print_usage_line(3, "default"); - print_usage_line(3, "static"); - print_usage_line(3, "pic"); - print_usage_line(3, "dynamic-no-pic"); - print_usage_line(0, ""); - - print_usage_line(1, "-disable-red-zone"); - print_usage_line(2, "Disable red zone on a supported freestanding target"); - print_usage_line(0, ""); - - print_usage_line(1, "-dynamic-map-calls"); - print_usage_line(2, "Use dynamic map calls to minimize code generation at the cost of runtime execution"); - print_usage_line(0, ""); + if (print_flag("-debug")) { + print_usage_line(2, "Enables debug information, and defines the global constant ODIN_DEBUG to be 'true'."); + } } if (check) { - print_usage_line(1, "-disallow-do"); - print_usage_line(2, "Disallows the 'do' keyword in the project"); - print_usage_line(0, ""); - - print_usage_line(1, "-default-to-nil-allocator"); - print_usage_line(2, "Sets the default allocator to be the nil_allocator, an allocator which does nothing"); - print_usage_line(0, ""); - - print_usage_line(1, "-strict-style"); - 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(0, ""); - - print_usage_line(1, "-ignore-warnings"); - print_usage_line(2, "Ignores warning messages"); - print_usage_line(0, ""); - - print_usage_line(1, "-warnings-as-errors"); - print_usage_line(2, "Treats warning messages as error messages"); - print_usage_line(0, ""); - - print_usage_line(1, "-terse-errors"); - print_usage_line(2, "Prints a terse error message without showing the code on that line and the location in that line"); - print_usage_line(0, ""); - - print_usage_line(1, "-error-pos-style:"); - print_usage_line(2, "Options are 'unix', 'odin' and 'default' (odin)"); - print_usage_line(2, "'odin' file/path(45:3)"); - print_usage_line(2, "'unix' file/path:45:3:"); - print_usage_line(0, ""); - - - print_usage_line(1, "-max-error-count:"); - print_usage_line(2, "Set the maximum number of errors that can be displayed before the compiler terminates"); - print_usage_line(2, "Must be an integer >0"); - print_usage_line(2, "If not set, the default max error count is %d", DEFAULT_MAX_ERROR_COLLECTOR_COUNT); - print_usage_line(0, ""); - - print_usage_line(1, "-foreign-error-procedures"); - print_usage_line(2, "States that the error procedues used in the runtime are defined in a separate translation unit"); - print_usage_line(0, ""); + if (print_flag("-default-to-nil-allocator")) { + print_usage_line(2, "Sets the default allocator to be the nil_allocator, an allocator which does nothing."); + } + if (print_flag("-define:=")) { + print_usage_line(2, "Defines a scalar boolean, integer or string as global constant."); + print_usage_line(2, "Example: -define:SPAM=123"); + print_usage_line(2, "Usage in code:"); + print_usage_line(3, "#config(SPAM, default_value)"); + } } if (run_or_build) { - print_usage_line(1, "-sanitize:"); - print_usage_line(1, "Enables sanitization analysis"); - print_usage_line(1, "Options are 'address', 'memory', and 'thread'"); - print_usage_line(1, "NOTE: This flag can be used multiple times"); - print_usage_line(0, ""); + if (print_flag("-disable-assert")) { + print_usage_line(2, "Disables the code generation of the built-in run-time 'assert' procedure, and defines the global constant ODIN_DISABLE_ASSERT to be 'true'."); + } + if (print_flag("-disable-red-zone")) { + print_usage_line(2, "Disables red zone on a supported freestanding target."); + } + } + + if (check) { + if (print_flag("-disallow-do")) { + print_usage_line(2, "Disallows the 'do' keyword in the project."); + } + } + + if (doc) { + if (print_flag("-doc-format")) { + print_usage_line(2, "Generates documentation as the .odin-doc format (useful for external tooling)."); + } } if (run_or_build) { - #if defined(GB_SYSTEM_WINDOWS) - print_usage_line(1, "-ignore-vs-search"); - print_usage_line(2, "[Windows only]"); - print_usage_line(2, "Ignores the Visual Studio search for library paths"); - print_usage_line(0, ""); + if (print_flag("-dynamic-map-calls")) { + print_usage_line(2, "Uses dynamic map calls to minimize code generation at the cost of runtime execution."); + } + } - print_usage_line(1, "-resource:"); - 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(0, ""); + if (check) { + if (print_flag("-error-pos-style:")) { + print_usage_line(2, "Available options:"); + print_usage_line(3, "-error-pos-style:unix file/path:45:3:"); + print_usage_line(3, "-error-pos-style:odin file/path(45:3)"); + print_usage_line(3, "-error-pos-style:default (Defaults to 'odin'.)"); + } - print_usage_line(1, "-pdb-name:"); - print_usage_line(2, "[Windows only]"); - print_usage_line(2, "Defines the generated PDB name when -debug is enabled"); - print_usage_line(2, "Example: -pdb-name:different.pdb"); - print_usage_line(0, ""); + if (print_flag("-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(1, "-subsystem: