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

View File

@@ -30,13 +30,13 @@ jobs:
gmake -C vendor/stb/src
gmake -C vendor/cgltf/src
gmake -C vendor/miniaudio/src
./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_amd64
./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_arm64
./odin check examples/all/sdl3 -vet -strict-style -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 test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
./odin test tests/core/speed.odin -file -all-packages -vet -strict-style -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
./odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:netbsd_amd64
./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 -vet-tabs -strict-style -vet-style -warnings-as-errors -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_arm64 -no-entry-point
./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 -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 -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)
./odin check tests/benchmark -vet -strict-style -no-entry-point
@@ -63,11 +63,11 @@ jobs:
gmake -C vendor/stb/src
gmake -C vendor/cgltf/src
gmake -C vendor/miniaudio/src
./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
./odin check examples/all/sdl3 -vet -strict-style -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/speed.odin -file -all-packages -vet -strict-style -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
./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 -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 -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 -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 -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)
./odin check tests/benchmark -vet -strict-style -no-entry-point
ci:
@@ -75,7 +75,7 @@ jobs:
fail-fast: false
matrix:
# 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 }}
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
@@ -123,17 +123,17 @@ jobs:
- name: Odin run -debug
run: ./odin run examples/demo -debug
- 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
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
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
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
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
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
run: |
cd tests/issues
@@ -141,43 +141,43 @@ jobs:
- name: Run demo on WASI WASM32
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
if: matrix.os == 'macos-14'
- 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
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
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
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
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
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
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
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
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
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
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:
name: Windows Build, Check, and Test
@@ -208,38 +208,38 @@ jobs:
shell: cmd
run: |
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
shell: cmd
run: |
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
shell: cmd
run: |
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
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin test tests/core/normal.odin -file -all-packages -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
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -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
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
copy vendor\lua\5.4\windows\*.dll .
odin test tests/vendor -all-packages -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
shell: cmd
run: |
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
shell: cmd
run: |
@@ -257,12 +257,6 @@ jobs:
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
cd tests\documentation
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
shell: cmd
run: |
@@ -299,25 +293,25 @@ jobs:
make -C vendor/miniaudio/src
- 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
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
run: sudo apt-get install -y qemu-user qemu-user-static gcc-12-riscv64-linux-gnu libc6-riscv64-cross
- name: Odin run
run: ./odin run examples/demo -vet -strict-style -disallow-do -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
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
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
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
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
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
.github/workflows/cover.yml vendored Normal file
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
.gitignore vendored
View File

@@ -277,6 +277,7 @@ odin
*.bin
demo.bin
libLLVM*.so*
*.a
# shared collection
shared/

View File

@@ -67,7 +67,7 @@ init_global_temporary_allocator :: proc(size: int, backup_allocator := context.a
// Prefer the procedure group `copy`.
@builtin
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 {
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`.
@builtin
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 {
intrinsics.mem_copy(raw_data(dst), raw_data(src), n)
}

View File

@@ -1,7 +1,7 @@
package runtime
import "base:intrinsics"
import "base:sanitizer"
// import "base:sanitizer"
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.capacity = uint(end - uintptr(block.base))
sanitizer.address_poison(block.base, block.capacity)
// sanitizer.address_poison(block.base, block.capacity)
// Should be zeroed
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) {
if block_to_free != nil {
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)
}
}
@@ -87,7 +87,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint)
return
}
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
return
}
@@ -167,7 +167,7 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
if arena.curr_block != nil {
intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used)
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
}
@@ -232,7 +232,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
// grow data in-place, adjusting next allocation
block.used = uint(new_end)
data = block.base[start:new_end]
sanitizer.address_unpoison(data)
// sanitizer.address_unpoison(data)
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)
amount_to_zero := block.used-temp.used
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
arena.total_used -= amount_to_zero
}

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
check_all.bat Normal file
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
)

78
check_all.sh Executable file
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
codecov.yml Normal file
View File

@@ -0,0 +1 @@
comment: false

View File

@@ -4,7 +4,13 @@ import "base:builtin"
import "base: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) {
data: [dynamic]T,
len: uint,
@@ -13,18 +19,31 @@ Queue :: struct($T: typeid) {
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 {
if q.data.allocator.procedure == nil {
q.data.allocator = allocator
}
clear(q)
q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{
data = nil,
len = 0,
cap = 0,
allocator = allocator,
}
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`.
// Any previous contents are not available.
/*
Initialize a `Queue` from a fixed `backing` slice into which modifications are
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 {
clear(q)
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
}
// 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 {
clear(q)
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
}
// 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)) {
delete(q.data)
}
// The length of the queue
/*
Return the length of the queue.
*/
len :: proc(q: $Q/Queue($T)) -> int {
return int(q.len)
}
// The current capacity of the queue
/*
Return the capacity of the queue.
*/
cap :: proc(q: $Q/Queue($T)) -> int {
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 {
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 {
if capacity > space(q^) {
return _grow(q, uint(capacity))
return _grow(q, uint(capacity))
}
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 {
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)
return q.data[idx]
}
front :: proc(q: ^$Q/Queue($T)) -> T {
return q.data[q.offset]
}
front_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
return &q.data[q.offset]
}
/*
Get a pointer to the element at index `i`.
back :: proc(q: ^$Q/Queue($T)) -> T {
idx := (q.offset+uint(q.len - 1))%builtin.len(q.data)
return q.data[idx]
}
back_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
idx := (q.offset+uint(q.len - 1))%builtin.len(q.data)
This will raise a bounds checking error if `i` is an invalid index.
*/
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))
idx := (uint(i)+q.offset)%builtin.len(q.data)
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) {
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)
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))
idx := (uint(i)+q.offset)%builtin.len(q.data)
/*
Get the element at the front of the queue.
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]
}
@(deprecated="Use `front_ptr` instead")
peek_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
runtime.bounds_check_error_loc(loc, 0, builtin.len(q.data))
idx := q.offset%builtin.len(q.data)
return &q.data[idx]
return front_ptr(q, loc)
}
@(deprecated="Use `back_ptr` instead")
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))
idx := (uint(q.len - 1)+q.offset)%builtin.len(q.data)
return &q.data[idx]
return back_ptr(q, loc)
}
// 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) {
if space(q^) == 0 {
_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
}
// 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) {
if space(q^) == 0 {
_grow(q) or_return
}
}
q.offset = uint(q.offset - 1 + builtin.len(q.data)) % builtin.len(q.data)
q.len += 1
q.data[q.offset] = elem
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) {
assert(condition=q.len > 0, loc=loc)
when !ODIN_NO_BOUNDS_CHECK {
ensure(q.len > 0, "Queue is empty.", loc)
}
q.len -= 1
idx := (q.offset+uint(q.len))%builtin.len(q.data)
elem = q.data[idx]
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) {
if q.len > 0 {
q.len -= 1
@@ -169,15 +361,25 @@ pop_back_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
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) {
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]
q.offset = (q.offset+1)%builtin.len(q.data)
q.len -= 1
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) {
if q.len > 0 {
elem = q.data[q.offset]
@@ -188,13 +390,18 @@ pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
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) {
n := uint(builtin.len(elems))
if space(q^) < int(n) {
_grow(q, q.len + n) or_return
}
sz := uint(builtin.len(q.data))
insert_from := (q.offset + q.len) % sz
insert_to := n
@@ -207,19 +414,31 @@ push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime
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) {
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 {
nu := uint(n)
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) {
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 {
q.len -= uint(n)
}
@@ -231,9 +450,14 @@ append_elem :: push_back
append_elems :: push_back_elems
push :: 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)) {
q.len = 0
q.offset = 0

23
core/dynlib/lb_haiku.odin Normal file
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 ""
}

View File

@@ -175,7 +175,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
data = bytes.clone(data)
}
t := &Tokenizer{}
t := new(Tokenizer)
init(t, string(data), path, error_handler)
doc = new(Document)
@@ -403,6 +403,7 @@ destroy :: proc(doc: ^Document) {
}
delete(doc.strings_to_free)
free(doc.tokenizer)
free(doc)
}

View File

@@ -19,7 +19,7 @@ SUBTAG_NAME :: "name"
SUBTAG_POS :: "pos"
SUBTAG_REQUIRED :: "required"
SUBTAG_HIDDEN :: "hidden"
SUBTAG_VARIADIC :: "variadic"
SUBTAG_MANIFOLD :: "manifold"
SUBTAG_FILE :: "file"
SUBTAG_PERMS :: "perms"
SUBTAG_INDISTINCT :: "indistinct"
@@ -28,7 +28,7 @@ TAG_USAGE :: "usage"
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_SHORT :: "h"

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
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:
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.
- `hidden`: hide this flag from the usage documentation.
- `required`: cause verification to fail if this argument is not set.
- `variadic`: take all remaining arguments when set, UNIX-style only.
- `manifold=N`: take several arguments at once, UNIX-style only.
- `file`: for `os.Handle` types, file open mode.
- `perms`: for `os.Handle` types, file open permissions.
- `indistinct`: allow the setting of distinct types by their base type.
@@ -47,8 +58,9 @@ you want to require 3 and only 3 arguments in a dynamic array, you would
specify `required=3<4`.
`variadic` may be given a number (`variadic=N`) above 1 to limit how many extra
arguments it consumes.
`manifold` may be given a number (`manifold=N`) above 1 to limit how many extra
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`.
@@ -160,7 +172,7 @@ at parse time.
--flag
--flag=argument
--flag argument
--flag argument repeating-argument
--flag argument (manifold-argument)
`-flag` may also be substituted for `--flag`.

View File

@@ -4,7 +4,7 @@ import "core:os"
Parse_Error_Reason :: enum {
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,
// The underlying type does not support the string value it is being set to.
Bad_Value,

View File

@@ -107,14 +107,14 @@ main :: proc() {
// 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."`,
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

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)
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.
field = reflect.struct_field_by_name(T, INTERNAL_VARIADIC_FLAG)
field = reflect.struct_field_by_name(T, INTERNAL_OVERFLOW_FLAG)
} else {
return Parse_Error {
.Extra_Positional,
@@ -117,8 +117,8 @@ set_unix_flag :: proc(model: ^$T, parser: ^Parser, name: string) -> (future_args
case runtime.Type_Info_Dynamic_Array:
future_args = 1
if tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok {
if length, is_variadic := get_struct_subtag(tag, SUBTAG_VARIADIC); is_variadic {
// Variadic arrays may specify how many arguments they consume at once.
if length, is_manifold := get_struct_subtag(tag, SUBTAG_MANIFOLD); is_manifold {
// Manifold arrays may specify how many arguments they consume at once.
// Otherwise, they take everything that's left.
if value, value_ok := strconv.parse_u64_of_base(length, 10); value_ok {
future_args = cast(int)value

View File

@@ -95,7 +95,7 @@ parse_one_unix_arg :: proc(model: ^$T, parser: ^Parser, arg: string) -> (
// `--`, and only `--`.
// Everything from now on will be treated as an argument.
future_args = max(int)
current_flag = INTERNAL_VARIADIC_FLAG
current_flag = INTERNAL_OVERFLOW_FLAG
return
}
}

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 {
case runtime.Type_Info_Map:
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.",
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)
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 {
fmt.assertf(value > 0,
"%T.%s has `%s` set to %i. It must be greater than zero.",
model_type, field.name, value, SUBTAG_VARIADIC, loc = loc)
model_type, field.name, value, SUBTAG_MANIFOLD, loc = loc)
fmt.assertf(value != 1,
"%T.%s has `%s` set to 1. This has no effect.",
model_type, field.name, SUBTAG_VARIADIC, loc = loc)
"%T.%s has `%s` set to 1. This is equivalent to not defining `%s`.",
model_type, field.name, SUBTAG_MANIFOLD, SUBTAG_MANIFOLD, loc = loc)
}
#partial switch specific_type_info in field.type.variant {
case runtime.Type_Info_Dynamic_Array:
fmt.assertf(style != .Odin,
"%T.%s has `%s` defined, but this only makes sense in UNIX-style parsing mode.",
model_type, field.name, SUBTAG_VARIADIC, loc = loc)
model_type, field.name, SUBTAG_MANIFOLD, loc = loc)
case:
fmt.panicf("%T.%s has `%s` defined, but this only makes sense on dynamic arrays.",
model_type, field.name, SUBTAG_VARIADIC, loc = loc)
model_type, field.name, SUBTAG_MANIFOLD, loc = loc)
}
}

View File

@@ -6,7 +6,7 @@ package flags
Parsing_Style :: enum {
// Odin-style: `-flag`, `-flag:option`, `-map:key=value`
Odin,
// UNIX-style: `-flag` or `--flag`, `--flag=argument`, `--flag argument repeating-argument`
// UNIX-style: `-flag` or `--flag`, `--flag=argument`, `--flag argument (manifold-argument)`
Unix,
}
@@ -61,7 +61,7 @@ parse :: proc(
}
case .Unix:
// Support for `-flag argument (repeating-argument ...)`
// Support for `-flag argument (manifold-argument ...)`
future_args: int
current_flag: string

View File

@@ -30,18 +30,18 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
is_positional: bool,
is_required: bool,
is_boolean: bool,
is_variadic: bool,
variadic_length: int,
is_manifold: bool,
manifold_length: int,
}
//
// POSITIONAL+REQUIRED, POSITIONAL, REQUIRED, NON_REQUIRED+NON_POSITIONAL, ...
//
sort_flags :: proc(i, j: Flag) -> slice.Ordering {
// `varg` goes to the end.
if i.name == INTERNAL_VARIADIC_FLAG {
// `overflow` goes to the end.
if i.name == INTERNAL_OVERFLOW_FLAG {
return .Greater
} else if j.name == INTERNAL_VARIADIC_FLAG {
} else if j.name == INTERNAL_OVERFLOW_FLAG {
return .Less
}
@@ -120,10 +120,10 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
flag.is_required = true
flag.required_min, flag.required_max, _ = parse_requirements(requirement)
}
if length_str, is_variadic := get_struct_subtag(args_tag, SUBTAG_VARIADIC); is_variadic {
flag.is_variadic = true
if length_str, is_manifold := get_struct_subtag(args_tag, SUBTAG_MANIFOLD); is_manifold {
flag.is_manifold = true
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:
requirement_spec := describe_array_requirements(flag)
if flag.is_variadic || flag.name == INTERNAL_VARIADIC_FLAG {
if flag.variadic_length == 0 {
if flag.is_manifold || flag.name == INTERNAL_OVERFLOW_FLAG {
if flag.manifold_length == 0 {
flag.type_description = fmt.tprintf("<%v, ...>%s",
specific_type_info.elem.id,
requirement_spec)
} else {
flag.type_description = fmt.tprintf("<%v, %i at once>%s",
specific_type_info.elem.id,
flag.variadic_length,
flag.manifold_length,
requirement_spec)
}
} 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)
} else if flag.is_boolean {
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)
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
}
strings.write_byte(&builder, ' ')
if flag.name == INTERNAL_VARIADIC_FLAG {
if flag.name == INTERNAL_OVERFLOW_FLAG {
strings.write_string(&builder, "...")
continue
}
@@ -252,7 +252,7 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
strings.write_byte(&builder, '\t')
if flag.name == INTERNAL_VARIADIC_FLAG {
if flag.name == INTERNAL_OVERFLOW_FLAG {
strings.write_string(&builder, flag.type_description)
} else {
strings.write_string(&builder, flag_prefix)

View File

@@ -160,6 +160,5 @@ destroy :: proc {
int_destroy :: proc(integers: ..^Int)
*/
int_destroy,
}
internal_rat_destroy,
}

View File

@@ -1660,13 +1660,13 @@ internal_int_sqrt :: proc(dest, src: ^Int, allocator := context.allocator) -> (e
if internal_gte(y, x) {
internal_swap(dest, x)
return nil
return internal_clamp(dest)
}
internal_swap(x, y)
}
internal_swap(dest, x)
return err
return internal_clamp(dest)
}
internal_sqrt :: proc { internal_int_sqrt, }

View File

@@ -310,7 +310,7 @@ int_atoi :: proc(res: ^Int, input: string, radix := i8(10), allocator := context
res.sign = sign
}
return nil
return internal_clamp(res)
}

View File

@@ -157,6 +157,8 @@ internal_rat_norm :: proc(z: ^Rat, allocator := context.allocator) -> (err: Erro
z.b.sign = .Zero_or_Positive
f := &Int{}
defer internal_int_destroy(f)
internal_int_gcd(f, &z.a, &z.b) or_return
if !internal_int_equals_digit(f, 1) {
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
defer if has_sign {
f = -builtin.abs(f)
}
exp := alen - blen
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) {
exact = false
}
if has_sign {
f = -builtin.abs(f)
}
return
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
package mem
import "base:runtime"
import "base:sanitizer"
// import "base:sanitizer"
/*
Rollback stack default block size.
@@ -134,7 +134,7 @@ rb_free_all :: proc(stack: ^Rollback_Stack) {
stack.head.next_block = nil
stack.head.last_alloc = nil
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)
}
res := ptr[:size]
sanitizer.address_unpoison(res)
// sanitizer.address_unpoison(res)
return res, nil
}
return nil, .Out_Of_Memory
@@ -338,7 +338,7 @@ rb_resize_bytes_non_zeroed :: proc(
block.offset += cast(uintptr)size - cast(uintptr)old_size
}
res := (ptr)[:size]
sanitizer.address_unpoison(res)
// sanitizer.address_unpoison(res)
#no_bounds_check return res, nil
}
}

View File

@@ -10,7 +10,7 @@
package mem_tlsf
import "base:intrinsics"
import "base:sanitizer"
// import "base:sanitizer"
import "base:runtime"
// 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
}
sanitizer.address_poison(new_pool_buf)
// sanitizer.address_poison(new_pool_buf)
// Allocate a new link in the `control.pool` tracking structure.
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)
}
@(private, require_results, no_sanitize_address)
@(private, require_results)
alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) {
res, err = alloc_bytes_non_zeroed(control, size, align)
if err == nil {
@@ -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) {
assert(control != nil)
// `size` is currently ignored
@@ -276,7 +277,7 @@ free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) {
block := block_from_ptr(ptr)
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 = block_merge_prev(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)
res = ([^]byte)(ptr)[:new_size]
sanitizer.address_unpoison(res)
// sanitizer.address_unpoison(res)
if 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)
}
@(private, require_results, no_sanitize_address)
@(private, require_results)
align_up :: proc(x, align: uint) -> (aligned: uint) {
assert(0 == (align & (align - 1)), "must align to a power of two")
return (x + (align - 1)) &~ (align - 1)
}
@(private, require_results, no_sanitize_address)
@(private, require_results)
align_down :: proc(x, align: uint) -> (aligned: uint) {
assert(0 == (align & (align - 1)), "must align to a power of two")
return x - (x & (align - 1))
}
@(private, require_results, no_sanitize_address)
@(private, require_results)
align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) {
assert(0 == (align & (align - 1)), "must align to a power of two")
align_mask := uintptr(align) - 1
@@ -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.
@(private, require_results, no_sanitize_address)
@(private, require_results)
adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) {
if size == 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.
@(private, require_results, no_sanitize_address)
@(private, require_results)
adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: runtime.Allocator_Error) {
if size == 0 {
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
// 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) {
if size < SMALL_BLOCK_SIZE {
// Store small blocks in first list.
@@ -550,7 +551,7 @@ mapping_insert :: proc(size: uint) -> (fl, sl: i32) {
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) {
rounded = 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)
@(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) {
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_mark_as_used(block)
res = ([^]byte)(block_to_ptr(block))[:size]
sanitizer.address_unpoison(res)
// sanitizer.address_unpoison(res)
}
return
}

View File

@@ -3,7 +3,7 @@ package mem_virtual
import "core:mem"
import "core:sync"
import "base:sanitizer"
// import "base:sanitizer"
Arena_Kind :: enum uint {
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 {
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
}
@@ -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.total_used = 0
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
}
@@ -82,7 +82,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
arena.kind = .Buffer
sanitizer.address_poison(buffer[:])
// sanitizer.address_poison(buffer[:])
block_base := raw_data(buffer)
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
}
sanitizer.address_unpoison(data)
// sanitizer.address_unpoison(data)
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])
}
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
} else if pos == 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.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)
}
}
@@ -219,9 +219,9 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
if arena.curr_block != nil {
curr_block_used := int(arena.curr_block.used)
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)
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
case .Static, .Buffer:
@@ -349,7 +349,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
if size < old_size {
// shrink data in-place
data = old_data[:size]
sanitizer.address_poison(old_data[size:old_size])
// sanitizer.address_poison(old_data[size:old_size])
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
arena.total_used += block.used - prev_used
data = block.base[start:new_end]
sanitizer.address_unpoison(data)
// sanitizer.address_unpoison(data)
return
}
}
@@ -374,7 +374,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
return
}
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
case .Query_Features:
set := (^mem.Allocator_Mode_Set)(old_memory)

View File

@@ -2,7 +2,7 @@ package mem_virtual
import "core:mem"
import "base:intrinsics"
import "base:sanitizer"
// import "base:sanitizer"
import "base:runtime"
_ :: runtime
@@ -22,7 +22,7 @@ reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Erro
@(no_sanitize_address)
commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
sanitizer.address_unpoison(data, size)
// sanitizer.address_unpoison(data, size)
return _commit(data, size)
}
@@ -35,13 +35,13 @@ reserve_and_commit :: proc "contextless" (size: uint) -> (data: []byte, err: All
@(no_sanitize_address)
decommit :: proc "contextless" (data: rawptr, size: uint) {
sanitizer.address_poison(data, size)
// sanitizer.address_poison(data, size)
_decommit(data, size)
}
@(no_sanitize_address)
release :: proc "contextless" (data: rawptr, size: uint) {
sanitizer.address_unpoison(data, size)
// sanitizer.address_unpoison(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]
block.used += size
sanitizer.address_unpoison(data)
// sanitizer.address_unpoison(data)
return
}

View File

@@ -64,6 +64,7 @@ Network_Error :: union #shared_nil {
UDP_Recv_Error,
Shutdown_Error,
Interfaces_Error,
Socket_Info_Error,
Socket_Option_Error,
Set_Blocking_Error,
Parse_Endpoint_Error,
@@ -260,6 +261,9 @@ DNS_Configuration :: struct {
resolv_conf: 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,
// so we don't have to do it each call.
name_servers: []Endpoint,

View File

@@ -22,69 +22,43 @@ package net
Haesbaert: Security fixes
*/
@(require) import "base:runtime"
import "core:mem"
import "core:strings"
import "core:time"
import "core:os"
import "core:math/rand"
/*
Default configuration for DNS resolution.
*/
@(require) import "core:sync"
dns_config_initialized: sync.Once
when ODIN_OS == .Windows {
DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{
resolv_conf = "",
hosts_file = "%WINDIR%\\system32\\drivers\\etc\\hosts",
dns_configuration := DNS_Configuration{
resolv_conf = "",
hosts_file = "%WINDIR%\\system32\\drivers\\etc\\hosts",
}
} else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{
resolv_conf = "/etc/resolv.conf",
hosts_file = "/etc/hosts",
dns_configuration := DNS_Configuration{
resolv_conf = "/etc/resolv.conf",
hosts_file = "/etc/hosts",
}
} else {
#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() {
/*
Resolve %ENVIRONMENT% placeholders in their paths.
*/
dns_configuration.resolv_conf, _ = replace_environment_path(dns_configuration.resolv_conf)
dns_configuration.hosts_file, _ = replace_environment_path(dns_configuration.hosts_file)
}
@(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
when ODIN_OS == .Windows {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
val := os.replace_environment_placeholders(dns_configuration.hosts_file, context.temp_allocator)
copy(dns_configuration.hosts_file_buf[:], val)
dns_configuration.hosts_file = string(dns_configuration.hosts_file_buf[:len(val)])
}
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.
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`.
*/
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)
}
@@ -219,6 +196,9 @@ get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocat
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) {
when ODIN_OS == .Windows {
sync.once_do(&dns_config_initialized, init_dns_configuration)
}
context.allocator = allocator
if type != .SRV {
@@ -440,6 +420,8 @@ load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> (
splits := strings.fields(line)
defer delete(splits)
(len(splits) >= 2) or_continue
ip_str := splits[0]
addr := parse_address(ip_str)
if addr == nil {
@@ -886,4 +868,4 @@ parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator
xid = hdr.id
return _records[:], xid, true
}
}

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[:])
}
}

View File

@@ -246,6 +246,23 @@ Shutdown_Error :: enum i32 {
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 {
None,
// No network connection, or the network stack is not initialized.

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 {
#partial switch posix.errno() {
case .ENOBUFS:

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 {
assert(errno != nil)
_last_error = errno

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 {
assert(errno != nil)
_last_error = errno

View File

@@ -18,3 +18,10 @@ _last_platform_error_string :: proc() -> string {
_set_last_platform_error :: proc(err: i32) {
_last_error = err
}
Parse_Endpoint_Error :: enum u32 {
None = 0,
Bad_Port = 1,
Bad_Address,
Bad_Hostname,
}

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 {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSAENETDOWN, .WSANOTINITIALISED:

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.
*/
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)
}
/*
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) {
return _accept_tcp(socket, options)
}

View File

@@ -137,11 +137,24 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_
}
@(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_len := posix.socklen_t(size_of(addr))
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
}

View File

@@ -140,12 +140,26 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
}
@(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
errno := freebsd.getsockname(cast(Fd)any_socket_to_socket(sock), &sockaddr)
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
}

View File

@@ -218,11 +218,24 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket,
}
@(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
errno := linux.getsockname(_unwrap_os_socket(sock), &addr)
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
}

View File

@@ -177,11 +177,25 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
}
@(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
sockaddrlen := c.int(size_of(sockaddr))
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
}

View File

@@ -2307,6 +2307,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
open := expect_token(p, .Open_Paren)
p.expr_level += 1
expr := parse_expr(p, false)
skip_possible_newline(p)
p.expr_level -= 1
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
for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF {
docs := p.lead_comment
name := parse_ident(p)
expect_token(p, .Colon)
type := parse_type(p)
@@ -2932,6 +2935,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
if p.curr_tok.kind == .String {
tag = expect_token(p, .String)
}
ok := allow_token(p, .Comma)
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.bit_size = bit_size
field.tag = tag
field.docs = docs
field.comments = p.line_comment
append(&fields, field)
allow_token(p, .Comma) or_break
if !ok {
break
}
}
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:
x := expr
cond := parse_expr(p, lhs)
skip_possible_newline(p)
else_tok := expect_token(p, .Else)
y := parse_expr(p, lhs)
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
}
if !is_using && is_blank_ident(import_name) {
error(p, import_name.pos, "illegal import name: '_'")
}
path := expect_token_after(p, .String, "import")
decl := ast.new(ast.Import_Decl, tok.pos, end_pos(path))

View File

@@ -8,7 +8,7 @@ import "base:runtime"
// Otherwise the returned value will be empty and the boolean will be false
// NOTE: the value will be allocated with the supplied allocator
@(require_results)
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
if key == "" {
return
}
@@ -29,17 +29,54 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
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
// It returns the value, which will be empty if the variable is not present
// To distinguish between an empty value and an unset value, use lookup_env
// NOTE: the value will be allocated with the supplied allocator
@(require_results)
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
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}
// set_env sets the value of the environment variable named by the key
set_env :: proc(key, value: string) -> Error {
k := win32.utf8_to_wstring(key)

View File

@@ -35,6 +35,9 @@ General_Error :: enum u32 {
File_Is_Pipe,
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 .File_Is_Pipe: return "file is pipe"
case .Not_Dir: return "file is not directory"
case .Env_Var_Not_Found: return "environment variable not found"
}
case io.Error:
switch e {

View File

@@ -4,6 +4,7 @@ import "base:intrinsics"
import "base:runtime"
import "core:io"
import "core:strconv"
import "core:strings"
import "core:unicode/utf8"
@@ -210,3 +211,55 @@ heap_free :: runtime.heap_free
processor_core_count :: proc() -> int {
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)
}

View File

@@ -1,26 +1,49 @@
package os2
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
// To distinguish between an empty value and an unset value, use lookup_env
// NOTE: the value will be allocated with the supplied allocator
@(require_results)
get_env :: proc(key: string, allocator: runtime.Allocator) -> string {
get_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> string {
value, _ := lookup_env(key, allocator)
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
// Otherwise the returned value will be empty and the boolean will be false
// NOTE: the value will be allocated with the supplied allocator
@(require_results)
lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
return _lookup_env(key, allocator)
lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) {
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
// Returns Error on failure
set_env :: proc(key, value: string) -> Error {
@@ -45,4 +68,55 @@ environ :: proc(allocator: runtime.Allocator) -> ([]string, Error) {
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)
}

View File

@@ -41,7 +41,7 @@ _lookup :: proc(key: string) -> (value: string, idx: int) {
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 {
_build_env()
}
@@ -53,6 +53,23 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
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 {
if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 {
_build_env()

View File

@@ -7,7 +7,7 @@ import "base:runtime"
import "core:strings"
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 == "" {
return
}
@@ -26,6 +26,36 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
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) {
temp_allocator := TEMP_ALLOCATOR_GUARD({})

View File

@@ -67,7 +67,7 @@ delete_string_if_not_original :: proc(str: string) {
}
@(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 {
return
}
@@ -79,6 +79,34 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
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)
_set_env :: proc(key, value: string) -> (err: Error) {
build_env() or_return

View File

@@ -4,7 +4,7 @@ package os2
import win32 "core:sys/windows"
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 == "" {
return
}
@@ -36,6 +36,36 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
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 {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
k := win32_utf8_to_wstring(key, temp_allocator) or_return

View File

@@ -28,7 +28,7 @@ General_Error :: enum u32 {
Pattern_Has_Separator,
No_HOME_Variable,
Wordexp_Failed,
Env_Var_Not_Found,
Unsupported,
}
@@ -77,7 +77,7 @@ error_string :: proc(ferr: Error) -> string {
case .Unsupported: return "unsupported"
case .Pattern_Has_Separator: return "pattern has separator"
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:
switch e {

View File

@@ -269,6 +269,7 @@ _write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (nt: i64, err: Error
return
}
@(no_sanitize_memory)
_file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) {
// TODO: Identify 0-sized "pseudo" files and return No_Size. This would
// eliminate the need for the _read_entire_pseudo_file procs.

View File

@@ -4,7 +4,6 @@ package os2
import "base:runtime"
import "core:encoding/ini"
import "core:strings"
import "core:sys/posix"
_user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
#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) {
if k == xdg_key {
we: posix.wordexp_t
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 replace_environment_placeholders(v, allocator), nil
}
}
return

View File

@@ -1090,9 +1090,10 @@ flush :: proc(fd: Handle) -> Error {
}
@(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)
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)
if cstr == nil {
return "", false
@@ -1101,11 +1102,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
}
@(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)
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 {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
key_cstring := strings.clone_to_cstring(key, context.temp_allocator)
@@ -1231,7 +1260,7 @@ _processor_core_count :: proc() -> int {
return 1
}
@(require_results)
@(private, require_results)
_alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__))
for _, i in res {
@@ -1240,6 +1269,11 @@ _alloc_command_line_arguments :: proc() -> []string {
return res
}
@(private, fini)
_delete_command_line_arguments :: proc() {
delete(args)
}
socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) {
result := _unix_socket(c.int(domain), c.int(type), c.int(protocol))
if result < 0 {

View File

@@ -662,7 +662,7 @@ last_write_time_by_name :: proc(name: string) -> (File_Time, Error) {
return File_Time(modified), nil
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_stat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -674,7 +674,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_lstat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -688,7 +688,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
s: OS_Stat = ---
result := _unix_fstat(fd, &s)
@@ -827,10 +827,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
}
@(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)
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)
if cstr == nil {
return "", false
@@ -839,11 +839,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
}
@(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)
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)
get_current_directory :: proc(allocator := context.allocator) -> string {
context.allocator = allocator
@@ -936,7 +964,7 @@ _processor_core_count :: proc() -> int {
}
@(require_results)
@(private, require_results)
_alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__))
for arg, i in runtime.args__ {
@@ -944,3 +972,8 @@ _alloc_command_line_arguments :: proc() -> []string {
}
return res
}
@(private, fini)
_delete_command_line_arguments :: proc() {
delete(args)
}

View File

@@ -316,7 +316,7 @@ file_size :: proc(fd: Handle) -> (i64, Error) {
// "Argv" arguments converted to Odin strings
args := _alloc_command_line_arguments()
@(require_results)
@(private, require_results)
_alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__))
for arg, i in runtime.args__ {
@@ -325,7 +325,12 @@ _alloc_command_line_arguments :: proc() -> []string {
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) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -339,7 +344,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_lstat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -353,7 +358,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
// deliberately uninitialized
s: OS_Stat = ---
@@ -463,9 +468,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
}
@(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)
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)
if cstr == nil {
return "", false
@@ -474,11 +480,40 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
}
@(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)
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)
_processor_core_count :: proc() -> int {
info: haiku.system_info

View File

@@ -250,6 +250,26 @@ current_thread_id :: proc "contextless" () -> int {
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
}
}
@(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}

View File

@@ -674,7 +674,7 @@ seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) {
return i64(res), nil
}
@(require_results)
@(require_results, no_sanitize_memory)
file_size :: proc(fd: Handle) -> (i64, Error) {
// deliberately uninitialized; the syscall fills this buffer for us
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
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_stat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -808,7 +808,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_lstat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -822,7 +822,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
// deliberately uninitialized; the syscall fills this buffer for us
s: OS_Stat = ---
@@ -946,7 +946,7 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
}
@(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)
path_str := strings.clone_to_cstring(key, context.temp_allocator)
// 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)
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)
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 {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
key_cstring := strings.clone_to_cstring(key, context.temp_allocator)
@@ -1069,7 +1097,7 @@ _processor_core_count :: proc() -> int {
return int(_unix_get_nprocs())
}
@(require_results)
@(private, require_results)
_alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__))
for arg, i in runtime.args__ {
@@ -1078,6 +1106,11 @@ _alloc_command_line_arguments :: proc() -> []string {
return res
}
@(private, fini)
_delete_command_line_arguments :: proc() {
delete(args)
}
@(require_results)
socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) {
result := unix.sys_socket(domain, type, protocol)

View File

@@ -724,7 +724,7 @@ last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
return File_Time(modified), nil
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_stat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -736,7 +736,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_lstat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -750,7 +750,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
s: OS_Stat = ---
result := _unix_fstat(fd, &s)
@@ -874,10 +874,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
}
@(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)
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)
if cstr == nil {
return "", false
@@ -886,11 +886,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
}
@(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)
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)
get_current_directory :: proc(allocator := context.allocator) -> string {
context.allocator = allocator
@@ -986,7 +1014,7 @@ _processor_core_count :: proc() -> int {
return 1
}
@(require_results)
@(private, require_results)
_alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__))
for arg, i in runtime.args__ {
@@ -994,3 +1022,8 @@ _alloc_command_line_arguments :: proc() -> []string {
}
return res
}
@(private, fini)
_delete_command_line_arguments :: proc() {
delete(args)
}

View File

@@ -639,7 +639,7 @@ last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) {
return File_Time(modified), nil
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_stat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -653,7 +653,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_lstat :: proc(path: string) -> (OS_Stat, Error) {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
cstr := strings.clone_to_cstring(path, context.temp_allocator)
@@ -667,7 +667,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) {
return s, nil
}
@(private, require_results)
@(private, require_results, no_sanitize_memory)
_fstat :: proc(fd: Handle) -> (OS_Stat, Error) {
// deliberately uninitialized
s: OS_Stat = ---
@@ -787,9 +787,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
}
@(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)
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)
if cstr == nil {
return "", false
@@ -798,11 +799,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
}
@(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)
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)
get_current_directory :: proc(allocator := context.allocator) -> string {
context.allocator = allocator
@@ -885,7 +914,7 @@ _processor_core_count :: proc() -> int {
return int(_sysconf(_SC_NPROCESSORS_ONLN))
}
@(require_results)
@(private, require_results)
_alloc_command_line_arguments :: proc() -> []string {
res := make([]string, len(runtime.args__))
for arg, i in runtime.args__ {
@@ -893,3 +922,8 @@ _alloc_command_line_arguments :: proc() -> []string {
}
return res
}
@(private, fini)
_delete_command_line_arguments :: proc() {
delete(args)
}

View File

@@ -27,7 +27,7 @@ stderr: Handle = 2
args := _alloc_command_line_arguments()
@(require_results)
@(private, require_results)
_alloc_command_line_arguments :: proc() -> (args: []string) {
args = make([]string, len(runtime.args__))
for &arg, i in args {
@@ -36,6 +36,11 @@ _alloc_command_line_arguments :: proc() -> (args: []string) {
return
}
@(private, fini)
_delete_command_line_arguments :: proc() {
delete(args)
}
// WASI works with "preopened" directories, the environment retrieves directories
// (for example with `wasmtime --dir=. module.wasm`) and those given directories
// are the only ones accessible by the application.
@@ -239,3 +244,27 @@ exit :: proc "contextless" (code: int) -> ! {
runtime._cleanup_runtime_contextless()
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}

View File

@@ -193,7 +193,7 @@ current_thread_id :: proc "contextless" () -> int {
@(require_results)
@(private, require_results)
_alloc_command_line_arguments :: proc() -> []string {
arg_count: i32
arg_list_ptr := win32.CommandLineToArgvW(win32.GetCommandLineW(), &arg_count)
@@ -215,6 +215,14 @@ _alloc_command_line_arguments :: proc() -> []string {
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
as Windows 10: 10 and 0 respectively.

View File

@@ -387,6 +387,25 @@ has_prefix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_c
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)
has_suffix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_comparable(E) {

View File

@@ -7,6 +7,14 @@ import "core:mem"
import "core:sync"
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.
*/
@@ -75,6 +83,8 @@ Raw_Chan :: struct {
r_waiting: int, // guarded by `mutex`
w_waiting: int, // guarded by `mutex`
did_read: bool, // lets a sender know if the value was read
// Buffered
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:
- the channel is unbuffered
- the channel's buffer is full
until the channel is being read from. `send` will return
`false` when attempting to send on an already closed channel.
until the channel is being read from or the channel is closed. `send` will
return `false` when attempting to send on an already closed channel.
**Inputs**
- `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:
- the channel is unbuffered
- the channel's buffer is empty
until the channel is being written to. `recv` will return
`false` when attempting to receive a message on an already closed channel.
until the channel is being written to or the channel is closed. `recv` will
return `false` when attempting to receive a message on an already closed
channel.
**Inputs**
- `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:
- the channel is unbuffered
- the channel's buffer is full
until the channel is being read from. `send_raw` will return
`false` when attempting to send on an already closed channel.
until the channel is being read from or the channel is closed. `send_raw` will
return `false` when attempting to send on an already closed channel.
Note: The message referenced by `msg_out` must match the size
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
}
c.did_read = false
defer c.did_read = false
mem.copy(c.unbuffered_data, msg_in, int(c.msg_size))
c.w_waiting += 1
if c.r_waiting > 0 {
sync.signal(&c.r_cond)
}
sync.wait(&c.w_cond, &c.mutex)
if c.closed && !c.did_read {
return false
}
ok = true
}
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:
- the channel is unbuffered
- the channel's buffer is empty
until the channel is being written to. `recv_raw` will return
`false` when attempting to receive a message on an already closed channel.
until the channel is being written to or the channel is closed. `recv_raw`
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
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
sync.guard(&c.mutex)
for !c.closed &&
c.w_waiting == 0 {
for !c.closed && c.w_waiting == 0 {
c.r_waiting += 1
sync.wait(&c.r_cond, &c.mutex)
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))
c.w_waiting -= 1
c.did_read = true
sync.signal(&c.w_cond)
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
sync.guard(&c.mutex)
if c.closed {
if c.closed || c.r_waiting - c.w_waiting <= 0 {
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
sync.guard(&c.mutex)
if c.closed || c.w_waiting == 0 {
if c.closed || c.w_waiting - c.r_waiting <= 0 {
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.,
if a call to `recv` or `recv_raw` would block
Returns whether a message can be read without blocking the current
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**
- `c`: The channel
@@ -1067,7 +1091,7 @@ can_recv :: proc "contextless" (c: ^Raw_Chan) -> bool {
if is_buffered(c) {
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
**Returns**
- `true` if a message can be send, `false` otherwise
- `true` if a message can be sent, `false` otherwise
Example:
@@ -1102,18 +1126,30 @@ can_send :: proc "contextless" (c: ^Raw_Chan) -> bool {
if is_buffered(c) {
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
(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`.
If the message is nil, corresponding send channel will be skipped.
**Inputs**
- `recv`: A slice of channels to read from
@@ -1145,18 +1181,18 @@ Example:
// where the value from the read should be stored
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("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("RECEIVED VALUE ", received_value)
// closing of a channel also affects the select operation
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)
}
@@ -1170,7 +1206,7 @@ Output:
*/
@(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 {
idx: int, // local to the slice that was given
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)
candidates := ([^]Select_Op)(intrinsics.alloca(candidate_count*size_of(Select_Op), align_of(Select_Op)))
count := 0
for c, i in recvs {
if can_recv(c) {
candidates[count] = {
is_recv = true,
idx = i,
try_loop: for {
count := 0
for c, i in recvs {
if can_recv(c) {
candidates[count] = {
is_recv = true,
idx = i,
}
count += 1
}
count += 1
}
}
for c, i in sends {
if can_send(c) {
candidates[count] = {
is_recv = false,
idx = i,
for c, i in sends {
if i > builtin.len(send_msgs)-1 || send_msgs[i] == nil {
continue
}
if can_send(c) {
candidates[count] = {
is_recv = false,
idx = i,
}
count += 1
}
count += 1
}
}
if count == 0 {
return
}
if count == 0 {
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]
if sel.is_recv {
ok = recv_raw(recvs[sel.idx], recv_out)
} else {
ok = send_raw(sends[sel.idx], send_msgs[sel.idx])
candidate_idx := rand.int_max(count) if count > 0 else 0
sel := candidates[candidate_idx]
if sel.is_recv {
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

View File

@@ -223,6 +223,11 @@ _Proc_Bsdinfo :: struct {
/*--==========================================================================--*/
/* Get window size */
TIOCGWINSZ :: 0x40087468
/*--==========================================================================--*/
syscall_fsync :: #force_inline proc "contextless" (fildes: c.int) -> bool {
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))
}
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 {
return cast(u64)intrinsics.syscall(unix_offset_syscall(.gettid))
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
package sys_freebsd
/* Get window size */
TIOCGWINSZ :: 0x40087468

View File

@@ -34,6 +34,7 @@ init_platform :: proc() {
} else {
os_version.platform = .MacOS
switch version.majorVersion {
case 26: ws(&b, "macOS Tahoe")
case 15: ws(&b, "macOS Sequoia")
case 14: ws(&b, "macOS Sonoma")
case 13: ws(&b, "macOS Ventura")

View File

@@ -1618,36 +1618,39 @@ PER_HPUX :: 0x0010
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,
RDOTH = 2,
WRGRP = 4,
RDGRP = 5,
WRUSR = 7,
RDUSR = 8,
DEST = 9,
LOCKED = 10,
}
/*
Shared memory flags bits
*/
IPC_Flags_Bits :: enum {
// Creation flags for shared memory.
IPC_CREAT = 9,
IPC_EXCL = 10,
IPC_NOWAIT = 11,
// Semaphore
SEM_UNDO = 9,
// Shared memory
SHM_HUGETLB = 11,
SHM_NORESERVE = 12,
// Usage flags for shared memory.
IPC_NOWAIT = 11,
SEM_UNDO = 9,
SHM_RDONLY = 12,
SHM_RND = 13,
SHM_REMAP = 14,
SHM_EXEC = 15,
// Message queue
MSG_NOERROR = 12,
MSG_EXCEPT = 13,
MSG_COPY = 14,

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_1GB :: transmute(Map_Flags)(u32(30) << 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

View File

@@ -937,17 +937,12 @@ IO_Vec :: struct {
}
/*
Access mode for shared memory
*/
IPC_Mode :: bit_set[IPC_Mode_Bits; u32]
/*
Flags used by IPC objects
Access modes and flags used by SystemV IPC procedures.
*/
IPC_Flags :: bit_set[IPC_Flags_Bits; i16]
/*
Permissions for IPC objects
Permissions for SystemV IPC primitives.
*/
IPC_Perm :: struct {
key: Key,
@@ -955,7 +950,7 @@ IPC_Perm :: struct {
gid: u32,
cuid: u32,
cgid: u32,
mode: IPC_Mode,
mode: IPC_Flags, // Only contains mode flags.
seq: u16,
_: [2 + 2*size_of(int)]u8,
}

View File

@@ -8,7 +8,10 @@ when ODIN_OS == .Darwin {
} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD {
foreign import lib "system:dl"
} else {
foreign import lib "system:c"
foreign import lib {
"system:c",
"system:dl",
}
}
// dlfcn.h - dynamic linking

View File

@@ -31,7 +31,7 @@ Unimplemented headers:
- iso646.h | Impossible
- math.h | See `core:c/libc`
- 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
- spawn.h | Use `fork`, `execve`, etc.
- stdarg.h | See `core:c/libc`
@@ -53,6 +53,8 @@ import "base:intrinsics"
import "core:c"
IS_SUPPORTED :: _IS_SUPPORTED
result :: enum c.int {
// Use `errno` and `strerror` for more information.
FAIL = -1,

View File

@@ -0,0 +1,10 @@
#+build !linux
#+build !darwin
#+build !netbsd
#+build !openbsd
#+build !freebsd
#+build !haiku
package posix
_IS_SUPPORTED :: false

View File

@@ -0,0 +1,5 @@
#+build linux, darwin, netbsd, openbsd, freebsd, haiku
package posix
_IS_SUPPORTED :: true

View File

@@ -350,4 +350,4 @@ NEWTEXTMETRICW :: struct {
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

View File

@@ -168,6 +168,7 @@ foreign kernel32 {
ResumeThread :: proc(thread: HANDLE) -> DWORD ---
GetThreadPriority :: proc(thread: HANDLE) -> c_int ---
SetThreadPriority :: proc(thread: HANDLE, priority: c_int) -> BOOL ---
GetThreadDescription :: proc(hThread: HANDLE, ppszThreadDescription: ^PCWSTR) -> HRESULT ---
SetThreadDescription :: proc(hThread: HANDLE, lpThreadDescription: PCWSTR) -> HRESULT ---
GetExitCodeThread :: proc(thread: HANDLE, exit_code: ^DWORD) -> BOOL ---
TerminateThread :: proc(thread: HANDLE, exit_code: DWORD) -> BOOL ---

View File

@@ -75,7 +75,7 @@ LANGIDFROMLCID :: #force_inline proc "contextless" (lcid: LCID) -> LANGID {
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 {
return nil
}
@@ -101,14 +101,42 @@ utf8_to_utf16 :: proc(s: string, allocator := context.temp_allocator) -> []u16 {
}
return text[:n]
}
utf8_to_wstring :: proc(s: string, allocator := context.temp_allocator) -> wstring {
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 {
return raw_data(res)
}
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
if N == 0 {
@@ -142,13 +170,49 @@ wstring_to_utf8 :: proc(s: wstring, N: int, allocator := context.temp_allocator)
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 {
return "", nil
}
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.
allowed_username :: proc(username: string) -> bool {

View File

@@ -589,7 +589,7 @@ WAVE_FORMAT_FLAC :: 0xF1AC /* flac.sourceforge.net */
WAVE_FORMAT_EXTENSIBLE :: 0xFFFE /* Microsoft */
WAVEFORMATEX :: struct {
WAVEFORMATEX :: struct #packed {
wFormatTag: WORD,
nChannels: WORD,
nSamplesPerSec: DWORD,
@@ -603,7 +603,7 @@ LPCWAVEFORMATEX :: ^WAVEFORMATEX
// 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.
// 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,
Samples: struct #raw_union {
wValidBitsPerSample: WORD, /* bits of precision */

View File

@@ -11,17 +11,17 @@ import "core:strings"
// - [[ https://invisible-island.net/ncurses/terminfo.src.html ]]
get_no_color :: proc() -> bool {
if no_color, ok := os.lookup_env("NO_COLOR"); ok {
defer delete(no_color)
buf: [128]u8
if no_color, err := os.lookup_env(buf[:], "NO_COLOR"); err == nil {
return no_color != ""
}
return false
}
get_environment_color :: proc() -> Color_Depth {
buf: [128]u8
// `COLORTERM` is non-standard but widespread and unambiguous.
if colorterm, ok := os.lookup_env("COLORTERM"); ok {
defer delete(colorterm)
if colorterm, err := os.lookup_env(buf[:], "COLORTERM"); err == nil {
// These are the only values that are typically advertised that have
// anything to do with color depth.
if colorterm == "truecolor" || colorterm == "24bit" {
@@ -29,8 +29,7 @@ get_environment_color :: proc() -> Color_Depth {
}
}
if term, ok := os.lookup_env("TERM"); ok {
defer delete(term)
if term, err := os.lookup_env(buf[:], "TERM"); err == nil {
if strings.contains(term, "-truecolor") {
return .True_Color
}

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.
LOG_LEVEL_DEFAULT : string : "debug" when ODIN_DEBUG else "info"
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.
USING_SHORT_LOGS : bool : #config(ODIN_TEST_SHORT_LOGS, false)
// 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
}
when ODIN_DEBUG {
log.debugf("Test #%i %s.%s changed state to %v.", task_channel.test_index, it.pkg, it.name, event.new_state)
when LOG_STATE_CHANGES {
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
@@ -741,7 +743,8 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
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 pkg := report.packages_by_name[it.pkg]
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.",
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 {
// We only write a new error message here if there wasn't one
// 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)
total_failure_count += 1
if passed {
total_success_count += 1
} else {
total_failure_count += 1
}
total_done_count += 1
}

View File

@@ -12,8 +12,26 @@ package testing
import "base:runtime"
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 {
Unknown,
Successful_Stop,
Illegal_Instruction,
Arithmetic_Error,
Segmentation_Fault,
@@ -21,7 +39,12 @@ Stop_Reason :: enum {
}
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()
}

View File

@@ -20,7 +20,8 @@ import "core:terminal/ansi"
@(private="file") stop_test_gate: sync.Mutex
@(private="file") stop_test_index: libc.sig_atomic_t
@(private="file") stop_test_reason: libc.sig_atomic_t
@(private="file") stop_test_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", 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) {
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)
for {
@@ -154,11 +178,15 @@ _should_stop_test :: proc() -> (test_index: int, reason: Stop_Reason, ok: bool)
intrinsics.atomic_store(&stop_test_alert, 0)
test_index = cast(int)intrinsics.atomic_load(&stop_test_index)
switch intrinsics.atomic_load(&stop_test_reason) {
case libc.SIGFPE: reason = .Arithmetic_Error
case libc.SIGILL: reason = .Illegal_Instruction
case libc.SIGSEGV: reason = .Segmentation_Fault
case SIGTRAP: reason = .Unhandled_Trap
if cast(bool)intrinsics.atomic_load(&stop_test_passed) {
reason = .Successful_Stop
} else {
switch intrinsics.atomic_load(&stop_test_signal) {
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
}

View File

@@ -21,6 +21,8 @@ import "core:mem"
_ :: reflect // alias reflect to nothing to force visibility for -vet
_ :: mem // in case TRACKING_MEMORY is not enabled
MAX_EXPECTED_ASSERTIONS_PER_TEST :: 5
// IMPORTANT NOTE: Compiler requires this layout
Test_Signature :: proc(^T)
@@ -155,3 +157,74 @@ set_fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location
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
}

View File

@@ -28,8 +28,6 @@ Creation_Error :: enum {
Expected_Delimiter,
// An unknown letter was supplied to `create_by_user` after the last delimiter.
Unknown_Flag,
// An unsupported flag was supplied.
Unsupported_Flag,
}
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.
Note: Does not handle `.Multiline` properly.
*/
Match_Iterator :: struct {
regex: Regular_Expression,
@@ -436,7 +433,6 @@ match_with_preallocated_capture :: proc(
/*
Iterate over a `Match_Iterator` and return successive captures.
Note: Does not handle `.Multiline` properly.
Inputs:
- it: Pointer to the `Match_Iterator` to iterate over.

View File

@@ -39,7 +39,8 @@ Type representing a thread handle and the associated with that thread data.
Thread :: struct {
using specific: Thread_Os_Specific,
flags: bit_set[Thread_State; u8],
// 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,
// The thread procedure.
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
`fn` procedure, the resources associated with the thread are going to be
automatically freed. **Do not** dereference the `^Thread` pointer, if this
flag is specified.
automatically freed.
**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
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
`fn` procedure, the resources associated with the thread are going to be
automatically freed. **Do not** dereference the `^Thread` pointer, if this
flag is specified.
automatically freed.
**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
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
`fn` procedure, the resources associated with the thread are going to be
automatically freed. **Do not** dereference the `^Thread` pointer, if this
flag is specified.
automatically freed.
**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
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
`fn` procedure, the resources associated with the thread are going to be
automatically freed. **Do not** dereference the `^Thread` pointer, if this
flag is specified.
automatically freed.
**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
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
`fn` procedure, the resources associated with the thread are going to be
automatically freed. **Do not** dereference the `^Thread` pointer, if this
flag is specified.
automatically freed.
**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
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
`fn` procedure, the resources associated with the thread are going to be
automatically freed. **Do not** dereference the `^Thread` pointer, if this
flag is specified.
automatically freed.
**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
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`

View File

@@ -120,6 +120,20 @@ pool_join :: proc(pool: ^Pool) {
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
for started_count < len(pool.threads) {
started_count = 0

View File

@@ -29,14 +29,10 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
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)
}
if .Joined in sync.atomic_load(&t.flags) {
return nil
}
// Enable thread's cancelability.
// NOTE(laytan): Darwin does not correctly/fully support all of this, not doing this does
// actually make pthread_cancel work in the capacity of my tests, while executing this would
@@ -124,7 +120,6 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
free(thread, thread.creation_allocator)
return nil
}
return thread
}
@@ -149,10 +144,13 @@ _join :: proc(t: ^Thread) {
// Prevent non-started threads from blocking main thread with initial wait
// condition.
if .Started not_in sync.atomic_load(&t.flags) {
for (.Started not_in sync.atomic_load(&t.flags)) {
_start(t)
}
posix.pthread_join(t.unix_thread, nil)
t.flags += {.Joined}
}
_join_multiple :: proc(threads: ..^Thread) {

View File

@@ -13,6 +13,7 @@ Thread_Os_Specific :: struct {
win32_thread: win32.HANDLE,
win32_thread_id: win32.DWORD,
mutex: sync.Mutex,
start_ok: sync.Sema,
}
_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 {
t := (^Thread)(t_)
if .Joined in sync.atomic_load(&t.flags) {
return 0
for (.Started not_in sync.atomic_load(&t.flags)) {
sync.wait(&t.start_ok)
}
t.id = sync.current_thread_id()
{
init_context := t.init_context
@@ -76,6 +75,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread {
thread.procedure = procedure
thread.win32_thread = win32_thread
thread.win32_thread_id = win32_thread_id
thread.id = int(win32_thread_id)
ok := win32.SetThreadPriority(win32_thread, _thread_priority_map[priority])
assert(ok == true)
@@ -103,16 +103,15 @@ _join :: proc(t: ^Thread) {
return
}
t.flags += {.Joined}
if .Started not_in t.flags {
t.flags += {.Started}
win32.ResumeThread(t.win32_thread)
for (.Started not_in sync.atomic_load(&t.flags)) {
_start(t)
}
win32.WaitForSingleObject(t.win32_thread, win32.INFINITE)
win32.CloseHandle(t.win32_thread)
t.win32_thread = win32.INVALID_HANDLE
t.flags += {.Joined}
}
_join_multiple :: proc(threads: ..^Thread) {
@@ -136,6 +135,7 @@ _join_multiple :: proc(threads: ..^Thread) {
for t in threads {
win32.CloseHandle(t.win32_thread)
t.win32_thread = win32.INVALID_HANDLE
t.flags += {.Joined}
}
}

View File

@@ -577,12 +577,7 @@ parse_tzif :: proc(_buffer: []u8, region_name: string, allocator := context.allo
footer_str := string(buffer[:end_idx])
// UTC is a special case, we don't need to alloc
if len(local_time_types) == 1 {
name := cstring(raw_data(timezone_string_table[local_time_types[0].idx:]))
if name != "UTC" {
return
}
if len(local_time_types) == 1 && local_time_types[0].utoff == 0 {
return nil, true
}

View File

@@ -45,4 +45,7 @@ package all
@(require) import stbi "vendor:stb/image"
@(require) import "vendor:stb/rect_pack"
@(require) import "vendor:stb/truetype"
@(require) import "vendor:stb/vorbis"
@(require) import "vendor:stb/vorbis"
@(require) import "vendor:kb_text_shape"

View File

@@ -540,6 +540,7 @@ gb_internal void report_os_info() {
}
switch (major) {
case 26: gb_printf("macOS Tahoe"); break;
case 15: gb_printf("macOS Sequoia"); break;
case 14: gb_printf("macOS Sonoma"); break;
case 13: gb_printf("macOS Ventura"); break;

View File

@@ -385,6 +385,13 @@ enum LinkerChoice : i32 {
Linker_COUNT,
};
enum SourceCodeLocationInfo : u8 {
SourceCodeLocationInfo_Normal = 0,
SourceCodeLocationInfo_Obfuscated = 1,
SourceCodeLocationInfo_Filename = 2,
SourceCodeLocationInfo_None = 3,
};
String linker_choices[Linker_COUNT] = {
str_lit("default"),
str_lit("lld"),
@@ -512,7 +519,7 @@ struct BuildContext {
bool dynamic_map_calls;
bool obfuscate_source_code_locations;
SourceCodeLocationInfo source_code_location_info;
bool min_link_libs;

View File

@@ -148,6 +148,11 @@ gb_internal bool does_require_msgSend_stret(Type *return_type) {
if (return_type == nullptr) {
return false;
}
if (build_context.metrics.os != TargetOs_darwin) {
return false;
}
if (build_context.metrics.arch == TargetArch_i386 || build_context.metrics.arch == TargetArch_amd64) {
i64 struct_limit = type_size_of(t_uintptr) << 1;
return type_size_of(return_type) > struct_limit;

View File

@@ -1334,12 +1334,16 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
has_instrumentation = false;
e->flags |= EntityFlag_Require;
} else if (ac.instrumentation_enter) {
init_core_source_code_location(ctx->checker);
if (!is_valid_instrumentation_call(e->type)) {
init_core_source_code_location(ctx->checker);
gbString s = type_to_string(e->type);
error(e->token, "@(instrumentation_enter) procedures must have the type '%s', got %s", instrumentation_proc_type_str, s);
gb_string_free(s);
}
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);
if (ctx->info->instrumentation_enter_entity != nullptr) {
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);
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);
if (ctx->info->instrumentation_exit_entity != nullptr) {
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.no_sanitize_address = ac.no_sanitize_address;
e->Procedure.no_sanitize_memory = ac.no_sanitize_memory;
e->deprecated_message = ac.deprecated_message;
e->warning_message = ac.warning_message;

View File

@@ -2424,27 +2424,27 @@ gb_internal void check_assignment_error_suggestion(CheckerContext *c, Operand *o
Type *s = src->Array.elem;
Type *d = dst->Slice.elem;
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)) {
Type *s = src->DynamicArray.elem;
Type *d = dst->Slice.elem;
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)) {
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)) {
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("\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 whereas slices in general are assumed to be mutable.\n");
} else if (is_type_u8_slice(src) && are_types_identical(dst, t_string) && o->mode != Addressing_Constant) {
error_line("\tSuggestion: the expression may be casted to %s\n", b);
error_line("\tSuggestion: The expression may be casted to %s\n", b);
} else if (check_integer_exceed_suggestion(c, o, type, max_bit_size)) {
return;
} else if (is_expr_inferred_fixed_array(c->type_hint_expr) && is_type_array_like(type) && is_type_array_like(o->type)) {
gbString s = expr_to_string(c->type_hint_expr);
error_line("\tSuggestion: make sure that `%s` is attached to the compound literal directly\n", s);
error_line("\tSuggestion: Make sure that `%s` is attached to the compound literal directly\n", s);
gb_string_free(s);
} else if (is_type_pointer(type) &&
o->mode == Addressing_Variable &&
@@ -3086,126 +3086,106 @@ gb_internal void check_shift(CheckerContext *c, Operand *x, Operand *y, Ast *nod
GB_ASSERT(node->kind == Ast_BinaryExpr);
ast_node(be, BinaryExpr, node);
ExactValue x_val = {};
if (x->mode == Addressing_Constant) {
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)) {
bool y_is_untyped = is_type_untyped(y->type);
if (y_is_untyped) {
convert_to_typed(c, y, t_untyped_integer);
if (y->mode == Addressing_Invalid) {
x->mode = Addressing_Invalid;
return;
}
} else {
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);
} else if (!is_type_unsigned(y->type)) {
gbString y_str = expr_to_string(y->expr);
error(y->expr, "Shift amount '%s' must be an unsigned integer", y_str);
gb_string_free(y_str);
x->mode = Addressing_Invalid;
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;
}
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->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;
}
TokenPos pos = ast_token(x->expr).pos;
if (x_is_untyped) {
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_is_untyped) {
convert_to_typed(c, y, t_uint);
}
}
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;
}
if (is_type_untyped(y->type)) {
convert_to_typed(c, y, t_uint);
if (x->mode == Addressing_Constant) {
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) {
if (check_is_assignable_to(c, operand, y)) {
return true;
@@ -5884,12 +5864,12 @@ typedef u32 UnpackFlags;
enum UnpackFlag : u32 {
UnpackFlag_None = 0,
UnpackFlag_AllowOk = 1<<0,
UnpackFlag_IsVariadic = 1<<1,
UnpackFlag_AllowUndef = 1<<2,
UnpackFlag_AllowUndef = 1<<1,
};
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 {
if (lhs == nullptr || c->decl == nullptr) {
return tuple_count;
@@ -5914,11 +5894,14 @@ gb_internal bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize
return tuple_count;
};
bool allow_ok = (flags & UnpackFlag_AllowOk) != 0;
bool is_variadic = (flags & UnpackFlag_IsVariadic) != 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;
isize tuple_index = 0;
for (Ast *rhs : rhs_arguments) {
@@ -5934,26 +5917,18 @@ gb_internal bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize
Type *type_hint = nullptr;
if (lhs != nullptr && tuple_index < lhs_count) {
// NOTE(bill): override DeclInfo for dependency
Entity *e = lhs[tuple_index];
if (e != nullptr) {
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;
if (lhs != nullptr) {
if (tuple_index < variadic_index) {
// NOTE(bill): override DeclInfo for dependency
Entity *e = lhs[tuple_index];
if (e != nullptr) {
type_hint = e->type;
}
}
} else if (lhs != nullptr && tuple_index >= lhs_count && is_variadic) {
// NOTE(bill): override DeclInfo for dependency
Entity *e = lhs[lhs_count-1];
if (e != nullptr) {
type_hint = e->type;
if (e->flags & EntityFlag_Ellipsis) {
} else if (is_variadic) {
Entity *e = lhs[variadic_index];
if (e != nullptr) {
GB_ASSERT(e->flags & EntityFlag_Ellipsis);
GB_ASSERT(is_type_slice(e->type));
GB_ASSERT(e->type->kind == Type_Slice);
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;
}
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;
isize lhs_count = -1;
if (proc_type == nullptr) {
if (proc_type == nullptr || proc_type == t_invalid) {
return nullptr;
}
GB_ASSERT(is_type_proc(proc_type));
TypeProc *pt = &base_type(proc_type)->Proc;
*is_variadic = pt->variadic;
if (!pt->is_polymorphic || pt->is_poly_specialized) {
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);
proc_type = base_type(proc_type);
if (proc_type == t_invalid) {
return false;
}
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);
@@ -6830,7 +6807,7 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c,
Entity **lhs = nullptr;
isize lhs_count = -1;
bool is_variadic = false;
i32 variadic_index = -1;
auto positional_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) {
Entity *e = procs[0];
lhs = populate_proc_parameter_list(c, e->type, &lhs_count, &is_variadic);
check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, is_variadic ? UnpackFlag_IsVariadic : UnpackFlag_None);
Type *pt = base_type(e->type);
if (pt != nullptr && is_type_proc(pt)) {
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)) {
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;
}
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) {
Ast *arg = named_args[i];
@@ -7343,13 +7344,16 @@ gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *op
defer (array_free(&named_operands));
if (positional_args.count > 0) {
isize lhs_count = -1;
bool is_variadic = false;
Entity **lhs = nullptr;
isize lhs_count = -1;
i32 variadic_index = -1;
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) {
@@ -8756,23 +8760,52 @@ gb_internal ExprKind check_basic_directive_expr(CheckerContext *c, Operand *o, A
String name = bd->name.string;
if (name == "file") {
String file = get_file_path_string(bd->token.pos.file_id);
if (build_context.obfuscate_source_code_locations) {
switch (build_context.source_code_location_info) {
case SourceCodeLocationInfo_Normal:
break;
case SourceCodeLocationInfo_Obfuscated:
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->value = exact_value_string(file);
} else if (name == "directory") {
String file = get_file_path_string(bd->token.pos.file_id);
String path = dir_from_path(file);
if (build_context.obfuscate_source_code_locations) {
switch (build_context.source_code_location_info) {
case SourceCodeLocationInfo_Normal:
break;
case SourceCodeLocationInfo_Obfuscated:
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->value = exact_value_string(path);
} else if (name == "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);
break;
case SourceCodeLocationInfo_Filename:
break;
case SourceCodeLocationInfo_None:
line = 0;
break;
}
o->type = t_untyped_integer;
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(""));
} else {
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");
break;
case SourceCodeLocationInfo_Filename:
break;
case SourceCodeLocationInfo_None:
p = str_lit("");
break;
}
o->type = t_untyped_string;
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);
o->mode = Addressing_Value;
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;
@@ -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;
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->expr = node;
return kind;

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) {
return nullptr;
}
@@ -430,7 +430,7 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O
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 '_'
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;
}
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;
@@ -2418,7 +2418,7 @@ gb_internal void check_assign_stmt(CheckerContext *ctx, Ast *node) {
isize lhs_count = as->lhs.count;
if (lhs_count == 0) {
error(as->op, "Missing lhs in assignment statement");
error(as->op, "Missing LHS in assignment statement");
return;
}
@@ -2451,7 +2451,7 @@ gb_internal void check_assign_stmt(CheckerContext *ctx, Ast *node) {
if (lhs_to_ignore[i]) {
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) {
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
Token op = as->op;
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;
}
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;
}
Operand lhs = {Addressing_Invalid};
@@ -2474,15 +2474,16 @@ gb_internal void check_assign_stmt(CheckerContext *ctx, Ast *node) {
ast_node(be, BinaryExpr, binary_expr);
be->op = op;
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->right = as->rhs[0];
check_expr(ctx, &lhs, as->lhs[0]);
check_binary_expr(ctx, &rhs, binary_expr, nullptr, true);
if (rhs.mode != Addressing_Invalid) {
// NOTE(bill): Only use the first one will be used
check_assignment_variable(ctx, &lhs, &rhs);
be->op.string = substring(be->op.string, 0, be->op.string.len - 1);
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