Merge branch 'master' into macharena

This commit is contained in:
Colin Davidson
2025-07-07 14:34:05 -07:00
170 changed files with 35253 additions and 3220 deletions
+45 -51
View File
@@ -30,13 +30,13 @@ jobs:
gmake -C vendor/stb/src gmake -C vendor/stb/src
gmake -C vendor/cgltf/src gmake -C vendor/cgltf/src
gmake -C vendor/miniaudio/src gmake -C vendor/miniaudio/src
./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_amd64 ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_amd64
./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_arm64 ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_arm64
./odin check examples/all/sdl3 -vet -strict-style -disallow-do -target:netbsd_amd64 -no-entry-point ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_amd64 -no-entry-point
./odin check examples/all/sdl3 -vet -strict-style -disallow-do -target:netbsd_arm64 -no-entry-point ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_arm64 -no-entry-point
./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/normal.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -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/core/speed.odin -file -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -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 ./odin test tests/vendor -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
(cd tests/issues; ./run.sh) (cd tests/issues; ./run.sh)
./odin check tests/benchmark -vet -strict-style -no-entry-point ./odin check tests/benchmark -vet -strict-style -no-entry-point
@@ -63,11 +63,11 @@ jobs:
gmake -C vendor/stb/src gmake -C vendor/stb/src
gmake -C vendor/cgltf/src gmake -C vendor/cgltf/src
gmake -C vendor/miniaudio/src gmake -C vendor/miniaudio/src
./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64 ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_amd64
./odin check examples/all/sdl3 -vet -strict-style -disallow-do -target:freebsd_amd64 -no-entry-point ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_amd64 -no-entry-point
./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/normal.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -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/core/speed.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -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 ./odin test tests/vendor -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
(cd tests/issues; ./run.sh) (cd tests/issues; ./run.sh)
./odin check tests/benchmark -vet -strict-style -no-entry-point ./odin check tests/benchmark -vet -strict-style -no-entry-point
ci: ci:
@@ -75,7 +75,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
# MacOS 13 runs on Intel, 14 runs on ARM # MacOS 13 runs on Intel, 14 runs on ARM
os: [macos-13, macos-14, ubuntu-latest] os: [macos-14, ubuntu-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel') || (matrix.os == 'ubuntu-latest' && 'Ubuntu') }} Build, Check, and Test name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel') || (matrix.os == 'ubuntu-latest' && 'Ubuntu') }} Build, Check, and Test
timeout-minutes: 15 timeout-minutes: 15
@@ -123,17 +123,17 @@ jobs:
- name: Odin run -debug - name: Odin run -debug
run: ./odin run examples/demo -debug run: ./odin run examples/demo -debug
- name: Odin check examples/all - name: Odin check examples/all
run: ./odin check examples/all -strict-style -vet -disallow-do run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do
- name: Odin check examples/all/sdl3 - name: Odin check examples/all/sdl3
run: ./odin check examples/all/sdl3 -strict-style -vet -disallow-do -no-entry-point run: ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point
- name: Normal Core library tests - 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 -sanitize:address run: ./odin test tests/core/normal.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Optimized Core library tests - 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 -sanitize:address run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Vendor library tests - 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 -sanitize:address run: ./odin test tests/vendor -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Internals tests - 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 -sanitize:address run: ./odin test tests/internal -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: GitHub Issue tests - name: GitHub Issue tests
run: | run: |
cd tests/issues cd tests/issues
@@ -141,43 +141,43 @@ jobs:
- name: Run demo on WASI WASM32 - name: Run demo on WASI WASM32
run: | run: |
./odin build examples/demo -target:wasi_wasm32 -vet -strict-style -disallow-do -out:demo ./odin build examples/demo -target:wasi_wasm32 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -out:demo
wasmtime ./demo.wasm wasmtime ./demo.wasm
if: matrix.os == 'macos-14' if: matrix.os == 'macos-14'
- name: Check benchmarks - name: Check benchmarks
run: ./odin check tests/benchmark -vet -strict-style -no-entry-point run: ./odin check tests/benchmark -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point
- name: Odin check examples/all for Linux i386 - name: Odin check examples/all for Linux i386
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_i386 run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_i386
- name: Odin check examples/all for Linux arm64 - name: Odin check examples/all for Linux arm64
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_arm64 run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_arm64
- name: Odin check examples/all for FreeBSD amd64 - name: Odin check examples/all for FreeBSD amd64
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64 run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_amd64
- name: Odin check examples/all for OpenBSD amd64 - name: Odin check examples/all for OpenBSD amd64
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64 run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:openbsd_amd64
- name: Odin check examples/all for js_wasm32 - name: Odin check examples/all for js_wasm32
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -no-entry-point -target:js_wasm32 run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:js_wasm32
- name: Odin check examples/all for js_wasm64p32 - name: Odin check examples/all for js_wasm64p32
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -no-entry-point -target:js_wasm64p32 run: ./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:js_wasm64p32
- name: Odin check examples/all/sdl3 for Linux i386 - name: Odin check examples/all/sdl3 for Linux i386
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_i386 run: ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:linux_i386
- name: Odin check examples/all/sdl3 for Linux arm64 - name: Odin check examples/all/sdl3 for Linux arm64
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_arm64 run: ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:linux_arm64
- name: Odin check examples/all/sdl3 for FreeBSD amd64 - name: Odin check examples/all/sdl3 for FreeBSD amd64
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:freebsd_amd64 run: ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:freebsd_amd64
- name: Odin check examples/all/sdl3 for OpenBSD amd64 - name: Odin check examples/all/sdl3 for OpenBSD amd64
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:openbsd_amd64 run: ./odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:openbsd_amd64
build_windows: build_windows:
name: Windows Build, Check, and Test name: Windows Build, Check, and Test
@@ -208,38 +208,38 @@ jobs:
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin run examples/demo -debug -vet -strict-style -disallow-do odin run examples/demo -debug -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do
- name: Odin check examples/all - name: Odin check examples/all
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin check examples/all -vet -strict-style -disallow-do odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do
- name: Odin check examples/all/sdl3 - name: Odin check examples/all/sdl3
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point
- name: Core library tests - name: Core library tests
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
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 -sanitize:address odin test tests/core/normal.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Optimized core library tests - name: Optimized core library tests
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat 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 -sanitize:address odin test tests/core/speed.odin -o:speed -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Vendor library tests - name: Vendor library tests
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
copy vendor\lua\5.4\windows\*.dll . 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 -sanitize:address odin test tests/vendor -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Odin internals tests - name: Odin internals tests
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address odin test tests/internal -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Check issues - name: Check issues
shell: cmd shell: cmd
run: | run: |
@@ -257,12 +257,6 @@ jobs:
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
cd tests\documentation cd tests\documentation
call build.bat call build.bat
- name: core:math/big tests
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
cd tests\core\math\big
call build.bat
- name: Odin check examples/all for Windows 32bits - name: Odin check examples/all for Windows 32bits
shell: cmd shell: cmd
run: | run: |
@@ -299,25 +293,25 @@ jobs:
make -C vendor/miniaudio/src make -C vendor/miniaudio/src
- name: Odin check examples/all - name: Odin check examples/all
run: ./odin check examples/all -target:linux_riscv64 -vet -strict-style -disallow-do run: ./odin check examples/all -target:linux_riscv64 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do
- name: Odin check examples/all/sdl3 - name: Odin check examples/all/sdl3
run: ./odin check examples/all/sdl3 -target:linux_riscv64 -vet -strict-style -disallow-do -no-entry-point run: ./odin check examples/all/sdl3 -target:linux_riscv64 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point
- name: Install riscv64 toolchain and qemu - 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 run: sudo apt-get install -y qemu-user qemu-user-static gcc-12-riscv64-linux-gnu libc6-riscv64-cross
- name: Odin run - 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 run: ./odin run examples/demo -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -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 - 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 run: ./odin run examples/demo -debug -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -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 - 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 run: ./odin test tests/core/normal.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -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 - 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 run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -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 - 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 run: ./odin test tests/internal -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -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
+60
View File
@@ -0,0 +1,60 @@
name: Test Coverage
on: [push, pull_request, workflow_dispatch]
jobs:
build_linux_amd64:
runs-on: ubuntu-latest
name: Linux AMD64 Test Coverage
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: Download LLVM (Ubuntu)
if: matrix.os == 'ubuntu-latest'
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 20
echo "/usr/lib/llvm-20/bin" >> $GITHUB_PATH
- name: Install kcov
run: |
sudo apt-get update
sudo apt-get install binutils-dev build-essential cmake libssl-dev libcurl4-openssl-dev libelf-dev libstdc++-12-dev zlib1g-dev libdw-dev libiberty-dev
git clone https://github.com/SimonKagstrom/kcov.git
mkdir kcov/build
cd kcov/build
cmake ..
sudo make
sudo make install
cd ../..
kcov --version
- name: Build Odin
run: ./build_odin.sh release
- name: Odin report
run: ./odin report
- name: Normal Core library tests
run: |
./odin build tests/core/normal.odin -build-mode:test -debug -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_amd64
mkdir kcov-out
kcov --exclude-path=tests,/usr kcov-out ./normal.bin .
- name: Optimized Core library tests
run: |
./odin build tests/core/speed.odin -build-mode:test -debug -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_amd64
kcov --exclude-path=tests,/usr kcov-out ./speed.bin .
- name: Internals tests
run: |
./odin build tests/internal -build-mode:test -debug -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_amd64
kcov --exclude-path=tests,/usr kcov-out ./internal .
- uses: codecov/codecov-action@v5
with:
name: Ubuntu Coverage # optional
token: ${{ secrets.CODECOV_TOKEN }}
verbose: true # optional (default = false
directory: kcov-out/kcov-merged
+1
View File
@@ -277,6 +277,7 @@ odin
*.bin *.bin
demo.bin demo.bin
libLLVM*.so* libLLVM*.so*
*.a
# shared collection # shared collection
shared/ shared/
+2 -2
View File
@@ -67,7 +67,7 @@ init_global_temporary_allocator :: proc(size: int, backup_allocator := context.a
// Prefer the procedure group `copy`. // Prefer the procedure group `copy`.
@builtin @builtin
copy_slice :: proc "contextless" (dst, src: $T/[]$E) -> int { copy_slice :: proc "contextless" (dst, src: $T/[]$E) -> int {
n := max(0, min(len(dst), len(src))) n := min(len(dst), len(src))
if n > 0 { if n > 0 {
intrinsics.mem_copy(raw_data(dst), raw_data(src), n*size_of(E)) intrinsics.mem_copy(raw_data(dst), raw_data(src), n*size_of(E))
} }
@@ -80,7 +80,7 @@ copy_slice :: proc "contextless" (dst, src: $T/[]$E) -> int {
// Prefer the procedure group `copy`. // Prefer the procedure group `copy`.
@builtin @builtin
copy_from_string :: proc "contextless" (dst: $T/[]$E/u8, src: $S/string) -> int { copy_from_string :: proc "contextless" (dst: $T/[]$E/u8, src: $S/string) -> int {
n := max(0, min(len(dst), len(src))) n := min(len(dst), len(src))
if n > 0 { if n > 0 {
intrinsics.mem_copy(raw_data(dst), raw_data(src), n) intrinsics.mem_copy(raw_data(dst), raw_data(src), n)
} }
@@ -1,7 +1,7 @@
package runtime package runtime
import "base:intrinsics" import "base:intrinsics"
import "base:sanitizer" // import "base:sanitizer"
DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE) DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE)
@@ -44,7 +44,7 @@ memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint
block.base = ([^]byte)(uintptr(block) + base_offset) block.base = ([^]byte)(uintptr(block) + base_offset)
block.capacity = uint(end - uintptr(block.base)) block.capacity = uint(end - uintptr(block.base))
sanitizer.address_poison(block.base, block.capacity) // sanitizer.address_poison(block.base, block.capacity)
// Should be zeroed // Should be zeroed
assert(block.used == 0) assert(block.used == 0)
@@ -55,7 +55,7 @@ memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint
memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) { memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) {
if block_to_free != nil { if block_to_free != nil {
allocator := block_to_free.allocator allocator := block_to_free.allocator
sanitizer.address_unpoison(block_to_free.base, block_to_free.capacity) // sanitizer.address_unpoison(block_to_free.base, block_to_free.capacity)
mem_free(block_to_free, allocator, loc) mem_free(block_to_free, allocator, loc)
} }
} }
@@ -87,7 +87,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint)
return return
} }
data = block.base[block.used+alignment_offset:][:min_size] data = block.base[block.used+alignment_offset:][:min_size]
sanitizer.address_unpoison(block.base[block.used:block.used+size]) // sanitizer.address_unpoison(block.base[block.used:block.used+size])
block.used += size block.used += size
return return
} }
@@ -167,7 +167,7 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
if arena.curr_block != nil { if arena.curr_block != nil {
intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used) intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used)
arena.curr_block.used = 0 arena.curr_block.used = 0
sanitizer.address_poison(arena.curr_block.base, arena.curr_block.capacity) // sanitizer.address_poison(arena.curr_block.base, arena.curr_block.capacity)
} }
arena.total_used = 0 arena.total_used = 0
} }
@@ -232,7 +232,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
// grow data in-place, adjusting next allocation // grow data in-place, adjusting next allocation
block.used = uint(new_end) block.used = uint(new_end)
data = block.base[start:new_end] data = block.base[start:new_end]
sanitizer.address_unpoison(data) // sanitizer.address_unpoison(data)
return return
} }
} }
@@ -306,7 +306,7 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
assert(block.used >= temp.used, "out of order use of arena_temp_end", loc) assert(block.used >= temp.used, "out of order use of arena_temp_end", loc)
amount_to_zero := block.used-temp.used amount_to_zero := block.used-temp.used
intrinsics.mem_zero(block.base[temp.used:], amount_to_zero) intrinsics.mem_zero(block.base[temp.used:], amount_to_zero)
sanitizer.address_poison(block.base[temp.used:block.capacity]) // sanitizer.address_poison(block.base[temp.used:block.capacity])
block.used = temp.used block.used = temp.used
arena.total_used -= amount_to_zero arena.total_used -= amount_to_zero
} }
+74
View File
@@ -0,0 +1,74 @@
#+no-instrumentation
package sanitizer
@(private="file")
MSAN_ENABLED :: .Memory in ODIN_SANITIZER_FLAGS
@(private="file")
@(default_calling_convention="system")
foreign {
__msan_unpoison :: proc(addr: rawptr, size: uint) ---
}
/*
Marks a slice as fully initialized.
Code instrumented with `-sanitize:memory` will be permitted to access any
address within the slice as if it had already been initialized.
When msan is not enabled this procedure does nothing.
*/
memory_unpoison_slice :: proc "contextless" (region: $T/[]$E) {
when MSAN_ENABLED {
__msan_unpoison(raw_data(region), size_of(E) * len(region))
}
}
/*
Marks a pointer as fully initialized.
Code instrumented with `-sanitize:memory` will be permitted to access memory
within the region the pointer points to as if it had already been initialized.
When msan is not enabled this procedure does nothing.
*/
memory_unpoison_ptr :: proc "contextless" (ptr: ^$T) {
when MSAN_ENABLED {
__msan_unpoison(ptr, size_of(T))
}
}
/*
Marks the region covering `[ptr, ptr+len)` as fully initialized.
Code instrumented with `-sanitize:memory` will be permitted to access memory
within this range as if it had already been initialized.
When msan is not enabled this procedure does nothing.
*/
memory_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
when MSAN_ENABLED {
__msan_unpoison(ptr, uint(len))
}
}
/*
Marks the region covering `[ptr, ptr+len)` as fully initialized.
Code instrumented with `-sanitize:memory` will be permitted to access memory
within this range as if it had already been initialized.
When msan is not enabled this procedure does nothing.
*/
memory_unpoison_rawptr_uint :: proc "contextless" (ptr: rawptr, len: uint) {
when MSAN_ENABLED {
__msan_unpoison(ptr, len)
}
}
memory_unpoison :: proc {
memory_unpoison_slice,
memory_unpoison_ptr,
memory_unpoison_rawptr,
memory_unpoison_rawptr_uint,
}
+75
View File
@@ -0,0 +1,75 @@
@echo off
if "%1" == "" (
echo Checking darwin_amd64 - expect vendor:cgltf panic
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:darwin_amd64
echo Checking darwin_arm64 - expect vendor:cgltf panic
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:darwin_arm64
echo Checking linux_i386
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_i386
echo Checking linux_amd64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_amd64
echo Checking linux_arm64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_arm64
echo Checking linux_arm32
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_arm32
echo Checking linux_riscv64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_riscv64
echo Checking windows_i386
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:windows_i386
echo Checking windows_amd64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:windows_amd64
echo Checking freebsd_amd64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_amd64
echo Checking freebsd_arm64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_arm64
echo Checking netbsd_amd64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_amd64
echo Checking netbsd_arm64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_arm64
echo Checking openbsd_amd64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:openbsd_amd64
)
if "%1" == "freestanding" (
echo Checking freestanding_wasm32
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm32
echo Checking freestanding_wasm64p32
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm64p32
echo Checking freestanding_amd64_sysv
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_amd64_sysv
echo Checking freestanding_amd64_win64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_amd64_win64
echo Checking freestanding_arm64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_arm64
echo Checking freestanding_arm32
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_arm32
echo Checking freestanding_riscv64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_riscv64
)
if "%1" == "rare" (
echo Checking essence_amd64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:essence_amd64
echo Checking freebsd_i386
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_i386
echo Checking haiku_amd64
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:haiku_amd64
)
if "%1" == "wasm" (
echo Checking freestanding_wasm32
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm32
echo Checking freestanding_wasm64p32
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm64p32
echo Checking wasi_wasm64p32
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:wasi_wasm64p32
echo Checking wasi_wasm32
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:wasi_wasm32
echo Checking js_wasm32
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:js_wasm32
echo Checking orca_wasm32
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:orca_wasm32
echo Checking js_wasm64p32
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:js_wasm64p32
)
Executable
+78
View File
@@ -0,0 +1,78 @@
#!/bin/sh
case $1 in
freestanding)
echo Checking freestanding_wasm32
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm32
echo Checking freestanding_wasm64p32
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm64p32
echo Checking freestanding_amd64_sysv
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_amd64_sysv
echo Checking freestanding_amd64_win64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_amd64_win64
echo Checking freestanding_arm64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_arm64
echo Checking freestanding_arm32
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_arm32
echo Checking freestanding_riscv64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_riscv64
;;
rare)
echo Checking essence_amd64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:essence_amd64
echo Checking freebsd_i386
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_i386
echo Checking haiku_amd64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:haiku_amd64
;;
wasm)
echo Checking freestanding_wasm32
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm32
echo Checking freestanding_wasm64p32
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freestanding_wasm64p32
echo Checking wasi_wasm64p32
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:wasi_wasm64p32
echo Checking wasi_wasm32
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:wasi_wasm32
echo Checking js_wasm32
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:js_wasm32
echo Checking orca_wasm32
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:orca_wasm32
echo Checking js_wasm64p32
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:js_wasm64p32
;;
*)
echo Checking darwin_amd64 - expect vendor:cgltf panic
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:darwin_amd64
echo Checking darwin_arm64 - expect vendor:cgltf panic
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:darwin_arm64
echo Checking linux_i386
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_i386
echo Checking linux_amd64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_amd64
echo Checking linux_arm64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_arm64
echo Checking linux_arm32
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_arm32
echo Checking linux_riscv64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_riscv64
echo Checking windows_i386
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:windows_i386
echo Checking windows_amd64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:windows_amd64
echo Checking freebsd_amd64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_amd64
echo Checking freebsd_arm64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_arm64
echo Checking netbsd_amd64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_amd64
echo Checking netbsd_arm64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_arm64
echo Checking openbsd_amd64
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:openbsd_amd64
;;
esac
+1
View File
@@ -0,0 +1 @@
comment: false
+282 -58
View File
@@ -4,7 +4,13 @@ import "base:builtin"
import "base:runtime" import "base:runtime"
_ :: runtime _ :: runtime
// Dynamically resizable double-ended queue/ring-buffer /*
`Queue` is a dynamically resizable double-ended queue/ring-buffer.
Being double-ended means that either end may be pushed onto or popped from
across the same block of memory, in any order, thus providing both stack and
queue-like behaviors in the same data structure.
*/
Queue :: struct($T: typeid) { Queue :: struct($T: typeid) {
data: [dynamic]T, data: [dynamic]T,
len: uint, len: uint,
@@ -13,18 +19,31 @@ Queue :: struct($T: typeid) {
DEFAULT_CAPACITY :: 16 DEFAULT_CAPACITY :: 16
// Procedure to initialize a queue /*
Initialize a `Queue` with a starting `capacity` and an `allocator`.
*/
init :: proc(q: ^$Q/Queue($T), capacity := DEFAULT_CAPACITY, allocator := context.allocator) -> runtime.Allocator_Error { init :: proc(q: ^$Q/Queue($T), capacity := DEFAULT_CAPACITY, allocator := context.allocator) -> runtime.Allocator_Error {
if q.data.allocator.procedure == nil {
q.data.allocator = allocator
}
clear(q) clear(q)
q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{
data = nil,
len = 0,
cap = 0,
allocator = allocator,
}
return reserve(q, capacity) return reserve(q, capacity)
} }
// 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`. Initialize a `Queue` from a fixed `backing` slice into which modifications are
// Any previous contents are not available. made directly.
The contents of the `backing` will be overwritten as items are pushed onto the
`Queue`. Any previous contents will not be available through the API but are
not explicitly zeroed either.
Note that procedures which need space to work (`push_back`, ...) will fail if
the backing slice runs out of space.
*/
init_from_slice :: proc(q: ^$Q/Queue($T), backing: []T) -> bool { init_from_slice :: proc(q: ^$Q/Queue($T), backing: []T) -> bool {
clear(q) clear(q)
q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{ q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{
@@ -36,8 +55,14 @@ init_from_slice :: proc(q: ^$Q/Queue($T), backing: []T) -> bool {
return true return true
} }
// Procedure to initialize a queue from a fixed backing slice. /*
// Existing contents are preserved and available on the queue. Initialize a `Queue` from a fixed `backing` slice into which modifications are
made directly.
The contents of the queue will start out with all of the elements in `backing`,
effectively creating a full queue from the slice. As such, no procedures will
be able to add more elements to the queue until some are taken off.
*/
init_with_contents :: proc(q: ^$Q/Queue($T), backing: []T) -> bool { init_with_contents :: proc(q: ^$Q/Queue($T), backing: []T) -> bool {
clear(q) clear(q)
q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{ q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{
@@ -50,84 +75,200 @@ init_with_contents :: proc(q: ^$Q/Queue($T), backing: []T) -> bool {
return true return true
} }
// Procedure to destroy a queue /*
Delete memory that has been dynamically allocated from a `Queue` that was setup with `init`.
Note that this procedure should not be used on queues setup with
`init_from_slice` or `init_with_contents`, as neither of those procedures keep
track of the allocator state of the underlying `backing` slice.
*/
destroy :: proc(q: ^$Q/Queue($T)) { destroy :: proc(q: ^$Q/Queue($T)) {
delete(q.data) delete(q.data)
} }
// The length of the queue /*
Return the length of the queue.
*/
len :: proc(q: $Q/Queue($T)) -> int { len :: proc(q: $Q/Queue($T)) -> int {
return int(q.len) return int(q.len)
} }
// The current capacity of the queue /*
Return the capacity of the queue.
*/
cap :: proc(q: $Q/Queue($T)) -> int { cap :: proc(q: $Q/Queue($T)) -> int {
return builtin.len(q.data) return builtin.len(q.data)
} }
// Remaining space in the queue (cap-len) /*
Return the remaining space in the queue.
This will be `cap() - len()`.
*/
space :: proc(q: $Q/Queue($T)) -> int { space :: proc(q: $Q/Queue($T)) -> int {
return builtin.len(q.data) - int(q.len) return builtin.len(q.data) - int(q.len)
} }
// Reserve enough space for at least the specified capacity /*
Reserve enough space in the queue for at least the specified capacity.
This may return an error if allocation failed.
*/
reserve :: proc(q: ^$Q/Queue($T), capacity: int) -> runtime.Allocator_Error { reserve :: proc(q: ^$Q/Queue($T), capacity: int) -> runtime.Allocator_Error {
if capacity > space(q^) { if capacity > space(q^) {
return _grow(q, uint(capacity)) return _grow(q, uint(capacity))
} }
return nil return nil
} }
/*
Shrink a queue's dynamically allocated array.
This has no effect if the queue was initialized with a backing slice.
*/
shrink :: proc(q: ^$Q/Queue($T), temp_allocator := context.temp_allocator, loc := #caller_location) {
if q.data.allocator.procedure == runtime.nil_allocator_proc {
return
}
if q.len > 0 && q.offset > 0 {
// Make the array contiguous again.
buffer := make([]T, q.len, temp_allocator)
defer delete(buffer, temp_allocator)
right := uint(builtin.len(q.data)) - q.offset
copy(buffer[:], q.data[q.offset:])
copy(buffer[right:], q.data[:q.offset])
copy(q.data[:], buffer[:])
q.offset = 0
}
builtin.shrink(&q.data, q.len, loc)
}
/*
Get the element at index `i`.
This will raise a bounds checking error if `i` is an invalid index.
*/
get :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> T { get :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> T {
runtime.bounds_check_error_loc(loc, i, builtin.len(q.data)) runtime.bounds_check_error_loc(loc, i, int(q.len))
idx := (uint(i)+q.offset)%builtin.len(q.data) idx := (uint(i)+q.offset)%builtin.len(q.data)
return q.data[idx] return q.data[idx]
} }
front :: proc(q: ^$Q/Queue($T)) -> T { /*
return q.data[q.offset] Get a pointer to the element at index `i`.
}
front_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
return &q.data[q.offset]
}
back :: proc(q: ^$Q/Queue($T)) -> T { This will raise a bounds checking error if `i` is an invalid index.
idx := (q.offset+uint(q.len - 1))%builtin.len(q.data) */
return q.data[idx] get_ptr :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> ^T {
} runtime.bounds_check_error_loc(loc, i, int(q.len))
back_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
idx := (q.offset+uint(q.len - 1))%builtin.len(q.data) idx := (uint(i)+q.offset)%builtin.len(q.data)
return &q.data[idx] return &q.data[idx]
} }
/*
Set the element at index `i` to `val`.
This will raise a bounds checking error if `i` is an invalid index.
*/
set :: proc(q: ^$Q/Queue($T), #any_int i: int, val: T, loc := #caller_location) { set :: proc(q: ^$Q/Queue($T), #any_int i: int, val: T, loc := #caller_location) {
runtime.bounds_check_error_loc(loc, i, builtin.len(q.data)) runtime.bounds_check_error_loc(loc, i, int(q.len))
idx := (uint(i)+q.offset)%builtin.len(q.data) idx := (uint(i)+q.offset)%builtin.len(q.data)
q.data[idx] = val q.data[idx] = val
} }
get_ptr :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> ^T {
runtime.bounds_check_error_loc(loc, i, builtin.len(q.data)) /*
Get the element at the front of the queue.
idx := (uint(i)+q.offset)%builtin.len(q.data)
This will raise a bounds checking error if the queue is empty.
*/
front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> T {
when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len > 0, "Queue is empty.", loc)
}
return q.data[q.offset]
}
/*
Get a pointer to the element at the front of the queue.
This will raise a bounds checking error if the queue is empty.
*/
front_ptr :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len > 0, "Queue is empty.", loc)
}
return &q.data[q.offset]
}
/*
Get the element at the back of the queue.
This will raise a bounds checking error if the queue is empty.
*/
back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> T {
when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len > 0, "Queue is empty.", loc)
}
idx := (q.offset+uint(q.len - 1))%builtin.len(q.data)
return q.data[idx]
}
/*
Get a pointer to the element at the back of the queue.
This will raise a bounds checking error if the queue is empty.
*/
back_ptr :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len > 0, "Queue is empty.", loc)
}
idx := (q.offset+uint(q.len - 1))%builtin.len(q.data)
return &q.data[idx] return &q.data[idx]
} }
@(deprecated="Use `front_ptr` instead")
peek_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T { peek_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
runtime.bounds_check_error_loc(loc, 0, builtin.len(q.data)) return front_ptr(q, loc)
idx := q.offset%builtin.len(q.data)
return &q.data[idx]
} }
@(deprecated="Use `back_ptr` instead")
peek_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T { peek_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
runtime.bounds_check_error_loc(loc, int(q.len - 1), builtin.len(q.data)) return back_ptr(q, loc)
idx := (uint(q.len - 1)+q.offset)%builtin.len(q.data)
return &q.data[idx]
} }
// Push an element to the back of the queue /*
Push an element to the back of the queue.
If there is no more space left and allocation fails to get more, this will
return false with an `Allocator_Error`.
Example:
import "base:runtime"
import "core:container/queue"
// This demonstrates typical queue behavior (First-In First-Out).
main :: proc() {
q: queue.Queue(int)
queue.init(&q)
queue.push_back(&q, 1)
queue.push_back(&q, 2)
queue.push_back(&q, 3)
// q.data is now [1, 2, 3, ...]
assert(queue.pop_front(&q) == 1)
assert(queue.pop_front(&q) == 2)
assert(queue.pop_front(&q) == 3)
}
*/
push_back :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error) { push_back :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error) {
if space(q^) == 0 { if space(q^) == 0 {
_grow(q) or_return _grow(q) or_return
@@ -138,27 +279,78 @@ push_back :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocato
return true, nil return true, nil
} }
// Push an element to the front of the queue /*
Push an element to the front of the queue.
If there is no more space left and allocation fails to get more, this will
return false with an `Allocator_Error`.
Example:
import "base:runtime"
import "core:container/queue"
// This demonstrates stack behavior (First-In Last-Out).
main :: proc() {
q: queue.Queue(int)
queue.init(&q)
queue.push_back(&q, 1)
queue.push_back(&q, 2)
queue.push_back(&q, 3)
// q.data is now [1, 2, 3, ...]
assert(queue.pop_back(&q) == 3)
assert(queue.pop_back(&q) == 2)
assert(queue.pop_back(&q) == 1)
}
*/
push_front :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error) { push_front :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error) {
if space(q^) == 0 { if space(q^) == 0 {
_grow(q) or_return _grow(q) or_return
} }
q.offset = uint(q.offset - 1 + builtin.len(q.data)) % builtin.len(q.data) q.offset = uint(q.offset - 1 + builtin.len(q.data)) % builtin.len(q.data)
q.len += 1 q.len += 1
q.data[q.offset] = elem q.data[q.offset] = elem
return true, nil return true, nil
} }
/*
Pop an element from the back of the queue.
// Pop an element from the back of the queue This will raise a bounds checking error if the queue is empty.
Example:
import "base:runtime"
import "core:container/queue"
// This demonstrates stack behavior (First-In Last-Out) at the far end of the data array.
main :: proc() {
q: queue.Queue(int)
queue.init(&q)
queue.push_front(&q, 1)
queue.push_front(&q, 2)
queue.push_front(&q, 3)
// q.data is now [..., 3, 2, 1]
log.infof("%#v", q)
assert(queue.pop_front(&q) == 3)
assert(queue.pop_front(&q) == 2)
assert(queue.pop_front(&q) == 1)
}
*/
pop_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) { pop_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) {
assert(condition=q.len > 0, loc=loc) when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len > 0, "Queue is empty.", loc)
}
q.len -= 1 q.len -= 1
idx := (q.offset+uint(q.len))%builtin.len(q.data) idx := (q.offset+uint(q.len))%builtin.len(q.data)
elem = q.data[idx] elem = q.data[idx]
return return
} }
// Safely pop an element from the back of the queue
/*
Pop an element from the back of the queue if one exists and return true.
Otherwise, return a nil element and false.
*/
pop_back_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) { pop_back_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
if q.len > 0 { if q.len > 0 {
q.len -= 1 q.len -= 1
@@ -169,15 +361,25 @@ pop_back_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
return return
} }
// Pop an element from the front of the queue /*
Pop an element from the front of the queue
This will raise a bounds checking error if the queue is empty.
*/
pop_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) { pop_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) {
assert(condition=q.len > 0, loc=loc) when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len > 0, "Queue is empty.", loc)
}
elem = q.data[q.offset] elem = q.data[q.offset]
q.offset = (q.offset+1)%builtin.len(q.data) q.offset = (q.offset+1)%builtin.len(q.data)
q.len -= 1 q.len -= 1
return return
} }
// Safely pop an element from the front of the queue
/*
Pop an element from the front of the queue if one exists and return true.
Otherwise, return a nil element and false.
*/
pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) { pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
if q.len > 0 { if q.len > 0 {
elem = q.data[q.offset] elem = q.data[q.offset]
@@ -188,13 +390,18 @@ pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
return return
} }
// Push multiple elements to the back of the queue /*
Push many elements at once to the back of the queue.
If there is not enough space left and allocation fails to get more, this will
return false with an `Allocator_Error`.
*/
push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime.Allocator_Error) { push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime.Allocator_Error) {
n := uint(builtin.len(elems)) n := uint(builtin.len(elems))
if space(q^) < int(n) { if space(q^) < int(n) {
_grow(q, q.len + n) or_return _grow(q, q.len + n) or_return
} }
sz := uint(builtin.len(q.data)) sz := uint(builtin.len(q.data))
insert_from := (q.offset + q.len) % sz insert_from := (q.offset + q.len) % sz
insert_to := n insert_to := n
@@ -207,19 +414,31 @@ push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime
return true, nil return true, nil
} }
// Consume `n` elements from the front of the queue /*
Consume `n` elements from the back of the queue.
This will raise a bounds checking error if the queue does not have enough elements.
*/
consume_front :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) { consume_front :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) {
assert(condition=int(q.len) >= n, loc=loc) when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len >= uint(n), "Queue does not have enough elements to consume.", loc)
}
if n > 0 { if n > 0 {
nu := uint(n) nu := uint(n)
q.offset = (q.offset + nu) % builtin.len(q.data) q.offset = (q.offset + nu) % builtin.len(q.data)
q.len -= nu q.len -= nu
} }
} }
// Consume `n` elements from the back of the queue /*
Consume `n` elements from the back of the queue.
This will raise a bounds checking error if the queue does not have enough elements.
*/
consume_back :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) { consume_back :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) {
assert(condition=int(q.len) >= n, loc=loc) when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len >= uint(n), "Queue does not have enough elements to consume.", loc)
}
if n > 0 { if n > 0 {
q.len -= uint(n) q.len -= uint(n)
} }
@@ -231,9 +450,14 @@ append_elem :: push_back
append_elems :: push_back_elems append_elems :: push_back_elems
push :: proc{push_back, push_back_elems} push :: proc{push_back, push_back_elems}
append :: proc{push_back, push_back_elems} append :: proc{push_back, push_back_elems}
enqueue :: push_back
dequeue :: pop_front
// Clear the contents of the queue /*
Reset the queue's length and offset to zero, letting it write new elements over
old memory, in effect clearing the accessible contents.
*/
clear :: proc(q: ^$Q/Queue($T)) { clear :: proc(q: ^$Q/Queue($T)) {
q.len = 0 q.len = 0
q.offset = 0 q.offset = 0
+23
View File
@@ -0,0 +1,23 @@
#+build haiku
#+private
package dynlib
import "base:runtime"
_LIBRARY_FILE_EXTENSION :: ""
_load_library :: proc(path: string, global_symbols: bool, allocator: runtime.Allocator) -> (Library, bool) {
return nil, false
}
_unload_library :: proc(library: Library) -> bool {
return false
}
_symbol_address :: proc(library: Library, symbol: string, allocator: runtime.Allocator) -> (ptr: rawptr, found: bool) {
return nil, false
}
_last_error :: proc() -> string {
return ""
}
+2 -1
View File
@@ -175,7 +175,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
data = bytes.clone(data) data = bytes.clone(data)
} }
t := &Tokenizer{} t := new(Tokenizer)
init(t, string(data), path, error_handler) init(t, string(data), path, error_handler)
doc = new(Document) doc = new(Document)
@@ -403,6 +403,7 @@ destroy :: proc(doc: ^Document) {
} }
delete(doc.strings_to_free) delete(doc.strings_to_free)
free(doc.tokenizer)
free(doc) free(doc)
} }
+2 -2
View File
@@ -19,7 +19,7 @@ SUBTAG_NAME :: "name"
SUBTAG_POS :: "pos" SUBTAG_POS :: "pos"
SUBTAG_REQUIRED :: "required" SUBTAG_REQUIRED :: "required"
SUBTAG_HIDDEN :: "hidden" SUBTAG_HIDDEN :: "hidden"
SUBTAG_VARIADIC :: "variadic" SUBTAG_MANIFOLD :: "manifold"
SUBTAG_FILE :: "file" SUBTAG_FILE :: "file"
SUBTAG_PERMS :: "perms" SUBTAG_PERMS :: "perms"
SUBTAG_INDISTINCT :: "indistinct" SUBTAG_INDISTINCT :: "indistinct"
@@ -28,7 +28,7 @@ TAG_USAGE :: "usage"
UNDOCUMENTED_FLAG :: "<This flag has not been documented yet.>" UNDOCUMENTED_FLAG :: "<This flag has not been documented yet.>"
INTERNAL_VARIADIC_FLAG :: "varg" INTERNAL_OVERFLOW_FLAG :: #config(ODIN_CORE_FLAGS_OVERFLOW_FLAG, "overflow")
RESERVED_HELP_FLAG :: "help" RESERVED_HELP_FLAG :: "help"
RESERVED_HELP_FLAG_SHORT :: "h" RESERVED_HELP_FLAG_SHORT :: "h"
+16 -4
View File
@@ -20,6 +20,17 @@ The format is similar to the Odin binary's way of handling compiler flags.
-<map>:<key>=<value> set map[key] to value -<map>:<key>=<value> set map[key] to value
Unhandled Arguments:
All unhandled positional arguments are placed into the `overflow` field on a
struct, if it exists. In UNIX-style parsing, the existence of a `--` on the
command line will also pass all arguments afterwards into this field.
If desired, the name of the field may be changed from `overflow` to any string
by setting the `ODIN_CORE_FLAGS_OVERFLOW_FLAG` compile-time config option with
`-define:ODIN_CORE_FLAGS_OVERFLOW_FLAG=<name>`.
Struct Tags: Struct Tags:
Users of the `core:encoding/json` package may be familiar with using tags to Users of the `core:encoding/json` package may be familiar with using tags to
@@ -32,7 +43,7 @@ Under the `args` tag, there are the following subtags:
- `pos=N`: place positional argument `N` into this flag. - `pos=N`: place positional argument `N` into this flag.
- `hidden`: hide this flag from the usage documentation. - `hidden`: hide this flag from the usage documentation.
- `required`: cause verification to fail if this argument is not set. - `required`: cause verification to fail if this argument is not set.
- `variadic`: take all remaining arguments when set, UNIX-style only. - `manifold=N`: take several arguments at once, UNIX-style only.
- `file`: for `os.Handle` types, file open mode. - `file`: for `os.Handle` types, file open mode.
- `perms`: for `os.Handle` types, file open permissions. - `perms`: for `os.Handle` types, file open permissions.
- `indistinct`: allow the setting of distinct types by their base type. - `indistinct`: allow the setting of distinct types by their base type.
@@ -47,8 +58,9 @@ you want to require 3 and only 3 arguments in a dynamic array, you would
specify `required=3<4`. specify `required=3<4`.
`variadic` may be given a number (`variadic=N`) above 1 to limit how many extra `manifold` may be given a number (`manifold=N`) above 1 to limit how many extra
arguments it consumes. arguments it consumes at once. If this number is not specified, it will take as
many arguments as can be converted to the underlying element type.
`file` determines the file open mode for an `os.Handle`. `file` determines the file open mode for an `os.Handle`.
@@ -160,7 +172,7 @@ at parse time.
--flag --flag
--flag=argument --flag=argument
--flag argument --flag argument
--flag argument repeating-argument --flag argument (manifold-argument)
`-flag` may also be substituted for `--flag`. `-flag` may also be substituted for `--flag`.
+1 -1
View File
@@ -4,7 +4,7 @@ import "core:os"
Parse_Error_Reason :: enum { Parse_Error_Reason :: enum {
None, None,
// An extra positional argument was given, and there is no `varg` field. // An extra positional argument was given, and there is no `overflow` field.
Extra_Positional, Extra_Positional,
// The underlying type does not support the string value it is being set to. // The underlying type does not support the string value it is being set to.
Bad_Value, Bad_Value,
+3 -3
View File
@@ -107,14 +107,14 @@ main :: proc() {
// assignments: map[string]u8 `args:"name=assign" usage:"Number of jobs per worker."`, // assignments: map[string]u8 `args:"name=assign" usage:"Number of jobs per worker."`,
// (Variadic) Only available in UNIX style: // (Manifold) Only available in UNIX style:
// bots: [dynamic]string `args:"variadic=2,required"`, // bots: [dynamic]string `args:"manifold=2,required"`,
verbose: bool `usage:"Show verbose output."`, verbose: bool `usage:"Show verbose output."`,
debug: bool `args:"hidden" usage:"print debug info"`, debug: bool `args:"hidden" usage:"print debug info"`,
varg: [dynamic]string `usage:"Any extra arguments go here."`, overflow: [dynamic]string `usage:"Any extra arguments go here."`,
} }
opt: Options opt: Options
+4 -4
View File
@@ -33,9 +33,9 @@ push_positional :: #force_no_inline proc (model: ^$T, parser: ^Parser, arg: stri
field, index, has_pos_assigned := get_field_by_pos(model, pos) field, index, has_pos_assigned := get_field_by_pos(model, pos)
if !has_pos_assigned { if !has_pos_assigned {
when intrinsics.type_has_field(T, INTERNAL_VARIADIC_FLAG) { when intrinsics.type_has_field(T, INTERNAL_OVERFLOW_FLAG) {
// Add it to the fallback array. // Add it to the fallback array.
field = reflect.struct_field_by_name(T, INTERNAL_VARIADIC_FLAG) field = reflect.struct_field_by_name(T, INTERNAL_OVERFLOW_FLAG)
} else { } else {
return Parse_Error { return Parse_Error {
.Extra_Positional, .Extra_Positional,
@@ -117,8 +117,8 @@ set_unix_flag :: proc(model: ^$T, parser: ^Parser, name: string) -> (future_args
case runtime.Type_Info_Dynamic_Array: case runtime.Type_Info_Dynamic_Array:
future_args = 1 future_args = 1
if tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok { if tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok {
if length, is_variadic := get_struct_subtag(tag, SUBTAG_VARIADIC); is_variadic { if length, is_manifold := get_struct_subtag(tag, SUBTAG_MANIFOLD); is_manifold {
// Variadic arrays may specify how many arguments they consume at once. // Manifold arrays may specify how many arguments they consume at once.
// Otherwise, they take everything that's left. // Otherwise, they take everything that's left.
if value, value_ok := strconv.parse_u64_of_base(length, 10); value_ok { if value, value_ok := strconv.parse_u64_of_base(length, 10); value_ok {
future_args = cast(int)value future_args = cast(int)value
+1 -1
View File
@@ -95,7 +95,7 @@ parse_one_unix_arg :: proc(model: ^$T, parser: ^Parser, arg: string) -> (
// `--`, and only `--`. // `--`, and only `--`.
// Everything from now on will be treated as an argument. // Everything from now on will be treated as an argument.
future_args = max(int) future_args = max(int)
current_flag = INTERNAL_VARIADIC_FLAG current_flag = INTERNAL_OVERFLOW_FLAG
return return
} }
} }
+13 -8
View File
@@ -59,7 +59,8 @@ validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_
} }
} }
if pos_str, has_pos := get_struct_subtag(args_tag, SUBTAG_POS); has_pos { pos_str, has_pos := get_struct_subtag(args_tag, SUBTAG_POS)
if has_pos {
#partial switch specific_type_info in field.type.variant { #partial switch specific_type_info in field.type.variant {
case runtime.Type_Info_Map: case runtime.Type_Info_Map:
fmt.panicf("%T.%s has `%s` defined, and this does not make sense on a map type.", fmt.panicf("%T.%s has `%s` defined, and this does not make sense on a map type.",
@@ -79,7 +80,7 @@ validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_
fmt.assertf(!reflect.is_boolean(field.type), "%T.%s is a required boolean. This is disallowed.", fmt.assertf(!reflect.is_boolean(field.type), "%T.%s is a required boolean. This is disallowed.",
model_type, field.name, loc = loc) model_type, field.name, loc = loc)
fmt.assertf(field.name != INTERNAL_VARIADIC_FLAG, "%T.%s is defined as required. This is disallowed.", fmt.assertf(field.name != INTERNAL_OVERFLOW_FLAG, "%T.%s is defined as required. This is disallowed.",
model_type, field.name, loc = loc) model_type, field.name, loc = loc)
if len(requirement) > 0 { if len(requirement) > 0 {
@@ -109,24 +110,28 @@ validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_
} }
} }
if length, is_variadic := get_struct_subtag(args_tag, SUBTAG_VARIADIC); is_variadic { if length, is_manifold := get_struct_subtag(args_tag, SUBTAG_MANIFOLD); is_manifold {
fmt.assertf(!has_pos,
"%T.%s has both `%s` and `%s` defined. This is disallowed.\n\tSuggestion: Use a dynamic array field named `%s` to accept unspecified positional arguments.",
model_type, field.name, SUBTAG_POS, SUBTAG_MANIFOLD, INTERNAL_OVERFLOW_FLAG, loc = loc)
if value, parse_ok := strconv.parse_u64_of_base(length, 10); parse_ok { if value, parse_ok := strconv.parse_u64_of_base(length, 10); parse_ok {
fmt.assertf(value > 0, fmt.assertf(value > 0,
"%T.%s has `%s` set to %i. It must be greater than zero.", "%T.%s has `%s` set to %i. It must be greater than zero.",
model_type, field.name, value, SUBTAG_VARIADIC, loc = loc) model_type, field.name, value, SUBTAG_MANIFOLD, loc = loc)
fmt.assertf(value != 1, fmt.assertf(value != 1,
"%T.%s has `%s` set to 1. This has no effect.", "%T.%s has `%s` set to 1. This is equivalent to not defining `%s`.",
model_type, field.name, SUBTAG_VARIADIC, loc = loc) model_type, field.name, SUBTAG_MANIFOLD, SUBTAG_MANIFOLD, loc = loc)
} }
#partial switch specific_type_info in field.type.variant { #partial switch specific_type_info in field.type.variant {
case runtime.Type_Info_Dynamic_Array: case runtime.Type_Info_Dynamic_Array:
fmt.assertf(style != .Odin, fmt.assertf(style != .Odin,
"%T.%s has `%s` defined, but this only makes sense in UNIX-style parsing mode.", "%T.%s has `%s` defined, but this only makes sense in UNIX-style parsing mode.",
model_type, field.name, SUBTAG_VARIADIC, loc = loc) model_type, field.name, SUBTAG_MANIFOLD, loc = loc)
case: case:
fmt.panicf("%T.%s has `%s` defined, but this only makes sense on dynamic arrays.", fmt.panicf("%T.%s has `%s` defined, but this only makes sense on dynamic arrays.",
model_type, field.name, SUBTAG_VARIADIC, loc = loc) model_type, field.name, SUBTAG_MANIFOLD, loc = loc)
} }
} }
+2 -2
View File
@@ -6,7 +6,7 @@ package flags
Parsing_Style :: enum { Parsing_Style :: enum {
// Odin-style: `-flag`, `-flag:option`, `-map:key=value` // Odin-style: `-flag`, `-flag:option`, `-map:key=value`
Odin, Odin,
// UNIX-style: `-flag` or `--flag`, `--flag=argument`, `--flag argument repeating-argument` // UNIX-style: `-flag` or `--flag`, `--flag=argument`, `--flag argument (manifold-argument)`
Unix, Unix,
} }
@@ -61,7 +61,7 @@ parse :: proc(
} }
case .Unix: case .Unix:
// Support for `-flag argument (repeating-argument ...)` // Support for `-flag argument (manifold-argument ...)`
future_args: int future_args: int
current_flag: string current_flag: string
+15 -15
View File
@@ -30,18 +30,18 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
is_positional: bool, is_positional: bool,
is_required: bool, is_required: bool,
is_boolean: bool, is_boolean: bool,
is_variadic: bool, is_manifold: bool,
variadic_length: int, manifold_length: int,
} }
// //
// POSITIONAL+REQUIRED, POSITIONAL, REQUIRED, NON_REQUIRED+NON_POSITIONAL, ... // POSITIONAL+REQUIRED, POSITIONAL, REQUIRED, NON_REQUIRED+NON_POSITIONAL, ...
// //
sort_flags :: proc(i, j: Flag) -> slice.Ordering { sort_flags :: proc(i, j: Flag) -> slice.Ordering {
// `varg` goes to the end. // `overflow` goes to the end.
if i.name == INTERNAL_VARIADIC_FLAG { if i.name == INTERNAL_OVERFLOW_FLAG {
return .Greater return .Greater
} else if j.name == INTERNAL_VARIADIC_FLAG { } else if j.name == INTERNAL_OVERFLOW_FLAG {
return .Less return .Less
} }
@@ -120,10 +120,10 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
flag.is_required = true flag.is_required = true
flag.required_min, flag.required_max, _ = parse_requirements(requirement) flag.required_min, flag.required_max, _ = parse_requirements(requirement)
} }
if length_str, is_variadic := get_struct_subtag(args_tag, SUBTAG_VARIADIC); is_variadic { if length_str, is_manifold := get_struct_subtag(args_tag, SUBTAG_MANIFOLD); is_manifold {
flag.is_variadic = true flag.is_manifold = true
if length, parse_ok := strconv.parse_u64_of_base(length_str, 10); parse_ok { if length, parse_ok := strconv.parse_u64_of_base(length_str, 10); parse_ok {
flag.variadic_length = cast(int)length flag.manifold_length = cast(int)length
} }
} }
} }
@@ -147,15 +147,15 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
case runtime.Type_Info_Dynamic_Array: case runtime.Type_Info_Dynamic_Array:
requirement_spec := describe_array_requirements(flag) requirement_spec := describe_array_requirements(flag)
if flag.is_variadic || flag.name == INTERNAL_VARIADIC_FLAG { if flag.is_manifold || flag.name == INTERNAL_OVERFLOW_FLAG {
if flag.variadic_length == 0 { if flag.manifold_length == 0 {
flag.type_description = fmt.tprintf("<%v, ...>%s", flag.type_description = fmt.tprintf("<%v, ...>%s",
specific_type_info.elem.id, specific_type_info.elem.id,
requirement_spec) requirement_spec)
} else { } else {
flag.type_description = fmt.tprintf("<%v, %i at once>%s", flag.type_description = fmt.tprintf("<%v, %i at once>%s",
specific_type_info.elem.id, specific_type_info.elem.id,
flag.variadic_length, flag.manifold_length,
requirement_spec) requirement_spec)
} }
} else { } else {
@@ -177,7 +177,7 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
} }
} }
if flag.name == INTERNAL_VARIADIC_FLAG { if flag.name == INTERNAL_OVERFLOW_FLAG {
flag.full_length = len(flag.type_description) flag.full_length = len(flag.type_description)
} else if flag.is_boolean { } else if flag.is_boolean {
flag.full_length = len(flag_prefix) + len(flag.name) + len(flag.type_description) flag.full_length = len(flag_prefix) + len(flag.name) + len(flag.type_description)
@@ -201,13 +201,13 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
strings.write_string(&builder, program) strings.write_string(&builder, program)
for flag in visible_flags { for flag in visible_flags {
if keep_it_short && !(flag.is_required || flag.is_positional || flag.name == INTERNAL_VARIADIC_FLAG) { if keep_it_short && !(flag.is_required || flag.is_positional || flag.name == INTERNAL_OVERFLOW_FLAG) {
continue continue
} }
strings.write_byte(&builder, ' ') strings.write_byte(&builder, ' ')
if flag.name == INTERNAL_VARIADIC_FLAG { if flag.name == INTERNAL_OVERFLOW_FLAG {
strings.write_string(&builder, "...") strings.write_string(&builder, "...")
continue continue
} }
@@ -252,7 +252,7 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
strings.write_byte(&builder, '\t') strings.write_byte(&builder, '\t')
if flag.name == INTERNAL_VARIADIC_FLAG { if flag.name == INTERNAL_OVERFLOW_FLAG {
strings.write_string(&builder, flag.type_description) strings.write_string(&builder, flag.type_description)
} else { } else {
strings.write_string(&builder, flag_prefix) strings.write_string(&builder, flag_prefix)
+2 -3
View File
@@ -160,6 +160,5 @@ destroy :: proc {
int_destroy :: proc(integers: ..^Int) int_destroy :: proc(integers: ..^Int)
*/ */
int_destroy, int_destroy,
} internal_rat_destroy,
}
+2 -2
View File
@@ -1660,13 +1660,13 @@ internal_int_sqrt :: proc(dest, src: ^Int, allocator := context.allocator) -> (e
if internal_gte(y, x) { if internal_gte(y, x) {
internal_swap(dest, x) internal_swap(dest, x)
return nil return internal_clamp(dest)
} }
internal_swap(x, y) internal_swap(x, y)
} }
internal_swap(dest, x) internal_swap(dest, x)
return err return internal_clamp(dest)
} }
internal_sqrt :: proc { internal_int_sqrt, } internal_sqrt :: proc { internal_int_sqrt, }
+1 -1
View File
@@ -310,7 +310,7 @@ int_atoi :: proc(res: ^Int, input: string, radix := i8(10), allocator := context
res.sign = sign res.sign = sign
} }
return nil return internal_clamp(res)
} }
+5 -3
View File
@@ -157,6 +157,8 @@ internal_rat_norm :: proc(z: ^Rat, allocator := context.allocator) -> (err: Erro
z.b.sign = .Zero_or_Positive z.b.sign = .Zero_or_Positive
f := &Int{} f := &Int{}
defer internal_int_destroy(f)
internal_int_gcd(f, &z.a, &z.b) or_return internal_int_gcd(f, &z.a, &z.b) or_return
if !internal_int_equals_digit(f, 1) { if !internal_int_equals_digit(f, 1) {
f.sign = .Zero_or_Positive f.sign = .Zero_or_Positive
@@ -378,9 +380,6 @@ internal_rat_to_float :: proc($T: typeid, z: ^Rat, allocator := context.allocato
} }
has_sign := a.sign != b.sign has_sign := a.sign != b.sign
defer if has_sign {
f = -builtin.abs(f)
}
exp := alen - blen exp := alen - blen
a2, b2 := &Int{}, &Int{} a2, b2 := &Int{}, &Int{}
@@ -440,6 +439,9 @@ internal_rat_to_float :: proc($T: typeid, z: ^Rat, allocator := context.allocato
if math.is_inf(f, 0) { if math.is_inf(f, 0) {
exact = false exact = false
} }
if has_sign {
f = -builtin.abs(f)
}
return return
} }
+412 -248
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -1,7 +1,7 @@
package mem package mem
import "base:runtime" import "base:runtime"
import "base:sanitizer" // import "base:sanitizer"
/* /*
Rollback stack default block size. Rollback stack default block size.
@@ -134,7 +134,7 @@ rb_free_all :: proc(stack: ^Rollback_Stack) {
stack.head.next_block = nil stack.head.next_block = nil
stack.head.last_alloc = nil stack.head.last_alloc = nil
stack.head.offset = 0 stack.head.offset = 0
sanitizer.address_poison(stack.head.buffer) // sanitizer.address_poison(stack.head.buffer)
} }
/* /*
@@ -241,7 +241,7 @@ rb_alloc_bytes_non_zeroed :: proc(
block.offset = cast(uintptr)len(block.buffer) block.offset = cast(uintptr)len(block.buffer)
} }
res := ptr[:size] res := ptr[:size]
sanitizer.address_unpoison(res) // sanitizer.address_unpoison(res)
return res, nil return res, nil
} }
return nil, .Out_Of_Memory return nil, .Out_Of_Memory
@@ -338,7 +338,7 @@ rb_resize_bytes_non_zeroed :: proc(
block.offset += cast(uintptr)size - cast(uintptr)old_size block.offset += cast(uintptr)size - cast(uintptr)old_size
} }
res := (ptr)[:size] res := (ptr)[:size]
sanitizer.address_unpoison(res) // sanitizer.address_unpoison(res)
#no_bounds_check return res, nil #no_bounds_check return res, nil
} }
} }
+15 -14
View File
@@ -10,7 +10,7 @@
package mem_tlsf package mem_tlsf
import "base:intrinsics" import "base:intrinsics"
import "base:sanitizer" // import "base:sanitizer"
import "base:runtime" import "base:runtime"
// log2 of number of linear subdivisions of block sizes. // log2 of number of linear subdivisions of block sizes.
@@ -210,7 +210,7 @@ alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) ->
return nil, .Out_Of_Memory return nil, .Out_Of_Memory
} }
sanitizer.address_poison(new_pool_buf) // sanitizer.address_poison(new_pool_buf)
// Allocate a new link in the `control.pool` tracking structure. // Allocate a new link in the `control.pool` tracking structure.
new_pool := new_clone(Pool{ new_pool := new_clone(Pool{
@@ -257,7 +257,7 @@ alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) ->
return block_prepare_used(control, block, adjust) return block_prepare_used(control, block, adjust)
} }
@(private, require_results, no_sanitize_address) @(private, require_results)
alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) { alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) {
res, err = alloc_bytes_non_zeroed(control, size, align) res, err = alloc_bytes_non_zeroed(control, size, align)
if err == nil { if err == nil {
@@ -267,6 +267,7 @@ alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byt
} }
@(no_sanitize_address)
free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) { free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) {
assert(control != nil) assert(control != nil)
// `size` is currently ignored // `size` is currently ignored
@@ -276,7 +277,7 @@ free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) {
block := block_from_ptr(ptr) block := block_from_ptr(ptr)
assert(!block_is_free(block), "block already marked as free") // double free assert(!block_is_free(block), "block already marked as free") // double free
sanitizer.address_poison(ptr, block.size) // sanitizer.address_poison(ptr, block.size)
block_mark_as_free(block) block_mark_as_free(block)
block = block_merge_prev(control, block) block = block_merge_prev(control, block)
block = block_merge_next(control, block) block = block_merge_next(control, block)
@@ -320,7 +321,7 @@ resize :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, align
block_trim_used(control, block, adjust) block_trim_used(control, block, adjust)
res = ([^]byte)(ptr)[:new_size] res = ([^]byte)(ptr)[:new_size]
sanitizer.address_unpoison(res) // sanitizer.address_unpoison(res)
if min_size < new_size { if min_size < new_size {
to_zero := ([^]byte)(ptr)[min_size:new_size] to_zero := ([^]byte)(ptr)[min_size:new_size]
@@ -483,19 +484,19 @@ block_mark_as_used :: proc(block: ^Block_Header) {
block_set_used(block) block_set_used(block)
} }
@(private, require_results, no_sanitize_address) @(private, require_results)
align_up :: proc(x, align: uint) -> (aligned: uint) { align_up :: proc(x, align: uint) -> (aligned: uint) {
assert(0 == (align & (align - 1)), "must align to a power of two") assert(0 == (align & (align - 1)), "must align to a power of two")
return (x + (align - 1)) &~ (align - 1) return (x + (align - 1)) &~ (align - 1)
} }
@(private, require_results, no_sanitize_address) @(private, require_results)
align_down :: proc(x, align: uint) -> (aligned: uint) { align_down :: proc(x, align: uint) -> (aligned: uint) {
assert(0 == (align & (align - 1)), "must align to a power of two") assert(0 == (align & (align - 1)), "must align to a power of two")
return x - (x & (align - 1)) return x - (x & (align - 1))
} }
@(private, require_results, no_sanitize_address) @(private, require_results)
align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) { align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) {
assert(0 == (align & (align - 1)), "must align to a power of two") assert(0 == (align & (align - 1)), "must align to a power of two")
align_mask := uintptr(align) - 1 align_mask := uintptr(align) - 1
@@ -505,7 +506,7 @@ align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) {
} }
// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum. // Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
@(private, require_results, no_sanitize_address) @(private, require_results)
adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) { adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) {
if size == 0 { if size == 0 {
return 0 return 0
@@ -519,7 +520,7 @@ adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) {
} }
// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum. // Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
@(private, require_results, no_sanitize_address) @(private, require_results)
adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: runtime.Allocator_Error) { adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: runtime.Allocator_Error) {
if size == 0 { if size == 0 {
return 0, nil return 0, nil
@@ -537,7 +538,7 @@ adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err:
// TLSF utility functions. In most cases these are direct translations of // TLSF utility functions. In most cases these are direct translations of
// the documentation in the research paper. // the documentation in the research paper.
@(optimization_mode="favor_size", private, require_results, no_sanitize_address) @(optimization_mode="favor_size", private, require_results)
mapping_insert :: proc(size: uint) -> (fl, sl: i32) { mapping_insert :: proc(size: uint) -> (fl, sl: i32) {
if size < SMALL_BLOCK_SIZE { if size < SMALL_BLOCK_SIZE {
// Store small blocks in first list. // Store small blocks in first list.
@@ -550,7 +551,7 @@ mapping_insert :: proc(size: uint) -> (fl, sl: i32) {
return return
} }
@(optimization_mode="favor_size", private, require_results, no_sanitize_address) @(optimization_mode="favor_size", private, require_results)
mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) { mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) {
rounded = size rounded = size
if size >= SMALL_BLOCK_SIZE { if size >= SMALL_BLOCK_SIZE {
@@ -561,7 +562,7 @@ mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) {
} }
// This version rounds up to the next block size (for allocations) // This version rounds up to the next block size (for allocations)
@(optimization_mode="favor_size", private, require_results, no_sanitize_address) @(optimization_mode="favor_size", private, require_results)
mapping_search :: proc(size: uint) -> (fl, sl: i32) { mapping_search :: proc(size: uint) -> (fl, sl: i32) {
return mapping_insert(mapping_round(size)) return mapping_insert(mapping_round(size))
} }
@@ -788,7 +789,7 @@ block_prepare_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint
block_trim_free(control, block, size) block_trim_free(control, block, size)
block_mark_as_used(block) block_mark_as_used(block)
res = ([^]byte)(block_to_ptr(block))[:size] res = ([^]byte)(block_to_ptr(block))[:size]
sanitizer.address_unpoison(res) // sanitizer.address_unpoison(res)
} }
return return
} }
+12 -12
View File
@@ -3,7 +3,7 @@ package mem_virtual
import "core:mem" import "core:mem"
import "core:sync" import "core:sync"
import "base:sanitizer" // import "base:sanitizer"
Arena_Kind :: enum uint { Arena_Kind :: enum uint {
Growing = 0, // Chained memory blocks (singly linked list). Growing = 0, // Chained memory blocks (singly linked list).
@@ -55,7 +55,7 @@ arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING
if arena.minimum_block_size == 0 { if arena.minimum_block_size == 0 {
arena.minimum_block_size = reserved arena.minimum_block_size = reserved
} }
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed]) // sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
return return
} }
@@ -68,7 +68,7 @@ arena_init_static :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_STATIC_R
arena.curr_block = memory_block_alloc(commit_size, reserved, {}) or_return arena.curr_block = memory_block_alloc(commit_size, reserved, {}) or_return
arena.total_used = 0 arena.total_used = 0
arena.total_reserved = arena.curr_block.reserved arena.total_reserved = arena.curr_block.reserved
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed]) // sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
return return
} }
@@ -82,7 +82,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
arena.kind = .Buffer arena.kind = .Buffer
sanitizer.address_poison(buffer[:]) // sanitizer.address_poison(buffer[:])
block_base := raw_data(buffer) block_base := raw_data(buffer)
block := (^Memory_Block)(block_base) block := (^Memory_Block)(block_base)
@@ -163,7 +163,7 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l
arena.total_used = arena.curr_block.used arena.total_used = arena.curr_block.used
} }
sanitizer.address_unpoison(data) // sanitizer.address_unpoison(data)
return return
} }
@@ -182,7 +182,7 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location)
mem.zero_slice(arena.curr_block.base[arena.curr_block.used:][:prev_pos-pos]) mem.zero_slice(arena.curr_block.base[arena.curr_block.used:][:prev_pos-pos])
} }
arena.total_used = arena.curr_block.used arena.total_used = arena.curr_block.used
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed]) // sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
return true return true
} else if pos == 0 { } else if pos == 0 {
arena.total_used = 0 arena.total_used = 0
@@ -200,7 +200,7 @@ arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_locat
arena.total_reserved -= free_block.reserved arena.total_reserved -= free_block.reserved
arena.curr_block = free_block.prev arena.curr_block = free_block.prev
sanitizer.address_poison(free_block.base[:free_block.committed]) // sanitizer.address_poison(free_block.base[:free_block.committed])
memory_block_dealloc(free_block) memory_block_dealloc(free_block)
} }
} }
@@ -219,9 +219,9 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
if arena.curr_block != nil { if arena.curr_block != nil {
curr_block_used := int(arena.curr_block.used) curr_block_used := int(arena.curr_block.used)
arena.curr_block.used = 0 arena.curr_block.used = 0
sanitizer.address_unpoison(arena.curr_block.base[:curr_block_used]) // sanitizer.address_unpoison(arena.curr_block.base[:curr_block_used])
mem.zero(arena.curr_block.base, curr_block_used) mem.zero(arena.curr_block.base, curr_block_used)
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed]) // sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
} }
arena.total_used = 0 arena.total_used = 0
case .Static, .Buffer: case .Static, .Buffer:
@@ -349,7 +349,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
if size < old_size { if size < old_size {
// shrink data in-place // shrink data in-place
data = old_data[:size] data = old_data[:size]
sanitizer.address_poison(old_data[size:old_size]) // sanitizer.address_poison(old_data[size:old_size])
return return
} }
@@ -363,7 +363,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
_ = alloc_from_memory_block(block, new_end - old_end, 1, default_commit_size=arena.default_commit_size) or_return _ = alloc_from_memory_block(block, new_end - old_end, 1, default_commit_size=arena.default_commit_size) or_return
arena.total_used += block.used - prev_used arena.total_used += block.used - prev_used
data = block.base[start:new_end] data = block.base[start:new_end]
sanitizer.address_unpoison(data) // sanitizer.address_unpoison(data)
return return
} }
} }
@@ -374,7 +374,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
return return
} }
copy(new_memory, old_data[:old_size]) copy(new_memory, old_data[:old_size])
sanitizer.address_poison(old_data[:old_size]) // sanitizer.address_poison(old_data[:old_size])
return new_memory, nil return new_memory, nil
case .Query_Features: case .Query_Features:
set := (^mem.Allocator_Mode_Set)(old_memory) set := (^mem.Allocator_Mode_Set)(old_memory)
+5 -5
View File
@@ -2,7 +2,7 @@ package mem_virtual
import "core:mem" import "core:mem"
import "base:intrinsics" import "base:intrinsics"
import "base:sanitizer" // import "base:sanitizer"
import "base:runtime" import "base:runtime"
_ :: runtime _ :: runtime
@@ -22,7 +22,7 @@ reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Erro
@(no_sanitize_address) @(no_sanitize_address)
commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error { commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
sanitizer.address_unpoison(data, size) // sanitizer.address_unpoison(data, size)
return _commit(data, size) return _commit(data, size)
} }
@@ -35,13 +35,13 @@ reserve_and_commit :: proc "contextless" (size: uint) -> (data: []byte, err: All
@(no_sanitize_address) @(no_sanitize_address)
decommit :: proc "contextless" (data: rawptr, size: uint) { decommit :: proc "contextless" (data: rawptr, size: uint) {
sanitizer.address_poison(data, size) // sanitizer.address_poison(data, size)
_decommit(data, size) _decommit(data, size)
} }
@(no_sanitize_address) @(no_sanitize_address)
release :: proc "contextless" (data: rawptr, size: uint) { release :: proc "contextless" (data: rawptr, size: uint) {
sanitizer.address_unpoison(data, size) // sanitizer.address_unpoison(data, size)
_release(data, size) _release(data, size)
} }
@@ -179,7 +179,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint,
data = block.base[block.used+alignment_offset:][:min_size] data = block.base[block.used+alignment_offset:][:min_size]
block.used += size block.used += size
sanitizer.address_unpoison(data) // sanitizer.address_unpoison(data)
return return
} }
+4
View File
@@ -64,6 +64,7 @@ Network_Error :: union #shared_nil {
UDP_Recv_Error, UDP_Recv_Error,
Shutdown_Error, Shutdown_Error,
Interfaces_Error, Interfaces_Error,
Socket_Info_Error,
Socket_Option_Error, Socket_Option_Error,
Set_Blocking_Error, Set_Blocking_Error,
Parse_Endpoint_Error, Parse_Endpoint_Error,
@@ -260,6 +261,9 @@ DNS_Configuration :: struct {
resolv_conf: string, resolv_conf: string,
hosts_file: string, hosts_file: string,
resolv_conf_buf: [128]u8,
hosts_file_buf: [128]u8,
// TODO: Allow loading these up with `reload_configuration()` call or the like, // TODO: Allow loading these up with `reload_configuration()` call or the like,
// so we don't have to do it each call. // so we don't have to do it each call.
name_servers: []Endpoint, name_servers: []Endpoint,
+29 -47
View File
@@ -22,69 +22,43 @@ package net
Haesbaert: Security fixes Haesbaert: Security fixes
*/ */
@(require) import "base:runtime"
import "core:mem" import "core:mem"
import "core:strings" import "core:strings"
import "core:time" import "core:time"
import "core:os" import "core:os"
import "core:math/rand" import "core:math/rand"
/* @(require) import "core:sync"
Default configuration for DNS resolution.
*/ dns_config_initialized: sync.Once
when ODIN_OS == .Windows { when ODIN_OS == .Windows {
DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{ dns_configuration := DNS_Configuration{
resolv_conf = "", resolv_conf = "",
hosts_file = "%WINDIR%\\system32\\drivers\\etc\\hosts", hosts_file = "%WINDIR%\\system32\\drivers\\etc\\hosts",
} }
} else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { } else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{ dns_configuration := DNS_Configuration{
resolv_conf = "/etc/resolv.conf", resolv_conf = "/etc/resolv.conf",
hosts_file = "/etc/hosts", hosts_file = "/etc/hosts",
} }
} else { } else {
#panic("Please add a configuration for this OS.") #panic("Please add a configuration for this OS.")
} }
@(init) /*
Replaces environment placeholders in `dns_configuration`. Only necessary on Windows.
Is automatically called, once, by `get_dns_records_*`.
*/
@(private)
init_dns_configuration :: proc() { init_dns_configuration :: proc() {
/* when ODIN_OS == .Windows {
Resolve %ENVIRONMENT% placeholders in their paths. runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
*/ val := os.replace_environment_placeholders(dns_configuration.hosts_file, context.temp_allocator)
dns_configuration.resolv_conf, _ = replace_environment_path(dns_configuration.resolv_conf) copy(dns_configuration.hosts_file_buf[:], val)
dns_configuration.hosts_file, _ = replace_environment_path(dns_configuration.hosts_file) dns_configuration.hosts_file = string(dns_configuration.hosts_file_buf[:len(val)])
}
@(fini, private)
destroy_dns_configuration :: proc() {
delete(dns_configuration.resolv_conf)
dns_configuration.resolv_conf = ""
delete(dns_configuration.hosts_file)
dns_configuration.hosts_file = ""
}
dns_configuration := DEFAULT_DNS_CONFIGURATION
// Always allocates for consistency.
replace_environment_path :: proc(path: string, allocator := context.allocator) -> (res: string, ok: bool) {
// Nothing to replace. Return a clone of the original.
if strings.count(path, "%") != 2 {
return strings.clone(path, allocator), true
} }
left := strings.index(path, "%") + 1
assert(left > 0 && left <= len(path)) // should be covered by there being two %
right := strings.index(path[left:], "%") + 1
assert(right > 0 && right <= len(path)) // should be covered by there being two %
env_key := path[left: right]
env_val := os.get_env(env_key, allocator)
defer delete(env_val)
res, _ = strings.replace(path, path[left - 1: right + 1], env_val, 1, allocator)
return res, true
} }
/* /*
Resolves a hostname to exactly one IP4 and IP6 endpoint. Resolves a hostname to exactly one IP4 and IP6 endpoint.
It's then up to you which one you use. It's then up to you which one you use.
@@ -204,6 +178,9 @@ resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Net
See `destroy_records`. See `destroy_records`.
*/ */
get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
when ODIN_OS == .Windows {
sync.once_do(&dns_config_initialized, init_dns_configuration)
}
return _get_dns_records_os(hostname, type, allocator) return _get_dns_records_os(hostname, type, allocator)
} }
@@ -219,6 +196,9 @@ get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocat
See `destroy_records`. See `destroy_records`.
*/ */
get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type, name_servers: []Endpoint, host_overrides: []DNS_Record, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type, name_servers: []Endpoint, host_overrides: []DNS_Record, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
when ODIN_OS == .Windows {
sync.once_do(&dns_config_initialized, init_dns_configuration)
}
context.allocator = allocator context.allocator = allocator
if type != .SRV { if type != .SRV {
@@ -440,6 +420,8 @@ load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> (
splits := strings.fields(line) splits := strings.fields(line)
defer delete(splits) defer delete(splits)
(len(splits) >= 2) or_continue
ip_str := splits[0] ip_str := splits[0]
addr := parse_address(ip_str) addr := parse_address(ip_str)
if addr == nil { if addr == nil {
@@ -886,4 +868,4 @@ parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator
xid = hdr.id xid = hdr.id
return _records[:], xid, true return _records[:], xid, true
} }
+1 -1
View File
@@ -79,4 +79,4 @@ _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator :
} }
return get_dns_records_from_nameservers(hostname, type, name_servers, host_overrides[:]) return get_dns_records_from_nameservers(hostname, type, name_servers, host_overrides[:])
} }
+17
View File
@@ -246,6 +246,23 @@ Shutdown_Error :: enum i32 {
Unknown, Unknown,
} }
Socket_Info_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Socket is invalid or not connected, or the manner given is invalid.
Invalid_Argument,
// The socket is valid, but unsupported by this opperation.
Unsupported_Socket,
// Connection was closed/aborted/shutdown.
Connection_Closed,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
Socket_Option_Error :: enum i32 { Socket_Option_Error :: enum i32 {
None, None,
// No network connection, or the network stack is not initialized. // No network connection, or the network stack is not initialized.
+17
View File
@@ -226,6 +226,23 @@ _shutdown_error :: proc() -> Shutdown_Error {
} }
} }
_socket_info_error :: proc() -> Socket_Info_Error {
#partial switch posix.errno() {
case .EBADF, .ENOTSOCK:
return .Invalid_Argument
case .ENOTCONN:
return .Network_Unreachable
case .EOPNOTSUPP:
return .Unsupported_Socket
case .EINVAL:
return .Connection_Closed
case .ENOBUFS:
return .Insufficient_Resources
case:
return .Unknown
}
}
_socket_option_error :: proc() -> Socket_Option_Error { _socket_option_error :: proc() -> Socket_Option_Error {
#partial switch posix.errno() { #partial switch posix.errno() {
case .ENOBUFS: case .ENOBUFS:
+18
View File
@@ -255,6 +255,24 @@ _shutdown_error :: proc(errno: freebsd.Errno) -> Shutdown_Error {
} }
} }
_socket_info_error :: proc(errno: freebsd.Errno) -> Socket_Info_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .ENOTSOCK, .EINVAL, .EFAULT:
return .Invalid_Argument
case .ENOTCONN:
return .Network_Unreachable
case .ECONNRESET:
return .Connection_Closed
case .ENOBUFS:
return .Insufficient_Resources
case:
return .Unknown
}
}
_socket_option_error :: proc(errno: freebsd.Errno) -> Socket_Option_Error { _socket_option_error :: proc(errno: freebsd.Errno) -> Socket_Option_Error {
assert(errno != nil) assert(errno != nil)
_last_error = errno _last_error = errno
+16
View File
@@ -258,6 +258,22 @@ _shutdown_error :: proc(errno: linux.Errno) -> Shutdown_Error {
} }
} }
_socket_info_error :: proc(errno: linux.Errno) -> Socket_Info_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .ENOTSOCK, .EFAULT, .EINVAL:
return .Invalid_Argument
case .ENOTCONN:
return .Network_Unreachable
case .ENOBUFS:
return .Insufficient_Resources
case:
return .Unknown
}
}
_socket_option_error :: proc(errno: linux.Errno) -> Socket_Option_Error { _socket_option_error :: proc(errno: linux.Errno) -> Socket_Option_Error {
assert(errno != nil) assert(errno != nil)
_last_error = errno _last_error = errno
+7
View File
@@ -18,3 +18,10 @@ _last_platform_error_string :: proc() -> string {
_set_last_platform_error :: proc(err: i32) { _set_last_platform_error :: proc(err: i32) {
_last_error = err _last_error = err
} }
Parse_Endpoint_Error :: enum u32 {
None = 0,
Bad_Port = 1,
Bad_Address,
Bad_Hostname,
}
+11
View File
@@ -234,6 +234,17 @@ _shutdown_error :: proc() -> Shutdown_Error {
} }
} }
_socket_info_error :: proc() -> Socket_Info_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSAEFAULT, .WSAEINPROGRESS, .WSAENOTSOCK, .WSAEINVAL:
return .Invalid_Argument
case .WSANOTINITIALISED, .WSAENETDOWN, .WSAENOTCONN:
return .Network_Unreachable
case:
return .Unknown
}
}
_socket_option_error :: proc() -> Socket_Option_Error { _socket_option_error :: proc() -> Socket_Option_Error {
#partial switch win.System_Error(win.WSAGetLastError()) { #partial switch win.System_Error(win.WSAGetLastError()) {
case .WSAENETDOWN, .WSANOTINITIALISED: case .WSAENETDOWN, .WSANOTINITIALISED:
+8 -1
View File
@@ -174,10 +174,17 @@ listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TC
/* /*
Returns the endpoint that the given socket is listening / bound on. Returns the endpoint that the given socket is listening / bound on.
*/ */
bound_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Listen_Error) { bound_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Socket_Info_Error) {
return _bound_endpoint(socket) return _bound_endpoint(socket)
} }
/*
Returns the endpoint that the given socket is connected to. (Peer's endpoint)
*/
peer_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Socket_Info_Error) {
return _peer_endpoint(socket)
}
accept_tcp :: proc(socket: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) { accept_tcp :: proc(socket: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) {
return _accept_tcp(socket, options) return _accept_tcp(socket, options)
} }
+15 -2
View File
@@ -137,11 +137,24 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_
} }
@(private) @(private)
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) { _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
addr: posix.sockaddr_storage addr: posix.sockaddr_storage
addr_len := posix.socklen_t(size_of(addr)) addr_len := posix.socklen_t(size_of(addr))
if posix.getsockname(posix.FD(any_socket_to_socket(sock)), (^posix.sockaddr)(&addr), &addr_len) != .OK { if posix.getsockname(posix.FD(any_socket_to_socket(sock)), (^posix.sockaddr)(&addr), &addr_len) != .OK {
err = _listen_error() err = _socket_info_error()
return
}
ep = _sockaddr_to_endpoint(&addr)
return
}
@(private)
_peer_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
addr: posix.sockaddr_storage
addr_len := posix.socklen_t(size_of(addr))
if posix.getpeername(posix.FD(any_socket_to_socket(sock)), (^posix.sockaddr)(&addr), &addr_len) != .OK {
err = _socket_info_error()
return return
} }
+16 -2
View File
@@ -140,12 +140,26 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
} }
@(private) @(private)
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) { _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
sockaddr: freebsd.Socket_Address_Storage sockaddr: freebsd.Socket_Address_Storage
errno := freebsd.getsockname(cast(Fd)any_socket_to_socket(sock), &sockaddr) errno := freebsd.getsockname(cast(Fd)any_socket_to_socket(sock), &sockaddr)
if errno != nil { if errno != nil {
err = _listen_error(errno) err = _socket_info_error(errno)
return
}
ep = _sockaddr_to_endpoint(&sockaddr)
return
}
@(private)
_peer_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
sockaddr: freebsd.Socket_Address_Storage
errno := freebsd.getpeername(cast(Fd)any_socket_to_socket(sock), &sockaddr)
if errno != nil {
err = _socket_info_error(errno)
return return
} }
+15 -2
View File
@@ -218,11 +218,24 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket,
} }
@(private) @(private)
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) { _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
addr: linux.Sock_Addr_Any addr: linux.Sock_Addr_Any
errno := linux.getsockname(_unwrap_os_socket(sock), &addr) errno := linux.getsockname(_unwrap_os_socket(sock), &addr)
if errno != .NONE { if errno != .NONE {
err = _listen_error(errno) err = _socket_info_error(errno)
return
}
ep = _wrap_os_addr(addr)
return
}
@(private)
_peer_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
addr: linux.Sock_Addr_Any
errno := linux.getpeername(_unwrap_os_socket(sock), &addr)
if errno != .NONE {
err = _socket_info_error(errno)
return return
} }
+16 -2
View File
@@ -177,11 +177,25 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
} }
@(private) @(private)
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) { _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
sockaddr: win.SOCKADDR_STORAGE_LH sockaddr: win.SOCKADDR_STORAGE_LH
sockaddrlen := c.int(size_of(sockaddr)) sockaddrlen := c.int(size_of(sockaddr))
if win.getsockname(win.SOCKET(any_socket_to_socket(sock)), &sockaddr, &sockaddrlen) == win.SOCKET_ERROR { if win.getsockname(win.SOCKET(any_socket_to_socket(sock)), &sockaddr, &sockaddrlen) == win.SOCKET_ERROR {
err = _listen_error() err = _socket_info_error()
return
}
ep = _sockaddr_to_endpoint(&sockaddr)
return
}
@(private)
_peer_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
sockaddr: win.SOCKADDR_STORAGE_LH
sockaddrlen := c.int(size_of(sockaddr))
res := win.getpeername(win.SOCKET(any_socket_to_socket(sock)), &sockaddr, &sockaddrlen)
if res < 0 {
err = _socket_info_error()
return return
} }
+10 -5
View File
@@ -2307,6 +2307,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
open := expect_token(p, .Open_Paren) open := expect_token(p, .Open_Paren)
p.expr_level += 1 p.expr_level += 1
expr := parse_expr(p, false) expr := parse_expr(p, false)
skip_possible_newline(p)
p.expr_level -= 1 p.expr_level -= 1
close := expect_token(p, .Close_Paren) close := expect_token(p, .Close_Paren)
@@ -2922,6 +2923,8 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
fields: [dynamic]^ast.Bit_Field_Field fields: [dynamic]^ast.Bit_Field_Field
for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF { for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF {
docs := p.lead_comment
name := parse_ident(p) name := parse_ident(p)
expect_token(p, .Colon) expect_token(p, .Colon)
type := parse_type(p) type := parse_type(p)
@@ -2932,6 +2935,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
if p.curr_tok.kind == .String { if p.curr_tok.kind == .String {
tag = expect_token(p, .String) tag = expect_token(p, .String)
} }
ok := allow_token(p, .Comma)
field := ast.new(ast.Bit_Field_Field, name.pos, bit_size) field := ast.new(ast.Bit_Field_Field, name.pos, bit_size)
@@ -2939,10 +2943,14 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
field.type = type field.type = type
field.bit_size = bit_size field.bit_size = bit_size
field.tag = tag field.tag = tag
field.docs = docs
field.comments = p.line_comment
append(&fields, field) append(&fields, field)
allow_token(p, .Comma) or_break if !ok {
break
}
} }
close := expect_closing_brace_of_field_list(p) close := expect_closing_brace_of_field_list(p)
@@ -3526,6 +3534,7 @@ parse_binary_expr :: proc(p: ^Parser, lhs: bool, prec_in: int) -> ^ast.Expr {
case .When: case .When:
x := expr x := expr
cond := parse_expr(p, lhs) cond := parse_expr(p, lhs)
skip_possible_newline(p)
else_tok := expect_token(p, .Else) else_tok := expect_token(p, .Else)
y := parse_expr(p, lhs) y := parse_expr(p, lhs)
te := ast.new(ast.Ternary_When_Expr, expr.pos, end_pos(p.prev_tok)) te := ast.new(ast.Ternary_When_Expr, expr.pos, end_pos(p.prev_tok))
@@ -3780,10 +3789,6 @@ parse_import_decl :: proc(p: ^Parser, kind := Import_Decl_Kind.Standard) -> ^ast
import_name.pos = p.curr_tok.pos import_name.pos = p.curr_tok.pos
} }
if !is_using && is_blank_ident(import_name) {
error(p, import_name.pos, "illegal import name: '_'")
}
path := expect_token_after(p, .String, "import") path := expect_token_after(p, .String, "import")
decl := ast.new(ast.Import_Decl, tok.pos, end_pos(path)) decl := ast.new(ast.Import_Decl, tok.pos, end_pos(path))
+39 -2
View File
@@ -8,7 +8,7 @@ import "base:runtime"
// Otherwise the returned value will be empty and the boolean will be false // Otherwise the returned value will be empty and the boolean will be false
// NOTE: the value will be allocated with the supplied allocator // NOTE: the value will be allocated with the supplied allocator
@(require_results) @(require_results)
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
if key == "" { if key == "" {
return return
} }
@@ -29,17 +29,54 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
return return
} }
// This version of `lookup_env` doesn't allocate and instead requires the user to provide a buffer.
// Note that it is limited to environment names and values of 512 utf-16 values each
// due to the necessary utf-8 <> utf-16 conversion.
@(require_results)
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
key_buf: [513]u16
wkey := win32.utf8_to_wstring(key_buf[:], key)
if wkey == nil {
return "", .Buffer_Full
}
n2 := win32.GetEnvironmentVariableW(wkey, nil, 0)
if n2 == 0 {
return "", .Env_Var_Not_Found
}
val_buf: [513]u16
n2 = win32.GetEnvironmentVariableW(wkey, raw_data(val_buf[:]), u32(len(val_buf[:])))
if n2 == 0 {
return "", .Env_Var_Not_Found
} else if int(n2) > len(buf) {
return "", .Buffer_Full
}
value = win32.utf16_to_utf8(buf, val_buf[:n2])
return value, nil
}
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
// get_env retrieves the value of the environment variable named by the key // 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 // 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 // To distinguish between an empty value and an unset value, use lookup_env
// NOTE: the value will be allocated with the supplied allocator // NOTE: the value will be allocated with the supplied allocator
@(require_results) @(require_results)
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
value, _ = lookup_env(key, allocator) value, _ = lookup_env(key, allocator)
return return
} }
@(require_results)
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
value, _ = lookup_env(buf, key)
return
}
get_env :: proc{get_env_alloc, get_env_buf}
// set_env sets the value of the environment variable named by the key // set_env sets the value of the environment variable named by the key
set_env :: proc(key, value: string) -> Error { set_env :: proc(key, value: string) -> Error {
k := win32.utf8_to_wstring(key) k := win32.utf8_to_wstring(key)
+4
View File
@@ -35,6 +35,9 @@ General_Error :: enum u32 {
File_Is_Pipe, File_Is_Pipe,
Not_Dir, Not_Dir,
// Environment variable not found.
Env_Var_Not_Found,
} }
@@ -82,6 +85,7 @@ error_string :: proc "contextless" (ferr: Error) -> string {
case .Pattern_Has_Separator: return "pattern has separator" case .Pattern_Has_Separator: return "pattern has separator"
case .File_Is_Pipe: return "file is pipe" case .File_Is_Pipe: return "file is pipe"
case .Not_Dir: return "file is not directory" case .Not_Dir: return "file is not directory"
case .Env_Var_Not_Found: return "environment variable not found"
} }
case io.Error: case io.Error:
switch e { switch e {
+53
View File
@@ -4,6 +4,7 @@ import "base:intrinsics"
import "base:runtime" import "base:runtime"
import "core:io" import "core:io"
import "core:strconv" import "core:strconv"
import "core:strings"
import "core:unicode/utf8" import "core:unicode/utf8"
@@ -210,3 +211,55 @@ heap_free :: runtime.heap_free
processor_core_count :: proc() -> int { processor_core_count :: proc() -> int {
return _processor_core_count() return _processor_core_count()
} }
// Always allocates for consistency.
replace_environment_placeholders :: proc(path: string, allocator := context.allocator) -> (res: string) {
path := path
sb: strings.Builder
strings.builder_init_none(&sb, allocator)
for len(path) > 0 {
switch path[0] {
case '%': // Windows
when ODIN_OS == .Windows {
for r, i in path[1:] {
if r == '%' {
env_key := path[1:i+1]
env_val := get_env(env_key, context.temp_allocator)
strings.write_string(&sb, env_val)
path = path[i+1:] // % is part of key, so skip 1 character extra
}
}
} else {
strings.write_rune(&sb, rune(path[0]))
}
case '$': // Posix
when ODIN_OS != .Windows {
env_key := ""
dollar_loop: for r, i in path[1:] {
switch r {
case 'A'..='Z', 'a'..='z', '0'..='9', '_': // Part of key ident
case:
env_key = path[1:i+1]
break dollar_loop
}
}
if len(env_key) > 0 {
env_val := get_env(env_key, context.temp_allocator)
strings.write_string(&sb, env_val)
path = path[len(env_key):]
}
} else {
strings.write_rune(&sb, rune(path[0]))
}
case:
strings.write_rune(&sb, rune(path[0]))
}
path = path[1:]
}
return strings.to_string(sb)
}
+79 -5
View File
@@ -1,26 +1,49 @@
package os2 package os2
import "base:runtime" import "base:runtime"
import "core:strings"
// get_env retrieves the value of the environment variable named by the key // `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 // 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 // To distinguish between an empty value and an unset value, use lookup_env
// NOTE: the value will be allocated with the supplied allocator // NOTE: the value will be allocated with the supplied allocator
@(require_results) @(require_results)
get_env :: proc(key: string, allocator: runtime.Allocator) -> string { get_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> string {
value, _ := lookup_env(key, allocator) value, _ := lookup_env(key, allocator)
return value return value
} }
// lookup_env gets the value of the environment variable named by the key // `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: this version takes a backing buffer for the string value
@(require_results)
get_env_buf :: proc(buf: []u8, key: string) -> string {
value, _ := lookup_env(buf, key)
return value
}
get_env :: proc{get_env_alloc, get_env_buf}
// `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 // 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 // Otherwise the returned value will be empty and the boolean will be false
// NOTE: the value will be allocated with the supplied allocator // NOTE: the value will be allocated with the supplied allocator
@(require_results) @(require_results)
lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
return _lookup_env(key, allocator) return _lookup_env_alloc(key, allocator)
} }
// This version of `lookup_env` doesn't allocate and instead requires the user to provide a buffer.
// Note that it is limited to environment names and values of 512 utf-16 values each
// due to the necessary utf-8 <> utf-16 conversion.
@(require_results)
lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
return _lookup_env_buf(buf, key)
}
lookup_env :: proc{lookup_env_alloc, lookup_env_buf}
// set_env sets the value of the environment variable named by the key // set_env sets the value of the environment variable named by the key
// Returns Error on failure // Returns Error on failure
set_env :: proc(key, value: string) -> Error { set_env :: proc(key, value: string) -> Error {
@@ -45,4 +68,55 @@ environ :: proc(allocator: runtime.Allocator) -> ([]string, Error) {
return _environ(allocator) return _environ(allocator)
} }
// Always allocates for consistency.
replace_environment_placeholders :: proc(path: string, allocator: runtime.Allocator) -> (res: string) {
path := path
sb: strings.Builder
strings.builder_init_none(&sb, allocator)
for len(path) > 0 {
switch path[0] {
case '%': // Windows
when ODIN_OS == .Windows {
for r, i in path[1:] {
if r == '%' {
env_key := path[1:i+1]
env_val := get_env(env_key, context.temp_allocator)
strings.write_string(&sb, env_val)
path = path[i+1:] // % is part of key, so skip 1 character extra
}
}
} else {
strings.write_rune(&sb, rune(path[0]))
}
case '$': // Posix
when ODIN_OS != .Windows {
env_key := ""
dollar_loop: for r, i in path[1:] {
switch r {
case 'A'..='Z', 'a'..='z', '0'..='9', '_': // Part of key ident
case:
env_key = path[1:i+1]
break dollar_loop
}
}
if len(env_key) > 0 {
env_val := get_env(env_key, context.temp_allocator)
strings.write_string(&sb, env_val)
path = path[len(env_key):]
}
} else {
strings.write_rune(&sb, rune(path[0]))
}
case:
strings.write_rune(&sb, rune(path[0]))
}
path = path[1:]
}
return strings.to_string(sb)
}
+18 -1
View File
@@ -41,7 +41,7 @@ _lookup :: proc(key: string) -> (value: string, idx: int) {
return "", -1 return "", -1
} }
_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { _lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 {
_build_env() _build_env()
} }
@@ -53,6 +53,23 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
return return
} }
_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 {
_build_env()
}
if v, idx := _lookup(key); idx != -1 {
if len(buf) >= len(v) {
copy(buf, v)
return string(buf[:len(v)]), nil
}
return "", .Buffer_Full
}
return "", .Env_Var_Not_Found
}
_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf}
_set_env :: proc(key, v_new: string) -> Error { _set_env :: proc(key, v_new: string) -> Error {
if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 {
_build_env() _build_env()
+31 -1
View File
@@ -7,7 +7,7 @@ import "base:runtime"
import "core:strings" import "core:strings"
import "core:sys/posix" import "core:sys/posix"
_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { _lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
if key == "" { if key == "" {
return return
} }
@@ -26,6 +26,36 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
return return
} }
_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, error: Error) {
if key == "" {
return
}
if len(key) + 1 > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, key)
}
cval := posix.getenv(cstring(raw_data(buf)))
if cval == nil {
return
}
if value = string(cval); value == "" {
return "", .Env_Var_Not_Found
} else {
if len(value) > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, value)
return string(buf[:len(value)]), nil
}
}
}
_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf}
_set_env :: proc(key, value: string) -> (err: Error) { _set_env :: proc(key, value: string) -> (err: Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({}) temp_allocator := TEMP_ALLOCATOR_GUARD({})
+29 -1
View File
@@ -67,7 +67,7 @@ delete_string_if_not_original :: proc(str: string) {
} }
@(require_results) @(require_results)
_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { _lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
if err := build_env(); err != nil { if err := build_env(); err != nil {
return return
} }
@@ -79,6 +79,34 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
return return
} }
_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, error: Error) {
if key == "" {
return
}
if len(key) + 1 > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, key)
}
sync.shared_guard(&g_env_mutex)
val, ok := g_env[key]
if !ok {
return "", .Env_Var_Not_Found
} else {
if len(val) > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, val)
return string(buf[:len(val)]), nil
}
}
}
_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf}
@(require_results) @(require_results)
_set_env :: proc(key, value: string) -> (err: Error) { _set_env :: proc(key, value: string) -> (err: Error) {
build_env() or_return build_env() or_return
+31 -1
View File
@@ -4,7 +4,7 @@ package os2
import win32 "core:sys/windows" import win32 "core:sys/windows"
import "base:runtime" import "base:runtime"
_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { _lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
if key == "" { if key == "" {
return return
} }
@@ -36,6 +36,36 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
return return
} }
// This version of `lookup_env` doesn't allocate and instead requires the user to provide a buffer.
// Note that it is limited to environment names and values of 512 utf-16 values each
// due to the necessary utf-8 <> utf-16 conversion.
@(require_results)
_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
key_buf: [513]u16
wkey := win32.utf8_to_wstring(key_buf[:], key)
if wkey == nil {
return "", .Buffer_Full
}
n2 := win32.GetEnvironmentVariableW(wkey, nil, 0)
if n2 == 0 {
return "", .Env_Var_Not_Found
}
val_buf: [513]u16
n2 = win32.GetEnvironmentVariableW(wkey, raw_data(val_buf[:]), u32(len(val_buf[:])))
if n2 == 0 {
return "", .Env_Var_Not_Found
} else if int(n2) > len(buf) {
return "", .Buffer_Full
}
value = win32.utf16_to_utf8(buf, val_buf[:n2])
return value, nil
}
_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf}
_set_env :: proc(key, value: string) -> Error { _set_env :: proc(key, value: string) -> Error {
temp_allocator := TEMP_ALLOCATOR_GUARD({}) temp_allocator := TEMP_ALLOCATOR_GUARD({})
k := win32_utf8_to_wstring(key, temp_allocator) or_return k := win32_utf8_to_wstring(key, temp_allocator) or_return
+2 -2
View File
@@ -28,7 +28,7 @@ General_Error :: enum u32 {
Pattern_Has_Separator, Pattern_Has_Separator,
No_HOME_Variable, No_HOME_Variable,
Wordexp_Failed, Env_Var_Not_Found,
Unsupported, Unsupported,
} }
@@ -77,7 +77,7 @@ error_string :: proc(ferr: Error) -> string {
case .Unsupported: return "unsupported" case .Unsupported: return "unsupported"
case .Pattern_Has_Separator: return "pattern has separator" case .Pattern_Has_Separator: return "pattern has separator"
case .No_HOME_Variable: return "no $HOME variable" case .No_HOME_Variable: return "no $HOME variable"
case .Wordexp_Failed: return "posix.wordexp was unable to expand" case .Env_Var_Not_Found: return "environment variable not found"
} }
case io.Error: case io.Error:
switch e { switch e {
+1
View File
@@ -269,6 +269,7 @@ _write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (nt: i64, err: Error
return return
} }
@(no_sanitize_memory)
_file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) { _file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) {
// TODO: Identify 0-sized "pseudo" files and return No_Size. This would // TODO: Identify 0-sized "pseudo" files and return No_Size. This would
// eliminate the need for the _read_entire_pseudo_file procs. // eliminate the need for the _read_entire_pseudo_file procs.
+1 -9
View File
@@ -4,7 +4,6 @@ package os2
import "base:runtime" import "base:runtime"
import "core:encoding/ini" import "core:encoding/ini"
import "core:strings" import "core:strings"
import "core:sys/posix"
_user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { _user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
#partial switch ODIN_OS { #partial switch ODIN_OS {
@@ -169,14 +168,7 @@ _xdg_user_dirs_lookup :: proc(xdg_key: string, allocator: runtime.Allocator) ->
for k, v in ini.iterate(&it) { for k, v in ini.iterate(&it) {
if k == xdg_key { if k == xdg_key {
we: posix.wordexp_t return replace_environment_placeholders(v, allocator), nil
defer posix.wordfree(&we)
if _err := posix.wordexp(strings.clone_to_cstring(v, temp_allocator), &we, nil); _err != nil || we.we_wordc != 1 {
return "", .Wordexp_Failed
}
return strings.clone_from_cstring(we.we_wordv[0], allocator)
} }
} }
return return
+37 -3
View File
@@ -1090,9 +1090,10 @@ flush :: proc(fd: Handle) -> Error {
} }
@(require_results) @(require_results)
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
path_str := strings.clone_to_cstring(key, context.temp_allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator)
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
cstr := _unix_getenv(path_str) cstr := _unix_getenv(path_str)
if cstr == nil { if cstr == nil {
return "", false return "", false
@@ -1101,11 +1102,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
} }
@(require_results) @(require_results)
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
if len(key) + 1 > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, key)
}
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
return "", .Env_Var_Not_Found
} else {
if len(value) > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, value)
return string(buf[:len(value)]), nil
}
}
}
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
@(require_results)
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
value, _ = lookup_env(key, allocator) value, _ = lookup_env(key, allocator)
return return
} }
@(require_results)
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
value, _ = lookup_env(buf, key)
return
}
get_env :: proc{get_env_alloc, get_env_buf}
set_env :: proc(key, value: string) -> Error { set_env :: proc(key, value: string) -> Error {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
key_cstring := strings.clone_to_cstring(key, context.temp_allocator) key_cstring := strings.clone_to_cstring(key, context.temp_allocator)
@@ -1231,7 +1260,7 @@ _processor_core_count :: proc() -> int {
return 1 return 1
} }
@(require_results) @(private, require_results)
_alloc_command_line_arguments :: proc() -> []string { _alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__)) res := make([]string, len(runtime.args__))
for _, i in res { for _, i in res {
@@ -1240,6 +1269,11 @@ _alloc_command_line_arguments :: proc() -> []string {
return res return res
} }
@(private, fini)
_delete_command_line_arguments :: proc() {
delete(args)
}
socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) { socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) {
result := _unix_socket(c.int(domain), c.int(type), c.int(protocol)) result := _unix_socket(c.int(domain), c.int(type), c.int(protocol))
if result < 0 { if result < 0 {
+40 -7
View File
@@ -662,7 +662,7 @@ last_write_time_by_name :: proc(name: string) -> (File_Time, Error) {
return File_Time(modified), nil return File_Time(modified), nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_stat :: proc(path: string) -> (OS_Stat, Error) { _stat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator) cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -674,7 +674,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil return s, nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_lstat :: proc(path: string) -> (OS_Stat, Error) { _lstat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator) cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -688,7 +688,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil return s, nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
s: OS_Stat = --- s: OS_Stat = ---
result := _unix_fstat(fd, &s) result := _unix_fstat(fd, &s)
@@ -827,10 +827,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
} }
@(require_results) @(require_results)
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
path_str := strings.clone_to_cstring(key, context.temp_allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator)
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
cstr := _unix_getenv(path_str) cstr := _unix_getenv(path_str)
if cstr == nil { if cstr == nil {
return "", false return "", false
@@ -839,11 +839,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
} }
@(require_results) @(require_results)
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
if len(key) + 1 > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, key)
}
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
return "", .Env_Var_Not_Found
} else {
if len(value) > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, value)
return string(buf[:len(value)]), nil
}
}
}
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
@(require_results)
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
value, _ = lookup_env(key, allocator) value, _ = lookup_env(key, allocator)
return return
} }
@(require_results)
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
value, _ = lookup_env(buf, key)
return
}
get_env :: proc{get_env_alloc, get_env_buf}
@(require_results) @(require_results)
get_current_directory :: proc(allocator := context.allocator) -> string { get_current_directory :: proc(allocator := context.allocator) -> string {
context.allocator = allocator context.allocator = allocator
@@ -936,7 +964,7 @@ _processor_core_count :: proc() -> int {
} }
@(require_results) @(private, require_results)
_alloc_command_line_arguments :: proc() -> []string { _alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__)) res := make([]string, len(runtime.args__))
for arg, i in runtime.args__ { for arg, i in runtime.args__ {
@@ -944,3 +972,8 @@ _alloc_command_line_arguments :: proc() -> []string {
} }
return res return res
} }
@(private, fini)
_delete_command_line_arguments :: proc() {
delete(args)
}
+41 -6
View File
@@ -316,7 +316,7 @@ file_size :: proc(fd: Handle) -> (i64, Error) {
// "Argv" arguments converted to Odin strings // "Argv" arguments converted to Odin strings
args := _alloc_command_line_arguments() args := _alloc_command_line_arguments()
@(require_results) @(private, require_results)
_alloc_command_line_arguments :: proc() -> []string { _alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__)) res := make([]string, len(runtime.args__))
for arg, i in runtime.args__ { for arg, i in runtime.args__ {
@@ -325,7 +325,12 @@ _alloc_command_line_arguments :: proc() -> []string {
return res return res
} }
@(private, require_results) @(private, fini)
_delete_command_line_arguments :: proc() {
delete(args)
}
@(private, require_results, no_sanitize_memory)
_stat :: proc(path: string) -> (OS_Stat, Error) { _stat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator) cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -339,7 +344,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil return s, nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_lstat :: proc(path: string) -> (OS_Stat, Error) { _lstat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator) cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -353,7 +358,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil return s, nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
// deliberately uninitialized // deliberately uninitialized
s: OS_Stat = --- s: OS_Stat = ---
@@ -463,9 +468,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
} }
@(require_results) @(require_results)
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
path_str := strings.clone_to_cstring(key, context.temp_allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator)
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
cstr := _unix_getenv(path_str) cstr := _unix_getenv(path_str)
if cstr == nil { if cstr == nil {
return "", false return "", false
@@ -474,11 +480,40 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
} }
@(require_results) @(require_results)
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
if len(key) + 1 > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, key)
}
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
return "", .Env_Var_Not_Found
} else {
if len(value) > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, value)
return string(buf[:len(value)]), nil
}
}
}
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
@(require_results)
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
value, _ = lookup_env(key, allocator) value, _ = lookup_env(key, allocator)
return return
} }
@(require_results)
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
value, _ = lookup_env(buf, key)
return
}
get_env :: proc{get_env_alloc, get_env_buf}
@(private, require_results) @(private, require_results)
_processor_core_count :: proc() -> int { _processor_core_count :: proc() -> int {
info: haiku.system_info info: haiku.system_info
+22 -2
View File
@@ -250,6 +250,26 @@ current_thread_id :: proc "contextless" () -> int {
return 0 return 0
} }
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { @(require_results)
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
return "", false return "", false
} }
@(require_results)
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
return "", .Env_Var_Not_Found
}
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
@(require_results)
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
value, _ = lookup_env(key, allocator)
return
}
@(require_results)
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
value, _ = lookup_env(buf, key)
return
}
get_env :: proc{get_env_alloc, get_env_buf}
+40 -7
View File
@@ -674,7 +674,7 @@ seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
return i64(res), nil return i64(res), nil
} }
@(require_results) @(require_results, no_sanitize_memory)
file_size :: proc(fd: Handle) -> (i64, Error) { file_size :: proc(fd: Handle) -> (i64, Error) {
// deliberately uninitialized; the syscall fills this buffer for us // deliberately uninitialized; the syscall fills this buffer for us
s: OS_Stat = --- s: OS_Stat = ---
@@ -794,7 +794,7 @@ last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
return File_Time(modified), nil return File_Time(modified), nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_stat :: proc(path: string) -> (OS_Stat, Error) { _stat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator) cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -808,7 +808,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil return s, nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_lstat :: proc(path: string) -> (OS_Stat, Error) { _lstat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator) cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -822,7 +822,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil return s, nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
// deliberately uninitialized; the syscall fills this buffer for us // deliberately uninitialized; the syscall fills this buffer for us
s: OS_Stat = --- s: OS_Stat = ---
@@ -946,7 +946,7 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
} }
@(require_results) @(require_results)
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
path_str := strings.clone_to_cstring(key, context.temp_allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator)
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
@@ -958,11 +958,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
} }
@(require_results) @(require_results)
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
if len(key) + 1 > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, key)
}
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
return "", .Env_Var_Not_Found
} else {
if len(value) > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, value)
return string(buf[:len(value)]), nil
}
}
}
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
@(require_results)
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
value, _ = lookup_env(key, allocator) value, _ = lookup_env(key, allocator)
return return
} }
@(require_results)
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
value, _ = lookup_env(buf, key)
return
}
get_env :: proc{get_env_alloc, get_env_buf}
set_env :: proc(key, value: string) -> Error { set_env :: proc(key, value: string) -> Error {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
key_cstring := strings.clone_to_cstring(key, context.temp_allocator) key_cstring := strings.clone_to_cstring(key, context.temp_allocator)
@@ -1069,7 +1097,7 @@ _processor_core_count :: proc() -> int {
return int(_unix_get_nprocs()) return int(_unix_get_nprocs())
} }
@(require_results) @(private, require_results)
_alloc_command_line_arguments :: proc() -> []string { _alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__)) res := make([]string, len(runtime.args__))
for arg, i in runtime.args__ { for arg, i in runtime.args__ {
@@ -1078,6 +1106,11 @@ _alloc_command_line_arguments :: proc() -> []string {
return res return res
} }
@(private, fini)
_delete_command_line_arguments :: proc() {
delete(args)
}
@(require_results) @(require_results)
socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) { socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) {
result := unix.sys_socket(domain, type, protocol) result := unix.sys_socket(domain, type, protocol)
+40 -7
View File
@@ -724,7 +724,7 @@ last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
return File_Time(modified), nil return File_Time(modified), nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_stat :: proc(path: string) -> (OS_Stat, Error) { _stat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator) cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -736,7 +736,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil return s, nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_lstat :: proc(path: string) -> (OS_Stat, Error) { _lstat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator) cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -750,7 +750,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil return s, nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
s: OS_Stat = --- s: OS_Stat = ---
result := _unix_fstat(fd, &s) result := _unix_fstat(fd, &s)
@@ -874,10 +874,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
} }
@(require_results) @(require_results)
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
path_str := strings.clone_to_cstring(key, context.temp_allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator)
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
cstr := _unix_getenv(path_str) cstr := _unix_getenv(path_str)
if cstr == nil { if cstr == nil {
return "", false return "", false
@@ -886,11 +886,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
} }
@(require_results) @(require_results)
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
if len(key) + 1 > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, key)
}
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
return "", .Env_Var_Not_Found
} else {
if len(value) > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, value)
return string(buf[:len(value)]), nil
}
}
}
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
@(require_results)
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
value, _ = lookup_env(key, allocator) value, _ = lookup_env(key, allocator)
return return
} }
@(require_results)
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
value, _ = lookup_env(buf, key)
return
}
get_env :: proc{get_env_alloc, get_env_buf}
@(require_results) @(require_results)
get_current_directory :: proc(allocator := context.allocator) -> string { get_current_directory :: proc(allocator := context.allocator) -> string {
context.allocator = allocator context.allocator = allocator
@@ -986,7 +1014,7 @@ _processor_core_count :: proc() -> int {
return 1 return 1
} }
@(require_results) @(private, require_results)
_alloc_command_line_arguments :: proc() -> []string { _alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__)) res := make([]string, len(runtime.args__))
for arg, i in runtime.args__ { for arg, i in runtime.args__ {
@@ -994,3 +1022,8 @@ _alloc_command_line_arguments :: proc() -> []string {
} }
return res return res
} }
@(private, fini)
_delete_command_line_arguments :: proc() {
delete(args)
}
+40 -6
View File
@@ -639,7 +639,7 @@ last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
return File_Time(modified), nil return File_Time(modified), nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_stat :: proc(path: string) -> (OS_Stat, Error) { _stat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator) cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -653,7 +653,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil return s, nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_lstat :: proc(path: string) -> (OS_Stat, Error) { _lstat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator) cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -667,7 +667,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil return s, nil
} }
@(private, require_results) @(private, require_results, no_sanitize_memory)
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) { _fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
// deliberately uninitialized // deliberately uninitialized
s: OS_Stat = --- s: OS_Stat = ---
@@ -787,9 +787,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
} }
@(require_results) @(require_results)
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
path_str := strings.clone_to_cstring(key, context.temp_allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator)
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
cstr := _unix_getenv(path_str) cstr := _unix_getenv(path_str)
if cstr == nil { if cstr == nil {
return "", false return "", false
@@ -798,11 +799,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
} }
@(require_results) @(require_results)
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
if len(key) + 1 > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, key)
}
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
return "", .Env_Var_Not_Found
} else {
if len(value) > len(buf) {
return "", .Buffer_Full
} else {
copy(buf, value)
return string(buf[:len(value)]), nil
}
}
}
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
@(require_results)
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
value, _ = lookup_env(key, allocator) value, _ = lookup_env(key, allocator)
return return
} }
@(require_results)
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
value, _ = lookup_env(buf, key)
return
}
get_env :: proc{get_env_alloc, get_env_buf}
@(require_results) @(require_results)
get_current_directory :: proc(allocator := context.allocator) -> string { get_current_directory :: proc(allocator := context.allocator) -> string {
context.allocator = allocator context.allocator = allocator
@@ -885,7 +914,7 @@ _processor_core_count :: proc() -> int {
return int(_sysconf(_SC_NPROCESSORS_ONLN)) return int(_sysconf(_SC_NPROCESSORS_ONLN))
} }
@(require_results) @(private, require_results)
_alloc_command_line_arguments :: proc() -> []string { _alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__)) res := make([]string, len(runtime.args__))
for arg, i in runtime.args__ { for arg, i in runtime.args__ {
@@ -893,3 +922,8 @@ _alloc_command_line_arguments :: proc() -> []string {
} }
return res return res
} }
@(private, fini)
_delete_command_line_arguments :: proc() {
delete(args)
}
+30 -1
View File
@@ -27,7 +27,7 @@ stderr: Handle = 2
args := _alloc_command_line_arguments() args := _alloc_command_line_arguments()
@(require_results) @(private, require_results)
_alloc_command_line_arguments :: proc() -> (args: []string) { _alloc_command_line_arguments :: proc() -> (args: []string) {
args = make([]string, len(runtime.args__)) args = make([]string, len(runtime.args__))
for &arg, i in args { for &arg, i in args {
@@ -36,6 +36,11 @@ _alloc_command_line_arguments :: proc() -> (args: []string) {
return return
} }
@(private, fini)
_delete_command_line_arguments :: proc() {
delete(args)
}
// WASI works with "preopened" directories, the environment retrieves directories // WASI works with "preopened" directories, the environment retrieves directories
// (for example with `wasmtime --dir=. module.wasm`) and those given directories // (for example with `wasmtime --dir=. module.wasm`) and those given directories
// are the only ones accessible by the application. // are the only ones accessible by the application.
@@ -239,3 +244,27 @@ exit :: proc "contextless" (code: int) -> ! {
runtime._cleanup_runtime_contextless() runtime._cleanup_runtime_contextless()
wasi.proc_exit(wasi.exitcode_t(code)) wasi.proc_exit(wasi.exitcode_t(code))
} }
@(require_results)
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
return "", false
}
@(require_results)
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
return "", .Env_Var_Not_Found
}
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
@(require_results)
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
value, _ = lookup_env(key, allocator)
return
}
@(require_results)
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
value, _ = lookup_env(buf, key)
return
}
get_env :: proc{get_env_alloc, get_env_buf}
+9 -1
View File
@@ -193,7 +193,7 @@ current_thread_id :: proc "contextless" () -> int {
@(require_results) @(private, require_results)
_alloc_command_line_arguments :: proc() -> []string { _alloc_command_line_arguments :: proc() -> []string {
arg_count: i32 arg_count: i32
arg_list_ptr := win32.CommandLineToArgvW(win32.GetCommandLineW(), &arg_count) arg_list_ptr := win32.CommandLineToArgvW(win32.GetCommandLineW(), &arg_count)
@@ -215,6 +215,14 @@ _alloc_command_line_arguments :: proc() -> []string {
return arg_list return arg_list
} }
@(private, fini)
_delete_command_line_arguments :: proc() {
for s in args {
delete(s)
}
delete(args)
}
/* /*
Windows 11 (preview) has the same major and minor version numbers Windows 11 (preview) has the same major and minor version numbers
as Windows 10: 10 and 0 respectively. as Windows 10: 10 and 0 respectively.
+19
View File
@@ -387,6 +387,25 @@ has_prefix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_c
return false return false
} }
/*
return the suffix length common between slices `a` and `b`.
slice.suffix_length([]u8{1, 2, 3, 4}, []u8{1, 2, 3, 4}) -> 4
slice.suffix_length([]u8{1, 2, 3, 4}, []u8{3, 4}) -> 2
slice.suffix_length([]u8{1, 2, 3, 4}, []u8{1}) -> 0
slice.suffix_length([]u8{1, 2, 3, 4}, []u8{1, 3, 5}) -> 0
slice.suffix_length([]u8{3, 4, 5}, []u8{3, 5}) -> 1
*/
@(require_results)
suffix_length :: proc(a, b: $T/[]$E) -> (n: int) where intrinsics.type_is_comparable(E) {
len_a, len_b := len(a), len(b)
_len := builtin.min(len_a, len_b)
#no_bounds_check for i := 1; i <= _len && a[len_a - i] == b[len_b - i]; i += 1 {
n += 1
}
return
}
@(require_results) @(require_results)
has_suffix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_comparable(E) { has_suffix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_comparable(E) {
+107 -48
View File
@@ -7,6 +7,14 @@ import "core:mem"
import "core:sync" import "core:sync"
import "core:math/rand" import "core:math/rand"
when ODIN_TEST {
/*
Hook for testing _try_select_raw allowing the test harness to manipulate the
channels prior to the select actually operating on them.
*/
__try_select_raw_pause : proc() = nil
}
/* /*
Determines what operations `Chan` supports. Determines what operations `Chan` supports.
*/ */
@@ -75,6 +83,8 @@ Raw_Chan :: struct {
r_waiting: int, // guarded by `mutex` r_waiting: int, // guarded by `mutex`
w_waiting: int, // guarded by `mutex` w_waiting: int, // guarded by `mutex`
did_read: bool, // lets a sender know if the value was read
// Buffered // Buffered
queue: ^Raw_Queue, queue: ^Raw_Queue,
@@ -412,8 +422,8 @@ as_recv :: #force_inline proc "contextless" (c: $C/Chan($T, $D)) -> (r: Chan(T,
Sends the specified message, blocking the current thread if: Sends the specified message, blocking the current thread if:
- the channel is unbuffered - the channel is unbuffered
- the channel's buffer is full - the channel's buffer is full
until the channel is being read from. `send` will return until the channel is being read from or the channel is closed. `send` will
`false` when attempting to send on an already closed channel. return `false` when attempting to send on an already closed channel.
**Inputs** **Inputs**
- `c`: The channel - `c`: The channel
@@ -484,8 +494,9 @@ try_send :: proc "contextless" (c: $C/Chan($T, $D), data: T) -> (ok: bool) where
Reads a message from the channel, blocking the current thread if: Reads a message from the channel, blocking the current thread if:
- the channel is unbuffered - the channel is unbuffered
- the channel's buffer is empty - the channel's buffer is empty
until the channel is being written to. `recv` will return until the channel is being written to or the channel is closed. `recv` will
`false` when attempting to receive a message on an already closed channel. return `false` when attempting to receive a message on an already closed
channel.
**Inputs** **Inputs**
- `c`: The channel - `c`: The channel
@@ -558,8 +569,8 @@ try_recv :: proc "contextless" (c: $C/Chan($T)) -> (data: T, ok: bool) where C.D
Sends the specified message, blocking the current thread if: Sends the specified message, blocking the current thread if:
- the channel is unbuffered - the channel is unbuffered
- the channel's buffer is full - the channel's buffer is full
until the channel is being read from. `send_raw` will return until the channel is being read from or the channel is closed. `send_raw` will
`false` when attempting to send on an already closed channel. return `false` when attempting to send on an already closed channel.
Note: The message referenced by `msg_out` must match the size Note: The message referenced by `msg_out` must match the size
and alignment used when the `Raw_Chan` was created. and alignment used when the `Raw_Chan` was created.
@@ -619,12 +630,23 @@ send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool) {
return false return false
} }
c.did_read = false
defer c.did_read = false
mem.copy(c.unbuffered_data, msg_in, int(c.msg_size)) mem.copy(c.unbuffered_data, msg_in, int(c.msg_size))
c.w_waiting += 1 c.w_waiting += 1
if c.r_waiting > 0 { if c.r_waiting > 0 {
sync.signal(&c.r_cond) sync.signal(&c.r_cond)
} }
sync.wait(&c.w_cond, &c.mutex) sync.wait(&c.w_cond, &c.mutex)
if c.closed && !c.did_read {
return false
}
ok = true ok = true
} }
return return
@@ -634,8 +656,9 @@ send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool) {
Reads a message from the channel, blocking the current thread if: Reads a message from the channel, blocking the current thread if:
- the channel is unbuffered - the channel is unbuffered
- the channel's buffer is empty - the channel's buffer is empty
until the channel is being written to. `recv_raw` will return until the channel is being written to or the channel is closed. `recv_raw`
`false` when attempting to receive a message on an already closed channel. will return `false` when attempting to receive a message on an already closed
channel.
Note: The location pointed to by `msg_out` must match the size Note: The location pointed to by `msg_out` must match the size
and alignment used when the `Raw_Chan` was created. and alignment used when the `Raw_Chan` was created.
@@ -698,8 +721,7 @@ recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> (ok: bool) {
} else if c.unbuffered_data != nil { // unbuffered } else if c.unbuffered_data != nil { // unbuffered
sync.guard(&c.mutex) sync.guard(&c.mutex)
for !c.closed && for !c.closed && c.w_waiting == 0 {
c.w_waiting == 0 {
c.r_waiting += 1 c.r_waiting += 1
sync.wait(&c.r_cond, &c.mutex) sync.wait(&c.r_cond, &c.mutex)
c.r_waiting -= 1 c.r_waiting -= 1
@@ -712,6 +734,7 @@ recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> (ok: bool) {
mem.copy(msg_out, c.unbuffered_data, int(c.msg_size)) mem.copy(msg_out, c.unbuffered_data, int(c.msg_size))
c.w_waiting -= 1 c.w_waiting -= 1
c.did_read = true
sync.signal(&c.w_cond) sync.signal(&c.w_cond)
ok = true ok = true
} }
@@ -771,7 +794,7 @@ try_send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool)
} else if c.unbuffered_data != nil { // unbuffered } else if c.unbuffered_data != nil { // unbuffered
sync.guard(&c.mutex) sync.guard(&c.mutex)
if c.closed { if c.closed || c.r_waiting - c.w_waiting <= 0 {
return false return false
} }
@@ -835,7 +858,7 @@ try_recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> bool {
} else if c.unbuffered_data != nil { // unbuffered } else if c.unbuffered_data != nil { // unbuffered
sync.guard(&c.mutex) sync.guard(&c.mutex)
if c.closed || c.w_waiting == 0 { if c.closed || c.w_waiting - c.r_waiting <= 0 {
return false return false
} }
@@ -1038,8 +1061,9 @@ is_closed :: proc "contextless" (c: ^Raw_Chan) -> bool {
} }
/* /*
Returns whether a message is ready to be read, i.e., Returns whether a message can be read without blocking the current
if a call to `recv` or `recv_raw` would block thread. Specifically, it checks if the channel is buffered and not full,
or if there is already a writer attempting to send a message.
**Inputs** **Inputs**
- `c`: The channel - `c`: The channel
@@ -1067,7 +1091,7 @@ can_recv :: proc "contextless" (c: ^Raw_Chan) -> bool {
if is_buffered(c) { if is_buffered(c) {
return c.queue.len > 0 return c.queue.len > 0
} }
return c.w_waiting > 0 return c.w_waiting - c.r_waiting > 0
} }
@@ -1080,7 +1104,7 @@ or if there is already a reader waiting for a message.
- `c`: The channel - `c`: The channel
**Returns** **Returns**
- `true` if a message can be send, `false` otherwise - `true` if a message can be sent, `false` otherwise
Example: Example:
@@ -1102,18 +1126,30 @@ can_send :: proc "contextless" (c: ^Raw_Chan) -> bool {
if is_buffered(c) { if is_buffered(c) {
return c.queue.len < c.queue.cap return c.queue.len < c.queue.cap
} }
return c.w_waiting == 0 return c.r_waiting - c.w_waiting > 0
}
/*
Specifies the direction of the selected channel.
*/
Select_Status :: enum {
None,
Recv,
Send,
} }
/* /*
Attempts to either send or receive messages on the specified channels. Attempts to either send or receive messages on the specified channels without blocking.
`select_raw` first identifies which channels have messages ready to be received `try_select_raw` first identifies which channels have messages ready to be received
and which are available for sending. It then randomly selects one operation and which are available for sending. It then randomly selects one operation
(either a send or receive) to perform. (either a send or receive) to perform.
If no channels have messages ready, the procedure is a noop.
Note: Each message in `send_msgs` corresponds to the send channel at the same index in `sends`. Note: Each message in `send_msgs` corresponds to the send channel at the same index in `sends`.
If the message is nil, corresponding send channel will be skipped.
**Inputs** **Inputs**
- `recv`: A slice of channels to read from - `recv`: A slice of channels to read from
@@ -1145,18 +1181,18 @@ Example:
// where the value from the read should be stored // where the value from the read should be stored
received_value: int received_value: int
idx, ok := chan.select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value) idx, ok := chan.try_select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value)
fmt.println("SELECT: ", idx, ok) fmt.println("SELECT: ", idx, ok)
fmt.println("RECEIVED VALUE ", received_value) fmt.println("RECEIVED VALUE ", received_value)
idx, ok = chan.select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value) idx, ok = chan.try_select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value)
fmt.println("SELECT: ", idx, ok) fmt.println("SELECT: ", idx, ok)
fmt.println("RECEIVED VALUE ", received_value) fmt.println("RECEIVED VALUE ", received_value)
// closing of a channel also affects the select operation // closing of a channel also affects the select operation
chan.close(c) chan.close(c)
idx, ok = chan.select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value) idx, ok = chan.try_select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value)
fmt.println("SELECT: ", idx, ok) fmt.println("SELECT: ", idx, ok)
} }
@@ -1170,7 +1206,7 @@ Output:
*/ */
@(require_results) @(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 { try_select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: []rawptr, recv_out: rawptr) -> (select_idx: int, status: Select_Status) #no_bounds_check {
Select_Op :: struct { Select_Op :: struct {
idx: int, // local to the slice that was given idx: int, // local to the slice that was given
is_recv: bool, is_recv: bool,
@@ -1178,43 +1214,66 @@ select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: []
candidate_count := builtin.len(recvs)+builtin.len(sends) candidate_count := builtin.len(recvs)+builtin.len(sends)
candidates := ([^]Select_Op)(intrinsics.alloca(candidate_count*size_of(Select_Op), align_of(Select_Op))) candidates := ([^]Select_Op)(intrinsics.alloca(candidate_count*size_of(Select_Op), align_of(Select_Op)))
count := 0
for c, i in recvs { try_loop: for {
if can_recv(c) { count := 0
candidates[count] = {
is_recv = true, for c, i in recvs {
idx = i, if can_recv(c) {
candidates[count] = {
is_recv = true,
idx = i,
}
count += 1
} }
count += 1
} }
}
for c, i in sends { for c, i in sends {
if can_send(c) { if i > builtin.len(send_msgs)-1 || send_msgs[i] == nil {
candidates[count] = { continue
is_recv = false, }
idx = i, if can_send(c) {
candidates[count] = {
is_recv = false,
idx = i,
}
count += 1
} }
count += 1
} }
}
if count == 0 { if count == 0 {
return return -1, .None
} }
select_idx = rand.int_max(count) if count > 0 else 0 when ODIN_TEST {
if __try_select_raw_pause != nil {
__try_select_raw_pause()
}
}
sel := candidates[select_idx] candidate_idx := rand.int_max(count) if count > 0 else 0
if sel.is_recv {
ok = recv_raw(recvs[sel.idx], recv_out) sel := candidates[candidate_idx]
} else { if sel.is_recv {
ok = send_raw(sends[sel.idx], send_msgs[sel.idx]) status = .Recv
if !try_recv_raw(recvs[sel.idx], recv_out) {
continue try_loop
}
} else {
status = .Send
if !try_send_raw(sends[sel.idx], send_msgs[sel.idx]) {
continue try_loop
}
}
return sel.idx, status
} }
return
} }
@(require_results, deprecated = "use try_select_raw")
select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: []rawptr, recv_out: rawptr) -> (select_idx: int, status: Select_Status) #no_bounds_check {
return try_select_raw(recvs, sends, send_msgs, recv_out)
}
/* /*
`Raw_Queue` is a non-thread-safe queue implementation designed to store messages `Raw_Queue` is a non-thread-safe queue implementation designed to store messages
@@ -223,6 +223,11 @@ _Proc_Bsdinfo :: struct {
/*--==========================================================================--*/ /*--==========================================================================--*/
/* Get window size */
TIOCGWINSZ :: 0x40087468
/*--==========================================================================--*/
syscall_fsync :: #force_inline proc "contextless" (fildes: c.int) -> bool { syscall_fsync :: #force_inline proc "contextless" (fildes: c.int) -> bool {
return !(cast(bool)intrinsics.syscall(unix_offset_syscall(.fsync), uintptr(fildes))) return !(cast(bool)intrinsics.syscall(unix_offset_syscall(.fsync), uintptr(fildes)))
} }
@@ -275,6 +280,10 @@ syscall_lseek :: #force_inline proc "contextless" (fd: c.int, offset: i64, whenc
return cast(i64)intrinsics.syscall(unix_offset_syscall(.lseek), uintptr(fd), uintptr(offset), uintptr(whence)) return cast(i64)intrinsics.syscall(unix_offset_syscall(.lseek), uintptr(fd), uintptr(offset), uintptr(whence))
} }
syscall_ioctl :: #force_inline proc "contextless" (fd: c.int, request: u32, arg: rawptr) -> c.int {
return (cast(c.int)intrinsics.syscall(unix_offset_syscall(.ioctl), uintptr(fd), uintptr(request), uintptr(arg)))
}
syscall_gettid :: #force_inline proc "contextless" () -> u64 { syscall_gettid :: #force_inline proc "contextless" () -> u64 {
return cast(u64)intrinsics.syscall(unix_offset_syscall(.gettid)) return cast(u64)intrinsics.syscall(unix_offset_syscall(.gettid))
} }
+890 -890
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
package sys_freebsd
/* Get window size */
TIOCGWINSZ :: 0x40087468
+1
View File
@@ -34,6 +34,7 @@ init_platform :: proc() {
} else { } else {
os_version.platform = .MacOS os_version.platform = .MacOS
switch version.majorVersion { switch version.majorVersion {
case 26: ws(&b, "macOS Tahoe")
case 15: ws(&b, "macOS Sequoia") case 15: ws(&b, "macOS Sequoia")
case 14: ws(&b, "macOS Sonoma") case 14: ws(&b, "macOS Sonoma")
case 13: ws(&b, "macOS Ventura") case 13: ws(&b, "macOS Ventura")
+18 -15
View File
@@ -1618,36 +1618,39 @@ PER_HPUX :: 0x0010
PER_MASK :: 0x00ff PER_MASK :: 0x00ff
/* /*
Bits for access modes for shared memory Bits for SystemV IPC flags.
In this enum, access modes are common for any shared memory. Prefixed
entries (i.e. `IPC_` or `SHM_`) denote flags, where `IPC_` are common flags
for all SystemV IPC primitives, and `SHM_`, `SEM_` and `MSG_` are specific
to shared memory segments, semaphores and message queues respectively.
These bits overlap, because they are meant to be used within the
context of specific procedures. Creation flags, used for `*get` procedures,
and usage flags used by all other IPC procedures. Do not mix creation and
usage flags, as well as flags prefixed differently (excluding `IPC_`
prefix).
*/ */
IPC_Mode_Bits :: enum { IPC_Flags_Bits :: enum {
// Access modes for shared memory.
WROTH = 1, WROTH = 1,
RDOTH = 2, RDOTH = 2,
WRGRP = 4, WRGRP = 4,
RDGRP = 5, RDGRP = 5,
WRUSR = 7, WRUSR = 7,
RDUSR = 8, RDUSR = 8,
DEST = 9, // Creation flags for shared memory.
LOCKED = 10,
}
/*
Shared memory flags bits
*/
IPC_Flags_Bits :: enum {
IPC_CREAT = 9, IPC_CREAT = 9,
IPC_EXCL = 10, IPC_EXCL = 10,
IPC_NOWAIT = 11,
// Semaphore
SEM_UNDO = 9,
// Shared memory
SHM_HUGETLB = 11, SHM_HUGETLB = 11,
SHM_NORESERVE = 12, SHM_NORESERVE = 12,
// Usage flags for shared memory.
IPC_NOWAIT = 11,
SEM_UNDO = 9,
SHM_RDONLY = 12, SHM_RDONLY = 12,
SHM_RND = 13, SHM_RND = 13,
SHM_REMAP = 14, SHM_REMAP = 14,
SHM_EXEC = 15, SHM_EXEC = 15,
// Message queue
MSG_NOERROR = 12, MSG_NOERROR = 12,
MSG_EXCEPT = 13, MSG_EXCEPT = 13,
MSG_COPY = 14, MSG_COPY = 14,
+4 -1
View File
@@ -391,4 +391,7 @@ MAP_HUGE_256MB :: transmute(Map_Flags)(u32(28) << MAP_HUGE_SHIFT)
MAP_HUGE_512MB :: transmute(Map_Flags)(u32(29) << MAP_HUGE_SHIFT) MAP_HUGE_512MB :: transmute(Map_Flags)(u32(29) << MAP_HUGE_SHIFT)
MAP_HUGE_1GB :: transmute(Map_Flags)(u32(30) << MAP_HUGE_SHIFT) MAP_HUGE_1GB :: transmute(Map_Flags)(u32(30) << MAP_HUGE_SHIFT)
MAP_HUGE_2GB :: transmute(Map_Flags)(u32(31) << MAP_HUGE_SHIFT) MAP_HUGE_2GB :: transmute(Map_Flags)(u32(31) << MAP_HUGE_SHIFT)
MAP_HUGE_16GB :: transmute(Map_Flags)(u32(34) << MAP_HUGE_SHIFT) MAP_HUGE_16GB :: transmute(Map_Flags)(u32(34) << MAP_HUGE_SHIFT)
/* Get window size */
TIOCGWINSZ :: 0x5413
+3 -8
View File
@@ -937,17 +937,12 @@ IO_Vec :: struct {
} }
/* /*
Access mode for shared memory Access modes and flags used by SystemV IPC procedures.
*/
IPC_Mode :: bit_set[IPC_Mode_Bits; u32]
/*
Flags used by IPC objects
*/ */
IPC_Flags :: bit_set[IPC_Flags_Bits; i16] IPC_Flags :: bit_set[IPC_Flags_Bits; i16]
/* /*
Permissions for IPC objects Permissions for SystemV IPC primitives.
*/ */
IPC_Perm :: struct { IPC_Perm :: struct {
key: Key, key: Key,
@@ -955,7 +950,7 @@ IPC_Perm :: struct {
gid: u32, gid: u32,
cuid: u32, cuid: u32,
cgid: u32, cgid: u32,
mode: IPC_Mode, mode: IPC_Flags, // Only contains mode flags.
seq: u16, seq: u16,
_: [2 + 2*size_of(int)]u8, _: [2 + 2*size_of(int)]u8,
} }
+4 -1
View File
@@ -8,7 +8,10 @@ when ODIN_OS == .Darwin {
} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { } else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD {
foreign import lib "system:dl" foreign import lib "system:dl"
} else { } else {
foreign import lib "system:c" foreign import lib {
"system:c",
"system:dl",
}
} }
// dlfcn.h - dynamic linking // dlfcn.h - dynamic linking
+3 -1
View File
@@ -31,7 +31,7 @@ Unimplemented headers:
- iso646.h | Impossible - iso646.h | Impossible
- math.h | See `core:c/libc` - math.h | See `core:c/libc`
- mqueue.h | Targets don't seem to have implemented it - mqueue.h | Targets don't seem to have implemented it
- regex.h | See `core:regex` - regex.h | See `core:text/regex`
- search.h | Not useful in Odin - search.h | Not useful in Odin
- spawn.h | Use `fork`, `execve`, etc. - spawn.h | Use `fork`, `execve`, etc.
- stdarg.h | See `core:c/libc` - stdarg.h | See `core:c/libc`
@@ -53,6 +53,8 @@ import "base:intrinsics"
import "core:c" import "core:c"
IS_SUPPORTED :: _IS_SUPPORTED
result :: enum c.int { result :: enum c.int {
// Use `errno` and `strerror` for more information. // Use `errno` and `strerror` for more information.
FAIL = -1, FAIL = -1,
+10
View File
@@ -0,0 +1,10 @@
#+build !linux
#+build !darwin
#+build !netbsd
#+build !openbsd
#+build !freebsd
#+build !haiku
package posix
_IS_SUPPORTED :: false
+5
View File
@@ -0,0 +1,5 @@
#+build linux, darwin, netbsd, openbsd, freebsd, haiku
package posix
_IS_SUPPORTED :: true
+1 -1
View File
@@ -350,4 +350,4 @@ NEWTEXTMETRICW :: struct {
ntmAvgWidth: UINT, ntmAvgWidth: UINT,
} }
FONTENUMPROCW :: #type proc(lpelf: ^ENUMLOGFONTW, lpntm: ^NEWTEXTMETRICW, FontType: DWORD, lParam: LPARAM) -> INT FONTENUMPROCW :: #type proc "system" (lpelf: ^ENUMLOGFONTW, lpntm: ^NEWTEXTMETRICW, FontType: DWORD, lParam: LPARAM) -> INT
+1
View File
@@ -168,6 +168,7 @@ foreign kernel32 {
ResumeThread :: proc(thread: HANDLE) -> DWORD --- ResumeThread :: proc(thread: HANDLE) -> DWORD ---
GetThreadPriority :: proc(thread: HANDLE) -> c_int --- GetThreadPriority :: proc(thread: HANDLE) -> c_int ---
SetThreadPriority :: proc(thread: HANDLE, priority: c_int) -> BOOL --- SetThreadPriority :: proc(thread: HANDLE, priority: c_int) -> BOOL ---
GetThreadDescription :: proc(hThread: HANDLE, ppszThreadDescription: ^PCWSTR) -> HRESULT ---
SetThreadDescription :: proc(hThread: HANDLE, lpThreadDescription: PCWSTR) -> HRESULT --- SetThreadDescription :: proc(hThread: HANDLE, lpThreadDescription: PCWSTR) -> HRESULT ---
GetExitCodeThread :: proc(thread: HANDLE, exit_code: ^DWORD) -> BOOL --- GetExitCodeThread :: proc(thread: HANDLE, exit_code: ^DWORD) -> BOOL ---
TerminateThread :: proc(thread: HANDLE, exit_code: DWORD) -> BOOL --- TerminateThread :: proc(thread: HANDLE, exit_code: DWORD) -> BOOL ---
+68 -4
View File
@@ -75,7 +75,7 @@ LANGIDFROMLCID :: #force_inline proc "contextless" (lcid: LCID) -> LANGID {
return LANGID(lcid) return LANGID(lcid)
} }
utf8_to_utf16 :: proc(s: string, allocator := context.temp_allocator) -> []u16 { utf8_to_utf16_alloc :: proc(s: string, allocator := context.temp_allocator) -> []u16 {
if len(s) < 1 { if len(s) < 1 {
return nil return nil
} }
@@ -101,14 +101,42 @@ utf8_to_utf16 :: proc(s: string, allocator := context.temp_allocator) -> []u16 {
} }
return text[:n] return text[:n]
} }
utf8_to_wstring :: proc(s: string, allocator := context.temp_allocator) -> wstring {
utf8_to_utf16_buf :: proc(buf: []u16, s: string) -> []u16 {
n1 := MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, raw_data(s), i32(len(s)), nil, 0)
if n1 == 0 {
return nil
} else if int(n1) > len(buf) {
return nil
}
n1 = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, raw_data(s), i32(len(s)), raw_data(buf[:]), n1)
if n1 == 0 {
return nil
} else if int(n1) > len(buf) {
return nil
}
return buf[:n1]
}
utf8_to_utf16 :: proc{utf8_to_utf16_alloc, utf8_to_utf16_buf}
utf8_to_wstring_alloc :: proc(s: string, allocator := context.temp_allocator) -> wstring {
if res := utf8_to_utf16(s, allocator); len(res) > 0 { if res := utf8_to_utf16(s, allocator); len(res) > 0 {
return raw_data(res) return raw_data(res)
} }
return nil return nil
} }
wstring_to_utf8 :: proc(s: wstring, N: int, allocator := context.temp_allocator) -> (res: string, err: runtime.Allocator_Error) { utf8_to_wstring_buf :: proc(buf: []u16, s: string) -> wstring {
if res := utf8_to_utf16(buf, s); len(res) > 0 {
return raw_data(res)
}
return nil
}
utf8_to_wstring :: proc{utf8_to_wstring_alloc, utf8_to_wstring_buf}
wstring_to_utf8_alloc :: proc(s: wstring, N: int, allocator := context.temp_allocator) -> (res: string, err: runtime.Allocator_Error) {
context.allocator = allocator context.allocator = allocator
if N == 0 { if N == 0 {
@@ -142,13 +170,49 @@ wstring_to_utf8 :: proc(s: wstring, N: int, allocator := context.temp_allocator)
return string(text[:n]), nil return string(text[:n]), nil
} }
utf16_to_utf8 :: proc(s: []u16, allocator := context.temp_allocator) -> (res: string, err: runtime.Allocator_Error) { wstring_to_utf8_buf :: proc(buf: []u8, s: wstring) -> (res: string) {
n := WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, s, -1, nil, 0, nil, nil)
if n == 0 {
return
} else if int(n) > len(buf) {
return
}
n2 := WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, s, -1, raw_data(buf), n, nil, nil)
if n2 == 0 {
return
} else if int(n2) > len(buf) {
return
}
for i in 0..<n2 {
if buf[i] == 0 {
n2 = i
break
}
}
return string(buf[:n2])
}
wstring_to_utf8 :: proc{wstring_to_utf8_alloc, wstring_to_utf8_buf}
utf16_to_utf8_alloc :: proc(s: []u16, allocator := context.temp_allocator) -> (res: string, err: runtime.Allocator_Error) {
if len(s) == 0 { if len(s) == 0 {
return "", nil return "", nil
} }
return wstring_to_utf8(raw_data(s), len(s), allocator) return wstring_to_utf8(raw_data(s), len(s), allocator)
} }
utf16_to_utf8_buf :: proc(buf: []u8, s: []u16) -> (res: string) {
if len(s) == 0 {
return
}
return wstring_to_utf8(buf, raw_data(s))
}
utf16_to_utf8 :: proc{utf16_to_utf8_alloc, utf16_to_utf8_buf}
// AdvAPI32, NetAPI32 and UserENV helpers. // AdvAPI32, NetAPI32 and UserENV helpers.
allowed_username :: proc(username: string) -> bool { allowed_username :: proc(username: string) -> bool {
+2 -2
View File
@@ -589,7 +589,7 @@ WAVE_FORMAT_FLAC :: 0xF1AC /* flac.sourceforge.net */
WAVE_FORMAT_EXTENSIBLE :: 0xFFFE /* Microsoft */ WAVE_FORMAT_EXTENSIBLE :: 0xFFFE /* Microsoft */
WAVEFORMATEX :: struct { WAVEFORMATEX :: struct #packed {
wFormatTag: WORD, wFormatTag: WORD,
nChannels: WORD, nChannels: WORD,
nSamplesPerSec: DWORD, nSamplesPerSec: DWORD,
@@ -603,7 +603,7 @@ LPCWAVEFORMATEX :: ^WAVEFORMATEX
// New wave format development should be based on the WAVEFORMATEXTENSIBLE structure. // New wave format development should be based on the WAVEFORMATEXTENSIBLE structure.
// WAVEFORMATEXTENSIBLE allows you to avoid having to register a new format tag with Microsoft. // WAVEFORMATEXTENSIBLE allows you to avoid having to register a new format tag with Microsoft.
// Simply define a new GUID value for the WAVEFORMATEXTENSIBLE.SubFormat field and use WAVE_FORMAT_EXTENSIBLE in the WAVEFORMATEXTENSIBLE.Format.wFormatTag field. // Simply define a new GUID value for the WAVEFORMATEXTENSIBLE.SubFormat field and use WAVE_FORMAT_EXTENSIBLE in the WAVEFORMATEXTENSIBLE.Format.wFormatTag field.
WAVEFORMATEXTENSIBLE :: struct { WAVEFORMATEXTENSIBLE :: struct #packed {
using Format: WAVEFORMATEX, using Format: WAVEFORMATEX,
Samples: struct #raw_union { Samples: struct #raw_union {
wValidBitsPerSample: WORD, /* bits of precision */ wValidBitsPerSample: WORD, /* bits of precision */
+5 -6
View File
@@ -11,17 +11,17 @@ import "core:strings"
// - [[ https://invisible-island.net/ncurses/terminfo.src.html ]] // - [[ https://invisible-island.net/ncurses/terminfo.src.html ]]
get_no_color :: proc() -> bool { get_no_color :: proc() -> bool {
if no_color, ok := os.lookup_env("NO_COLOR"); ok { buf: [128]u8
defer delete(no_color) if no_color, err := os.lookup_env(buf[:], "NO_COLOR"); err == nil {
return no_color != "" return no_color != ""
} }
return false return false
} }
get_environment_color :: proc() -> Color_Depth { get_environment_color :: proc() -> Color_Depth {
buf: [128]u8
// `COLORTERM` is non-standard but widespread and unambiguous. // `COLORTERM` is non-standard but widespread and unambiguous.
if colorterm, ok := os.lookup_env("COLORTERM"); ok { if colorterm, err := os.lookup_env(buf[:], "COLORTERM"); err == nil {
defer delete(colorterm)
// These are the only values that are typically advertised that have // These are the only values that are typically advertised that have
// anything to do with color depth. // anything to do with color depth.
if colorterm == "truecolor" || colorterm == "24bit" { if colorterm == "truecolor" || colorterm == "24bit" {
@@ -29,8 +29,7 @@ get_environment_color :: proc() -> Color_Depth {
} }
} }
if term, ok := os.lookup_env("TERM"); ok { if term, err := os.lookup_env(buf[:], "TERM"); err == nil {
defer delete(term)
if strings.contains(term, "-truecolor") { if strings.contains(term, "-truecolor") {
return .True_Color return .True_Color
} }
+12 -5
View File
@@ -57,6 +57,8 @@ SHARED_RANDOM_SEED : u64 : #config(ODIN_TEST_RANDOM_SEED, 0)
// Set the lowest log level for this test run. // Set the lowest log level for this test run.
LOG_LEVEL_DEFAULT : string : "debug" when ODIN_DEBUG else "info" LOG_LEVEL_DEFAULT : string : "debug" when ODIN_DEBUG else "info"
LOG_LEVEL : string : #config(ODIN_TEST_LOG_LEVEL, LOG_LEVEL_DEFAULT) LOG_LEVEL : string : #config(ODIN_TEST_LOG_LEVEL, LOG_LEVEL_DEFAULT)
// Report a message at the info level when a test has changed its state.
LOG_STATE_CHANGES : bool : #config(ODIN_TEST_LOG_STATE_CHANGES, false)
// Show only the most necessary logging information. // Show only the most necessary logging information.
USING_SHORT_LOGS : bool : #config(ODIN_TEST_SHORT_LOGS, false) USING_SHORT_LOGS : bool : #config(ODIN_TEST_SHORT_LOGS, false)
// Output a report of the tests to the given path. // Output a report of the tests to the given path.
@@ -631,8 +633,8 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
total_done_count += 1 total_done_count += 1
} }
when ODIN_DEBUG { when LOG_STATE_CHANGES {
log.debugf("Test #%i %s.%s changed state to %v.", task_channel.test_index, it.pkg, it.name, event.new_state) log.infof("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_state = event.new_state
@@ -741,7 +743,8 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
if test_index, reason, ok := should_stop_test(); ok { if test_index, reason, ok := should_stop_test(); ok {
#no_bounds_check report.all_test_states[test_index] = .Failed passed := reason == .Successful_Stop
#no_bounds_check report.all_test_states[test_index] = .Successful if passed else .Failed
#no_bounds_check it := internal_tests[test_index] #no_bounds_check it := internal_tests[test_index]
#no_bounds_check pkg := report.packages_by_name[it.pkg] #no_bounds_check pkg := report.packages_by_name[it.pkg]
pkg.frame_ready = false pkg.frame_ready = false
@@ -762,7 +765,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
fmt.assertf(task_data != nil, "A signal (%v) was raised to stop test #%i %s.%s, but its task data is missing.", 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) reason, test_index, it.pkg, it.name)
if !task_data.t._fail_now_called { if !passed && !task_data.t._fail_now_called {
if test_index not_in failed_test_reason_map { if test_index not_in failed_test_reason_map {
// We only write a new error message here if there wasn't one // We only write a new error message here if there wasn't one
// already, because the message we can provide based only on // already, because the message we can provide based only on
@@ -780,7 +783,11 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
end_t(&task_data.t) end_t(&task_data.t)
total_failure_count += 1 if passed {
total_success_count += 1
} else {
total_failure_count += 1
}
total_done_count += 1 total_done_count += 1
} }
+24 -1
View File
@@ -12,8 +12,26 @@ package testing
import "base:runtime" import "base:runtime"
import "core:log" import "core:log"
@(private, thread_local)
local_test_expected_failures: struct {
signal: i32,
message_count: int,
messages: [MAX_EXPECTED_ASSERTIONS_PER_TEST]string,
location_count: int,
locations: [MAX_EXPECTED_ASSERTIONS_PER_TEST]runtime.Source_Code_Location,
}
@(private, thread_local)
local_test_assertion_raised: struct {
message: string,
location: runtime.Source_Code_Location,
}
Stop_Reason :: enum { Stop_Reason :: enum {
Unknown, Unknown,
Successful_Stop,
Illegal_Instruction, Illegal_Instruction,
Arithmetic_Error, Arithmetic_Error,
Segmentation_Fault, Segmentation_Fault,
@@ -21,7 +39,12 @@ Stop_Reason :: enum {
} }
test_assertion_failure_proc :: proc(prefix, message: string, loc: runtime.Source_Code_Location) -> ! { test_assertion_failure_proc :: proc(prefix, message: string, loc: runtime.Source_Code_Location) -> ! {
log.fatalf("%s: %s", prefix, message, location = loc) if local_test_expected_failures.message_count + local_test_expected_failures.location_count > 0 {
local_test_assertion_raised = { message, loc }
log.debugf("%s\n\tmessage: %q\n\tlocation: %w", prefix, message, loc)
} else {
log.fatalf("%s: %s", prefix, message, location = loc)
}
runtime.trap() runtime.trap()
} }
+35 -7
View File
@@ -20,7 +20,8 @@ import "core:terminal/ansi"
@(private="file") stop_test_gate: sync.Mutex @(private="file") stop_test_gate: sync.Mutex
@(private="file") stop_test_index: libc.sig_atomic_t @(private="file") stop_test_index: libc.sig_atomic_t
@(private="file") stop_test_reason: libc.sig_atomic_t @(private="file") stop_test_signal: libc.sig_atomic_t
@(private="file") stop_test_passed: libc.sig_atomic_t
@(private="file") stop_test_alert: libc.sig_atomic_t @(private="file") stop_test_alert: libc.sig_atomic_t
@(private="file", thread_local) @(private="file", thread_local)
@@ -99,7 +100,30 @@ This is a dire bug and should be reported to the Odin developers.
if sync.mutex_guard(&stop_test_gate) { if sync.mutex_guard(&stop_test_gate) {
intrinsics.atomic_store(&stop_test_index, local_test_index) 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_signal, cast(libc.sig_atomic_t)sig)
passed: bool
check_passing: {
if location := local_test_assertion_raised.location; location != {} {
for i in 0..<local_test_expected_failures.location_count {
if local_test_expected_failures.locations[i] == location {
passed = true
break check_passing
}
}
}
if message := local_test_assertion_raised.message; message != "" {
for i in 0..<local_test_expected_failures.message_count {
if local_test_expected_failures.messages[i] == message {
passed = true
break check_passing
}
}
}
if signal := local_test_expected_failures.signal; signal == sig {
passed = true
}
}
intrinsics.atomic_store(&stop_test_passed, cast(libc.sig_atomic_t)passed)
intrinsics.atomic_store(&stop_test_alert, 1) intrinsics.atomic_store(&stop_test_alert, 1)
for { for {
@@ -154,11 +178,15 @@ _should_stop_test :: proc() -> (test_index: int, reason: Stop_Reason, ok: bool)
intrinsics.atomic_store(&stop_test_alert, 0) intrinsics.atomic_store(&stop_test_alert, 0)
test_index = cast(int)intrinsics.atomic_load(&stop_test_index) test_index = cast(int)intrinsics.atomic_load(&stop_test_index)
switch intrinsics.atomic_load(&stop_test_reason) { if cast(bool)intrinsics.atomic_load(&stop_test_passed) {
case libc.SIGFPE: reason = .Arithmetic_Error reason = .Successful_Stop
case libc.SIGILL: reason = .Illegal_Instruction } else {
case libc.SIGSEGV: reason = .Segmentation_Fault switch intrinsics.atomic_load(&stop_test_signal) {
case SIGTRAP: reason = .Unhandled_Trap 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 ok = true
} }
+73
View File
@@ -21,6 +21,8 @@ import "core:mem"
_ :: reflect // alias reflect to nothing to force visibility for -vet _ :: reflect // alias reflect to nothing to force visibility for -vet
_ :: mem // in case TRACKING_MEMORY is not enabled _ :: mem // in case TRACKING_MEMORY is not enabled
MAX_EXPECTED_ASSERTIONS_PER_TEST :: 5
// IMPORTANT NOTE: Compiler requires this layout // IMPORTANT NOTE: Compiler requires this layout
Test_Signature :: proc(^T) Test_Signature :: proc(^T)
@@ -155,3 +157,74 @@ set_fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location
location = loc, location = loc,
}) })
} }
/*
Let the test runner know that it should expect an assertion failure from a
specific location in the source code for this test.
In the event that an assertion fails, a debug message will be logged with its
exact message and location in a copyable format to make it convenient to write
tests which use this API.
This procedure may be called up to 5 times with different locations.
This is a limitation for the sake of simplicity in the implementation, and you
should consider breaking up your tests into smaller procedures if you need to
check for asserts in more than 2 places.
*/
expect_assert_from :: proc(t: ^T, expected_place: runtime.Source_Code_Location, caller_loc := #caller_location) {
count := local_test_expected_failures.location_count
if count == MAX_EXPECTED_ASSERTIONS_PER_TEST {
panic("This test cannot handle that many expected assertions based on matching the location.", caller_loc)
}
local_test_expected_failures.locations[count] = expected_place
local_test_expected_failures.location_count += 1
}
/*
Let the test runner know that it should expect an assertion failure with a
specific message for this test.
In the event that an assertion fails, a debug message will be logged with its
exact message and location in a copyable format to make it convenient to write
tests which use this API.
This procedure may be called up to 5 times with different messages.
This is a limitation for the sake of simplicity in the implementation, and you
should consider breaking up your tests into smaller procedures if you need to
check for more than a couple different assertion messages.
*/
expect_assert_message :: proc(t: ^T, expected_message: string, caller_loc := #caller_location) {
count := local_test_expected_failures.message_count
if count == MAX_EXPECTED_ASSERTIONS_PER_TEST {
panic("This test cannot handle that many expected assertions based on matching the message.", caller_loc)
}
local_test_expected_failures.messages[count] = expected_message
local_test_expected_failures.message_count += 1
}
expect_assert :: proc {
expect_assert_from,
expect_assert_message,
}
/*
Let the test runner know that it should expect a signal to be raised within
this test.
This API is for advanced users, as arbitrary signals will not be caught; only
the ones already handled by the test runner, such as
- SIGINT, (interrupt)
- SIGTERM, (polite termination)
- SIGILL, (illegal instruction)
- SIGFPE, (arithmetic error)
- SIGSEGV, and (segmentation fault)
- SIGTRAP (only on POSIX systems). (trap / debug trap)
Note that only one signal can be expected per test.
*/
expect_signal :: proc(t: ^T, #any_int sig: i32) {
local_test_expected_failures.signal = sig
}
-4
View File
@@ -28,8 +28,6 @@ Creation_Error :: enum {
Expected_Delimiter, Expected_Delimiter,
// An unknown letter was supplied to `create_by_user` after the last delimiter. // An unknown letter was supplied to `create_by_user` after the last delimiter.
Unknown_Flag, Unknown_Flag,
// An unsupported flag was supplied.
Unsupported_Flag,
} }
Error :: union #shared_nil { Error :: union #shared_nil {
@@ -69,7 +67,6 @@ Regular_Expression :: struct {
/* /*
An iterator to repeatedly match a pattern against a string, to be used with `*_iterator` procedures. An iterator to repeatedly match a pattern against a string, to be used with `*_iterator` procedures.
Note: Does not handle `.Multiline` properly.
*/ */
Match_Iterator :: struct { Match_Iterator :: struct {
regex: Regular_Expression, regex: Regular_Expression,
@@ -436,7 +433,6 @@ match_with_preallocated_capture :: proc(
/* /*
Iterate over a `Match_Iterator` and return successive captures. Iterate over a `Match_Iterator` and return successive captures.
Note: Does not handle `.Multiline` properly.
Inputs: Inputs:
- it: Pointer to the `Match_Iterator` to iterate over. - it: Pointer to the `Match_Iterator` to iterate over.
+26 -13
View File
@@ -39,7 +39,8 @@ Type representing a thread handle and the associated with that thread data.
Thread :: struct { Thread :: struct {
using specific: Thread_Os_Specific, using specific: Thread_Os_Specific,
flags: bit_set[Thread_State; u8], flags: bit_set[Thread_State; u8],
// Thread ID. // Thread ID. Depending on the platform, may start out as 0 (zero) until the thread
// has had a chance to run.
id: int, id: int,
// The thread procedure. // The thread procedure.
procedure: Thread_Proc, procedure: Thread_Proc,
@@ -257,8 +258,10 @@ 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 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 `fn` procedure, the resources associated with the thread are going to be
automatically freed. **Do not** dereference the `^Thread` pointer, if this automatically freed.
flag is specified.
**Do not** dereference the `^Thread` pointer, if this flag is specified.
That includes calling `join`, which needs to dereference ^Thread`.
**IMPORTANT**: If `init_context` is specified and the default temporary allocator **IMPORTANT**: If `init_context` is specified and the default temporary allocator
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
@@ -290,8 +293,10 @@ 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 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 `fn` procedure, the resources associated with the thread are going to be
automatically freed. **Do not** dereference the `^Thread` pointer, if this automatically freed.
flag is specified.
**Do not** dereference the `^Thread` pointer, if this flag is specified.
That includes calling `join`, which needs to dereference ^Thread`.
**IMPORTANT**: If `init_context` is specified and the default temporary allocator **IMPORTANT**: If `init_context` is specified and the default temporary allocator
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
@@ -327,8 +332,10 @@ 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 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 `fn` procedure, the resources associated with the thread are going to be
automatically freed. **Do not** dereference the `^Thread` pointer, if this automatically freed.
flag is specified.
**Do not** dereference the `^Thread` pointer, if this flag is specified.
That includes calling `join`, which needs to dereference ^Thread`.
**IMPORTANT**: If `init_context` is specified and the default temporary allocator **IMPORTANT**: If `init_context` is specified and the default temporary allocator
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
@@ -370,8 +377,10 @@ 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 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 `fn` procedure, the resources associated with the thread are going to be
automatically freed. **Do not** dereference the `^Thread` pointer, if this automatically freed.
flag is specified.
**Do not** dereference the `^Thread` pointer, if this flag is specified.
That includes calling `join`, which needs to dereference ^Thread`.
**IMPORTANT**: If `init_context` is specified and the default temporary allocator **IMPORTANT**: If `init_context` is specified and the default temporary allocator
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
@@ -419,8 +428,10 @@ 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 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 `fn` procedure, the resources associated with the thread are going to be
automatically freed. **Do not** dereference the `^Thread` pointer, if this automatically freed.
flag is specified.
**Do not** dereference the `^Thread` pointer, if this flag is specified.
That includes calling `join`, which needs to dereference ^Thread`.
**IMPORTANT**: If `init_context` is specified and the default temporary allocator **IMPORTANT**: If `init_context` is specified and the default temporary allocator
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
@@ -470,8 +481,10 @@ 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 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 `fn` procedure, the resources associated with the thread are going to be
automatically freed. **Do not** dereference the `^Thread` pointer, if this automatically freed.
flag is specified.
**Do not** dereference the `^Thread` pointer, if this flag is specified.
That includes calling `join`, which needs to dereference ^Thread`.
**IMPORTANT**: If `init_context` is specified and the default temporary allocator **IMPORTANT**: If `init_context` is specified and the default temporary allocator
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
+14
View File
@@ -120,6 +120,20 @@ pool_join :: proc(pool: ^Pool) {
yield() yield()
unstarted_count: int
for t in pool.threads {
flags := intrinsics.atomic_load(&t.flags)
if .Started not_in flags {
unstarted_count += 1
}
}
// most likely the user forgot to call `pool_start`
// exit here, so we don't hang forever
if len(pool.threads) == unstarted_count {
return
}
started_count: int started_count: int
for started_count < len(pool.threads) { for started_count < len(pool.threads) {
started_count = 0 started_count = 0
+5 -7
View File
@@ -29,14 +29,10 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
t.id = sync.current_thread_id() t.id = sync.current_thread_id()
if .Started not_in sync.atomic_load(&t.flags) { for (.Started not_in sync.atomic_load(&t.flags)) {
sync.wait(&t.start_ok) sync.wait(&t.start_ok)
} }
if .Joined in sync.atomic_load(&t.flags) {
return nil
}
// Enable thread's cancelability. // Enable thread's cancelability.
// NOTE(laytan): Darwin does not correctly/fully support all of this, not doing this does // 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 // actually make pthread_cancel work in the capacity of my tests, while executing this would
@@ -124,7 +120,6 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
free(thread, thread.creation_allocator) free(thread, thread.creation_allocator)
return nil return nil
} }
return thread return thread
} }
@@ -149,10 +144,13 @@ _join :: proc(t: ^Thread) {
// Prevent non-started threads from blocking main thread with initial wait // Prevent non-started threads from blocking main thread with initial wait
// condition. // condition.
if .Started not_in sync.atomic_load(&t.flags) { for (.Started not_in sync.atomic_load(&t.flags)) {
_start(t) _start(t)
} }
posix.pthread_join(t.unix_thread, nil) posix.pthread_join(t.unix_thread, nil)
t.flags += {.Joined}
} }
_join_multiple :: proc(threads: ..^Thread) { _join_multiple :: proc(threads: ..^Thread) {
+9 -9
View File
@@ -13,6 +13,7 @@ Thread_Os_Specific :: struct {
win32_thread: win32.HANDLE, win32_thread: win32.HANDLE,
win32_thread_id: win32.DWORD, win32_thread_id: win32.DWORD,
mutex: sync.Mutex, mutex: sync.Mutex,
start_ok: sync.Sema,
} }
_thread_priority_map := [Thread_Priority]i32{ _thread_priority_map := [Thread_Priority]i32{
@@ -27,12 +28,10 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
__windows_thread_entry_proc :: proc "system" (t_: rawptr) -> win32.DWORD { __windows_thread_entry_proc :: proc "system" (t_: rawptr) -> win32.DWORD {
t := (^Thread)(t_) t := (^Thread)(t_)
if .Joined in sync.atomic_load(&t.flags) { for (.Started not_in sync.atomic_load(&t.flags)) {
return 0 sync.wait(&t.start_ok)
} }
t.id = sync.current_thread_id()
{ {
init_context := t.init_context init_context := t.init_context
@@ -76,6 +75,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
thread.procedure = procedure thread.procedure = procedure
thread.win32_thread = win32_thread thread.win32_thread = win32_thread
thread.win32_thread_id = win32_thread_id thread.win32_thread_id = win32_thread_id
thread.id = int(win32_thread_id)
ok := win32.SetThreadPriority(win32_thread, _thread_priority_map[priority]) ok := win32.SetThreadPriority(win32_thread, _thread_priority_map[priority])
assert(ok == true) assert(ok == true)
@@ -103,16 +103,15 @@ _join :: proc(t: ^Thread) {
return return
} }
t.flags += {.Joined} for (.Started not_in sync.atomic_load(&t.flags)) {
_start(t)
if .Started not_in t.flags {
t.flags += {.Started}
win32.ResumeThread(t.win32_thread)
} }
win32.WaitForSingleObject(t.win32_thread, win32.INFINITE) win32.WaitForSingleObject(t.win32_thread, win32.INFINITE)
win32.CloseHandle(t.win32_thread) win32.CloseHandle(t.win32_thread)
t.win32_thread = win32.INVALID_HANDLE t.win32_thread = win32.INVALID_HANDLE
t.flags += {.Joined}
} }
_join_multiple :: proc(threads: ..^Thread) { _join_multiple :: proc(threads: ..^Thread) {
@@ -136,6 +135,7 @@ _join_multiple :: proc(threads: ..^Thread) {
for t in threads { for t in threads {
win32.CloseHandle(t.win32_thread) win32.CloseHandle(t.win32_thread)
t.win32_thread = win32.INVALID_HANDLE t.win32_thread = win32.INVALID_HANDLE
t.flags += {.Joined}
} }
} }
+1 -6
View File
@@ -577,12 +577,7 @@ parse_tzif :: proc(_buffer: []u8, region_name: string, allocator := context.allo
footer_str := string(buffer[:end_idx]) footer_str := string(buffer[:end_idx])
// UTC is a special case, we don't need to alloc // UTC is a special case, we don't need to alloc
if len(local_time_types) == 1 { if len(local_time_types) == 1 && local_time_types[0].utoff == 0 {
name := cstring(raw_data(timezone_string_table[local_time_types[0].idx:]))
if name != "UTC" {
return
}
return nil, true return nil, true
} }
+4 -1
View File
@@ -45,4 +45,7 @@ package all
@(require) import stbi "vendor:stb/image" @(require) import stbi "vendor:stb/image"
@(require) import "vendor:stb/rect_pack" @(require) import "vendor:stb/rect_pack"
@(require) import "vendor:stb/truetype" @(require) import "vendor:stb/truetype"
@(require) import "vendor:stb/vorbis" @(require) import "vendor:stb/vorbis"
@(require) import "vendor:kb_text_shape"
+1
View File
@@ -540,6 +540,7 @@ gb_internal void report_os_info() {
} }
switch (major) { switch (major) {
case 26: gb_printf("macOS Tahoe"); break;
case 15: gb_printf("macOS Sequoia"); break; case 15: gb_printf("macOS Sequoia"); break;
case 14: gb_printf("macOS Sonoma"); break; case 14: gb_printf("macOS Sonoma"); break;
case 13: gb_printf("macOS Ventura"); break; case 13: gb_printf("macOS Ventura"); break;
+8 -1
View File
@@ -385,6 +385,13 @@ enum LinkerChoice : i32 {
Linker_COUNT, Linker_COUNT,
}; };
enum SourceCodeLocationInfo : u8 {
SourceCodeLocationInfo_Normal = 0,
SourceCodeLocationInfo_Obfuscated = 1,
SourceCodeLocationInfo_Filename = 2,
SourceCodeLocationInfo_None = 3,
};
String linker_choices[Linker_COUNT] = { String linker_choices[Linker_COUNT] = {
str_lit("default"), str_lit("default"),
str_lit("lld"), str_lit("lld"),
@@ -512,7 +519,7 @@ struct BuildContext {
bool dynamic_map_calls; bool dynamic_map_calls;
bool obfuscate_source_code_locations; SourceCodeLocationInfo source_code_location_info;
bool min_link_libs; bool min_link_libs;
+5
View File
@@ -148,6 +148,11 @@ gb_internal bool does_require_msgSend_stret(Type *return_type) {
if (return_type == nullptr) { if (return_type == nullptr) {
return false; return false;
} }
if (build_context.metrics.os != TargetOs_darwin) {
return false;
}
if (build_context.metrics.arch == TargetArch_i386 || build_context.metrics.arch == TargetArch_amd64) { if (build_context.metrics.arch == TargetArch_i386 || build_context.metrics.arch == TargetArch_amd64) {
i64 struct_limit = type_size_of(t_uintptr) << 1; i64 struct_limit = type_size_of(t_uintptr) << 1;
return type_size_of(return_type) > struct_limit; return type_size_of(return_type) > struct_limit;
+8
View File
@@ -1334,12 +1334,16 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
has_instrumentation = false; has_instrumentation = false;
e->flags |= EntityFlag_Require; e->flags |= EntityFlag_Require;
} else if (ac.instrumentation_enter) { } else if (ac.instrumentation_enter) {
init_core_source_code_location(ctx->checker);
if (!is_valid_instrumentation_call(e->type)) { if (!is_valid_instrumentation_call(e->type)) {
init_core_source_code_location(ctx->checker); init_core_source_code_location(ctx->checker);
gbString s = type_to_string(e->type); 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); error(e->token, "@(instrumentation_enter) procedures must have the type '%s', got %s", instrumentation_proc_type_str, s);
gb_string_free(s); gb_string_free(s);
} }
if ((e->scope->flags & (ScopeFlag_File|ScopeFlag_Pkg)) == 0) {
error(e->token, "@(instrumentation_enter) procedures must be declared at the file scope");
}
MUTEX_GUARD(&ctx->info->instrumentation_mutex); MUTEX_GUARD(&ctx->info->instrumentation_mutex);
if (ctx->info->instrumentation_enter_entity != nullptr) { if (ctx->info->instrumentation_enter_entity != nullptr) {
error(e->token, "@(instrumentation_enter) has already been set"); error(e->token, "@(instrumentation_enter) has already been set");
@@ -1356,6 +1360,9 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
error(e->token, "@(instrumentation_exit) procedures must have the type '%s', got %s", instrumentation_proc_type_str, s); error(e->token, "@(instrumentation_exit) procedures must have the type '%s', got %s", instrumentation_proc_type_str, s);
gb_string_free(s); gb_string_free(s);
} }
if ((e->scope->flags & (ScopeFlag_File|ScopeFlag_Pkg)) == 0) {
error(e->token, "@(instrumentation_exit) procedures must be declared at the file scope");
}
MUTEX_GUARD(&ctx->info->instrumentation_mutex); MUTEX_GUARD(&ctx->info->instrumentation_mutex);
if (ctx->info->instrumentation_exit_entity != nullptr) { if (ctx->info->instrumentation_exit_entity != nullptr) {
error(e->token, "@(instrumentation_exit) has already been set"); error(e->token, "@(instrumentation_exit) has already been set");
@@ -1370,6 +1377,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
e->Procedure.has_instrumentation = has_instrumentation; e->Procedure.has_instrumentation = has_instrumentation;
e->Procedure.no_sanitize_address = ac.no_sanitize_address; e->Procedure.no_sanitize_address = ac.no_sanitize_address;
e->Procedure.no_sanitize_memory = ac.no_sanitize_memory;
e->deprecated_message = ac.deprecated_message; e->deprecated_message = ac.deprecated_message;
e->warning_message = ac.warning_message; e->warning_message = ac.warning_message;
+189 -140
View File
@@ -2424,27 +2424,27 @@ gb_internal void check_assignment_error_suggestion(CheckerContext *c, Operand *o
Type *s = src->Array.elem; Type *s = src->Array.elem;
Type *d = dst->Slice.elem; Type *d = dst->Slice.elem;
if (are_types_identical(s, d)) { if (are_types_identical(s, d)) {
error_line("\tSuggestion: the array expression may be sliced with %s[:]\n", a); error_line("\tSuggestion: The array expression may be sliced with %s[:]\n", a);
} }
} else if (is_type_dynamic_array(src) && is_type_slice(dst)) { } else if (is_type_dynamic_array(src) && is_type_slice(dst)) {
Type *s = src->DynamicArray.elem; Type *s = src->DynamicArray.elem;
Type *d = dst->Slice.elem; Type *d = dst->Slice.elem;
if (are_types_identical(s, d)) { if (are_types_identical(s, d)) {
error_line("\tSuggestion: the dynamic array expression may be sliced with %s[:]\n", a); error_line("\tSuggestion: The dynamic array expression may be sliced with %s[:]\n", a);
} }
}else if (are_types_identical(src, dst) && !are_types_identical(o->type, type)) { }else if (are_types_identical(src, dst) && !are_types_identical(o->type, type)) {
error_line("\tSuggestion: the expression may be directly casted to type %s\n", b); error_line("\tSuggestion: The expression may be directly casted to type %s\n", b);
} else if (are_types_identical(src, t_string) && is_type_u8_slice(dst)) { } else if (are_types_identical(src, t_string) && is_type_u8_slice(dst)) {
error_line("\tSuggestion: a string may be transmuted to %s\n", b); error_line("\tSuggestion: A string may be transmuted to %s\n", b);
error_line("\t This is an UNSAFE operation as string data is assumed to be immutable, \n"); error_line("\t This is an UNSAFE operation as string data is assumed to be immutable,\n");
error_line("\t whereas slices in general are assumed to be mutable.\n"); 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) { } 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); error_line("\tSuggestion: The expression may be casted to %s\n", b);
} else if (check_integer_exceed_suggestion(c, o, type, max_bit_size)) { } else if (check_integer_exceed_suggestion(c, o, type, max_bit_size)) {
return; return;
} else if (is_expr_inferred_fixed_array(c->type_hint_expr) && is_type_array_like(type) && is_type_array_like(o->type)) { } 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); 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); error_line("\tSuggestion: Make sure that `%s` is attached to the compound literal directly\n", s);
gb_string_free(s); gb_string_free(s);
} else if (is_type_pointer(type) && } else if (is_type_pointer(type) &&
o->mode == Addressing_Variable && o->mode == Addressing_Variable &&
@@ -3086,126 +3086,106 @@ gb_internal void check_shift(CheckerContext *c, Operand *x, Operand *y, Ast *nod
GB_ASSERT(node->kind == Ast_BinaryExpr); GB_ASSERT(node->kind == Ast_BinaryExpr);
ast_node(be, BinaryExpr, node); ast_node(be, BinaryExpr, node);
ExactValue x_val = {}; bool y_is_untyped = is_type_untyped(y->type);
if (x->mode == Addressing_Constant) { if (y_is_untyped) {
x_val = exact_value_to_integer(x->value);
}
bool x_is_untyped = is_type_untyped(x->type);
if (!(is_type_integer(x->type) || (x_is_untyped && x_val.kind == ExactValue_Integer))) {
gbString err_str = expr_to_string(x->expr);
error(node, "Shifted operand '%s' must be an integer", err_str);
gb_string_free(err_str);
x->mode = Addressing_Invalid;
return;
}
if (is_type_unsigned(y->type)) {
} else if (is_type_untyped(y->type)) {
convert_to_typed(c, y, t_untyped_integer); convert_to_typed(c, y, t_untyped_integer);
if (y->mode == Addressing_Invalid) { if (y->mode == Addressing_Invalid) {
x->mode = Addressing_Invalid; x->mode = Addressing_Invalid;
return; return;
} }
} else { } else if (!is_type_unsigned(y->type)) {
gbString err_str = expr_to_string(y->expr); gbString y_str = expr_to_string(y->expr);
error(node, "Shift amount '%s' must be an unsigned integer", err_str); error(y->expr, "Shift amount '%s' must be an unsigned integer", y_str);
gb_string_free(err_str); gb_string_free(y_str);
x->mode = Addressing_Invalid; x->mode = Addressing_Invalid;
return; return;
} }
bool x_is_untyped = is_type_untyped(x->type);
if (!(x_is_untyped || is_type_integer(x->type))) {
gbString x_str = expr_to_string(x->expr);
error(x->expr, "Shifted operand '%s' must be an integer", x_str);
gb_string_free(x_str);
x->mode = Addressing_Invalid;
return;
}
if (y->mode == Addressing_Constant) {
if (big_int_is_neg(&y->value.value_integer)) {
gbString y_str = expr_to_string(y->expr);
error(y->expr, "Shift amount '%s' cannot be negative", y_str);
gb_string_free(y_str);
x->mode = Addressing_Invalid;
return;
}
BigInt max_shift = {};
big_int_from_u64(&max_shift, MAX_BIG_INT_SHIFT);
if (big_int_cmp(&y->value.value_integer, &max_shift) > 0) {
gbString y_str = expr_to_string(y->expr);
error(y->expr, "Shift amount '%s' must be <= %u", y_str, MAX_BIG_INT_SHIFT);
gb_string_free(y_str);
x->mode = Addressing_Invalid;
return;
}
if (x->mode == Addressing_Constant) {
if (x_is_untyped) {
convert_to_typed(c, x, t_untyped_integer);
if (x->mode == Addressing_Invalid) {
return;
}
x->expr = node;
x->value = exact_value_shift(be->op.kind, exact_value_to_integer(x->value), exact_value_to_integer(y->value));
if (x->mode == Addressing_Constant) {
if (y->mode == Addressing_Constant) {
ExactValue y_val = exact_value_to_integer(y->value);
if (y_val.kind != ExactValue_Integer) {
gbString err_str = expr_to_string(y->expr);
error(node, "Shift amount '%s' must be an unsigned integer", err_str);
gb_string_free(err_str);
x->mode = Addressing_Invalid;
return; return;
} }
BigInt max_shift = {};
big_int_from_u64(&max_shift, MAX_BIG_INT_SHIFT);
if (big_int_cmp(&y_val.value_integer, &max_shift) > 0) {
gbString err_str = expr_to_string(y->expr);
error(node, "Shift amount too large: '%s'", err_str);
gb_string_free(err_str);
x->mode = Addressing_Invalid;
return;
}
if (!is_type_integer(x->type)) {
// NOTE(bill): It could be an untyped float but still representable
// as an integer
x->type = t_untyped_integer;
}
x->expr = node; x->expr = node;
x->value = exact_value_shift(be->op.kind, x_val, y_val); x->value = exact_value_shift(be->op.kind, x->value, y->value);
check_is_expressible(c, x, x->type);
if (is_type_typed(x->type)) {
check_is_expressible(c, x, x->type);
}
return; return;
} }
TokenPos pos = ast_token(x->expr).pos; if (y_is_untyped) {
if (x_is_untyped) { convert_to_typed(c, y, t_uint);
if (x->expr != nullptr) {
x->expr->tav.is_lhs = true;
}
x->mode = Addressing_Value;
if (type_hint) {
if (is_type_integer(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);
error(node, "Conversion of shifted operand '%s' to '%s' is not allowed", x_str, to_type);
gb_string_free(x_str);
gb_string_free(to_type);
x->mode = Addressing_Invalid;
}
} else if (!is_type_integer(x->type)) {
gbString x_str = expr_to_string(x->expr);
error(node, "Non-integer shifted operand '%s' is not allowed", x_str);
gb_string_free(x_str);
x->mode = Addressing_Invalid;
}
// x->value = x_val;
return;
} }
}
if (y->mode == Addressing_Constant && big_int_is_neg(&y->value.value_integer)) {
gbString err_str = expr_to_string(y->expr);
error(node, "Shift amount cannot be negative: '%s'", err_str);
gb_string_free(err_str);
}
if (!is_type_integer(x->type)) {
gbString err_str = expr_to_string(x->expr);
error(node, "Shift operand '%s' must be an integer", err_str);
gb_string_free(err_str);
x->mode = Addressing_Invalid;
return; return;
} }
if (is_type_untyped(y->type)) { if (x->mode == Addressing_Constant) {
convert_to_typed(c, y, t_uint); if (x_is_untyped) {
if (type_hint) {
if (is_type_integer(type_hint)) {
convert_to_typed(c, x, type_hint);
} else if (is_type_any(type_hint)) {
convert_to_typed(c, x, default_type(t_untyped_integer));
} else {
gbString x_str = expr_to_string(x->expr);
gbString type_str = type_to_string(type_hint);
error(x->expr, "Shifted operand '%s' cannot convert to non-integer type '%s'", x_str, type_str);
gb_string_free(x_str);
gb_string_free(type_str);
x->mode = Addressing_Invalid;
return;
}
} else {
check_is_expressible(c, x, default_type(t_untyped_integer));
}
if (x->mode == Addressing_Invalid) {
return;
}
}
x->mode = Addressing_Value;
} }
x->mode = Addressing_Value;
} }
gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type *y) { gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type *y) {
if (check_is_assignable_to(c, operand, y)) { if (check_is_assignable_to(c, operand, y)) {
return true; return true;
@@ -5884,12 +5864,12 @@ typedef u32 UnpackFlags;
enum UnpackFlag : u32 { enum UnpackFlag : u32 {
UnpackFlag_None = 0, UnpackFlag_None = 0,
UnpackFlag_AllowOk = 1<<0, UnpackFlag_AllowOk = 1<<0,
UnpackFlag_IsVariadic = 1<<1, UnpackFlag_AllowUndef = 1<<1,
UnpackFlag_AllowUndef = 1<<2,
}; };
gb_internal bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize lhs_count, Array<Operand> *operands, Slice<Ast *> const &rhs_arguments, UnpackFlags flags) { gb_internal bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize lhs_count, Array<Operand> *operands, Slice<Ast *> const &rhs_arguments, UnpackFlags flags,
isize variadic_index = -1) {
auto const &add_dependencies_from_unpacking = [](CheckerContext *c, Entity **lhs, isize lhs_count, isize tuple_index, isize tuple_count) -> isize { auto const &add_dependencies_from_unpacking = [](CheckerContext *c, Entity **lhs, isize lhs_count, isize tuple_index, isize tuple_count) -> isize {
if (lhs == nullptr || c->decl == nullptr) { if (lhs == nullptr || c->decl == nullptr) {
return tuple_count; return tuple_count;
@@ -5914,11 +5894,14 @@ gb_internal bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize
return tuple_count; return tuple_count;
}; };
bool allow_ok = (flags & UnpackFlag_AllowOk) != 0; bool allow_ok = (flags & UnpackFlag_AllowOk) != 0;
bool is_variadic = (flags & UnpackFlag_IsVariadic) != 0;
bool allow_undef = (flags & UnpackFlag_AllowUndef) != 0; bool allow_undef = (flags & UnpackFlag_AllowUndef) != 0;
bool is_variadic = variadic_index > -1;
if (!is_variadic) {
variadic_index = lhs_count;
}
bool optional_ok = false; bool optional_ok = false;
isize tuple_index = 0; isize tuple_index = 0;
for (Ast *rhs : rhs_arguments) { for (Ast *rhs : rhs_arguments) {
@@ -5934,26 +5917,18 @@ gb_internal bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize
Type *type_hint = nullptr; Type *type_hint = nullptr;
if (lhs != nullptr) {
if (lhs != nullptr && tuple_index < lhs_count) { if (tuple_index < variadic_index) {
// NOTE(bill): override DeclInfo for dependency // NOTE(bill): override DeclInfo for dependency
Entity *e = lhs[tuple_index]; Entity *e = lhs[tuple_index];
if (e != nullptr) { if (e != nullptr) {
type_hint = e->type; type_hint = e->type;
if (e->flags & EntityFlag_Ellipsis) {
GB_ASSERT(is_type_slice(e->type));
GB_ASSERT(e->type->kind == Type_Slice);
type_hint = e->type->Slice.elem;
} }
} } else if (is_variadic) {
} else if (lhs != nullptr && tuple_index >= lhs_count && is_variadic) { Entity *e = lhs[variadic_index];
// NOTE(bill): override DeclInfo for dependency if (e != nullptr) {
Entity *e = lhs[lhs_count-1]; GB_ASSERT(e->flags & EntityFlag_Ellipsis);
if (e != nullptr) {
type_hint = e->type;
if (e->flags & EntityFlag_Ellipsis) {
GB_ASSERT(is_type_slice(e->type)); GB_ASSERT(is_type_slice(e->type));
GB_ASSERT(e->type->kind == Type_Slice);
type_hint = e->type->Slice.elem; type_hint = e->type->Slice.elem;
} }
} }
@@ -6493,17 +6468,16 @@ gb_internal bool is_call_expr_field_value(AstCallExpr *ce) {
return ce->args[0]->kind == Ast_FieldValue; return ce->args[0]->kind == Ast_FieldValue;
} }
gb_internal Entity **populate_proc_parameter_list(CheckerContext *c, Type *proc_type, isize *lhs_count_, bool *is_variadic) { gb_internal Entity **populate_proc_parameter_list(CheckerContext *c, Type *proc_type, isize *lhs_count_) {
Entity **lhs = nullptr; Entity **lhs = nullptr;
isize lhs_count = -1; isize lhs_count = -1;
if (proc_type == nullptr) { if (proc_type == nullptr || proc_type == t_invalid) {
return nullptr; return nullptr;
} }
GB_ASSERT(is_type_proc(proc_type)); GB_ASSERT(is_type_proc(proc_type));
TypeProc *pt = &base_type(proc_type)->Proc; TypeProc *pt = &base_type(proc_type)->Proc;
*is_variadic = pt->variadic;
if (!pt->is_polymorphic || pt->is_poly_specialized) { if (!pt->is_polymorphic || pt->is_poly_specialized) {
if (pt->params != nullptr) { if (pt->params != nullptr) {
@@ -6697,6 +6671,9 @@ gb_internal bool check_call_arguments_single(CheckerContext *c, Ast *call, Opera
GB_ASSERT(proc_type != nullptr); GB_ASSERT(proc_type != nullptr);
proc_type = base_type(proc_type); proc_type = base_type(proc_type);
if (proc_type == t_invalid) {
return false;
}
GB_ASSERT(proc_type->kind == Type_Proc); GB_ASSERT(proc_type->kind == Type_Proc);
CallArgumentError err = check_call_arguments_internal(c, call, e, proc_type, positional_operands, named_operands, show_error_mode, data); CallArgumentError err = check_call_arguments_internal(c, call, e, proc_type, positional_operands, named_operands, show_error_mode, data);
@@ -6830,7 +6807,7 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c,
Entity **lhs = nullptr; Entity **lhs = nullptr;
isize lhs_count = -1; isize lhs_count = -1;
bool is_variadic = false; i32 variadic_index = -1;
auto positional_operands = array_make<Operand>(heap_allocator(), 0, 0); auto positional_operands = array_make<Operand>(heap_allocator(), 0, 0);
auto named_operands = array_make<Operand>(heap_allocator(), 0, 0); auto named_operands = array_make<Operand>(heap_allocator(), 0, 0);
@@ -6839,9 +6816,14 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c,
if (procs.count == 1) { if (procs.count == 1) {
Entity *e = procs[0]; Entity *e = procs[0];
Type *pt = base_type(e->type);
lhs = populate_proc_parameter_list(c, e->type, &lhs_count, &is_variadic); if (pt != nullptr && is_type_proc(pt)) {
check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, is_variadic ? UnpackFlag_IsVariadic : UnpackFlag_None); lhs = populate_proc_parameter_list(c, pt, &lhs_count);
if (pt->Proc.variadic) {
variadic_index = pt->Proc.variadic_index;
}
}
check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, UnpackFlag_None, variadic_index);
if (check_named_arguments(c, e->type, named_args, &named_operands, true)) { if (check_named_arguments(c, e->type, named_args, &named_operands, true)) {
check_call_arguments_single(c, call, operand, check_call_arguments_single(c, call, operand,
@@ -6901,11 +6883,30 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c,
} }
lhs[param_index] = e; lhs[param_index] = e;
} }
for (Entity *p : procs) {
Type *pt = base_type(p->type);
if (!(pt != nullptr && is_type_proc(pt))) {
continue;
}
if (pt->Proc.is_polymorphic) {
if (variadic_index == -1) {
variadic_index = pt->Proc.variadic_index;
} else if (variadic_index != pt->Proc.variadic_index) {
variadic_index = -1;
break;
}
} else {
variadic_index = -1;
break;
}
}
} }
} }
} }
check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, is_variadic ? UnpackFlag_IsVariadic : UnpackFlag_None); check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, UnpackFlag_None, variadic_index);
for_array(i, named_args) { for_array(i, named_args) {
Ast *arg = named_args[i]; Ast *arg = named_args[i];
@@ -7343,13 +7344,16 @@ gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *op
defer (array_free(&named_operands)); defer (array_free(&named_operands));
if (positional_args.count > 0) { if (positional_args.count > 0) {
isize lhs_count = -1;
bool is_variadic = false;
Entity **lhs = nullptr; Entity **lhs = nullptr;
isize lhs_count = -1;
i32 variadic_index = -1;
if (pt != nullptr) { if (pt != nullptr) {
lhs = populate_proc_parameter_list(c, proc_type, &lhs_count, &is_variadic); lhs = populate_proc_parameter_list(c, proc_type, &lhs_count);
if (pt->variadic) {
variadic_index = pt->variadic_index;
}
} }
check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, is_variadic ? UnpackFlag_IsVariadic : UnpackFlag_None); check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, UnpackFlag_None, variadic_index);
} }
if (named_args.count > 0) { if (named_args.count > 0) {
@@ -8756,23 +8760,52 @@ gb_internal ExprKind check_basic_directive_expr(CheckerContext *c, Operand *o, A
String name = bd->name.string; String name = bd->name.string;
if (name == "file") { if (name == "file") {
String file = get_file_path_string(bd->token.pos.file_id); String file = get_file_path_string(bd->token.pos.file_id);
if (build_context.obfuscate_source_code_locations) { switch (build_context.source_code_location_info) {
case SourceCodeLocationInfo_Normal:
break;
case SourceCodeLocationInfo_Obfuscated:
file = obfuscate_string(file, "F"); file = obfuscate_string(file, "F");
break;
case SourceCodeLocationInfo_Filename:
file = last_path_element(file);
break;
case SourceCodeLocationInfo_None:
file = str_lit("");
break;
} }
o->type = t_untyped_string; o->type = t_untyped_string;
o->value = exact_value_string(file); o->value = exact_value_string(file);
} else if (name == "directory") { } else if (name == "directory") {
String file = get_file_path_string(bd->token.pos.file_id); String file = get_file_path_string(bd->token.pos.file_id);
String path = dir_from_path(file); String path = dir_from_path(file);
if (build_context.obfuscate_source_code_locations) { switch (build_context.source_code_location_info) {
case SourceCodeLocationInfo_Normal:
break;
case SourceCodeLocationInfo_Obfuscated:
path = obfuscate_string(path, "D"); path = obfuscate_string(path, "D");
break;
case SourceCodeLocationInfo_Filename:
path = last_path_element(path);
break;
case SourceCodeLocationInfo_None:
path = str_lit("");
break;
} }
o->type = t_untyped_string; o->type = t_untyped_string;
o->value = exact_value_string(path); o->value = exact_value_string(path);
} else if (name == "line") { } else if (name == "line") {
i32 line = bd->token.pos.line; i32 line = bd->token.pos.line;
if (build_context.obfuscate_source_code_locations) { switch (build_context.source_code_location_info) {
case SourceCodeLocationInfo_Normal:
break;
case SourceCodeLocationInfo_Obfuscated:
line = obfuscate_i32(line); line = obfuscate_i32(line);
break;
case SourceCodeLocationInfo_Filename:
break;
case SourceCodeLocationInfo_None:
line = 0;
break;
} }
o->type = t_untyped_integer; o->type = t_untyped_integer;
o->value = exact_value_i64(line); o->value = exact_value_i64(line);
@@ -8783,8 +8816,17 @@ gb_internal ExprKind check_basic_directive_expr(CheckerContext *c, Operand *o, A
o->value = exact_value_string(str_lit("")); o->value = exact_value_string(str_lit(""));
} else { } else {
String p = c->proc_name; String p = c->proc_name;
if (build_context.obfuscate_source_code_locations) { switch (build_context.source_code_location_info) {
case SourceCodeLocationInfo_Normal:
break;
case SourceCodeLocationInfo_Obfuscated:
p = obfuscate_string(p, "P"); p = obfuscate_string(p, "P");
break;
case SourceCodeLocationInfo_Filename:
break;
case SourceCodeLocationInfo_None:
p = str_lit("");
break;
} }
o->type = t_untyped_string; o->type = t_untyped_string;
o->value = exact_value_string(p); o->value = exact_value_string(p);
@@ -11087,7 +11129,7 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast
case_ast_node(u, Uninit, node); case_ast_node(u, Uninit, node);
o->mode = Addressing_Value; o->mode = Addressing_Value;
o->type = t_untyped_uninit; o->type = t_untyped_uninit;
error(node, "Use of --- outside of variable declaration"); error(node, "Global variables will always be zeroed if left unassigned, --- is disallowed");
case_end; case_end;
@@ -11319,6 +11361,13 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast
node->viral_state_flags |= de->expr->viral_state_flags; node->viral_state_flags |= de->expr->viral_state_flags;
if (o->mode == Addressing_Invalid) { if (o->mode == Addressing_Invalid) {
o->mode = Addressing_Invalid;
o->expr = node;
return kind;
} else if (o->mode == Addressing_Type) {
gbString str = expr_to_string(o->expr);
error(o->expr, "Cannot dereference '%s' because it is a type", str);
o->mode = Addressing_Invalid; o->mode = Addressing_Invalid;
o->expr = node; o->expr = node;
return kind; return kind;
+11 -10
View File
@@ -418,7 +418,7 @@ gb_internal bool check_is_terminating(Ast *node, String const &label) {
gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, Operand *rhs) { gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, Operand *rhs, String context_name) {
if (rhs->mode == Addressing_Invalid) { if (rhs->mode == Addressing_Invalid) {
return nullptr; return nullptr;
} }
@@ -430,7 +430,7 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O
Ast *node = unparen_expr(lhs->expr); Ast *node = unparen_expr(lhs->expr);
check_no_copy_assignment(*rhs, str_lit("assignment")); check_no_copy_assignment(*rhs, context_name);
// NOTE(bill): Ignore assignments to '_' // NOTE(bill): Ignore assignments to '_'
if (is_blank_ident(node)) { if (is_blank_ident(node)) {
@@ -630,7 +630,7 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O
ctx->bit_field_bit_size = lhs_e->Variable.bit_field_bit_size; ctx->bit_field_bit_size = lhs_e->Variable.bit_field_bit_size;
} }
check_assignment(ctx, rhs, assignment_type, str_lit("assignment")); check_assignment(ctx, rhs, assignment_type, context_name);
ctx->bit_field_bit_size = prev_bit_field_bit_size; ctx->bit_field_bit_size = prev_bit_field_bit_size;
@@ -2418,7 +2418,7 @@ gb_internal void check_assign_stmt(CheckerContext *ctx, Ast *node) {
isize lhs_count = as->lhs.count; isize lhs_count = as->lhs.count;
if (lhs_count == 0) { if (lhs_count == 0) {
error(as->op, "Missing lhs in assignment statement"); error(as->op, "Missing LHS in assignment statement");
return; return;
} }
@@ -2451,7 +2451,7 @@ gb_internal void check_assign_stmt(CheckerContext *ctx, Ast *node) {
if (lhs_to_ignore[i]) { if (lhs_to_ignore[i]) {
continue; continue;
} }
check_assignment_variable(ctx, &lhs_operands[i], &rhs_operands[i]); check_assignment_variable(ctx, &lhs_operands[i], &rhs_operands[i], str_lit("assignment"));
} }
if (lhs_count != rhs_count) { if (lhs_count != rhs_count) {
error(as->lhs[0], "Assignment count mismatch '%td' = '%td'", lhs_count, rhs_count); error(as->lhs[0], "Assignment count mismatch '%td' = '%td'", lhs_count, rhs_count);
@@ -2461,11 +2461,11 @@ gb_internal void check_assign_stmt(CheckerContext *ctx, Ast *node) {
// a += 1; // Single-sided // a += 1; // Single-sided
Token op = as->op; Token op = as->op;
if (as->lhs.count != 1 || as->rhs.count != 1) { if (as->lhs.count != 1 || as->rhs.count != 1) {
error(op, "Assignment operation '%.*s' requires single-valued expressions", LIT(op.string)); error(op, "Assignment operator '%.*s' requires single-valued operands", LIT(op.string));
return; return;
} }
if (!gb_is_between(op.kind, Token__AssignOpBegin+1, Token__AssignOpEnd-1)) { if (!gb_is_between(op.kind, Token__AssignOpBegin+1, Token__AssignOpEnd-1)) {
error(op, "Unknown Assignment operation '%.*s'", LIT(op.string)); error(op, "Unknown assignment operator '%.*s'", LIT(op.string));
return; return;
} }
Operand lhs = {Addressing_Invalid}; Operand lhs = {Addressing_Invalid};
@@ -2474,15 +2474,16 @@ gb_internal void check_assign_stmt(CheckerContext *ctx, Ast *node) {
ast_node(be, BinaryExpr, binary_expr); ast_node(be, BinaryExpr, binary_expr);
be->op = op; be->op = op;
be->op.kind = cast(TokenKind)(cast(i32)be->op.kind - (Token_AddEq - Token_Add)); be->op.kind = cast(TokenKind)(cast(i32)be->op.kind - (Token_AddEq - Token_Add));
// NOTE(bill): Only use the first one will be used // NOTE(bill): Only use the first one will be used
be->left = as->lhs[0]; be->left = as->lhs[0];
be->right = as->rhs[0]; be->right = as->rhs[0];
check_expr(ctx, &lhs, as->lhs[0]); check_expr(ctx, &lhs, as->lhs[0]);
check_binary_expr(ctx, &rhs, binary_expr, nullptr, true); check_binary_expr(ctx, &rhs, binary_expr, nullptr, true);
if (rhs.mode != Addressing_Invalid) { if (rhs.mode != Addressing_Invalid) {
// NOTE(bill): Only use the first one will be used be->op.string = substring(be->op.string, 0, be->op.string.len - 1);
check_assignment_variable(ctx, &lhs, &rhs); rhs.expr = binary_expr;
check_assignment_variable(ctx, &lhs, &rhs, str_lit("assignment operation"));
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More