Merge branch 'master' into bill/feoramalloc

This commit is contained in:
gingerBill
2026-06-29 15:08:38 +01:00
1510 changed files with 259241 additions and 63093 deletions

14
.gitattributes vendored
View File

@@ -1,6 +1,16 @@
*.odin linguist-language=Odin
* text=auto
# These files must always have *nix line-endings
Makefile text eol=lf
*.sh text eol=lf
*.sh text eol=lf
vendor/box2d/lib/box2d_windows_amd64_avx2.lib filter=lfs diff=lfs merge=lfs -text
vendor/box2d/lib/box2d_windows_amd64_sse2.lib filter=lfs diff=lfs merge=lfs -text
vendor/miniaudio/lib/miniaudio.lib filter=lfs diff=lfs merge=lfs -text
vendor/sdl3/SDL3.dll filter=lfs diff=lfs merge=lfs -text
vendor/sdl3/SDL3.lib filter=lfs diff=lfs merge=lfs -text
vendor/sdl3/mixer/*.dll filter=lfs diff=lfs merge=lfs -text
vendor/sdl3/mixer/*.lib filter=lfs diff=lfs merge=lfs -text
vendor/wgpu/lib/wgpu-windows-x86_64-msvc-release/lib/wgpu_native.dll filter=lfs diff=lfs merge=lfs -text
vendor/wgpu/lib/wgpu-windows-x86_64-msvc-release/lib/wgpu_native.dll.lib filter=lfs diff=lfs merge=lfs -text
vendor/wgpu/lib/wgpu-windows-x86_64-msvc-release/lib/wgpu_native.lib filter=lfs diff=lfs merge=lfs -text
vendor/wgpu/lib/wgpu-windows-x86_64-msvc-release/lib/wgpu_native.pdb filter=lfs diff=lfs merge=lfs -text

View File

@@ -6,7 +6,7 @@ jobs:
name: NetBSD Build, Check, and Test
runs-on: ubuntu-latest
env:
PKGSRC_BRANCH: 2024Q3
PKGSRC_BRANCH: 2025Q4
steps:
- uses: actions/checkout@v4
- name: Build, Check, and Test
@@ -30,14 +30,14 @@ 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 vendor/sdl3 -vet -strict-style -disallow-do -target:netbsd_amd64 -no-entry-point
./odin check vendor/sdl3 -vet -strict-style -disallow-do -target:netbsd_arm64 -no-entry-point
./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 run tests/heap_allocator -vet -strict-style -disallow-do -debug -- -allocator=feoramalloc -vmem-tests -serial-tests -parallel-tests
./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 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
@@ -64,52 +64,64 @@ 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 vendor/sdl3 -vet -strict-style -disallow-do -target:freebsd_amd64 -no-entry-point
./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 run tests/heap_allocator -vet -strict-style -disallow-do -debug -- -allocator=feoramalloc -vmem-tests -serial-tests -parallel-tests
./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 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:
strategy:
fail-fast: false
matrix:
# MacOS 13 runs on Intel, 14 runs on ARM
os: [ubuntu-latest, macos-13, macos-14]
os: [macos-15-intel, macos-latest, ubuntu-latest, ubuntu-24.04-arm]
runs-on: ${{ matrix.os }}
name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel' || 'Ubuntu') }} Build, Check, and Test
name: ${{ matrix.os == 'macos-latest' && 'MacOS ARM' || (matrix.os == 'macos-15-intel' && 'MacOS Intel') || (matrix.os == 'ubuntu-latest' && 'Ubuntu') || (matrix.os == 'ubuntu-24.04-arm' && 'Ubuntu ARM') }} Build, Check, and Test
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- name: Download LLVM (Linux)
if: matrix.os == 'ubuntu-latest'
- name: Download LLVM (MacOS Intel)
if: matrix.os == 'macos-15-intel'
run: |
brew update
brew install llvm@20 lua@5.4 lld
echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH
- name: Download LLVM (MacOS ARM)
if: matrix.os == 'macos-latest'
run: |
brew update
brew install llvm@22 wasmtime lua@5.4 lld
echo "$(brew --prefix llvm@22)/bin" >> $GITHUB_PATH
- name: Download LLVM (Ubuntu)
if: matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-24.04-arm'
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: Download LLVM (MacOS Intel)
if: matrix.os == 'macos-13'
run: |
brew update
brew install llvm@20 lua@5.4 lld
- name: Download LLVM (MacOS ARM)
if: matrix.os == 'macos-14'
run: |
brew update
brew install llvm@20 wasmtime lua@5.4 lld
- name: Build Odin
run: ./build_odin.sh release
- name: Odin version
run: ./odin version
- name: Odin report
run: ./odin report
- name: Get needed vendor libs
if: matrix.os == 'ubuntu-24.04-arm'
run: sudo apt-get install -y liblua5.4-dev
- name: Install libcurl (Ubuntu)
if: matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-24.04-arm'
run: |
sudo apt-get install libcurl4-openssl-dev libmbedtls-dev
- name: Install libcurl (macOS)
if: matrix.os == 'macos-15-intel' || matrix.os == 'macos-latest'
run: |
brew install curl mbedtls
- name: Compile needed Vendor
run: |
make -C vendor/stb/src
@@ -122,58 +134,65 @@ 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
- name: Odin check vendor/sdl3
run: ./odin check vendor/sdl3 -strict-style -vet -disallow-do -no-entry-point
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 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point
- name: Odin heap allocator tests
run: ./odin run tests/heap_allocator -vet -strict-style -disallow-do -debug -sanitize:thread -- -allocator=feoramalloc -vmem-tests -serial-tests -parallel-tests
- name: Normal Core library tests
run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
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
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
- name: Wycheproof tests
run: ./odin test tests/core/crypto/wycheproof -vet -vet-tabs -strict-style -vet-style -vet-cast -warnings-as-errors -disallow-do -o:speed
- name: Noise Protocol Framework tests
run: ./odin test tests/core/crypto/noise -vet -vet-tabs -strict-style -vet-style -vet-cast -warnings-as-errors -disallow-do -o:speed
- 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
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
if: matrix.os != 'macos-15-intel' && matrix.os != 'macos-latest'
- name: Vendor library tests (MacOS ARM)
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 -extra-linker-flags:"-L/opt/homebrew/opt/lua@5.4/lib"
if: matrix.os == 'macos-latest'
- name: Vendor library tests (MacOS Intel)
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 -extra-linker-flags:"-L/usr/local/opt/lua@5.4/lib"
if: matrix.os == 'macos-15-intel'
- 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
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
./run.sh
- name: Check benchmarks
run: ./odin check tests/benchmark -vet -strict-style -no-entry-point
- name: Odin check examples/all for Linux i386
run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_i386
if: matrix.os == 'ubuntu-latest'
- name: Odin check examples/all for Linux arm64
run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_arm64
if: matrix.os == 'ubuntu-latest'
- name: Odin check examples/all for FreeBSD amd64
run: ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
if: matrix.os == 'ubuntu-latest'
- name: Odin check examples/all for OpenBSD amd64
run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64
if: matrix.os == 'ubuntu-latest'
- name: Odin check vendor/sdl3 for Linux i386
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_i386
if: matrix.os == 'ubuntu-latest'
- name: Odin check vendor/sdl3 for Linux arm64
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_arm64
if: matrix.os == 'ubuntu-latest'
- name: Odin check vendor/sdl3 for FreeBSD amd64
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:freebsd_amd64
if: matrix.os == 'ubuntu-latest'
- name: Odin check vendor/sdl3 for OpenBSD amd64
run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:openbsd_amd64
if: matrix.os == 'ubuntu-latest'
- name: Run demo on WASI WASM32
run: |
./odin build examples/demo -target:wasi_wasm32 -vet -strict-style -disallow-do -out:demo
./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'
if: matrix.os == 'macos-latest'
- name: Check benchmarks
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 -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:linux_i386
- name: Odin check examples/all for OpenBSD amd64
if: matrix.os == 'ubuntu-latest'
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 -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 -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 -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point -target:linux_i386
- name: Odin check examples/all/sdl3 for OpenBSD amd64
if: matrix.os == 'ubuntu-latest'
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
@@ -184,7 +203,7 @@ jobs:
- name: build Odin
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
./build.bat 1
- name: Odin version
run: ./odin version
@@ -193,28 +212,28 @@ jobs:
- name: Odin check
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
odin check examples/demo -vet
- name: Odin run
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
odin run examples/demo
- name: Odin run -debug
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin run examples/demo -debug -vet -strict-style -disallow-do
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
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
- name: Odin check vendor/sdl3
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
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 vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
odin check examples/all/sdl3 -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -no-entry-point
- name: Odin heap allocator tests
shell: cmd
run: |
@@ -223,51 +242,55 @@ jobs:
- 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
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
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
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 -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
- name: Wycheproof tests
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
odin test tests/core/crypto/wycheproof -vet -vet-tabs -strict-style -vet-style -vet-cast -warnings-as-errors -disallow-do -o:speed -microarch:native
- name: Noise Protocol Framework tests
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
odin test tests/core/crypto/noise -vet -vet-tabs -strict-style -vet-style -vet-cast -warnings-as-errors -disallow-do -o:speed -microarch:native
- name: Vendor library tests
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
copy vendor\lua\5.4\windows\*.dll .
odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
odin test tests/vendor -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -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
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
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: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
cd tests/issues
call run.bat
- name: Check benchmarks
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
odin check tests/benchmark -vet -strict-style -no-entry-point
- name: Odin documentation tests
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
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: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
odin check examples/all -strict-style -target:windows_i386
build_linux_riscv64:
@@ -284,6 +307,10 @@ jobs:
sudo ./llvm.sh 18
echo "/usr/lib/llvm-18/bin" >> $GITHUB_PATH
- name: Install libcurl
run: |
sudo apt-get install libcurl4-openssl-dev libmbedtls-dev
- name: Build Odin
run: ./build_odin.sh release
@@ -300,28 +327,28 @@ 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 vendor/sdl3
run: ./odin check vendor/sdl3 -target:linux_riscv64 -vet -strict-style -disallow-do -no-entry-point
- name: Odin check examples/all/sdl3
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: Odin heap allocator tests
run: ./odin run tests/heap_allocator -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 -debug -- -allocator=feoramalloc -vmem-tests -serial-tests -parallel-tests
- name: Normal Core library tests
run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
run: ./odin test tests/core/normal.odin -file -all-packages -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -target:linux_riscv64 -extra-linker-flags:"-fuse-ld=/usr/bin/riscv64-linux-gnu-gcc-12 -static -Wl,-static" -no-rpath
- name: Optimized Core library tests
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

View File

@@ -12,6 +12,8 @@ jobs:
runs-on: windows-2022
steps:
- uses: actions/checkout@v4
with:
lfs: true
- name: build Odin
shell: cmd
run: |
@@ -42,30 +44,34 @@ jobs:
name: windows_artifacts
path: dist
build_linux:
name: Linux Build
strategy:
matrix:
arch: [amd64, arm64]
env:
ARCH: ${{ matrix.arch }}
DOCKER_IMAGE: ${{ matrix.arch == 'amd64' && 'alpine:3.24' || 'arm64v8/alpine:3.24' }}
name: Linux Build ${{ matrix.arch }}
if: github.repository == 'odin-lang/Odin'
runs-on: ubuntu-latest
runs-on: ${{ matrix.arch == 'amd64' && 'ubuntu-latest' || 'ubuntu-24.04-arm' }}
steps:
- uses: actions/checkout@v4
- uses: jirutka/setup-alpine@v1
with:
branch: edge
- name: (Linux) Download LLVM
lfs: true
- name: (Linux) Download LLVM and Build Odin
run: |
apk add --no-cache \
musl-dev llvm20-dev clang20 git mold lz4 \
libxml2-static llvm20-static zlib-static zstd-static \
make
shell: alpine.sh --root {0}
- name: build odin
# NOTE: this build does slow compile times because of musl
run: ci/build_linux_static.sh
shell: alpine.sh {0}
docker run --rm -v "$PWD:/src" -w /src $DOCKER_IMAGE sh -c '
apk add --no-cache \
musl-dev llvm20-dev clang20 build-base git mold lz4 \
libxml2-static llvm20-static zlib-static zstd-static \
make &&
git config --global --add safe.directory /src &&
./ci/build_linux_static.sh
'
- name: Odin run
run: ./odin run examples/demo
- name: Copy artifacts
run: |
FILE="odin-linux-amd64-nightly+$(date -I)"
FILE="odin-linux-$ARCH-nightly+$(date -I)"
mkdir $FILE
cp odin $FILE
cp LICENSE $FILE
@@ -74,27 +80,36 @@ jobs:
cp -r core $FILE
cp -r vendor $FILE
cp -r examples $FILE
./ci/remove_windows_binaries.sh $FILE
# Creating a tarball so executable permissions are retained, see https://github.com/actions/upload-artifact/issues/38
tar -czvf dist.tar.gz $FILE
- name: Odin run
run: |
FILE="odin-linux-amd64-nightly+$(date -I)"
FILE="odin-linux-$ARCH-nightly+$(date -I)"
$FILE/odin run examples/demo
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: linux_artifacts
name: ${{ matrix.arch == 'amd64' && 'linux_artifacts' || 'linux_arm_artifacts' }}
path: dist.tar.gz
build_macos:
name: MacOS Build
strategy:
matrix:
arch: [amd64, arm64]
env:
ARCH: ${{ matrix.arch }}
name: Macos Build ${{ matrix.arch }}
if: github.repository == 'odin-lang/Odin'
runs-on: macos-13
runs-on: ${{ matrix.arch == 'amd64' && 'macos-15-intel' || 'macos-latest' }}
steps:
- uses: actions/checkout@v4
with:
lfs: true
- name: Download LLVM and setup PATH
run: |
brew update
brew install llvm@20 dylibbundler lld
brew install llvm@20 dylibbundler lld@20
brew link llvm@20
- name: build odin
# These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to
@@ -102,7 +117,7 @@ jobs:
run: CXXFLAGS="-L/usr/lib/system -L/usr/lib" make nightly
- name: Bundle
run: |
FILE="odin-macos-amd64-nightly+$(date -I)"
FILE="odin-macos-$ARCH-nightly+$(date -I)"
mkdir $FILE
cp odin $FILE
cp LICENSE $FILE
@@ -111,59 +126,22 @@ jobs:
cp -r core $FILE
cp -r vendor $FILE
cp -r examples $FILE
./ci/remove_windows_binaries.sh $FILE
dylibbundler -b -x $FILE/odin -d $FILE/libs -od -p @executable_path/libs
# Creating a tarball so executable permissions are retained, see https://github.com/actions/upload-artifact/issues/38
tar -czvf dist.tar.gz $FILE
- name: Odin run
run: |
FILE="odin-macos-amd64-nightly+$(date -I)"
FILE="odin-macos-$ARCH-nightly+$(date -I)"
$FILE/odin run examples/demo
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: macos_artifacts
path: dist.tar.gz
build_macos_arm:
name: MacOS ARM Build
if: github.repository == 'odin-lang/Odin'
runs-on: macos-14 # ARM machine
steps:
- uses: actions/checkout@v4
- name: Download LLVM and setup PATH
run: |
brew update
brew install llvm@20 dylibbundler lld
- name: build odin
# These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to
# not link with libunwind bundled with LLVM but link with libunwind on the system.
run: CXXFLAGS="-L/usr/lib/system -L/usr/lib" make nightly
- name: Bundle
run: |
FILE="odin-macos-arm64-nightly+$(date -I)"
mkdir $FILE
cp odin $FILE
cp LICENSE $FILE
cp -r shared $FILE
cp -r base $FILE
cp -r core $FILE
cp -r vendor $FILE
cp -r examples $FILE
dylibbundler -b -x $FILE/odin -d $FILE/libs -od -p @executable_path/libs
# Creating a tarball so executable permissions are retained, see https://github.com/actions/upload-artifact/issues/38
tar -czvf dist.tar.gz $FILE
- name: Odin run
run: |
FILE="odin-macos-arm64-nightly+$(date -I)"
$FILE/odin run examples/demo
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: macos_arm_artifacts
name: ${{ matrix.arch == 'amd64' && 'macos_artifacts' || 'macos_arm_artifacts' }}
path: dist.tar.gz
upload_b2:
runs-on: [ubuntu-latest]
needs: [build_windows, build_macos, build_macos_arm, build_linux]
needs: [build_windows, build_macos, build_linux]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
@@ -192,6 +170,12 @@ jobs:
name: linux_artifacts
path: linux_artifacts
- name: Download Ubuntu ARM artifacts
uses: actions/download-artifact@v4.1.7
with:
name: linux_arm_artifacts
path: linux_arm_artifacts
- name: Download macOS artifacts
uses: actions/download-artifact@v4.1.7
with:
@@ -219,6 +203,7 @@ jobs:
file linux_artifacts/dist.tar.gz
python3 ci/nightly.py artifact windows-amd64 windows_artifacts/
python3 ci/nightly.py artifact linux-amd64 linux_artifacts/dist.tar.gz
python3 ci/nightly.py artifact linux-arm64 linux_arm_artifacts/dist.tar.gz
python3 ci/nightly.py artifact macos-amd64 macos_artifacts/dist.tar.gz
python3 ci/nightly.py artifact macos-arm64 macos_arm_artifacts/dist.tar.gz
python3 ci/nightly.py prune

18
.gitignore vendored
View File

@@ -277,6 +277,10 @@ odin
*.bin
demo.bin
libLLVM*.so*
*.a
# WASM
*.wasm
# shared collection
shared/
@@ -293,5 +297,17 @@ build.sh
# RAD debugger project file
*.raddbg
*.rdi
tests/issues/build/*
misc/featuregen/featuregen
# Clangd stuff
.cache/
.clangd
compile_commands.json
# Dev cmake helpers
build/
cmake-build*/
CMakeLists.txt
sandbox/

37
LICENSE
View File

@@ -1,26 +1,17 @@
Copyright (c) 2016-2024 Ginger Bill. All rights reserved.
Copyright (c) 2016-2025 Ginger Bill. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

View File

@@ -7,13 +7,232 @@ nil :: nil
false :: 0!=0
true :: 0==0
ODIN_OS :: ODIN_OS
ODIN_ARCH :: ODIN_ARCH
ODIN_ENDIAN :: ODIN_ENDIAN
ODIN_VENDOR :: ODIN_VENDOR
ODIN_VERSION :: ODIN_VERSION
ODIN_ROOT :: ODIN_ROOT
ODIN_DEBUG :: ODIN_DEBUG
// The following constants are added in `checker.cpp`'s `init_universal` procedure.
/*
An `enum` value indicating the target's CPU architecture.
Possible values are: `.amd64`, `.i386`, `.arm32`, `.arm64`, `.wasm32`, `.wasm64p32`, and `.riscv64`.
*/
ODIN_ARCH :: ODIN_ARCH
/*
A `string` indicating the target's CPU architecture.
Possible values are: "amd64", "i386", "arm32", "arm64", "wasm32", "wasm64p32", "riscv64".
*/
ODIN_ARCH_STRING :: ODIN_ARCH_STRING
/*
An `enum` value indicating the type of compiled output, chosen using `-build-mode`.
Possible values are: `.Executable`, `.Dynamic`, `.Static`, `.Object`, `.Assembly`, and `.LLVM_IR`.
*/
ODIN_BUILD_MODE :: ODIN_BUILD_MODE
/*
A `string` containing the name of the folder that contains the entry point,
e.g. for `%ODIN_ROOT%/examples/demo`, this would contain `demo`.
*/
ODIN_BUILD_PROJECT_NAME :: ODIN_BUILD_PROJECT_NAME
/*
An `i64` containing the time at which the executable was compiled, in nanoseconds.
This is compatible with the `time.Time` type, i.e. `time.Time{_nsec=ODIN_COMPILE_TIMESTAMP}`
*/
ODIN_COMPILE_TIMESTAMP :: ODIN_COMPILE_TIMESTAMP
/*
`true` if the `-debug` command line switch is passed, which enables debug info generation.
*/
ODIN_DEBUG :: ODIN_DEBUG
/*
`true` if the `-default-to-nil-allocator` command line switch is passed,
which sets the initial `context.allocator` to an allocator that does nothing.
*/
ODIN_DEFAULT_TO_NIL_ALLOCATOR :: ODIN_DEFAULT_TO_NIL_ALLOCATOR
/*
`true` if the `-default-to-panic-allocator` command line switch is passed,
which sets the initial `context.allocator` to an allocator that panics if allocated from.
*/
ODIN_DEFAULT_TO_PANIC_ALLOCATOR :: ODIN_DEFAULT_TO_PANIC_ALLOCATOR
/*
`true` if the `-disable-assert` command line switch is passed,
which removes all calls to `assert` from the program.
*/
ODIN_DISABLE_ASSERT :: ODIN_DISABLE_ASSERT
/*
An `enum` value indicating the endianness of the target.
Possible values are: `.Little` and `.Big`.
*/
ODIN_ENDIAN :: ODIN_ENDIAN
/*
An `string` indicating the endianness of the target.
Possible values are: "little" and "big".
*/
ODIN_ENDIAN_STRING :: ODIN_ENDIAN_STRING
/*
An `enum` value set using the `-error-pos-style` switch, indicating the source location style used for compile errors and warnings.
Possible values are: `.Default` (Odin-style) and `.Unix`.
*/
ODIN_ERROR_POS_STYLE :: ODIN_ERROR_POS_STYLE
/*
`true` if the `-foreign-error-procedures` command line switch is passed,
which inhibits generation of runtime error procedures, so that they can be in a separate compilation unit.
*/
ODIN_FOREIGN_ERROR_PROCEDURES :: ODIN_FOREIGN_ERROR_PROCEDURES
/*
A `string` describing the microarchitecture used for code generation.
If not set using the `-microarch` command line switch, the compiler will pick a default.
Possible values include, but are not limited to: "sandybridge", "x86-64-v2".
*/
ODIN_MICROARCH_STRING :: ODIN_MICROARCH_STRING
/*
An `int` value representing the minimum OS version given to the linker, calculated as `major * 10_000 + minor * 100 + revision`.
If not set using the `-minimum-os-version` command line switch, it defaults to `0`, except on Darwin, where it's `11_00_00`.
*/
ODIN_MINIMUM_OS_VERSION :: ODIN_MINIMUM_OS_VERSION
/*
`true` if the `-no-bounds-check` command line switch is passed, which disables bounds checking at runtime.
*/
ODIN_NO_BOUNDS_CHECK :: ODIN_NO_BOUNDS_CHECK
/*
`true` if the `-no-crt` command line switch is passed, which inhibits linking with the C Runtime Library, a.k.a. LibC.
*/
ODIN_NO_CRT :: ODIN_NO_CRT
/*
`true` if the `-no-entry-point` command line switch is passed, which makes the declaration of a `main` procedure optional.
*/
ODIN_NO_ENTRY_POINT :: ODIN_NO_ENTRY_POINT
/*
`true` if the `-no-rtti` command line switch is passed, which inhibits generation of full Runtime Type Information.
*/
ODIN_NO_RTTI :: ODIN_NO_RTTI
/*
`true` if the `-no-type-assert` command line switch is passed, which disables type assertion checking program wide.
*/
ODIN_NO_TYPE_ASSERT :: ODIN_NO_TYPE_ASSERT
/*
An `enum` value indicating the optimization level selected using the `-o` command line switch.
Possible values are: `.None`, `.Minimal`, `.Size`, `.Speed`, and `.Aggressive`.
If `ODIN_OPTIMIZATION_MODE` is anything other than `.None` or `.Minimal`, the compiler will also perform a unity build,
and `ODIN_USE_SEPARATE_MODULES` will be set to `false` as a result.
*/
ODIN_OPTIMIZATION_MODE :: ODIN_OPTIMIZATION_MODE
/*
An `enum` value indicating what the target operating system is.
*/
ODIN_OS :: ODIN_OS
/*
A `string` indicating what the target operating system is.
*/
ODIN_OS_STRING :: ODIN_OS_STRING
/*
An `enum` value indicating the platform subtarget, chosen using the `-subtarget` switch.
Possible values are: `.Default` `.iPhone`, .iPhoneSimulator, and `.Android`.
*/
ODIN_PLATFORM_SUBTARGET :: ODIN_PLATFORM_SUBTARGET
/*
A `string` representing the path of the folder containing the Odin compiler,
relative to which we expect to find the `base` and `core` package collections.
*/
ODIN_ROOT :: ODIN_ROOT
/*
A `bit_set` indicating the sanitizer flags set using the `-sanitize` command line switch.
Supported flags are `.Address`, `.Memory`, and `.Thread`.
*/
ODIN_SANITIZER_FLAGS :: ODIN_SANITIZER_FLAGS
/*
`true` if the code is being compiled via an invocation of `odin test`.
*/
ODIN_TEST :: ODIN_TEST
/*
`true` if built using the experimental Tilde backend.
*/
ODIN_TILDE :: ODIN_TILDE
/*
`true` by default, meaning each each package is built into its own object file, and then linked together.
`false` if the `-use-single-module` command line switch to force a unity build is provided.
If `ODIN_OPTIMIZATION_MODE` is anything other than `.None` or `.Minimal`, the compiler will also perform a unity build,
and this constant will also be set to `false`.
*/
ODIN_USE_SEPARATE_MODULES :: ODIN_USE_SEPARATE_MODULES
/*
`true` if Valgrind integration is supported on the target.
*/
ODIN_VALGRIND_SUPPORT :: ODIN_VALGRIND_SUPPORT
/*
A `string` which identifies the compiler being used. The official compiler sets this to `"odin"`.
*/
ODIN_VENDOR :: ODIN_VENDOR
/*
A `string` containing the version of the Odin compiler, typically in the format `dev-YYYY-MM`.
*/
ODIN_VERSION :: ODIN_VERSION
/*
A `string` containing the Git hash part of the Odin version.
Empty if `.git` could not be detected at the time the compiler was built.
*/
ODIN_VERSION_HASH :: ODIN_VERSION_HASH
/*
An `enum` set by the `-subsystem` flag, specifying which Windows subsystem the PE file was created for.
Possible values are:
`.Unknown` - Default and only value on non-Windows platforms
`.Console` - Default on Windows
`.Windows` - Can be used by graphical applications so Windows doesn't open an empty console
There are some other possible values for e.g. EFI applications, but only Console and Windows are supported.
See also: https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header64
*/
ODIN_WINDOWS_SUBSYSTEM :: ODIN_WINDOWS_SUBSYSTEM
/*
An `string` set by the `-subsystem` flag, specifying which Windows subsystem the PE file was created for.
Possible values are:
"UNKNOWN" - Default and only value on non-Windows platforms
"CONSOLE" - Default on Windows
"WINDOWS" - Can be used by graphical applications so Windows doesn't open an empty console
There are some other possible values for e.g. EFI applications, but only Console and Windows are supported.
See also: https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header64
*/
ODIN_WINDOWS_SUBSYSTEM_STRING :: ODIN_WINDOWS_SUBSYSTEM_STRING
/*
`true` if LLVM supports the f16 type.
*/
__ODIN_LLVM_F16_SUPPORTED :: __ODIN_LLVM_F16_SUPPORTED
byte :: u8 // alias
@@ -119,7 +338,8 @@ jmag :: proc(value: Quaternion) -> Float ---
kmag :: proc(value: Quaternion) -> Float ---
conj :: proc(value: Complex_Or_Quaternion) -> Complex_Or_Quaternion ---
expand_values :: proc(value: Struct_Or_Array) -> (A, B, C, ...) ---
expand_values :: proc(value: Struct_Or_Array) -> (A, B, C, ...) ---
compress_values :: proc(values: ...) -> Struct_Or_Array_Like_Type ---
min :: proc(values: ..T) -> T ---
max :: proc(values: ..T) -> T ---
@@ -130,3 +350,6 @@ soa_zip :: proc(slices: ...) -> #soa[]Struct ---
soa_unzip :: proc(value: $S/#soa[]$E) -> (slices: ...) ---
unreachable :: proc() -> ! ---
// Where T is a string, slice, dynamic array, or pointer to an array type
raw_data :: proc(t: $T) -> rawptr

View File

@@ -4,6 +4,7 @@ package intrinsics
import "base:runtime"
// Package-Related
is_package_imported :: proc(package_name: string) -> bool ---
@@ -32,11 +33,14 @@ trap :: proc() -> ! ---
alloca :: proc(size, align: int) -> [^]u8 ---
cpu_relax :: proc() ---
read_cycle_counter :: proc() -> i64 ---
read_cycle_counter_frequency :: proc() -> i64 ---
count_ones :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) ---
count_zeros :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) ---
count_trailing_zeros :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) ---
count_leading_zeros :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) ---
count_trailing_ones :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) ---
count_leading_ones :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) ---
reverse_bits :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) ---
byte_swap :: proc(x: $T) -> T where type_is_integer(T) || type_is_float(T) ---
@@ -74,7 +78,9 @@ prefetch_write_instruction :: proc(address: rawptr, #const locality: i32 /* 0..=
prefetch_write_data :: proc(address: rawptr, #const locality: i32 /* 0..=3 */) ---
// Compiler Hints
expect :: proc(val, expected_val: $T) -> T ---
expect :: proc(val, expected_val: $T) -> T ---
likely :: proc(val: $T) -> T where type_is_boolean(T) ---
unlikely :: proc(val: $T) -> T where type_is_boolean(T) ---
// Linux and Darwin Only
syscall :: proc(id: uintptr, args: ..uintptr) -> uintptr ---
@@ -137,9 +143,13 @@ type_is_rune :: proc($T: typeid) -> bool ---
type_is_float :: proc($T: typeid) -> bool ---
type_is_complex :: proc($T: typeid) -> bool ---
type_is_quaternion :: proc($T: typeid) -> bool ---
type_is_string :: proc($T: typeid) -> bool ---
type_is_typeid :: proc($T: typeid) -> bool ---
type_is_any :: proc($T: typeid) -> bool ---
type_is_string :: proc($T: typeid) -> bool ---
type_is_string16 :: proc($T: typeid) -> bool ---
type_is_cstring :: proc($T: typeid) -> bool ---
type_is_cstring16 :: proc($T: typeid) -> bool ---
type_is_endian_platform :: proc($T: typeid) -> bool ---
type_is_endian_little :: proc($T: typeid) -> bool ---
@@ -152,6 +162,7 @@ type_is_indexable :: proc($T: typeid) -> bool ---
type_is_sliceable :: proc($T: typeid) -> bool ---
type_is_comparable :: proc($T: typeid) -> bool ---
type_is_simple_compare :: proc($T: typeid) -> bool --- // easily compared using memcmp (== and !=)
type_is_nearly_simple_compare :: proc($T: typeid) -> bool --- // easily compared using memcmp (including floats)
type_is_dereferenceable :: proc($T: typeid) -> bool ---
type_is_valid_map_key :: proc($T: typeid) -> bool ---
type_is_valid_matrix_elements :: proc($T: typeid) -> bool ---
@@ -169,8 +180,12 @@ type_is_union :: proc($T: typeid) -> bool ---
type_is_enum :: proc($T: typeid) -> bool ---
type_is_proc :: proc($T: typeid) -> bool ---
type_is_bit_set :: proc($T: typeid) -> bool ---
type_is_bit_field :: proc($T: typeid) -> bool ---
type_is_simd_vector :: proc($T: typeid) -> bool ---
type_is_matrix :: proc($T: typeid) -> bool ---
type_is_fixed_capacity_dynamic_array :: proc($T: typeid) -> bool ---
type_is_internally_pointer_like :: proc($T: typeid) -> bool ---
type_has_nil :: proc($T: typeid) -> bool ---
@@ -193,12 +208,17 @@ type_bit_set_underlying_type :: proc($T: typeid) -> typeid where type_is_bit_set
type_has_field :: proc($T: typeid, $name: string) -> bool ---
type_field_type :: proc($T: typeid, $name: string) -> typeid ---
type_field_bit_size :: proc($T: typeid, $name: string) -> int where type_is_bit_field(T) ---
type_field_bit_offset :: proc($T: typeid, $name: string) -> int where type_is_bit_field(T) ---
type_proc_parameter_count :: proc($T: typeid) -> int where type_is_proc(T) ---
type_proc_return_count :: proc($T: typeid) -> int where type_is_proc(T) ---
type_proc_parameter_type :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
type_proc_return_type :: proc($T: typeid, index: int) -> typeid where type_is_proc(T) ---
type_proc_calling_convention :: proc($T: typeid) -> Odin_Calling_Convention where type_is_proc(T) ---
type_struct_field_count :: proc($T: typeid) -> int where type_is_struct(T) ---
type_struct_has_implicit_padding :: proc($T: typeid) -> bool where type_is_struct(T) ---
@@ -208,10 +228,17 @@ type_polymorphic_record_parameter_value :: proc($T: typeid, index: int) -> $V --
type_is_specialized_polymorphic_record :: proc($T: typeid) -> bool ---
type_is_unspecialized_polymorphic_record :: proc($T: typeid) -> bool ---
type_is_subtype_of :: proc($T, $U: typeid) -> bool ---
type_is_subtype_of :: proc($T, $U: typeid) -> bool ---
type_is_superset_of :: proc($Super, $Sub: typeid) -> bool ---
type_field_index_of :: proc($T: typeid, $name: string) -> uintptr ---
type_fixed_capacity_dynamic_array_len_offset :: proc($T: typeid/[dynamic; $N]$E) -> uintptr ---
// "Contiguous" means that the set of enum constants, when sorted, have a difference of either 0 or 1 between consecutive values.
// This is the exact opposite of "sparse".
type_enum_is_contiguous :: proc($T: typeid) -> bool where type_is_enum(T) ---
type_equal_proc :: proc($T: typeid) -> (equal: proc "contextless" (rawptr, rawptr) -> bool) where type_is_comparable(T) ---
type_hasher_proc :: proc($T: typeid) -> (hasher: proc "contextless" (data: rawptr, seed: uintptr) -> uintptr) where type_is_comparable(T) ---
@@ -221,12 +248,25 @@ type_map_cell_info :: proc($T: typeid) -> ^runtime.Map_Cell_Info ---
type_convert_variants_to_pointers :: proc($T: typeid) -> typeid where type_is_union(T) ---
type_merge :: proc($U, $V: typeid) -> typeid where type_is_union(U), type_is_union(V) ---
type_integer_to_unsigned :: proc($T: typeid) -> type where type_is_integer(T), !type_is_unsigned(T) ---
type_integer_to_signed :: proc($T: typeid) -> type where type_is_integer(T), type_is_unsigned(T) ---
type_has_shared_fields :: proc($U, $V: typeid) -> bool where type_is_struct(U), type_is_struct(V) ---
// Returns the canonicalized name of the type, of which is used to produce the pseudo-unique 'typeid'
type_canonical_name :: proc($T: typeid) -> string ---
constant_utf16_cstring :: proc($literal: string) -> [^]u16 ---
constant_log2 :: proc($v: $T) -> T where type_is_integer(T) ---
constant_floor :: proc($v: $T) -> T where type_is_integer(T) || type_is_float(T) ---
constant_trunc :: proc($v: $T) -> T where type_is_integer(T) || type_is_float(T) ---
constant_ceil :: proc($v: $T) -> T where type_is_integer(T) || type_is_float(T) ---
constant_round :: proc($v: $T) -> T where type_is_integer(T) || type_is_float(T) ---
// SIMD related
simd_add :: proc(a, b: #simd[N]T) -> #simd[N]T ---
simd_sub :: proc(a, b: #simd[N]T) -> #simd[N]T ---
@@ -306,6 +346,7 @@ simd_indices :: proc($T: typeid/#simd[$N]$E) -> T where type_is_numeric(T) ---
simd_shuffle :: proc(a, b: #simd[N]T, indices: ..int) -> #simd[len(indices)]T ---
simd_select :: proc(cond: #simd[N]boolean_or_integer, true, false: #simd[N]T) -> #simd[N]T ---
simd_runtime_swizzle :: proc(table: #simd[N]T, indices: #simd[N]T) -> #simd[N]T where type_is_integer(T) ---
// Lane-wise operations
simd_ceil :: proc(a: #simd[N]any_float) -> #simd[N]any_float ---
@@ -314,7 +355,11 @@ simd_trunc :: proc(a: #simd[N]any_float) -> #simd[N]any_float ---
// rounding to the nearest integral value; if two values are equally near, rounds to the even one
simd_nearest :: proc(a: #simd[N]any_float) -> #simd[N]any_float ---
simd_to_bits :: proc(v: #simd[N]T) -> #simd[N]Integer where size_of(T) == size_of(Integer), type_is_unsigned(Integer) ---
simd_approx_recip :: proc(x: #simd[N]T) -> #simd[N]T where type_is_float(T)) ---
simd_approx_recip_sqrt :: proc(x: #simd[N]T) -> #simd[N]T where type_is_float(T)) ---
simd_to_bits :: proc(v: #simd[N]T) -> #simd[N]Integer where size_of(T) == size_of(Integer), type_is_unsigned(Integer) ---
simd_to_bits_signed :: proc(v: #simd[N]T) -> #simd[N]Integer where size_of(T) == size_of(Integer), !type_is_unsigned(Integer) ---
// equivalent to a swizzle with descending indices, e.g. reserve(a, 3, 2, 1, 0)
simd_lanes_reverse :: proc(a: #simd[N]T) -> #simd[N]T ---
@@ -322,6 +367,19 @@ simd_lanes_reverse :: proc(a: #simd[N]T) -> #simd[N]T ---
simd_lanes_rotate_left :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T ---
simd_lanes_rotate_right :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T ---
// return {b[0], a[1], b[2], a[3], ...}
simd_odd_even :: proc(a, b: #simd[N]T) -> #simd[N]T ---
// Returns the sums of N consecutive lanes
simd_sums_of_n :: proc(a: #simd[LANES]T, $N: uint) -> #simd[LANES/N]T where is_power_of_two(N) ---
simd_pairwise_add :: proc(a, b: #simd[LANES]T) -> #simd[LANES]T where LANES % 2 == 0 ---
simd_pairwise_sub :: proc(a, b: #simd[LANES]T) -> #simd[LANES]T where LANES % 2 == 0 ---
simd_interleave :: proc(a, ..#simd[LANES/N]T) -> #simd[LANES]T where N >= 1 ---
simd_deinterleave :: proc(a: #simd[LANES]T, $N: uint) -> (..#simd[LANES/N]T) where N >= 1, LANES % N == 0 --- // returns N multiple vectors
// Checks if the current target supports the given target features.
//
// Takes a constant comma-seperated string (eg: "sha512,sse4.1"), or a procedure type which has either
@@ -330,6 +388,9 @@ simd_lanes_rotate_right :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T ---
has_target_feature :: proc($test: $T) -> bool where type_is_string(T) || type_is_proc(T) ---
// Utility Calls
concatenate :: proc(x, y: $T, z: ..T) -> T where type_is_array(T) || type_is_slice(T) ---
// Returns the value of the procedure where `x` must be a call expression
procedure_of :: proc(x: $T) -> T where type_is_proc(T) ---
@@ -353,22 +414,34 @@ x86_cpuid :: proc(ax, cx: u32) -> (eax, ebx, ecx, edx: u32) ---
x86_xgetbv :: proc(cx: u32) -> (eax, edx: u32) ---
// C specific things
c_va_list :: struct{/*platform specific implementation*/}
c_va_start :: proc(list: ^c_va_list, /*#c_vararg parameter*/ args: ..$T) ---
c_va_end :: proc(list: ^c_va_list) ---
c_va_copy :: proc(dst, src: ^c_va_list) ---
c_va_arg :: proc(list: ^c_va_list, $T: typeid) -> T ---
// Darwin targets only
objc_object :: struct{}
objc_selector :: struct{}
objc_class :: struct{}
objc_ivar :: struct{}
objc_id :: ^objc_object
objc_SEL :: ^objc_selector
objc_Class :: ^objc_class
objc_Ivar :: ^objc_ivar
objc_id :: ^objc_object
objc_SEL :: ^objc_selector
objc_Class :: ^objc_class
objc_Ivar :: ^objc_ivar
objc_instancetype :: distinct objc_id
objc_find_selector :: proc($name: string) -> objc_SEL ---
objc_register_selector :: proc($name: string) -> objc_SEL ---
objc_find_class :: proc($name: string) -> objc_Class ---
objc_register_class :: proc($name: string) -> objc_Class ---
objc_ivar_get :: proc(self: ^$T) -> ^$U ---
objc_block :: proc(invoke: $T, ..any) -> ^Objc_Block(T) where type_is_proc(T) ---
objc_super :: proc(obj: ^$T) -> ^$U where type_is_subtype_of(T, objc_object) && type_is_subtype_of(U, objc_object) ---
valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr ---

View File

@@ -15,7 +15,7 @@
//
// IMPORTANT NOTE(bill): `type_info_of` cannot be used within a
// #shared_global_scope due to the internals of the compiler.
// This could change at a later date if the all these data structures are
// This could change at a later date if all these data structures are
// implemented within the compiler rather than in this "preload" file
//
#+no-instrumentation
@@ -23,8 +23,27 @@ package runtime
import "base:intrinsics"
/*
Fast_Math_Flag :: enum u8 {
Allow_Reassoc = 0,
No_NaNs = 1,
No_Infs = 2,
No_Signed_Zeros = 3,
Allow_Reciprocal = 4,
Allow_Contract = 5,
Approx_Func = 6,
}
*/
Fast_Math_Flag :: intrinsics.Fast_Math_Flag
// Fast_Math_Flags :: distinct bit_set[Fast_Math_Flag; u32]
Fast_Math_Flags :: intrinsics.Fast_Math_Flags
// NOTE(bill): This must match the compiler's
Calling_Convention :: enum u8 {
/*
enum u8 {
Invalid = 0,
Odin = 1,
Contextless = 2,
@@ -39,7 +58,13 @@ Calling_Convention :: enum u8 {
Win64 = 9,
SysV = 10,
Preserve_None = 11,
Preserve_Most = 12,
Preserve_All = 13,
}
*/
Calling_Convention :: type_of(ODIN_DEFAULT_CALLING_CONVENTION)
Type_Info_Enum_Value :: distinct i64
@@ -61,6 +86,11 @@ Type_Info_Struct_Soa_Kind :: enum u8 {
Dynamic = 3,
}
Type_Info_String_Encoding_Kind :: enum u8 {
UTF_8 = 0,
UTF_16 = 1,
}
// Variant Types
Type_Info_Named :: struct {
name: string,
@@ -73,7 +103,7 @@ Type_Info_Rune :: struct {}
Type_Info_Float :: struct {endianness: Platform_Endianness}
Type_Info_Complex :: struct {}
Type_Info_Quaternion :: struct {}
Type_Info_String :: struct {is_cstring: bool}
Type_Info_String :: struct {is_cstring: bool, encoding: Type_Info_String_Encoding_Kind}
Type_Info_Boolean :: struct {}
Type_Info_Any :: struct {}
Type_Info_Type_Id :: struct {}
@@ -113,10 +143,11 @@ Type_Info_Parameters :: struct { // Only used for procedures parameters and resu
Type_Info_Struct_Flags :: distinct bit_set[Type_Info_Struct_Flag; u8]
Type_Info_Struct_Flag :: enum u8 {
packed = 0,
raw_union = 1,
no_copy = 2,
align = 3,
packed = 0,
raw_union = 1,
all_or_none = 2,
align = 3,
simple = 4,
}
Type_Info_Struct :: struct {
@@ -131,7 +162,7 @@ Type_Info_Struct :: struct {
flags: Type_Info_Struct_Flags,
// These are only set iff this structure is an SOA structure
// These are only set if and only if (⟺) this structure is an SOA structure
soa_kind: Type_Info_Struct_Soa_Kind,
soa_len: i32,
soa_base_type: ^Type_Info,
@@ -160,10 +191,11 @@ Type_Info_Map :: struct {
map_info: ^Map_Info,
}
Type_Info_Bit_Set :: struct {
elem: ^Type_Info,
underlying: ^Type_Info, // Possibly nil
lower: i64,
upper: i64,
elem: ^Type_Info,
underlying: ^Type_Info,
explicit_underlying: bool, // false = bit_set[T], true = bit_set[T, U]
lower: i64,
upper: i64,
}
Type_Info_Simd_Vector :: struct {
elem: ^Type_Info,
@@ -195,6 +227,14 @@ Type_Info_Bit_Field :: struct {
field_count: int,
}
Type_Info_Fixed_Capacity_Dynamic_Array :: struct {
elem: ^Type_Info,
elem_size: int,
capacity: int,
len_offset: uintptr,
}
Type_Info_Flag :: enum u8 {
Comparable = 0,
Simple_Compare = 1,
@@ -235,6 +275,7 @@ Type_Info :: struct {
Type_Info_Matrix,
Type_Info_Soa_Pointer,
Type_Info_Bit_Field,
Type_Info_Fixed_Capacity_Dynamic_Array,
},
}
@@ -259,7 +300,79 @@ when ODIN_OS == .Windows {
dll_instance: rawptr
}
// IMPORTANT NOTE(bill): Must be in this order (as the compiler relies upon it)
// This is safe to change. The log2 size of a cache-line. At minimum it has to
// be six though. Higher cache line sizes are permitted.
MAP_CACHE_LINE_LOG2 :: 6
// The size of a cache-line.
MAP_CACHE_LINE_SIZE :: 1 << MAP_CACHE_LINE_LOG2
// The minimum cache-line size allowed by this implementation is 64 bytes since
// we need 6 bits in the base pointer to store the integer log2 capacity, which
// at maximum is 63. Odin uses signed integers to represent length and capacity,
// so only 63 bits are needed in the maximum case.
#assert(MAP_CACHE_LINE_SIZE >= 64)
// Map_Cell type that packs multiple T in such a way to ensure that each T stays
// aligned by align_of(T) and such that align_of(Map_Cell(T)) % MAP_CACHE_LINE_SIZE == 0
//
// This means a value of type T will never straddle a cache-line.
//
// When multiple Ts can fit in a single cache-line the data array will have more
// than one element. When it cannot, the data array will have one element and
// an array of Map_Cell(T) will be padded to stay a multiple of MAP_CACHE_LINE_SIZE.
//
// We rely on the type system to do all the arithmetic and padding for us here.
//
// The usual array[index] indexing for []T backed by a []Map_Cell(T) becomes a bit
// more involved as there now may be internal padding. The indexing now becomes
//
// N :: len(Map_Cell(T){}.data)
// i := index / N
// j := index % N
// cell[i].data[j]
//
// However, since len(Map_Cell(T){}.data) is a compile-time constant, there are some
// optimizations we can do to eliminate the need for any divisions as N will
// be bounded by [1, 64).
//
// In the optimal case, len(Map_Cell(T){}.data) = 1 so the cell array can be treated
// as a regular array of T, which is the case for hashes.
Map_Cell :: struct($T: typeid) #align(MAP_CACHE_LINE_SIZE) {
data: [MAP_CACHE_LINE_SIZE / size_of(T) when 0 < size_of(T) && size_of(T) < MAP_CACHE_LINE_SIZE else 1]T,
}
// So we can operate on a cell data structure at runtime without any type
// information, we have a simple table that stores some traits about the cell.
//
// 32-bytes on 64-bit
// 16-bytes on 32-bit
Map_Cell_Info :: struct {
size_of_type: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
align_of_type: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
size_of_cell: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
elements_per_cell: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
}
Map_Hash :: uintptr
// When working with the type-erased structure at runtime we need information
// about the map to make working with it possible. This info structure stores
// that.
//
// `Map_Info` and `Map_Cell_Info` are read only data structures and cannot be
// modified after creation
//
// 32-bytes on 64-bit
// 16-bytes on 32-bit
Map_Info :: struct {
ks: ^Map_Cell_Info, // 8-bytes on 64-bit, 4-bytes on 32-bit
vs: ^Map_Cell_Info, // 8-bytes on 64-bit, 4-bytes on 32-bit
key_hasher: proc "contextless" (key: rawptr, seed: Map_Hash) -> Map_Hash, // 8-bytes on 64-bit, 4-bytes on 32-bit
key_equal: proc "contextless" (lhs, rhs: rawptr) -> bool, // 8-bytes on 64-bit, 4-bytes on 32-bit
}
Source_Code_Location :: struct {
@@ -397,6 +510,11 @@ Raw_String :: struct {
len: int,
}
Raw_String16 :: struct {
data: [^]u16,
len: int,
}
Raw_Slice :: struct {
data: rawptr,
len: int,
@@ -409,6 +527,11 @@ Raw_Dynamic_Array :: struct {
allocator: Allocator,
}
Raw_Fixed_Capacity_Dynamic_Array :: struct($Capacity: uint, $T: typeid) {
data: [Capacity]T,
len: int,
}
// The raw, type-erased representation of a map.
//
// 32-bytes on 64-bit
@@ -450,6 +573,12 @@ Raw_Cstring :: struct {
}
#assert(size_of(Raw_Cstring) == size_of(cstring))
Raw_Cstring16 :: struct {
data: [^]u16,
}
#assert(size_of(Raw_Cstring16) == size_of(cstring16))
Raw_Soa_Pointer :: struct {
data: rawptr,
index: int,
@@ -473,11 +602,9 @@ Raw_Quaternion256_Vector_Scalar :: struct {vector: [3]f64, scalar: f64}
Windows,
Darwin,
Linux,
Essence,
FreeBSD,
OpenBSD,
NetBSD,
Haiku,
WASI,
JS,
Orca,
@@ -529,7 +656,6 @@ Odin_Build_Mode_Type :: type_of(ODIN_BUILD_MODE)
/*
// Defined internally by the compiler
Odin_Endian_Type :: enum int {
Unknown,
Little,
Big,
}
@@ -542,11 +668,9 @@ ALL_ODIN_OS_TYPES :: Odin_OS_Types{
.Windows,
.Darwin,
.Linux,
.Essence,
.FreeBSD,
.OpenBSD,
.NetBSD,
.Haiku,
.WASI,
.JS,
.Orca,
@@ -557,7 +681,8 @@ ALL_ODIN_OS_TYPES :: Odin_OS_Types{
// Defined internally by the compiler
Odin_Platform_Subtarget_Type :: enum int {
Default,
iOS,
iPhone,
iPhoneSimulator
Android,
}
*/
@@ -565,6 +690,8 @@ Odin_Platform_Subtarget_Type :: type_of(ODIN_PLATFORM_SUBTARGET)
Odin_Platform_Subtarget_Types :: bit_set[Odin_Platform_Subtarget_Type]
@(builtin)
ODIN_PLATFORM_SUBTARGET_IOS :: ODIN_PLATFORM_SUBTARGET == .iPhone || ODIN_PLATFORM_SUBTARGET == .iPhoneSimulator
/*
// Defined internally by the compiler
@@ -617,6 +744,8 @@ _cleanup_runtime_contextless :: proc "contextless" () {
/////////////////////////////
// type_info_base returns the base-type of a `^Type_Info` stripping the `distinct`ness from the first level
@(require_results)
type_info_base :: proc "contextless" (info: ^Type_Info) -> ^Type_Info {
if info == nil {
return nil
@@ -632,7 +761,10 @@ type_info_base :: proc "contextless" (info: ^Type_Info) -> ^Type_Info {
return base
}
// type_info_core returns the core-type of a `^Type_Info` stripping the `distinct`ness from the first level AND/OR
// returns the backing integer type of an enum `^Type_Info`.
// This is also aliased as `type_info_base_without_enum`
@(require_results)
type_info_core :: proc "contextless" (info: ^Type_Info) -> ^Type_Info {
if info == nil {
return nil
@@ -649,8 +781,36 @@ type_info_core :: proc "contextless" (info: ^Type_Info) -> ^Type_Info {
}
return base
}
// type_info_underlying returns the underlying (backing) type of a `^Type_Info` stripping the `distinct`ness from the first level AND/OR
// returns the backing integer type of an enum `^Type_Info` AND/OR the underlying integer type of a bit_set or bit_field.
@(require_results)
type_info_underlying :: proc "contextless" (info: ^Type_Info) -> ^Type_Info {
if info == nil {
return nil
}
base := info
loop: for {
#partial switch i in base.variant {
case Type_Info_Named: base = i.base
case Type_Info_Enum: base = i.base
case Type_Info_Bit_Set: base = i.underlying
case Type_Info_Bit_Field: base = i.backing_type
case: break loop
}
}
return base
}
// `type_info_base_without_enum` returns the core-type of a `^Type_Info` stripping the `distinct`ness from the first level AND/OR
// returns the backing integer type of an enum or bit_set `^Type_Info`.
// This is also aliased as `type_info_core`
type_info_base_without_enum :: type_info_core
@(require_results)
__type_info_of :: proc "contextless" (id: typeid) -> ^Type_Info #no_bounds_check {
n := u64(len(type_table))
i := transmute(u64)id % n
@@ -665,16 +825,32 @@ __type_info_of :: proc "contextless" (id: typeid) -> ^Type_Info #no_bounds_check
}
when !ODIN_NO_RTTI {
// typeid_base returns the base-type of a `typeid` stripping the `distinct`ness from the first level
@(require_results)
typeid_base :: proc "contextless" (id: typeid) -> typeid {
ti := type_info_of(id)
ti = type_info_base(ti)
return ti.id
}
// typeid_core returns the core-type of a `typeid` stripping the `distinct`ness from the first level AND/OR
// returns the backing integer type of an enum `typeid`.
// This is also aliased as `typeid_base_without_enum`
@(require_results)
typeid_core :: proc "contextless" (id: typeid) -> typeid {
ti := type_info_core(type_info_of(id))
return ti.id
}
// typeid_base_without_enum returns the core-type of a `typeid` stripping the `distinct`ness from the first level AND/OR
// returns the backing integer type of an enum or bit_set `typeid`.
// This is also aliased as `typeid_core`
typeid_base_without_enum :: typeid_core
@(require_results)
typeid_underlying :: proc "contextless" (id: typeid) -> typeid {
ti := type_info_underlying(type_info_of(id))
return ti.id
}
}
@@ -689,11 +865,15 @@ default_logger_proc :: proc(data: rawptr, level: Logger_Level, text: string, opt
// Nothing
}
// Returns the default logger used by `context.logger`
@(require_results)
default_logger :: proc() -> Logger {
return Logger{default_logger_proc, nil, Logger_Level.Debug, nil}
}
// Returns the default `context`
@(require_results)
default_context :: proc "contextless" () -> Context {
c: Context
__init_context(&c)

File diff suppressed because it is too large Load Diff

View File

@@ -105,8 +105,8 @@ make_soa_aligned :: proc($T: typeid/#soa[]$E, #any_int length, alignment: int, a
allocator := allocator
if allocator.procedure == nil {
allocator = context.allocator
assert(allocator.procedure != nil)
}
assert(allocator.procedure != nil)
new_bytes: []byte
new_bytes, err = allocator.procedure(
@@ -178,8 +178,31 @@ resize_soa :: proc(array: ^$T/#soa[dynamic]$E, #any_int length: int, loc := #cal
if array == nil {
return nil
}
reserve_soa(array, length, loc) or_return
footer := raw_soa_footer(array)
if length > footer.cap {
reserve_soa(array, length, loc) or_return
} else if size_of(E) > 0 && length > footer.len {
ti := type_info_base(type_info_of(typeid_of(T)))
si := &ti.variant.(Type_Info_Struct)
field_count := len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)
data := (^rawptr)(array)^
soa_offset := 0
for i in 0..<field_count {
type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
soa_offset = align_forward_int(soa_offset, align_of(E))
mem_zero(rawptr(uintptr(data) + uintptr(soa_offset) + uintptr(type.size * footer.len)), type.size * (length - footer.len))
soa_offset += type.size * footer.cap
}
}
footer.len = length
return nil
}
@@ -217,8 +240,8 @@ _reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, zero_memory: boo
if array.allocator.procedure == nil {
array.allocator = context.allocator
assert(array.allocator.procedure != nil)
}
assert(array.allocator.procedure != nil)
footer := raw_soa_footer(array)
if size_of(E) == 0 {
@@ -249,17 +272,77 @@ _reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, zero_memory: boo
old_data := (^rawptr)(array)^
resize: if old_data != nil {
new_bytes, resize_err := array.allocator.procedure(
array.allocator.data, .Resize_Non_Zeroed, new_size, max_align,
old_data, old_size, loc,
)
new_data := raw_data(new_bytes)
#partial switch resize_err {
case .Mode_Not_Implemented: break resize
case .None: // continue resizing
case: return resize_err
}
footer.cap = capacity
old_offset := 0
new_offset := 0
// Correct data memory
// from: |x x y y z z _ _ _|
// to: |x x _ y y _ z z _|
// move old data to the end of the new allocation to avoid overlap
old_data = rawptr(uintptr(new_data) + uintptr(new_size - old_size))
mem_copy(old_data, new_data, old_size)
// now: |_ _ _ x x y y z z|
for i in 0..<field_count {
type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
old_offset = align_forward_int(old_offset, max_align)
new_offset = align_forward_int(new_offset, max_align)
new_data_elem := rawptr(uintptr(new_data) + uintptr(new_offset))
old_data_elem := rawptr(uintptr(old_data) + uintptr(old_offset))
old_size_elem := type.size * old_cap
new_size_elem := type.size * capacity
mem_copy(new_data_elem, old_data_elem, old_size_elem)
(^rawptr)(uintptr(array) + i*size_of(rawptr))^ = new_data_elem
if zero_memory {
mem_zero(rawptr(uintptr(new_data_elem) + uintptr(old_size_elem)), new_size_elem - old_size_elem)
}
old_offset += old_size_elem
new_offset += new_size_elem
}
return nil
}
new_bytes := array.allocator.procedure(
array.allocator.data, .Alloc if zero_memory else .Alloc_Non_Zeroed, new_size, max_align,
nil, old_size, loc,
) or_return
new_data := raw_data(new_bytes)
footer.cap = capacity
old_offset := 0
new_offset := 0
// Correct data memory
// from: |x x y y z z| ... |_ _ _ _ _ _ _ _ _|
// to: |x x _ y y _ z z _|
for i in 0..<field_count {
type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
@@ -277,10 +360,12 @@ _reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, zero_memory: boo
new_offset += type.size * capacity
}
array.allocator.procedure(
array.allocator.data, .Free, 0, max_align,
old_data, old_size, loc,
) or_return
if old_data != nil {
array.allocator.procedure(
array.allocator.data, .Free, 0, max_align,
old_data, old_size, loc,
) or_return
}
return nil
}
@@ -416,6 +501,121 @@ append_soa :: proc{
}
// `append_nothing_soa` appends an empty value to a dynamic SOA array. It returns `1, nil` if successful, and `0, err` when it was not possible,
// whatever `err` happens to be.
@builtin
append_nothing_soa :: proc(array: ^$T/#soa[dynamic]$E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
if array == nil {
return 0, nil
}
prev_len := len(array)
resize_soa(array, len(array)+1, loc) or_return
return len(array)-prev_len, nil
}
// `inject_at_elem_soa` injects an element in a dynamic SOA array at a specified index and moves the previous elements after that index "across"
@builtin
inject_at_elem_soa :: proc(array: ^$T/#soa[dynamic]$E, #any_int index: int, #no_broadcast arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
when !ODIN_NO_BOUNDS_CHECK {
ensure(index >= 0, "Index must be positive.", loc)
}
if array == nil {
return
}
n := max(len(array), index)
m :: 1
new_len := n + m
resize_soa(array, new_len, loc) or_return
when size_of(E) != 0 {
ti := type_info_base(type_info_of(typeid_of(T)))
si := &ti.variant.(Type_Info_Struct)
field_count := len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)
item_offset := 0
arg_copy := arg
arg_ptr := &arg_copy
for i in 0..<field_count {
data := (^uintptr)(uintptr(array) + uintptr(si.offsets[i]))^
type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
item_offset = align_forward_int(item_offset, type.align)
src := data + uintptr(index * type.size)
dst := data + uintptr((index + m) * type.size)
mem_copy(rawptr(dst), rawptr(src), (n - index) * type.size)
mem_copy(rawptr(src), rawptr(uintptr(arg_ptr) + uintptr(item_offset)), type.size)
item_offset += type.size
}
}
ok = true
return
}
// `inject_at_elems_soa` injects multiple elements in a dynamic SOA array at a specified index and moves the previous elements after that index "across"
@builtin
inject_at_elems_soa :: proc(array: ^$T/#soa[dynamic]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
when !ODIN_NO_BOUNDS_CHECK {
ensure(index >= 0, "Index must be positive.", loc)
}
if array == nil {
return
}
if len(args) == 0 {
ok = true
return
}
n := max(len(array), index)
m := len(args)
new_len := n + m
resize_soa(array, new_len, loc) or_return
when size_of(E) != 0 {
ti := type_info_base(type_info_of(typeid_of(T)))
si := &ti.variant.(Type_Info_Struct)
field_count := len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)
item_offset := 0
args_ptr := &args[0]
for i in 0..<field_count {
data := (^uintptr)(uintptr(array) + uintptr(si.offsets[i]))^
type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
item_offset = align_forward_int(item_offset, type.align)
src := data + uintptr(index * type.size)
dst := data + uintptr((index + m) * type.size)
mem_copy(rawptr(dst), rawptr(src), (n - index) * type.size)
for j in 0..<len(args) {
d := rawptr(src + uintptr(j*type.size))
s := rawptr(uintptr(args_ptr) + uintptr(item_offset) + uintptr(j*size_of(E)))
mem_copy(d, s, type.size)
}
item_offset += type.size
}
}
ok = true
return
}
// `inject_at_soa` injects something into a dynamic SOA array at a specified index and moves the previous elements after that index "across"
@builtin inject_at_soa :: proc{inject_at_elem_soa, inject_at_elems_soa}
@builtin
delete_soa_slice :: proc(array: $T/#soa[]$E, allocator := context.allocator, loc := #caller_location) -> Allocator_Error {
field_count :: len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)
when field_count != 0 {
@@ -426,6 +626,7 @@ delete_soa_slice :: proc(array: $T/#soa[]$E, allocator := context.allocator, loc
return nil
}
@builtin
delete_soa_dynamic_array :: proc(array: $T/#soa[dynamic]$E, loc := #caller_location) -> Allocator_Error {
field_count :: len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)
when field_count != 0 {
@@ -444,7 +645,7 @@ delete_soa :: proc{
delete_soa_dynamic_array,
}
@builtin
clear_soa_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) {
field_count :: len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)
when field_count != 0 {

View File

@@ -23,7 +23,16 @@ nil_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
return nil, .None
}
nil_allocator :: proc() -> Allocator {
// nil_allocator returns an allocator which will return `nil` for any result.
// * `.Alloc`, `.Alloc_Non_Zero`, `.Resize`, `.Resize_Non_Zeroed` will return `nil, .Out_Of_Memory`
// * `.Free` will return `nil, .None`
// * `.Free_All` will return `nil, .Mode_Not_Implemented`
// * `.Query_Features`, `.Query_Info` will return `nil, .Mode_Not_Implemented`
//
// This is extremely useful for creating a dynamic array from a buffer which does not nothing
// on a resize/reserve beyond the originally allocated memory.
@(require_results)
nil_allocator :: proc "contextless" () -> Allocator {
return Allocator{
procedure = nil_allocator_proc,
data = nil,
@@ -72,6 +81,10 @@ panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
return nil, nil
}
// panic_allocator returns an allocator which will panic for any non-zero-sized allocation or `query_info`
//
// This is extremely useful for to check when something does a memory operation when it should not, and thus panic.
@(require_results)
panic_allocator :: proc() -> Allocator {
return Allocator{
procedure = panic_allocator_proc,

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)
@@ -52,10 +52,13 @@ memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint
return
}
memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) {
memory_block_dealloc :: proc "contextless" (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)
context = default_context()
context.allocator = allocator
mem_free(block_to_free, allocator, loc)
}
}
@@ -87,22 +90,13 @@ 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
}
@(require_results)
arena_alloc :: proc(arena: ^Arena, size, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
align_forward_uint :: proc "contextless" (ptr, align: uint) -> uint {
p := ptr
modulo := p & (align-1)
if modulo != 0 {
p += align - modulo
}
return p
}
assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc)
size := size
@@ -167,12 +161,12 @@ 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
}
arena_destroy :: proc(arena: ^Arena, loc := #caller_location) {
arena_destroy :: proc "contextless" (arena: ^Arena, loc := #caller_location) {
for arena.curr_block != nil {
free_block := arena.curr_block
arena.curr_block = free_block.prev
@@ -184,6 +178,7 @@ arena_destroy :: proc(arena: ^Arena, loc := #caller_location) {
arena.total_capacity = 0
}
@(require_results)
arena_allocator :: proc(arena: ^Arena) -> Allocator {
return Allocator{arena_allocator_proc, arena}
}
@@ -231,8 +226,9 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
if start < old_end && old_end == block.used && new_end <= block.capacity {
// grow data in-place, adjusting next allocation
block.used = uint(new_end)
arena.total_used = uint(new_end)
data = block.base[start:new_end]
sanitizer.address_unpoison(data)
// sanitizer.address_unpoison(data)
return
}
}
@@ -306,7 +302,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

@@ -4,11 +4,12 @@ DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE: int : #config(DEFAULT_TEMP_ALLOCATOR_BACKIN
NO_DEFAULT_TEMP_ALLOCATOR: bool : ODIN_OS == .Freestanding || ODIN_DEFAULT_TO_NIL_ALLOCATOR
when NO_DEFAULT_TEMP_ALLOCATOR {
// `Default_Temp_Allocator` is a `nil_allocator` when `NO_DEFAULT_TEMP_ALLOCATOR` is `true`.
Default_Temp_Allocator :: struct {}
default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) {}
default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator: Allocator) {}
default_temp_allocator_destroy :: proc(s: ^Default_Temp_Allocator) {}
default_temp_allocator_destroy :: proc "contextless" (s: ^Default_Temp_Allocator) {}
default_temp_allocator_proc :: nil_allocator_proc
@@ -20,15 +21,20 @@ when NO_DEFAULT_TEMP_ALLOCATOR {
default_temp_allocator_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
}
} else {
// `Default_Temp_Allocator` is an `Arena` based type of allocator. See `runtime.Arena` for its implementation.
// The default `context.temp_allocator` is typically called with `free_all(context.temp_allocator)` once per "frame-loop"
// to prevent it from "leaking" memory.
//
// Note: `Default_Temp_Allocator` is a `nil_allocator` when `NO_DEFAULT_TEMP_ALLOCATOR` is `true`.
Default_Temp_Allocator :: struct {
arena: Arena,
}
default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator := context.allocator) {
default_temp_allocator_init :: proc(s: ^Default_Temp_Allocator, size: int, backing_allocator: Allocator) {
_ = arena_init(&s.arena, uint(size), backing_allocator)
}
default_temp_allocator_destroy :: proc(s: ^Default_Temp_Allocator) {
default_temp_allocator_destroy :: proc "contextless" (s: ^Default_Temp_Allocator) {
if s != nil {
arena_destroy(&s.arena)
s^ = {}
@@ -56,7 +62,7 @@ when NO_DEFAULT_TEMP_ALLOCATOR {
}
@(fini, private)
_destroy_temp_allocator_fini :: proc() {
_destroy_temp_allocator_fini :: proc "contextless" () {
default_temp_allocator_destroy(&global_default_temp_allocator_data)
}
}
@@ -70,7 +76,7 @@ DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD :: #force_inline proc(ignore := false, loc :=
}
}
@(require_results)
default_temp_allocator :: proc(allocator: ^Default_Temp_Allocator) -> Allocator {
return Allocator{
procedure = default_temp_allocator_proc,

244
base/runtime/doc.odin Normal file
View File

@@ -0,0 +1,244 @@
/*
Declarations which are required by the compiler
## Descriptions of files
There are a lot of files in this package and below is described roughly what
kind of functionality is placed in different files:
| File pattern | Description
|----------------------|------------------------------------------------------|
| `core.odin` | Contains the declarations that compiler will require to be present. Contains context-related declarations, `Type_Info` declarations and some other types used to implement the runtime and other packages. |
| `core_builtin*.odin` | Contain `@(builtin)` declarations that can be used without importing the package. Most of them aren't required by the compiler |
| `default_*.odin` | Contain default implementations for context allocators |
| `entry_*.odin` | Contain OS-specific entry points |
| `os_specific_*.odin` | Contain OS-specific utility procedures |
| `*internal*.odin` | Contain implementations for internal procedures that can be called by the compiler |
## Implementing custom runtime
For embedded and kernel development it might be required to re-implement parts
of the `base:runtime` package. This can include changing the default printing
procedures that handle console output when the program panics, custom
entry-points, tailored for a specific platform or execution environment, or
simply switching up implementations of some procedures.
In case this is required, the following is suggested:
1. Define `$ODIN_ROOT` environment variable to point to a directory within your
project that contains the following directories: `base/`, `core/` and `vendor/`.
2. Inside the `$ODIN_ROOT/base` subdirectory, implement the *necessary
declarations*.
What constitutes the necessary definitions is described below.
### Context-related
The compiler will require these declarations as they concern the `context`
variable.
* `Maybe`
* `Source_Code_Location`
* `Context`
* `Allocator`
* `Random_Generator`
* `Logger`
* `__init_context`
### Runtime initialization/cleanup
These are not strictly required for compilation, but if global variables or
`@(init)`/`@(fini)` blocks are used, these procedures need to be called inside
the entry point.
* `_startup_runtime`
* `_cleanup_runtime`
### Type assertion check
These procedures are called every time `.(Type)` expressions are used in order
to check the union tag or the underlying type of `any` before returning the
value of the underlying type. These are not required if `-no-type-assert` is
specified.
* `type_assertion_check`
* `type_assertion_check2` (takes in typeid)
### Bounds checking procedures
These procedures are called every time index or slicing expression are used in
order to perform bounds-checking before the actual operation. These are not
required if the `-no-bounds-check` option is specified.
* `bounds_check_error`
* `matrix_bounds_check_error`
* `slice_expr_error_hi`
* `slice_expr_error_lo_hi`
* `multi_pointer_slice_expr_error`
### cstring calls
If `cstring` or `cstring16` types are used, these procedures are required.
* `cstring_to_string`
* `cstring_len`
* `cstring16_to_string16`
* `cstring16_len`
### Comparison
These procedures are required for comparison operators between strings and other
compound types to function properly. If strings, structs nor unions are compared,
only `string_eq` procedure is required.
* `memory_equal`
* `memory_compare`
* `memory_compare_zero`
* `cstring_eq`
* `cstring16_eq`
* `cstring_ne`
* `cstring16_ne`
* `cstring_lt`
* `cstring16_lt`
* `cstring_gt`
* `cstring16_gt`
* `cstring_le`
* `cstring16_le`
* `cstring_ge`
* `cstring16_ge`
* `string_eq`
* `string16_eq`
* `string_ne`
* `string16_ne`
* `string_lt`
* `string16_lt`
* `string_gt`
* `string16_gt`
* `string_le`
* `string16_le`
* `string_ge`
* `string16_ge`
* `complex32_eq`
* `complex32_ne`
* `complex64_eq`
* `complex64_ne`
* `complex128_eq`
* `complex128_ne`
* `quaternion64_eq`
* `quaternion64_ne`
* `quaternion128_eq`
* `quaternion128_ne`
* `quaternion256_eq`
* `quaternion256_ne`
### for-in `string` type
These procedures are required to iterate strings using `for ... in` loop. If this
kind of loop isn't used, these procedures aren't required.
* `string_decode_rune`
* `string_decode_last_rune` (for `#reverse for`)
### Required when RTTI is enabled (the vast majority of targets)
These declarations are required unless the `-no-rtti` compiler option is
specified. Note that in order to be useful, some other procedures need to be
implemented. Those procedures aren't mentioned here as the compiler won't
complain if they're missing.
* `Type_Info`
* `type_table`
* `__type_info_of`
### Hashing
Required if maps are used
* `default_hasher`
* `default_hasher_cstring`
* `default_hasher_string`
### Pseudo-CRT required procedured due to LLVM but useful in general
* `memset`
* `memcpy`
* `memove`
### Procedures required by the LLVM backend if u128/i128 is used
* `umodti3`
* `udivti3`
* `modti3`
* `divti3`
* `fixdfti`
* `fixunsdfti`
* `fixunsdfdi`
* `floattidf`
* `floattidf_unsigned`
* `truncsfhf2`
* `truncdfhf2`
* `gnu_h2f_ieee`
* `gnu_f2h_ieee`
* `extendhfsf2`
### Procedures required by the LLVM backend if f16 is used (WASM only)
* `__ashlti3`
* `__multi3`
### When -no-crt is defined (windows only)
* `_tls_index`
* `_fltused`
### Arithmetic
* `quo_complex32`
* `quo_complex64`
* `quo_complex128`
* `mul_quaternion64`
* `mul_quaternion128`
* `mul_quaternion256`
* `quo_quaternion64`
* `quo_quaternion128`
* `quo_quaternion256`
* `abs_complex32`
* `abs_complex64`
* `abs_complex128`
* `abs_quaternion64`
* `abs_quaternion128`
* `abs_quaternion256`
## Map specific calls
* `map_seed_from_map_data`
* `__dynamic_map_check_grow` (for static map calls)
* `map_insert_hash_dynamic` (for static map calls)
* `__dynamic_map_get` (for dynamic map calls)
* `__dynamic_map_set` (for dynamic map calls)
## Dynamic literals (`[dynamic]T` and `map[K]V`) (can be disabled with `-no-dynamic-literals`)
* `__dynamic_array_reserve`
* `__dynamic_array_append`
* `__dynamic_map_reserve`
### Objective-C specific
* `objc_lookUpClass`
* `sel_registerName`
* `objc_allocateClassPair`
### Other required declarations
This is required without conditions.
* `Load_Directory_File`
*/
package runtime

View File

@@ -1,180 +0,0 @@
package runtime
/*
package runtime has numerous entities (declarations) which are required by the compiler to function.
## Basic types and calls (and anything they rely on)
Source_Code_Location
Context
Allocator
Logger
__init_context
_cleanup_runtime
## cstring calls
cstring_to_string
cstring_len
## Required when RTTI is enabled (the vast majority of targets)
Type_Info
type_table
__type_info_of
## Hashing
default_hasher
default_hasher_cstring
default_hasher_string
## Pseudo-CRT required procedured due to LLVM but useful in general
memset
memcpy
memove
## Procedures required by the LLVM backend if u128/i128 is used
umodti3
udivti3
modti3
divti3
fixdfti
fixunsdfti
fixunsdfdi
floattidf
floattidf_unsigned
truncsfhf2
truncdfhf2
gnu_h2f_ieee
gnu_f2h_ieee
extendhfsf2
## Procedures required by the LLVM backend if f16 is used
__ashlti3 // wasm specific
__multi3 // wasm specific
## Required an entry point is defined (i.e. 'main')
args__
## When -no-crt is defined (and not a wasm target) (mostly due to LLVM)
_tls_index
_fltused
## Bounds checking procedures (when not disabled with -no-bounds-check)
bounds_check_error
matrix_bounds_check_error
slice_expr_error_hi
slice_expr_error_lo_hi
multi_pointer_slice_expr_error
## Type assertion check
type_assertion_check
type_assertion_check2 // takes in typeid
## Arithmetic
quo_complex32
quo_complex64
quo_complex128
mul_quaternion64
mul_quaternion128
mul_quaternion256
quo_quaternion64
quo_quaternion128
quo_quaternion256
abs_complex32
abs_complex64
abs_complex128
abs_quaternion64
abs_quaternion128
abs_quaternion256
## Comparison
memory_equal
memory_compare
memory_compare_zero
cstring_eq
cstring_ne
cstring_lt
cstring_gt
cstring_le
cstring_gt
string_eq
string_ne
string_lt
string_gt
string_le
string_gt
complex32_eq
complex32_ne
complex64_eq
complex64_ne
complex128_eq
complex128_ne
quaternion64_eq
quaternion64_ne
quaternion128_eq
quaternion128_ne
quaternion256_eq
quaternion256_ne
## Map specific calls
map_seed_from_map_data
__dynamic_map_check_grow // static map calls
map_insert_hash_dynamic // static map calls
__dynamic_map_get // dynamic map calls
__dynamic_map_set // dynamic map calls
## Dynamic literals ([dynamic]T and map[K]V) (can be disabled with -no-dynamic-literals)
__dynamic_array_reserve
__dynamic_array_append
__dynamic_map_reserve
## Objective-C specific
objc_lookUpClass
sel_registerName
objc_allocateClassPair
## for-in `string` type
string_decode_rune
string_decode_last_rune // #reverse for
*/

View File

@@ -18,8 +18,8 @@ __dynamic_array_reserve :: proc(array_: rawptr, elem_size, elem_align: int, cap:
// assuming that appending/reserving will set the allocator, if it is not already set.
if array.allocator.procedure == nil {
array.allocator = context.allocator
assert(array.allocator.procedure != nil)
}
assert(array.allocator.procedure != nil)
if cap <= array.cap {
return true
@@ -52,8 +52,8 @@ __dynamic_array_shrink :: proc(array_: rawptr, elem_size, elem_align: int, new_c
// assuming that appending/reserving will set the allocator, if it is not already set.
if array.allocator.procedure == nil {
array.allocator = context.allocator
assert(array.allocator.procedure != nil)
}
assert(array.allocator.procedure != nil)
if new_cap > array.cap {
return

View File

@@ -1,3 +1,4 @@
#+build !bedrock
package runtime
import "base:intrinsics"
@@ -6,8 +7,6 @@ _ :: intrinsics
// High performance, cache-friendly, open-addressed Robin Hood hashing hash map
// data structure with various optimizations for Odin.
//
// Copyright 2022 (c) Dale Weiler
//
// The core of the hash map data structure is the Raw_Map struct which is a
// type-erased representation of the map. This type-erased representation is
// used in two ways: static and dynamic. When static type information is known,
@@ -49,60 +48,6 @@ MAP_MIN_LOG2_CAPACITY :: 3 // 8 elements
// Has to be less than 100% though.
#assert(MAP_LOAD_FACTOR < 100)
// This is safe to change. The log2 size of a cache-line. At minimum it has to
// be six though. Higher cache line sizes are permitted.
MAP_CACHE_LINE_LOG2 :: 6
// The size of a cache-line.
MAP_CACHE_LINE_SIZE :: 1 << MAP_CACHE_LINE_LOG2
// The minimum cache-line size allowed by this implementation is 64 bytes since
// we need 6 bits in the base pointer to store the integer log2 capacity, which
// at maximum is 63. Odin uses signed integers to represent length and capacity,
// so only 63 bits are needed in the maximum case.
#assert(MAP_CACHE_LINE_SIZE >= 64)
// Map_Cell type that packs multiple T in such a way to ensure that each T stays
// aligned by align_of(T) and such that align_of(Map_Cell(T)) % MAP_CACHE_LINE_SIZE == 0
//
// This means a value of type T will never straddle a cache-line.
//
// When multiple Ts can fit in a single cache-line the data array will have more
// than one element. When it cannot, the data array will have one element and
// an array of Map_Cell(T) will be padded to stay a multiple of MAP_CACHE_LINE_SIZE.
//
// We rely on the type system to do all the arithmetic and padding for us here.
//
// The usual array[index] indexing for []T backed by a []Map_Cell(T) becomes a bit
// more involved as there now may be internal padding. The indexing now becomes
//
// N :: len(Map_Cell(T){}.data)
// i := index / N
// j := index % N
// cell[i].data[j]
//
// However, since len(Map_Cell(T){}.data) is a compile-time constant, there are some
// optimizations we can do to eliminate the need for any divisions as N will
// be bounded by [1, 64).
//
// In the optimal case, len(Map_Cell(T){}.data) = 1 so the cell array can be treated
// as a regular array of T, which is the case for hashes.
Map_Cell :: struct($T: typeid) #align(MAP_CACHE_LINE_SIZE) {
data: [MAP_CACHE_LINE_SIZE / size_of(T) when 0 < size_of(T) && size_of(T) < MAP_CACHE_LINE_SIZE else 1]T,
}
// So we can operate on a cell data structure at runtime without any type
// information, we have a simple table that stores some traits about the cell.
//
// 32-bytes on 64-bit
// 16-bytes on 32-bit
Map_Cell_Info :: struct {
size_of_type: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
align_of_type: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
size_of_cell: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
elements_per_cell: uintptr, // 8-bytes on 64-bit, 4-bytes on 32-bits
}
// map_cell_info :: proc "contextless" ($T: typeid) -> ^Map_Cell_Info {...}
map_cell_info :: intrinsics.type_map_cell_info
@@ -228,8 +173,6 @@ map_data :: #force_inline proc "contextless" (m: Raw_Map) -> uintptr {
}
Map_Hash :: uintptr
TOMBSTONE_MASK :: 1<<(size_of(Map_Hash)*8 - 1)
// Procedure to check if a slot is empty for a given hash. This is represented
@@ -285,26 +228,11 @@ map_desired_position :: #force_inline proc "contextless" (m: Raw_Map, hash: Map_
map_probe_distance :: #force_inline proc "contextless" (m: Raw_Map, hash: Map_Hash, slot: uintptr) -> uintptr {
// We do not use map_cap since we know the capacity will not be zero here.
capacity := uintptr(1) << map_log2_cap(m)
return (slot + capacity - map_desired_position(m, hash)) & (capacity - 1)
}
// When working with the type-erased structure at runtime we need information
// about the map to make working with it possible. This info structure stores
// that.
//
// `Map_Info` and `Map_Cell_Info` are read only data structures and cannot be
// modified after creation
//
// 32-bytes on 64-bit
// 16-bytes on 32-bit
Map_Info :: struct {
ks: ^Map_Cell_Info, // 8-bytes on 64-bit, 4-bytes on 32-bit
vs: ^Map_Cell_Info, // 8-bytes on 64-bit, 4-bytes on 32-bit
key_hasher: proc "contextless" (key: rawptr, seed: Map_Hash) -> Map_Hash, // 8-bytes on 64-bit, 4-bytes on 32-bit
key_equal: proc "contextless" (lhs, rhs: rawptr) -> bool, // 8-bytes on 64-bit, 4-bytes on 32-bit
// return (slot + capacity - map_desired_position(m, hash)) & (capacity - 1)
return (slot - uintptr(hash)) & (capacity - 1) // NOTE(bill): this is equivalent to the above, but less operations
}
// The Map_Info structure is basically a pseudo-table of information for a given K and V pair.
// map_info :: proc "contextless" ($T: typeid/map[$K]$V) -> ^Map_Info {...}
map_info :: intrinsics.type_map_info
@@ -905,22 +833,22 @@ __dynamic_map_check_grow :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info:
return nil, false
}
__dynamic_map_set_without_hash :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, key, value: rawptr, loc := #caller_location) -> rawptr {
__dynamic_map_set_without_hash :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, key, value: rawptr, loc := #caller_location) -> (value_ptr: rawptr, err: Allocator_Error) #optional_allocator_error {
return __dynamic_map_set(m, info, info.key_hasher(key, map_seed(m^)), key, value, loc)
}
// IMPORTANT: USED WITHIN THE COMPILER
__dynamic_map_set :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, hash: Map_Hash, key, value: rawptr, loc := #caller_location) -> rawptr {
__dynamic_map_set :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, hash: Map_Hash, key, value: rawptr, loc := #caller_location) -> (value_ptr: rawptr, err: Allocator_Error) #optional_allocator_error {
if found := __dynamic_map_get(m, info, hash, key); found != nil {
intrinsics.mem_copy_non_overlapping(found, value, info.vs.size_of_type)
return found
return found, nil
}
hash := hash
err, has_grown := __dynamic_map_check_grow(m, info, loc)
if err != nil {
return nil
err_grow, has_grown := __dynamic_map_check_grow(m, info, loc)
if err_grow != nil {
return nil, err_grow
}
if has_grown {
hash = info.key_hasher(key, map_seed(m^))
@@ -930,7 +858,7 @@ __dynamic_map_set :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_In
if result != 0 {
m.len += 1
}
return rawptr(result)
return rawptr(result), nil
}
__dynamic_map_set_extra_without_hash :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, key, value: rawptr, loc := #caller_location) -> (prev_key_ptr, value_ptr: rawptr) {
return __dynamic_map_set_extra(m, info, info.key_hasher(key, map_seed(m^)), key, value, loc)
@@ -985,6 +913,9 @@ __dynamic_map_entry :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_
// IMPORTANT: USED WITHIN THE COMPILER
@(private)
__dynamic_map_reserve :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uint, loc := #caller_location) -> Allocator_Error {
if m == nil {
return nil
}
return map_reserve_dynamic(m, info, uintptr(new_capacity), loc)
}
@@ -1057,4 +988,4 @@ default_hasher_quaternion256 :: proc "contextless" (x, y, z, w: f64, seed: uintp
seed = default_hasher_f64(z, seed)
seed = default_hasher_f64(w, seed)
return seed
}
}

View File

@@ -1,5 +1,5 @@
#+private
#+build linux, darwin, freebsd, openbsd, netbsd, haiku
#+build linux, darwin, freebsd, openbsd, netbsd
#+no-instrumentation
package runtime
@@ -10,13 +10,15 @@ when ODIN_BUILD_MODE == .Dynamic {
_odin_entry_point :: proc "c" (argc: i32, argv: [^]cstring) {
args__ = argv[:argc]
context = default_context()
#force_no_inline _startup_runtime()
when !ODIN_BEDROCK { #force_no_inline _startup_runtime() }
intrinsics.__entry_point()
}
@(link_name="_odin_exit_point", linkage="strong", require/*, link_section=".fini"*/)
_odin_exit_point :: proc "c" () {
context = default_context()
#force_no_inline _cleanup_runtime()
when !ODIN_BEDROCK {
#force_no_inline _cleanup_runtime()
}
}
@(link_name="main", linkage="strong", require)
main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 {
@@ -43,9 +45,9 @@ when ODIN_BUILD_MODE == .Dynamic {
_start_odin :: proc "c" (argc: i32, argv: [^]cstring) -> ! {
args__ = argv[:argc]
context = default_context()
#force_no_inline _startup_runtime()
when !ODIN_BEDROCK { #force_no_inline _startup_runtime() }
intrinsics.__entry_point()
#force_no_inline _cleanup_runtime()
when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() }
intrinsics.syscall(SYS_exit, 0)
unreachable()
}
@@ -54,9 +56,9 @@ when ODIN_BUILD_MODE == .Dynamic {
main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 {
args__ = argv[:argc]
context = default_context()
#force_no_inline _startup_runtime()
when !ODIN_BEDROCK { #force_no_inline _startup_runtime() }
intrinsics.__entry_point()
#force_no_inline _cleanup_runtime()
when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() }
return 0
}
}

View File

@@ -3,6 +3,7 @@ bits 64
extern _start_odin
global _start
section .note.GNU-stack
section .text
;; Entry point for programs that specify -no-crt option
@@ -35,7 +36,7 @@ _start:
xor rbp, rbp
;; Load argc into 1st param reg, argv into 2nd param reg
pop rdi
mov rdx, rsi
mov rsi, rsp
;; Align stack pointer down to 16-bytes (sysv calling convention)
and rsp, -16
;; Call into odin entry point

View File

@@ -16,10 +16,10 @@ when ODIN_BUILD_MODE == .Dynamic {
switch dll_forward_reason {
case .Process_Attach:
#force_no_inline _startup_runtime()
when !ODIN_BEDROCK { #force_no_inline _startup_runtime() }
intrinsics.__entry_point()
case .Process_Detach:
#force_no_inline _cleanup_runtime()
when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() }
case .Thread_Attach:
break
case .Thread_Detach:
@@ -28,13 +28,25 @@ when ODIN_BUILD_MODE == .Dynamic {
return true
}
} else when !ODIN_TEST && !ODIN_NO_ENTRY_POINT {
when ODIN_ARCH == .i386 || ODIN_NO_CRT {
when ODIN_ARCH == .i386 && !ODIN_NO_CRT {
// Windows i386 with CRT: libcmt provides mainCRTStartup which calls _main
// Note: "c" calling convention adds underscore prefix automatically on i386
@(link_name="main", linkage="strong", require)
main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 {
args__ = argv[:argc]
context = default_context()
when !ODIN_BEDROCK { #force_no_inline _startup_runtime() }
intrinsics.__entry_point()
when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() }
return 0
}
} else when ODIN_NO_CRT {
@(link_name="mainCRTStartup", linkage="strong", require)
mainCRTStartup :: proc "system" () -> i32 {
context = default_context()
#force_no_inline _startup_runtime()
when !ODIN_BEDROCK { #force_no_inline _startup_runtime() }
intrinsics.__entry_point()
#force_no_inline _cleanup_runtime()
when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() }
return 0
}
} else {
@@ -42,9 +54,9 @@ when ODIN_BUILD_MODE == .Dynamic {
main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 {
args__ = argv[:argc]
context = default_context()
#force_no_inline _startup_runtime()
when !ODIN_BEDROCK { #force_no_inline _startup_runtime() }
intrinsics.__entry_point()
#force_no_inline _cleanup_runtime()
when !ODIN_BEDROCK { #force_no_inline _cleanup_runtime() }
return 0
}
}

View File

@@ -12,7 +12,7 @@ bounds_trap :: proc "contextless" () -> ! {
}
@(no_instrumentation)
type_assertion_trap :: proc "contextless" () -> ! {
type_assertion_trap_contextless :: proc "contextless" () -> ! {
when ODIN_OS == .Windows {
windows_trap_type_assertion()
} else when ODIN_OS == .Orca {
@@ -137,20 +137,22 @@ matrix_bounds_check_error :: proc "contextless" (file: string, line, column: i32
when ODIN_NO_RTTI {
type_assertion_check :: proc "contextless" (ok: bool, file: string, line, column: i32) {
type_assertion_check_with_context :: proc "odin" (ok: bool, file: string, line, column: i32) {
if ok {
return
}
@(cold, no_instrumentation)
handle_error :: proc "contextless" (file: string, line, column: i32) -> ! {
print_caller_location(Source_Code_Location{file, line, column, ""})
print_string(" Invalid type assertion\n")
type_assertion_trap()
handle_error :: proc "odin" (file: string, line, column: i32) -> ! {
p := context.assertion_failure_proc
if p == nil {
p = default_assertion_failure_proc
}
p("type assertion", "Invalid type assertion", Source_Code_Location{file, line, column, ""})
}
handle_error(file, line, column)
}
type_assertion_check2 :: proc "contextless" (ok: bool, file: string, line, column: i32) {
type_assertion_check_contextless :: proc "contextless" (ok: bool, file: string, line, column: i32) {
if ok {
return
}
@@ -158,12 +160,71 @@ when ODIN_NO_RTTI {
handle_error :: proc "contextless" (file: string, line, column: i32) -> ! {
print_caller_location(Source_Code_Location{file, line, column, ""})
print_string(" Invalid type assertion\n")
type_assertion_trap()
type_assertion_trap_contextless()
}
handle_error(file, line, column)
}
type_assertion_check2_with_context :: proc "odin" (ok: bool, file: string, line, column: i32) {
if ok {
return
}
@(cold, no_instrumentation)
handle_error :: proc "odin" (file: string, line, column: i32) -> ! {
p := context.assertion_failure_proc
if p == nil {
p = default_assertion_failure_proc
}
p("type assertion", "Invalid type assertion", Source_Code_Location{file, line, column, ""})
}
handle_error(file, line, column)
}
type_assertion_check2_contextless :: proc "contextless" (ok: bool, file: string, line, column: i32) {
if ok {
return
}
@(cold, no_instrumentation)
handle_error :: proc "contextless" (file: string, line, column: i32) -> ! {
print_caller_location(Source_Code_Location{file, line, column, ""})
print_string(" Invalid type assertion\n")
type_assertion_trap_contextless()
}
handle_error(file, line, column)
}
} else {
type_assertion_check :: proc "contextless" (ok: bool, file: string, line, column: i32, from, to: typeid) {
@(private="file")
TYPE_ASSERTION_BUFFER_SIZE :: 1024
type_assertion_check_with_context :: proc "odin" (ok: bool, file: string, line, column: i32, from, to: typeid) {
if ok {
return
}
@(cold, no_instrumentation)
handle_error :: proc "odin" (file: string, line, column: i32, from, to: typeid) -> ! {
do_msg :: proc "contextless" (i: ^int, buf: []byte, file: string, line, column: i32, from, to: typeid) -> bool {
write_string(i, buf, "Invalid type assertion from ") or_return
write_typeid(i, buf, from) or_return
write_string(i, buf, " to ") or_return
write_typeid(i, buf, to) or_return
return true
}
buf: [TYPE_ASSERTION_BUFFER_SIZE]byte
i := 0
_ = do_msg(&i, buf[:], file, line, column, from, to)
p := context.assertion_failure_proc
if p == nil {
p = default_assertion_failure_proc
}
p("type assertion", string(buf[:i]), Source_Code_Location{file, line, column, ""})
}
handle_error(file, line, column, from, to)
}
type_assertion_check_contextless :: proc "contextless" (ok: bool, file: string, line, column: i32, from, to: typeid) {
if ok {
return
}
@@ -175,47 +236,90 @@ when ODIN_NO_RTTI {
print_string(" to ")
print_typeid(to)
print_byte('\n')
type_assertion_trap()
type_assertion_trap_contextless()
}
handle_error(file, line, column, from, to)
}
type_assertion_check2 :: proc "contextless" (ok: bool, file: string, line, column: i32, from, to: typeid, from_data: rawptr) {
@(private="file")
type_assertion_variant_type :: proc "contextless" (id: typeid, data: rawptr) -> typeid {
if id == nil || data == nil {
return id
}
ti := type_info_base(type_info_of(id))
#partial switch v in ti.variant {
case Type_Info_Any:
return (^any)(data).id
case Type_Info_Union:
if v.tag_type == nil {
if (^rawptr)(data)^ == nil {
return nil
}
return v.variants[0].id
}
tag_ptr := uintptr(data) + v.tag_offset
idx := 0
switch v.tag_type.size {
case 1: idx = int( (^u8)(tag_ptr)^); if !v.no_nil { idx -= 1 }
case 2: idx = int( (^u16)(tag_ptr)^); if !v.no_nil { idx -= 1 }
case 4: idx = int( (^u32)(tag_ptr)^); if !v.no_nil { idx -= 1 }
case 8: idx = int( (^u64)(tag_ptr)^); if !v.no_nil { idx -= 1 }
case 16: idx = int((^u128)(tag_ptr)^); if !v.no_nil { idx -= 1 }
}
if idx < 0 {
return nil
} else if idx < len(v.variants) {
return v.variants[idx].id
}
}
return id
}
type_assertion_check2_with_context :: proc "odin" (ok: bool, file: string, line, column: i32, from, to: typeid, from_data: rawptr) {
if ok {
return
}
variant_type :: proc "contextless" (id: typeid, data: rawptr) -> typeid {
if id == nil || data == nil {
return id
}
ti := type_info_base(type_info_of(id))
#partial switch v in ti.variant {
case Type_Info_Any:
return (^any)(data).id
case Type_Info_Union:
tag_ptr := uintptr(data) + v.tag_offset
idx := 0
switch v.tag_type.size {
case 1: idx = int((^u8)(tag_ptr)^) - 1
case 2: idx = int((^u16)(tag_ptr)^) - 1
case 4: idx = int((^u32)(tag_ptr)^) - 1
case 8: idx = int((^u64)(tag_ptr)^) - 1
case 16: idx = int((^u128)(tag_ptr)^) - 1
}
if idx < 0 {
return nil
} else if idx < len(v.variants) {
return v.variants[idx].id
@(cold, no_instrumentation)
handle_error :: proc "odin" (file: string, line, column: i32, from, to: typeid, from_data: rawptr) -> ! {
do_msg :: proc "contextless" (i: ^int, buf: []byte, file: string, line, column: i32, from, to, actual: typeid) -> bool {
write_string(i, buf, "Invalid type assertion from ") or_return
write_typeid(i, buf, from) or_return
write_string(i, buf, " to ") or_return
write_typeid(i, buf, to) or_return
if actual != from {
write_string(i, buf, ", actual type: ") or_return
write_typeid(i, buf, actual) or_return
}
return true
}
return id
actual := type_assertion_variant_type(from, from_data)
buf: [TYPE_ASSERTION_BUFFER_SIZE]byte
i := 0
_ = do_msg(&i, buf[:], file, line, column, from, to, actual)
p := context.assertion_failure_proc
if p == nil {
p = default_assertion_failure_proc
}
p("type assertion", string(buf[:i]), Source_Code_Location{file, line, column, ""})
}
handle_error(file, line, column, from, to, from_data)
}
type_assertion_check2_contextless :: proc "contextless" (ok: bool, file: string, line, column: i32, from, to: typeid, from_data: rawptr) {
if ok {
return
}
@(cold, no_instrumentation)
handle_error :: proc "contextless" (file: string, line, column: i32, from, to: typeid, from_data: rawptr) -> ! {
actual := variant_type(from, from_data)
actual := type_assertion_variant_type(from, from_data)
print_caller_location(Source_Code_Location{file, line, column, ""})
print_string(" Invalid type assertion from ")
@@ -227,7 +331,7 @@ when ODIN_NO_RTTI {
print_typeid(actual)
}
print_byte('\n')
type_assertion_trap()
type_assertion_trap_contextless()
}
handle_error(file, line, column, from, to, from_data)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,217 @@
#+vet !cast
#+build !bedrock
package runtime
import "base:intrinsics"
@(private="file")
IS_WASM :: ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32
@(link_name="__floattidf", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
floattidf :: proc "c" (a: i128) -> f64 {
DBL_MANT_DIG :: 53
if a == 0 {
return 0.0
}
a := a
N :: size_of(i128) * 8
s := a >> (N-1)
a = (a ~ s) - s
sd: = N - intrinsics.count_leading_zeros(a) // number of significant digits
e := i32(sd - 1) // exponent
if sd > DBL_MANT_DIG {
switch sd {
case DBL_MANT_DIG + 1:
a <<= 1
case DBL_MANT_DIG + 2:
// okay
case:
a = i128(u128(a) >> u128(sd - (DBL_MANT_DIG+2))) |
i128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0)
}
a |= i128((a & 4) != 0)
a += 1
a >>= 2
if a & (i128(1) << DBL_MANT_DIG) != 0 {
a >>= 1
e += 1
}
} else {
a <<= u128(DBL_MANT_DIG - sd) & 127
}
fb: [2]u32
fb[1] = (u32(s) & 0x80000000) | // sign
(u32(e + 1023) << 20) | // exponent
u32((u64(a) >> 32) & 0x000FFFFF) // mantissa-high
fb[0] = u32(a) // mantissa-low
return transmute(f64)fb
}
@(link_name="__floattidf_unsigned", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
floattidf_unsigned :: proc "c" (a: u128) -> f64 {
DBL_MANT_DIG :: 53
if a == 0 {
return 0.0
}
a := a
N :: size_of(u128) * 8
sd: = N - intrinsics.count_leading_zeros(a) // number of significant digits
e := i32(sd - 1) // exponent
if sd > DBL_MANT_DIG {
switch sd {
case DBL_MANT_DIG + 1:
a <<= 1
case DBL_MANT_DIG + 2:
// okay
case:
a = u128(u128(a) >> u128(sd - (DBL_MANT_DIG+2))) |
u128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0)
}
a |= u128((a & 4) != 0)
a += 1
a >>= 2
if a & (1 << DBL_MANT_DIG) != 0 {
a >>= 1
e += 1
}
} else {
a <<= u128(DBL_MANT_DIG - sd)
}
fb: [2]u32
fb[1] = (0) | // sign
u32((e + 1023) << 20) | // exponent
u32((u64(a) >> 32) & 0x000FFFFF) // mantissa-high
fb[0] = u32(a) // mantissa-low
return transmute(f64)fb
}
@(link_name="__fixunsdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
fixunsdfti :: #force_no_inline proc "c" (a: f64) -> u128 {
// TODO(bill): implement `fixunsdfti` correctly
x := u64(a)
return u128(x)
}
@(link_name="__fixunsdfdi", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
fixunsdfdi :: #force_no_inline proc "c" (a: f64) -> i128 {
// TODO(bill): implement `fixunsdfdi` correctly
x := i64(a)
return i128(x)
}
@(link_name="__umodti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
umodti3 :: proc "c" (a, b: u128) -> u128 {
r: u128 = ---
_ = udivmod128(a, b, &r)
return r
}
@(link_name="__udivmodti4", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
udivmodti4 :: proc "c" (a, b: u128, rem: ^u128) -> u128 {
return udivmod128(a, b, rem)
}
when !IS_WASM {
@(link_name="__udivti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
udivti3 :: proc "c" (a, b: u128) -> u128 {
return udivmodti4(a, b, nil)
}
}
@(link_name="__modti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
modti3 :: proc "c" (a, b: i128) -> i128 {
s_a := a >> (128 - 1)
s_b := b >> (128 - 1)
an := (a ~ s_a) - s_a
bn := (b ~ s_b) - s_b
r: u128 = ---
_ = udivmod128(u128(an), u128(bn), &r)
return (i128(r) ~ s_a) - s_a
}
@(link_name="__divmodti4", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
divmodti4 :: proc "c" (a, b: i128, rem: ^i128) -> i128 {
s_a := a >> (128 - 1) // -1 if negative or 0
s_b := b >> (128 - 1)
an := (a ~ s_a) - s_a // absolute
bn := (b ~ s_b) - s_b
s_b ~= s_a // quotient sign
u_s_b := u128(s_b)
u_s_a := u128(s_a)
r: u128 = ---
u := i128((udivmodti4(u128(an), u128(bn), &r) ~ u_s_b) - u_s_b) // negate if negative
rem^ = i128((r ~ u_s_a) - u_s_a)
return u
}
@(link_name="__divti3", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
divti3 :: proc "c" (a, b: i128) -> i128 {
s_a := a >> (128 - 1) // -1 if negative or 0
s_b := b >> (128 - 1)
an := (a ~ s_a) - s_a // absolute
bn := (b ~ s_b) - s_b
s_a ~= s_b // quotient sign
u_s_a := u128(s_a)
return i128((udivmodti4(u128(an), u128(bn), nil) ~ u_s_a) - u_s_a) // negate if negative
}
@(link_name="__fixdfti", linkage=RUNTIME_LINKAGE, require=RUNTIME_REQUIRE)
fixdfti :: proc "c" (a: u64) -> i128 {
significandBits :: 52
typeWidth :: (size_of(u64)*8)
exponentBits :: (typeWidth - significandBits - 1)
maxExponent :: ((1 << exponentBits) - 1)
exponentBias :: (maxExponent >> 1)
implicitBit :: (u64(1) << significandBits)
significandMask :: (implicitBit - 1)
signBit :: (u64(1) << (significandBits + exponentBits))
absMask :: (signBit - 1)
exponentMask :: (absMask ~ significandMask)
// Break a into sign, exponent, significand
aRep := a
aAbs := aRep & absMask
sign := i128(-1 if aRep & signBit != 0 else 1)
exponent := u64((aAbs >> significandBits) - exponentBias)
significand := u64((aAbs & significandMask) | implicitBit)
// If exponent is negative, the result is zero.
if exponent < 0 {
return 0
}
// If the value is too large for the integer type, saturate.
if exponent >= size_of(i128) * 8 {
return max(i128) if sign == 1 else min(i128)
}
// If 0 <= exponent < significandBits, right shift to get the result.
// Otherwise, shift left.
if exponent < significandBits {
return sign * i128(significand >> (significandBits - exponent))
} else {
return sign * (i128(significand) << (exponent - significandBits))
}
}

View File

@@ -2,6 +2,20 @@ package runtime
_OS_Errno :: distinct int
HAS_RAND_BYTES :: _HAS_RAND_BYTES
stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
return _stderr_write(data)
}
rand_bytes :: proc "contextless" (dst: []byte) {
when HAS_RAND_BYTES {
_rand_bytes(dst)
} else {
panic_contextless("base/runtime: no runtime entropy source")
}
}
exit :: proc "contextless" (code: int) -> ! {
_exit(code)
}

View File

@@ -4,6 +4,8 @@ package runtime
foreign import libc "system:c"
_HAS_RAND_BYTES :: true
@(default_calling_convention="c")
foreign libc {
@(link_name="write")
@@ -14,6 +16,8 @@ foreign libc {
} else {
__error :: proc() -> ^i32 ---
}
arc4random_buf :: proc(buf: [^]byte, nbytes: uint) ---
}
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
@@ -24,3 +28,15 @@ _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
}
return int(ret), 0
}
_rand_bytes :: proc "contextless" (dst: []byte) {
arc4random_buf(raw_data(dst), len(dst))
}
_exit :: proc "contextless" (code: int) -> ! {
@(default_calling_convention="c")
foreign libc {
exit :: proc(status: i32) -> ! ---
}
exit(i32(code))
}

View File

@@ -4,6 +4,8 @@ package runtime
import "base:intrinsics"
_HAS_RAND_BYTES :: true
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
STDERR :: 2
when ODIN_NO_CRT {
@@ -26,3 +28,25 @@ _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
return 0, _OS_Errno(__error()^)
}
}
foreign import libc "system:System"
_rand_bytes :: proc "contextless" (dst: []byte) {
// This process used to use Security/RandomCopyBytes, however
// on every version of MacOS (>= 10.12) that we care about,
// arc4random is implemented securely.
@(default_calling_convention="c")
foreign libc {
arc4random_buf :: proc(buf: [^]byte, nbytes: uint) ---
}
arc4random_buf(raw_data(dst), len(dst))
}
_exit :: proc "contextless" (code: int) -> ! {
@(default_calling_convention="c")
foreign libc {
exit :: proc(status: i32) -> ! ---
}
exit(i32(code))
}

View File

@@ -2,7 +2,13 @@
#+private
package runtime
_HAS_RAND_BYTES :: false
// TODO(bill): reimplement `os.write`
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
return 0, -1
}
_exit :: proc "contextless" (code: int) -> ! {
trap()
}

View File

@@ -1,21 +0,0 @@
#+build haiku
#+private
package runtime
foreign import libc "system:c"
foreign libc {
@(link_name="write")
_unix_write :: proc(fd: i32, buf: rawptr, size: int) -> int ---
_errnop :: proc() -> ^i32 ---
}
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
ret := _unix_write(2, raw_data(data), len(data))
if ret < len(data) {
err := _errnop()
return int(ret), _OS_Errno(err^ if err != nil else 0)
}
return int(ret), 0
}

View File

@@ -4,6 +4,8 @@ package runtime
foreign import "odin_env"
_HAS_RAND_BYTES :: true
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
foreign odin_env {
write :: proc "contextless" (fd: u32, p: []byte) ---
@@ -11,3 +13,24 @@ _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
write(1, data)
return len(data), 0
}
_rand_bytes :: proc "contextless" (dst: []byte) {
foreign odin_env {
@(link_name = "rand_bytes")
env_rand_bytes :: proc "contextless" (buf: []byte) ---
}
MAX_PER_CALL_BYTES :: 65536 // 64kiB
dst := dst
for len(dst) > 0 {
to_read := min(len(dst), MAX_PER_CALL_BYTES)
env_rand_bytes(dst[:to_read])
dst = dst[to_read:]
}
}
_exit :: proc "contextless" (code: int) -> ! {
trap()
}

View File

@@ -3,6 +3,8 @@ package runtime
import "base:intrinsics"
_HAS_RAND_BYTES :: true
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
when ODIN_ARCH == .amd64 {
SYS_write :: uintptr(1)
@@ -24,3 +26,64 @@ _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
}
return ret, 0
}
_rand_bytes :: proc "contextless" (dst: []byte) {
when ODIN_ARCH == .amd64 {
SYS_getrandom :: uintptr(318)
} else when ODIN_ARCH == .arm64 {
SYS_getrandom :: uintptr(278)
} else when ODIN_ARCH == .i386 {
SYS_getrandom :: uintptr(355)
} else when ODIN_ARCH == .arm32 {
SYS_getrandom :: uintptr(384)
} else when ODIN_ARCH == .riscv64 {
SYS_getrandom :: uintptr(278)
} else {
#panic("base/runtime: no SYS_getrandom definition for target")
}
ERR_EINTR :: 4
ERR_ENOSYS :: 38
MAX_PER_CALL_BYTES :: 33554431 // 2^25 - 1
dst := dst
l := len(dst)
for l > 0 {
to_read := min(l, MAX_PER_CALL_BYTES)
ret := int(intrinsics.syscall(SYS_getrandom, uintptr(raw_data(dst[:to_read])), uintptr(to_read), uintptr(0)))
switch ret {
case -ERR_EINTR:
// Call interupted by a signal handler, just retry the
// request.
continue
case -ERR_ENOSYS:
// The kernel is apparently prehistoric (< 3.17 circa 2014)
// and does not support getrandom.
panic_contextless("base/runtime: getrandom not available in kernel")
case:
if ret < 0 {
// All other failures are things that should NEVER happen
// unless the kernel interface changes (ie: the Linux
// developers break userland).
panic_contextless("base/runtime: getrandom failed")
}
}
l -= ret
dst = dst[ret:]
}
}
_exit :: proc "contextless" (code: int) -> ! {
SYS_exit_group ::
231 when ODIN_ARCH == .amd64 else
248 when ODIN_ARCH == .arm32 else
94 when ODIN_ARCH == .arm64 else
252 when ODIN_ARCH == .i386 else
94 when ODIN_ARCH == .riscv64 else
0
intrinsics.syscall(uintptr(SYS_exit_group), uintptr(i32(code)))
unreachable()
}

View File

@@ -4,6 +4,8 @@ package runtime
import "base:intrinsics"
_HAS_RAND_BYTES :: false
// Constants allowing to specify the level of logging verbosity.
log_level :: enum u32 {
// Only errors are logged.
@@ -41,3 +43,8 @@ _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
return len(data), 0
}
_exit :: proc "contextless" (code: int) -> ! {
trap()
}

View File

@@ -4,6 +4,8 @@ package runtime
foreign import wasi "wasi_snapshot_preview1"
_HAS_RAND_BYTES :: true
@(default_calling_convention="contextless")
foreign wasi {
fd_write :: proc(
@@ -23,6 +25,12 @@ foreign wasi {
argv: [^]cstring,
argv_buf: [^]byte,
) -> u16 ---
@(private="file")
proc_exit :: proc(rval: u32) -> ! ---
@(private ="file")
random_get :: proc(buf: []u8) -> u16 ---
}
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
@@ -31,6 +39,12 @@ _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
return int(n), _OS_Errno(err)
}
_rand_bytes :: proc "contextless" (dst: []byte) {
if errno := random_get(dst); errno != 0 {
panic_contextless("base/runtime: wasi.random_get failed")
}
}
_wasi_setup_args :: proc() {
num_of_args, size_of_args: uint
if errno := args_sizes_get(&num_of_args, &size_of_args); errno != 0 {
@@ -53,3 +67,8 @@ _wasi_setup_args :: proc() {
delete(args_buf)
}
}
_exit :: proc "contextless" (code: int) -> ! {
proc_exit(u32(code))
}

View File

@@ -2,8 +2,11 @@
#+private
package runtime
foreign import bcrypt "system:Bcrypt.lib"
foreign import kernel32 "system:Kernel32.lib"
_HAS_RAND_BYTES :: true
@(private="file")
@(default_calling_convention="system")
foreign kernel32 {
@@ -14,6 +17,14 @@ foreign kernel32 {
SetHandleInformation :: proc(hObject: rawptr, dwMask: u32, dwFlags: u32) -> b32 ---
WriteFile :: proc(hFile: rawptr, lpBuffer: rawptr, nNumberOfBytesToWrite: u32, lpNumberOfBytesWritten: ^u32, lpOverlapped: rawptr) -> b32 ---
GetLastError :: proc() -> u32 ---
ExitProcess :: proc(code: u32) -> ! ---
}
@(private="file")
@(default_calling_convention="system")
foreign bcrypt {
BCryptGenRandom :: proc(hAlgorithm: rawptr, pBuffer: [^]u8, cbBuffer: u32, dwFlags: u32) -> i32 ---
}
_stderr_write :: proc "contextless" (data: []byte) -> (n: int, err: _OS_Errno) #no_bounds_check {
@@ -49,3 +60,31 @@ _stderr_write :: proc "contextless" (data: []byte) -> (n: int, err: _OS_Errno) #
n = int(total_write)
return
}
_rand_bytes :: proc "contextless" (dst: []byte) {
ensure_contextless(u64(len(dst)) <= u64(max(u32)), "base/runtime: oversized rand_bytes request")
BCRYPT_USE_SYSTEM_PREFERRED_RNG :: 0x00000002
ERROR_INVALID_HANDLE :: 6
ERROR_INVALID_PARAMETER :: 87
ret := BCryptGenRandom(nil, raw_data(dst), u32(len(dst)), BCRYPT_USE_SYSTEM_PREFERRED_RNG)
switch ret {
case 0:
case ERROR_INVALID_HANDLE:
// The handle to the first parameter is invalid.
// This should not happen here, since we explicitly pass nil to it
panic_contextless("base/runtime: BCryptGenRandom Invalid handle for hAlgorithm")
case ERROR_INVALID_PARAMETER:
// One of the parameters was invalid
panic_contextless("base/runtime: BCryptGenRandom Invalid parameter")
case:
// Unknown error
panic_contextless("base/runtime: BCryptGenRandom failed")
}
}
_exit :: proc "contextless" (code: int) -> ! {
ExitProcess(u32(code))
}

View File

@@ -184,10 +184,11 @@ print_rune :: #force_no_inline proc "contextless" (r: rune) -> int #no_bounds_ch
print_u64 :: #force_no_inline proc "contextless" (x: u64) #no_bounds_check {
b :: u64(10)
u := x
a: [129]byte
i := len(a)
b := u64(10)
u := x
for u >= b {
i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b]
u /= b
@@ -199,11 +200,9 @@ print_u64 :: #force_no_inline proc "contextless" (x: u64) #no_bounds_check {
print_i64 :: #force_no_inline proc "contextless" (x: i64) #no_bounds_check {
b :: i64(10)
u := x
neg := u < 0
u = abs(u)
b :: u64(10)
u := u64(abs(x))
neg := x < 0
a: [129]byte
i := len(a)
@@ -281,11 +280,22 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) {
print_byte('i' if info.signed else 'u')
print_u64(u64(8*ti.size))
}
switch info.endianness {
case .Platform: // nothing
case .Little: print_string("le")
case .Big: print_string("be")
}
case Type_Info_Rune:
print_string("rune")
case Type_Info_Float:
print_byte('f')
print_u64(u64(8*ti.size))
switch info.endianness {
case .Platform: // nothing
case .Little: print_string("le")
case .Big: print_string("be")
}
case Type_Info_Complex:
print_string("complex")
print_u64(u64(8*ti.size))
@@ -293,7 +303,14 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) {
print_string("quaternion")
print_u64(u64(8*ti.size))
case Type_Info_String:
if info.is_cstring {
print_byte('c')
}
print_string("string")
switch info.encoding {
case .UTF_8: /**/
case .UTF_16: print_string("16")
}
case Type_Info_Boolean:
switch ti.id {
case bool: print_string("bool")
@@ -375,6 +392,12 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) {
print_string("[]")
print_type(info.elem)
case Type_Info_Fixed_Capacity_Dynamic_Array:
print_string("[dynamic; ")
print_u64(u64(info.capacity))
print_string("]")
print_type(info.elem)
case Type_Info_Map:
print_string("map[")
print_type(info.key)
@@ -401,9 +424,10 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) {
}
print_string("struct ")
if .packed in info.flags { print_string("#packed ") }
if .raw_union in info.flags { print_string("#raw_union ") }
if .no_copy in info.flags { print_string("#no_copy ") }
if .packed in info.flags { print_string("#packed ") }
if .raw_union in info.flags { print_string("#raw_union ") }
if .all_or_none in info.flags { print_string("#all_or_none ") }
if .simple in info.flags { print_string("#simple ") }
if .align in info.flags {
print_string("#align(")
print_u64(u64(ti.align))
@@ -460,7 +484,7 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) {
print_string("..")
print_i64(info.upper)
}
if info.underlying != nil {
if info.explicit_underlying {
print_string("; ")
print_type(info.underlying)
}
@@ -488,6 +512,9 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) {
print_type(info.elem)
case Type_Info_Matrix:
if info.layout == .Row_Major {
print_string("#row_major ")
}
print_string("matrix[")
print_u64(u64(info.row_count))
print_string(", ")
@@ -496,3 +523,425 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) {
print_type(info.elem)
}
}
@(require_results)
write_string :: proc "contextless" (i: ^int, dst: []byte, src: string) -> bool {
if i^ < len(dst) {
i^ += copy(dst[i^:], src)
return true
}
return false
}
@(require_results)
write_byte :: proc "contextless" (i: ^int, dst: []byte, src: byte) -> bool {
if i^ < len(dst) {
dst[i^] = src
i^ += 1
return true
}
return false
}
@(require_results)
write_u64 :: proc "contextless" (j: ^int, dst: []byte, x: u64) -> bool {
if j^ < len(dst) {
b :: u64(10)
u := x
a: [129]byte
i := len(a)
for u >= b {
i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b]
u /= b
}
i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b]
return write_string(j, dst, string(a[i:]))
}
return false
}
@(require_results)
write_i64 :: proc "contextless" (j: ^int, dst: []byte, x: i64) -> bool {
if j^ < len(dst) {
b :: u64(10)
u := u64(abs(x))
neg := x < 0
a: [129]byte
i := len(a)
for u >= b {
i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b]
u /= b
}
i -= 1; a[i] = _INTEGER_DIGITS_VAR[u % b]
if neg {
i -= 1; a[i] = '-'
}
return write_string(j, dst, string(a[i:]))
}
return false
}
@(require_results)
write_caller_location :: #force_no_inline proc "contextless" (i: ^int, buf: []byte, loc: Source_Code_Location) -> bool {
write_string(i, buf, loc.file_path) or_return
when ODIN_ERROR_POS_STYLE == .Default {
write_byte(i, buf, '(') or_return
write_u64 (i, buf, u64(loc.line)) or_return
if loc.column != 0 {
write_byte(i, buf, ':') or_return
write_u64 (i, buf, u64(loc.column)) or_return
}
write_byte(i, buf, ')') or_return
return true
} else when ODIN_ERROR_POS_STYLE == .Unix {
write_byte(i, buf, ':') or_return
write_u64 (i, buf, u64(loc.line)) or_return
if loc.column != 0 {
write_byte(i, buf, ':') or_return
write_u64 (i, buf, u64(loc.column)) or_return
}
write_byte(i, buf, ':') or_return
return true
} else {
#panic("unhandled ODIN_ERROR_POS_STYLE")
}
}
@(require_results)
write_typeid :: #force_no_inline proc "contextless" (i: ^int, buf: []byte, id: typeid) -> bool {
when ODIN_NO_RTTI {
if id == nil {
write_string(i, buf, "nil") or_return
} else {
write_string(i, buf, "<unknown type>") or_return
}
} else {
if id == nil {
write_string(i, buf, "nil") or_return
} else {
ti := type_info_of(id)
write_write_type(i, buf, ti) or_return
}
}
return true
}
@(require_results)
write_rune :: #force_no_inline proc "contextless" (i: ^int, buf: []byte, r: rune) -> (written: int, ok: bool) #no_bounds_check {
RUNE_SELF :: 0x80
if r < RUNE_SELF {
write_byte(i, buf,byte(r)) or_return
return 1, true
}
b, n := encode_rune(r)
prev := i^
write_string(i, buf, string(b[:n])) or_return
return i^ - prev, true
}
@(require_results)
write_encoded_rune :: #force_no_inline proc "contextless" (i: ^int, buf: []byte, r: rune) -> bool {
write_byte(i, buf, '\'') or_return
switch r {
case '\a': write_string(i, buf, "\\a") or_return
case '\b': write_string(i, buf, "\\b") or_return
case '\e': write_string(i, buf, "\\e") or_return
case '\f': write_string(i, buf, "\\f") or_return
case '\n': write_string(i, buf, "\\n") or_return
case '\r': write_string(i, buf, "\\r") or_return
case '\t': write_string(i, buf, "\\t") or_return
case '\v': write_string(i, buf, "\\v") or_return
case:
if r <= 0 {
write_string(i, buf, "\\x00") or_return
} else if r < 32 {
n0, n1 := u8(r) >> 4, u8(r) & 0xf
write_string(i, buf, "\\x") or_return
write_byte (i, buf, _INTEGER_DIGITS_VAR[n0]) or_return
write_byte (i, buf, _INTEGER_DIGITS_VAR[n1]) or_return
} else {
_ = write_rune(i, buf, r) or_return
}
}
write_byte(i, buf, '\'') or_return
return true
}
@(optimization_mode="favor_size")
write_write_type :: #force_no_inline proc "contextless" (i: ^int, buf: []byte, ti: ^Type_Info) -> bool {
if ti == nil {
write_string(i, buf, "nil") or_return
return true
}
switch info in ti.variant {
case Type_Info_Named:
write_string(i, buf, info.name) or_return
case Type_Info_Integer:
switch ti.id {
case int: write_string(i, buf, "int") or_return
case uint: write_string(i, buf, "uint") or_return
case uintptr: write_string(i, buf, "uintptr") or_return
case:
write_byte(i, buf, 'i' if info.signed else 'u') or_return
write_u64 (i, buf, u64(8*ti.size)) or_return
}
switch info.endianness {
case .Platform: // nothing
case .Little: write_string(i, buf, "le") or_return
case .Big: write_string(i, buf, "be") or_return
}
case Type_Info_Rune:
write_string(i, buf, "rune") or_return
case Type_Info_Float:
write_byte(i, buf, 'f') or_return
write_u64(i, buf, u64(8*ti.size)) or_return
switch info.endianness {
case .Platform: // nothing
case .Little: write_string(i, buf, "le") or_return
case .Big: write_string(i, buf, "be") or_return
}
case Type_Info_Complex:
write_string(i, buf, "complex") or_return
write_u64 (i, buf, u64(8*ti.size)) or_return
case Type_Info_Quaternion:
write_string(i, buf, "quaternion") or_return
write_u64 (i, buf, u64(8*ti.size)) or_return
case Type_Info_String:
if info.is_cstring {
write_byte(i, buf, 'c') or_return
}
write_string(i, buf, "string") or_return
switch info.encoding {
case .UTF_8: /**/
case .UTF_16: write_string(i, buf, "16") or_return
}
case Type_Info_Boolean:
switch ti.id {
case bool: write_string(i, buf, "bool") or_return
case:
write_byte(i, buf, 'b') or_return
write_u64 (i, buf, u64(8*ti.size)) or_return
}
case Type_Info_Any:
write_string(i, buf, "any") or_return
case Type_Info_Type_Id:
write_string(i, buf, "typeid") or_return
case Type_Info_Pointer:
if info.elem == nil {
write_string(i, buf, "rawptr") or_return
} else {
write_string (i, buf, "^") or_return
write_write_type(i, buf, info.elem) or_return
}
case Type_Info_Multi_Pointer:
write_string (i, buf, "[^]") or_return
write_write_type(i, buf, info.elem) or_return
case Type_Info_Soa_Pointer:
write_string (i, buf, "#soa ^") or_return
write_write_type(i, buf, info.elem) or_return
case Type_Info_Procedure:
write_string(i, buf, "proc") or_return
if info.params == nil {
write_string(i, buf, "()") or_return
} else {
t := info.params.variant.(Type_Info_Parameters)
write_byte(i, buf, '(') or_return
for t, j in t.types {
if j > 0 { write_string(i, buf, ", ") or_return }
write_write_type(i, buf, t) or_return
}
write_string(i, buf, ")") or_return
}
if info.results != nil {
write_string (i, buf, " -> ") or_return
write_write_type(i, buf, info.results) or_return
}
case Type_Info_Parameters:
count := len(info.names)
if count != 1 { write_byte(i, buf, '(') or_return }
for name, j in info.names {
if j > 0 { write_string(i, buf, ", ") or_return }
t := info.types[j]
if len(name) > 0 {
write_string(i, buf, name) or_return
write_string(i, buf, ": ") or_return
}
write_write_type(i, buf, t) or_return
}
if count != 1 { write_string(i, buf, ")") or_return }
case Type_Info_Array:
write_byte (i, buf, '[') or_return
write_u64 (i, buf, u64(info.count)) or_return
write_byte (i, buf, ']') or_return
write_write_type(i, buf, info.elem) or_return
case Type_Info_Enumerated_Array:
if info.is_sparse {
write_string(i, buf, "#sparse") or_return
}
write_byte (i, buf, '[') or_return
write_write_type(i, buf, info.index) or_return
write_byte (i, buf, ']') or_return
write_write_type(i, buf, info.elem) or_return
case Type_Info_Dynamic_Array:
write_string (i, buf, "[dynamic]") or_return
write_write_type(i, buf, info.elem) or_return
case Type_Info_Slice:
write_string (i, buf, "[]") or_return
write_write_type(i, buf, info.elem) or_return
case Type_Info_Fixed_Capacity_Dynamic_Array:
write_string (i, buf, "[dynamic; ") or_return
write_u64 (i, buf, u64(info.capacity)) or_return
write_string (i, buf, "]") or_return
write_write_type(i, buf, info.elem) or_return
case Type_Info_Map:
write_string (i, buf, "map[") or_return
write_write_type(i, buf, info.key) or_return
write_byte (i, buf, ']') or_return
write_write_type(i, buf, info.value) or_return
case Type_Info_Struct:
switch info.soa_kind {
case .None: // Ignore
case .Fixed:
write_string (i, buf, "#soa[") or_return
write_u64 (i, buf, u64(info.soa_len)) or_return
write_byte (i, buf, ']') or_return
write_write_type(i, buf, info.soa_base_type) or_return
return true
case .Slice:
write_string (i, buf, "#soa[]") or_return
write_write_type(i, buf, info.soa_base_type) or_return
return true
case .Dynamic:
write_string (i, buf, "#soa[dynamic]") or_return
write_write_type(i, buf, info.soa_base_type) or_return
return true
}
write_string(i, buf, "struct ") or_return
if .packed in info.flags { write_string(i, buf, "#packed ") or_return }
if .raw_union in info.flags { write_string(i, buf, "#raw_union ") or_return }
if .all_or_none in info.flags { write_string(i, buf, "#all_or_none ") or_return }
if .simple in info.flags { write_string(i, buf, "#simple ") or_return }
if .align in info.flags {
write_string(i, buf, "#align(") or_return
write_u64(i, buf, u64(ti.align)) or_return
write_string(i, buf, ") ") or_return
}
write_byte(i, buf, '{') or_return
for name, j in info.names[:info.field_count] {
if j > 0 { write_string(i, buf, ", ") or_return }
write_string (i, buf, name) or_return
write_string (i, buf, ": ") or_return
write_write_type(i, buf, info.types[j]) or_return
}
write_byte(i, buf, '}') or_return
case Type_Info_Union:
write_string(i, buf, "union ") or_return
if info.custom_align {
write_string(i, buf, "#align(") or_return
write_u64 (i, buf, u64(ti.align)) or_return
write_string(i, buf, ") ") or_return
}
if info.no_nil {
write_string(i, buf, "#no_nil ") or_return
}
write_byte(i, buf, '{') or_return
for variant, j in info.variants {
if j > 0 { write_string(i, buf, ", ") or_return }
write_write_type(i, buf, variant) or_return
}
write_string(i, buf, "}") or_return
case Type_Info_Enum:
write_string (i, buf, "enum ") or_return
write_write_type(i, buf, info.base) or_return
write_string (i, buf, " {") or_return
for name, j in info.names {
if j > 0 { write_string(i, buf, ", ") or_return }
write_string(i, buf, name) or_return
}
write_string(i, buf, "}") or_return
case Type_Info_Bit_Set:
write_string(i, buf, "bit_set[") or_return
#partial switch elem in type_info_base(info.elem).variant {
case Type_Info_Enum:
write_write_type(i, buf, info.elem) or_return
case Type_Info_Rune:
write_encoded_rune(i, buf, rune(info.lower)) or_return
write_string (i, buf, "..") or_return
write_encoded_rune(i, buf, rune(info.upper)) or_return
case:
write_i64 (i, buf, info.lower) or_return
write_string(i, buf, "..") or_return
write_i64 (i, buf, info.upper) or_return
}
if info.explicit_underlying {
write_string (i, buf, "; ") or_return
write_write_type(i, buf, info.underlying) or_return
}
write_byte(i, buf, ']') or_return
case Type_Info_Bit_Field:
write_string (i, buf, "bit_field ") or_return
write_write_type(i, buf, info.backing_type) or_return
write_string (i, buf, " {") or_return
for name, j in info.names[:info.field_count] {
if j > 0 { write_string(i, buf, ", ") or_return }
write_string (i, buf, name) or_return
write_string (i, buf, ": ") or_return
write_write_type(i, buf, info.types[j]) or_return
write_string (i, buf, " | ") or_return
write_u64 (i, buf, u64(info.bit_sizes[j])) or_return
}
write_byte(i, buf, '}') or_return
case Type_Info_Simd_Vector:
write_string (i, buf, "#simd[") or_return
write_u64 (i, buf, u64(info.count)) or_return
write_byte (i, buf, ']') or_return
write_write_type(i, buf, info.elem) or_return
case Type_Info_Matrix:
if info.layout == .Row_Major {
write_string(i, buf, "#row_major ") or_return
}
write_string (i, buf, "matrix[") or_return
write_u64 (i, buf, u64(info.row_count)) or_return
write_string (i, buf, ", ") or_return
write_u64 (i, buf, u64(info.column_count)) or_return
write_string (i, buf, "]") or_return
write_write_type(i, buf, info.elem) or_return
}
return true
}

View File

@@ -1,9 +1,12 @@
#+private
package runtime
@(priority_index=-1e6)
@(priority_index=-1e5)
foreign import ObjC "system:objc"
@(priority_index=-1e6)
foreign import libSystem "system:System"
import "base:intrinsics"
objc_id :: ^intrinsics.objc_object
@@ -12,16 +15,25 @@ objc_SEL :: ^intrinsics.objc_selector
objc_Ivar :: ^intrinsics.objc_ivar
objc_BOOL :: bool
objc_super :: struct {
receiver: objc_id,
super_class: objc_Class,
}
objc_IMP :: proc "c" (object: objc_id, sel: objc_SEL, #c_vararg args: ..any) -> objc_id
foreign ObjC {
sel_registerName :: proc "c" (name: cstring) -> objc_SEL ---
objc_msgSend :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
objc_msgSend_fpret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 ---
objc_msgSend_fp2ret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 ---
objc_msgSend_stret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
objc_msgSend :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
objc_msgSend_fpret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 ---
objc_msgSend_fp2ret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 ---
objc_msgSend_stret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
// See: https://github.com/opensource-apple/objc4/blob/cd5e62a5597ea7a31dccef089317abb3a661c154/runtime/objc-abi.h#L111
objc_msgSendSuper2 :: proc "c" (super: rawptr, op: objc_SEL, #c_vararg args: ..any) -> objc_id ---
objc_msgSendSuper2_stret :: proc "c" (super: ^objc_super, op: objc_SEL, #c_vararg args: ..any) ---
objc_lookUpClass :: proc "c" (name: cstring) -> objc_Class ---
objc_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class ---
@@ -30,6 +42,15 @@ foreign ObjC {
class_addIvar :: proc "c" (cls: objc_Class, name: cstring, size: uint, alignment: u8, types: cstring) -> objc_BOOL ---
class_getInstanceVariable :: proc "c" (cls : objc_Class, name: cstring) -> objc_Ivar ---
class_getInstanceSize :: proc "c" (cls : objc_Class) -> uint ---
class_getSuperclass :: proc "c" (cls : objc_Class) -> objc_Class ---
ivar_getOffset :: proc "c" (v: objc_Ivar) -> uintptr ---
object_getClass :: proc "c" (obj: objc_id) -> objc_Class ---
}
foreign libSystem {
_NSConcreteGlobalBlock: intrinsics.objc_class
_NSConcreteStackBlock: intrinsics.objc_class
_Block_object_assign :: proc "c" (rawptr, rawptr, i32) ---
_Block_object_dispose :: proc "c" (rawptr, i32) ---
}

View File

@@ -3,8 +3,8 @@ package runtime
init_default_context_for_js: Context
@(init, private="file")
init_default_context :: proc() {
init_default_context_for_js = context
init_default_context :: proc "contextless" () {
__init_context(&init_default_context_for_js)
}
@(export)

View File

@@ -41,88 +41,3 @@ random_generator_reset_u64 :: proc(rg: Random_Generator, p: u64) {
rg.procedure(rg.data, .Reset, ([^]byte)(&p)[:size_of(p)])
}
}
Default_Random_State :: struct {
state: u64,
inc: u64,
}
default_random_generator_proc :: proc(data: rawptr, mode: Random_Generator_Mode, p: []byte) {
@(require_results)
read_u64 :: proc "contextless" (r: ^Default_Random_State) -> u64 {
old_state := r.state
r.state = old_state * 6364136223846793005 + (r.inc|1)
xor_shifted := (((old_state >> 59) + 5) ~ old_state) * 12605985483714917081
rot := (old_state >> 59)
return (xor_shifted >> rot) | (xor_shifted << ((-rot) & 63))
}
@(thread_local)
global_rand_seed: Default_Random_State
init :: proc "contextless" (r: ^Default_Random_State, seed: u64) {
seed := seed
if seed == 0 {
seed = u64(intrinsics.read_cycle_counter())
}
r.state = 0
r.inc = (seed << 1) | 1
_ = read_u64(r)
r.state += seed
_ = read_u64(r)
}
r: ^Default_Random_State = ---
if data == nil {
r = &global_rand_seed
} else {
r = cast(^Default_Random_State)data
}
switch mode {
case .Read:
if r.state == 0 && r.inc == 0 {
init(r, 0)
}
switch len(p) {
case size_of(u64):
// Fast path for a 64-bit destination.
intrinsics.unaligned_store((^u64)(raw_data(p)), read_u64(r))
case:
// All other cases.
pos := i8(0)
val := u64(0)
for &v in p {
if pos == 0 {
val = read_u64(r)
pos = 7
}
v = byte(val)
val >>= 8
pos -= 1
}
}
case .Reset:
seed: u64
mem_copy_non_overlapping(&seed, raw_data(p), min(size_of(seed), len(p)))
init(r, seed)
case .Query_Info:
if len(p) != size_of(Random_Generator_Query_Info) {
return
}
info := (^Random_Generator_Query_Info)(raw_data(p))
info^ += {.Uniform, .Resettable}
}
}
@(require_results)
default_random_generator :: proc "contextless" (state: ^Default_Random_State = nil) -> Random_Generator {
return {
procedure = default_random_generator_proc,
data = state,
}
}

View File

@@ -0,0 +1,164 @@
package runtime
import "base:intrinsics"
// This is an implementation of the Chacha8Rand DRBG, as specified
// in https://github.com/C2SP/C2SP/blob/main/chacha8rand.md
//
// There is a tradeoff to be made between state-size and performance,
// in terms of the amount of rng output buffered.
//
// The sensible buffer sizes are:
// - 256-bytes: 128-bit SIMD with 16x vector registers (SSE2)
// - 512-bytes: 128-bit SIMD with 32x vector registers (ARMv8),
// 256-bit SIMD with 16x vector registers (AVX2),
// - 1024-bytes: AVX-512
//
// Notes:
// - Smaller than 256-bytes is possible but would require redundant
// calls to the ChaCha8 function, which is prohibitively expensive.
// - Larger than 1024-bytes is possible but pointless as the construct
// is defined around 992-bytes of RNG output and 32-bytes of input
// per iteration.
//
// This implementation opts for a 1024-byte buffer for simplicity,
// under the rationale that modern extremely memory constrained targets
// provide suitable functionality in hardware, and the language makes
// supporting the various SIMD flavors easy.
@(private = "file")
RNG_SEED_SIZE :: 32
@(private)
RNG_OUTPUT_PER_ITER :: 1024 - RNG_SEED_SIZE
@(private)
CHACHA_SIGMA_0: u32 : 0x61707865
@(private)
CHACHA_SIGMA_1: u32 : 0x3320646e
@(private)
CHACHA_SIGMA_2: u32 : 0x79622d32
@(private)
CHACHA_SIGMA_3: u32 : 0x6b206574
@(private)
CHACHA_ROUNDS :: 8
Default_Random_State :: struct {
_buf: [1024]byte,
_off: int,
_seeded: bool,
}
@(require_results)
default_random_generator :: proc "contextless" (state: ^Default_Random_State = nil) -> Random_Generator {
return {
procedure = default_random_generator_proc,
data = state,
}
}
default_random_generator_proc :: proc(data: rawptr, mode: Random_Generator_Mode, p: []byte) {
@(thread_local)
state: Default_Random_State
r: ^Default_Random_State = &state
if data != nil {
r = cast(^Default_Random_State)data
}
next_seed := r._buf[RNG_OUTPUT_PER_ITER:]
switch mode {
case .Read:
if !r._seeded { // Unlikely.
rand_bytes(next_seed)
r._off = RNG_OUTPUT_PER_ITER // Force refill.
r._seeded = true
}
assert(r._off <= RNG_OUTPUT_PER_ITER, "chacha8rand/BUG: outputed key material")
if r._off >= RNG_OUTPUT_PER_ITER { // Unlikely.
chacha8rand_refill(r)
}
// We are guaranteed to have at least some RNG output buffered.
//
// As an invariant each read will consume a multiple of 8-bytes
// of output at a time.
assert(r._off <= RNG_OUTPUT_PER_ITER - 8, "chacha8rand/BUG: less than 8-bytes of output available")
assert(r._off % 8 == 0, "chacha8rand/BUG: buffered output is not a multiple of 8-bytes")
p_len := len(p)
if p_len == size_of(u64) {
#no_bounds_check {
// Fast path for a 64-bit destination.
src := (^u64)(raw_data(r._buf[r._off:]))
intrinsics.unaligned_store((^u64)(raw_data(p)), src^)
src^ = 0 // Erasure (backtrack resistance)
r._off += 8
}
return
}
p_ := p
for remaining := p_len; remaining > 0; {
sz := min(remaining, RNG_OUTPUT_PER_ITER - r._off)
#no_bounds_check {
copy(p_[:sz], r._buf[r._off:])
p_ = p_[sz:]
remaining -= sz
}
rounded_sz := ((sz + 7) / 8) * 8
new_off := r._off + rounded_sz
#no_bounds_check if new_off < RNG_OUTPUT_PER_ITER {
// Erasure (backtrack resistance)
intrinsics.mem_zero(raw_data(r._buf[r._off:]), rounded_sz)
r._off = new_off
} else {
// Can omit erasure since we are overwriting the entire
// buffer.
chacha8rand_refill(r)
}
}
case .Reset:
// If no seed is passed, the next call to .Read will attempt to
// reseed from the system entropy source.
if len(p) == 0 {
r._seeded = false
return
}
// The cryptographic security of the output depends entirely
// on the quality of the entropy in the seed, we will allow
// re-seeding (as it makes testing easier), but callers that
// decide to provide arbitrary seeds are on their own as far
// as ensuring high-quality entropy.
intrinsics.mem_zero(raw_data(next_seed), RNG_SEED_SIZE)
copy(next_seed, p)
r._seeded = true
r._off = RNG_OUTPUT_PER_ITER // Force a refill.
case .Query_Info:
if len(p) != size_of(Random_Generator_Query_Info) {
return
}
info := (^Random_Generator_Query_Info)(raw_data(p))
info^ += {.Uniform, .Cryptographic, .Resettable}
}
}
@(private = "file")
chacha8rand_refill :: proc(r: ^Default_Random_State) {
assert(r._seeded == true, "chacha8rand/BUG: unseeded refill")
// i386 has insufficient vector registers to use the
// accelerated path at the moment.
when ODIN_ARCH == .amd64 && intrinsics.has_target_feature("avx2") {
chacha8rand_refill_simd256(r)
} else when HAS_HARDWARE_SIMD && ODIN_ARCH != .i386 {
chacha8rand_refill_simd128(r)
} else {
chacha8rand_refill_ref(r)
}
r._off = 0
}

View File

@@ -0,0 +1,145 @@
package runtime
import "base:intrinsics"
@(private)
chacha8rand_refill_ref :: proc(r: ^Default_Random_State) {
// Initialize the base state.
k: [^]u32 = (^u32)(raw_data(r._buf[RNG_OUTPUT_PER_ITER:]))
when ODIN_ENDIAN == .Little {
s4 := k[0]
s5 := k[1]
s6 := k[2]
s7 := k[3]
s8 := k[4]
s9 := k[5]
s10 := k[6]
s11 := k[7]
} else {
s4 := intrinsics.byte_swap(k[0])
s5 := intrinsics.byte_swap(k[1])
s6 := intrinsics.byte_swap(k[2])
s7 := intrinsics.byte_swap(k[3])
s8 := intrinsics.byte_swap(k[4])
s9 := intrinsics.byte_swap(k[5])
s10 := intrinsics.byte_swap(k[6])
s11 := intrinicss.byte_swap(k[7])
}
s12: u32 // Counter starts at 0.
s13, s14, s15: u32 // IV of all 0s.
dst: [^]u32 = (^u32)(raw_data(r._buf[:]))
// At least with LLVM21 force_inline produces identical perf to
// manual inlining, yay.
quarter_round := #force_inline proc "contextless" (a, b, c, d: u32) -> (u32, u32, u32, u32) {
a, b, c, d := a, b, c, d
a += b
d ~= a
d = rotl(d, 16)
c += d
b ~= c
b = rotl(b, 12)
a += b
d ~= a
d = rotl(d, 8)
c += d
b ~= c
b = rotl(b, 7)
return a, b, c, d
}
// Filippo Valsorda made an observation that only one of the column
// round depends on the counter (s12), so it is worth precomputing
// and reusing across multiple blocks. As far as I know, only Go's
// chacha implementation does this.
p1, p5, p9, p13 := quarter_round(CHACHA_SIGMA_1, s5, s9, s13)
p2, p6, p10, p14 := quarter_round(CHACHA_SIGMA_2, s6, s10, s14)
p3, p7, p11, p15 := quarter_round(CHACHA_SIGMA_3, s7, s11, s15)
// 4 groups
for g := 0; g < 4; g = g + 1 {
// 4 blocks per group
for n := 0; n < 4; n = n + 1 {
// First column round that depends on the counter
p0, p4, p8, p12 := quarter_round(CHACHA_SIGMA_0, s4, s8, s12)
// First diagonal round
x0, x5, x10, x15 := quarter_round(p0, p5, p10, p15)
x1, x6, x11, x12 := quarter_round(p1, p6, p11, p12)
x2, x7, x8, x13 := quarter_round(p2, p7, p8, p13)
x3, x4, x9, x14 := quarter_round(p3, p4, p9, p14)
for i := CHACHA_ROUNDS - 2; i > 0; i = i - 2 {
x0, x4, x8, x12 = quarter_round(x0, x4, x8, x12)
x1, x5, x9, x13 = quarter_round(x1, x5, x9, x13)
x2, x6, x10, x14 = quarter_round(x2, x6, x10, x14)
x3, x7, x11, x15 = quarter_round(x3, x7, x11, x15)
x0, x5, x10, x15 = quarter_round(x0, x5, x10, x15)
x1, x6, x11, x12 = quarter_round(x1, x6, x11, x12)
x2, x7, x8, x13 = quarter_round(x2, x7, x8, x13)
x3, x4, x9, x14 = quarter_round(x3, x4, x9, x14)
}
// Interleave 4 blocks
// NB: The additions of sigma and the counter are omitted
STRIDE :: 4
d_ := dst[n:]
when ODIN_ENDIAN == .Little {
d_[STRIDE*0] = x0
d_[STRIDE*1] = x1
d_[STRIDE*2] = x2
d_[STRIDE*3] = x3
d_[STRIDE*4] = x4 + s4
d_[STRIDE*5] = x5 + s5
d_[STRIDE*6] = x6 + s6
d_[STRIDE*7] = x7 + s7
d_[STRIDE*8] = x8 + s8
d_[STRIDE*9] = x9 + s9
d_[STRIDE*10] = x10 + s10
d_[STRIDE*11] = x11 + s11
d_[STRIDE*12] = x12
d_[STRIDE*13] = x13 + s13
d_[STRIDE*14] = x14 + s14
d_[STRIDE*15] = x15 + s15
} else {
d_[STRIDE*0] = intrinsics.byte_swap(x0)
d_[STRIDE*1] = intrinsics.byte_swap(x1)
d_[STRIDE*2] = intrinsics.byte_swap(x2)
d_[STRIDE*3] = intrinsics.byte_swap(x3)
d_[STRIDE*4] = intrinsics.byte_swap(x4 + s4)
d_[STRIDE*5] = intrinsics.byte_swap(x5 + s5)
d_[STRIDE*6] = intrinsics.byte_swap(x6 + s6)
d_[STRIDE*7] = intrinsics.byte_swap(x7 + s7)
d_[STRIDE*8] = intrinsics.byte_swap(x8 + s8)
d_[STRIDE*9] = intrinsics.byte_swap(x9 + s9)
d_[STRIDE*10] = intrinsics.byte_swap(x10 + s10)
d_[STRIDE*11] = intrinsics.byte_swap(x11 + s11)
d_[STRIDE*12] = intrinsics.byte_swap(x12)
d_[STRIDE*13] = intrinsics.byte_swap(x13 + s13)
d_[STRIDE*14] = intrinsics.byte_swap(x14 + s14)
d_[STRIDE*15] = intrinsics.byte_swap(x15 + s15)
}
s12 = s12 + 1 // Increment the counter
}
dst = dst[16*4:]
}
}
// This replicates `rotate_left32` from `core:math/bits`, under the
// assumption that this will live in `base:runtime`.
@(require_results, private = "file")
rotl :: #force_inline proc "contextless" (x: u32, k: int) -> u32 {
n :: 32
s := uint(k) & (n-1)
return x << s | x >> (n-s)
}

View File

@@ -0,0 +1,290 @@
#+build !i386
package runtime
import "base:intrinsics"
@(private = "file")
u32x4 :: #simd[4]u32
@(private = "file")
S0: u32x4 : {CHACHA_SIGMA_0, CHACHA_SIGMA_0, CHACHA_SIGMA_0, CHACHA_SIGMA_0}
@(private = "file")
S1: u32x4 : {CHACHA_SIGMA_1, CHACHA_SIGMA_1, CHACHA_SIGMA_1, CHACHA_SIGMA_1}
@(private = "file")
S2: u32x4 : {CHACHA_SIGMA_2, CHACHA_SIGMA_2, CHACHA_SIGMA_2, CHACHA_SIGMA_2}
@(private = "file")
S3: u32x4 : {CHACHA_SIGMA_3, CHACHA_SIGMA_3, CHACHA_SIGMA_3, CHACHA_SIGMA_3}
@(private = "file")
_ROT_7L: u32x4 : {7, 7, 7, 7}
@(private = "file")
_ROT_7R: u32x4 : {25, 25, 25, 25}
@(private = "file")
_ROT_12L: u32x4 : {12, 12, 12, 12}
@(private = "file")
_ROT_12R: u32x4 : {20, 20, 20, 20}
@(private = "file")
_ROT_8L: u32x4 : {8, 8, 8, 8}
@(private = "file")
_ROT_8R: u32x4 : {24, 24, 24, 24}
@(private = "file")
_ROT_16: u32x4 : {16, 16, 16, 16}
@(private = "file")
_CTR_INC_4: u32x4 : {4, 4, 4, 4}
@(private = "file")
_CTR_INC_8: u32x4 : {8, 8, 8, 8}
when ODIN_ENDIAN == .Big {
@(private = "file")
_byteswap_u32x4 :: #force_inline proc "contextless" (v: u32x4) -> u32x4 {
u8x16 :: #simd[16]u8
return(
transmute(u32x4)simd.shuffle(
transmute(u8x16)v,
transmute(u8x16)v,
3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12,
)
)
}
}
@(private)
chacha8rand_refill_simd128 :: proc(r: ^Default_Random_State) {
// Initialize the base state.
k: [^]u32 = (^u32)(raw_data(r._buf[RNG_OUTPUT_PER_ITER:]))
when ODIN_ENDIAN == .Little {
s4_ := k[0]
s5_ := k[1]
s6_ := k[2]
s7_ := k[3]
s8_ := k[4]
s9_ := k[5]
s10_ := k[6]
s11_ := k[7]
} else {
s4_ := intrinsics.byte_swap(k[0])
s5_ := intrinsics.byte_swap(k[1])
s6_ := intrinsics.byte_swap(k[2])
s7_ := intrinsics.byte_swap(k[3])
s8_ := intrinsics.byte_swap(k[4])
s9_ := intrinsics.byte_swap(k[5])
s10_ := intrinsics.byte_swap(k[6])
s11_ := intrinicss.byte_swap(k[7])
}
// 4-lane ChaCha8.
s4 := u32x4{s4_, s4_, s4_, s4_}
s5 := u32x4{s5_, s5_, s5_, s5_}
s6 := u32x4{s6_, s6_, s6_, s6_}
s7 := u32x4{s7_, s7_, s7_, s7_}
s8 := u32x4{s8_, s8_, s8_, s8_}
s9 := u32x4{s9_, s9_, s9_, s9_}
s10 := u32x4{s10_, s10_, s10_, s10_}
s11 := u32x4{s11_, s11_, s11_, s11_}
s12 := u32x4{0, 1, 2, 3}
s13, s14, s15: u32x4
dst: [^]u32x4 = (^u32x4)(raw_data(r._buf[:]))
quarter_round := #force_inline proc "contextless" (a, b, c, d: u32x4) -> (u32x4, u32x4, u32x4, u32x4) {
a, b, c, d := a, b, c, d
a = intrinsics.simd_add(a, b)
d = intrinsics.simd_bit_xor(d, a)
d = intrinsics.simd_bit_xor(intrinsics.simd_shl(d, _ROT_16), intrinsics.simd_shr(d, _ROT_16))
c = intrinsics.simd_add(c, d)
b = intrinsics.simd_bit_xor(b, c)
b = intrinsics.simd_bit_xor(intrinsics.simd_shl(b, _ROT_12L), intrinsics.simd_shr(b, _ROT_12R))
a = intrinsics.simd_add(a, b)
d = intrinsics.simd_bit_xor(d, a)
d = intrinsics.simd_bit_xor(intrinsics.simd_shl(d, _ROT_8L), intrinsics.simd_shr(d, _ROT_8R))
c = intrinsics.simd_add(c, d)
b = intrinsics.simd_bit_xor(b, c)
b = intrinsics.simd_bit_xor(intrinsics.simd_shl(b, _ROT_7L), intrinsics.simd_shr(b, _ROT_7R))
return a, b, c, d
}
// 8 blocks at a time.
//
// Note:
// This uses a ton of registers so it is only worth it on targets
// that have something like 32 128-bit registers. This is currently
// all ARMv8 targets, and RISC-V Zvl128b (`V` application profile)
// targets.
//
// While our current definition of `.arm32` is 32-bit ARMv8, this
// may change in the future (ARMv7 is still relevant), and things
// like Cortex-A8/A9 does "pretend" 128-bit SIMD 64-bits at a time
// thus needs bemchmarking.
when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 {
for _ in 0..<2 {
x0_0, x1_0, x2_0, x3_0 := S0, S1, S2, S3
x4_0, x5_0, x6_0, x7_0 := s4, s5, s6, s7
x8_0, x9_0, x10_0, x11_0 := s8, s9, s10, s11
x12_0, x13_0, x14_0, x15_0 := s12, s13, s14, s15
x0_1, x1_1, x2_1, x3_1 := S0, S1, S2, S3
x4_1, x5_1, x6_1, x7_1 := s4, s5, s6, s7
x8_1, x9_1, x10_1, x11_1 := s8, s9, s10, s11
x12_1 := intrinsics.simd_add(s12, _CTR_INC_4)
x13_1, x14_1, x15_1 := s13, s14, s15
for i := CHACHA_ROUNDS; i > 0; i = i - 2 {
x0_0, x4_0, x8_0, x12_0 = quarter_round(x0_0, x4_0, x8_0, x12_0)
x0_1, x4_1, x8_1, x12_1 = quarter_round(x0_1, x4_1, x8_1, x12_1)
x1_0, x5_0, x9_0, x13_0 = quarter_round(x1_0, x5_0, x9_0, x13_0)
x1_1, x5_1, x9_1, x13_1 = quarter_round(x1_1, x5_1, x9_1, x13_1)
x2_0, x6_0, x10_0, x14_0 = quarter_round(x2_0, x6_0, x10_0, x14_0)
x2_1, x6_1, x10_1, x14_1 = quarter_round(x2_1, x6_1, x10_1, x14_1)
x3_0, x7_0, x11_0, x15_0 = quarter_round(x3_0, x7_0, x11_0, x15_0)
x3_1, x7_1, x11_1, x15_1 = quarter_round(x3_1, x7_1, x11_1, x15_1)
x0_0, x5_0, x10_0, x15_0 = quarter_round(x0_0, x5_0, x10_0, x15_0)
x0_1, x5_1, x10_1, x15_1 = quarter_round(x0_1, x5_1, x10_1, x15_1)
x1_0, x6_0, x11_0, x12_0 = quarter_round(x1_0, x6_0, x11_0, x12_0)
x1_1, x6_1, x11_1, x12_1 = quarter_round(x1_1, x6_1, x11_1, x12_1)
x2_0, x7_0, x8_0, x13_0 = quarter_round(x2_0, x7_0, x8_0, x13_0)
x2_1, x7_1, x8_1, x13_1 = quarter_round(x2_1, x7_1, x8_1, x13_1)
x3_0, x4_0, x9_0, x14_0 = quarter_round(x3_0, x4_0, x9_0, x14_0)
x3_1, x4_1, x9_1, x14_1 = quarter_round(x3_1, x4_1, x9_1, x14_1)
}
when ODIN_ENDIAN == .Little {
intrinsics.unaligned_store((^u32x4)(dst[0:]), x0_0)
intrinsics.unaligned_store((^u32x4)(dst[1:]), x1_0)
intrinsics.unaligned_store((^u32x4)(dst[2:]), x2_0)
intrinsics.unaligned_store((^u32x4)(dst[3:]), x3_0)
intrinsics.unaligned_store((^u32x4)(dst[4:]), intrinsics.simd_add(x4_0, s4))
intrinsics.unaligned_store((^u32x4)(dst[5:]), intrinsics.simd_add(x5_0, s5))
intrinsics.unaligned_store((^u32x4)(dst[6:]), intrinsics.simd_add(x6_0, s6))
intrinsics.unaligned_store((^u32x4)(dst[7:]), intrinsics.simd_add(x7_0, s7))
intrinsics.unaligned_store((^u32x4)(dst[8:]), intrinsics.simd_add(x8_0, s8))
intrinsics.unaligned_store((^u32x4)(dst[9:]), intrinsics.simd_add(x9_0, s9))
intrinsics.unaligned_store((^u32x4)(dst[10:]), intrinsics.simd_add(x10_0, s10))
intrinsics.unaligned_store((^u32x4)(dst[11:]), intrinsics.simd_add(x11_0, s11))
intrinsics.unaligned_store((^u32x4)(dst[12:]), x12_0)
intrinsics.unaligned_store((^u32x4)(dst[13:]), intrinsics.simd_add(x13_0, s13))
intrinsics.unaligned_store((^u32x4)(dst[14:]), intrinsics.simd_add(x14_0, s14))
intrinsics.unaligned_store((^u32x4)(dst[15:]), intrinsics.simd_add(x15_0, s15))
intrinsics.unaligned_store((^u32x4)(dst[16:]), x0_1)
intrinsics.unaligned_store((^u32x4)(dst[17:]), x1_1)
intrinsics.unaligned_store((^u32x4)(dst[18:]), x2_1)
intrinsics.unaligned_store((^u32x4)(dst[19:]), x3_1)
intrinsics.unaligned_store((^u32x4)(dst[20:]), intrinsics.simd_add(x4_1, s4))
intrinsics.unaligned_store((^u32x4)(dst[21:]), intrinsics.simd_add(x5_1, s5))
intrinsics.unaligned_store((^u32x4)(dst[22:]), intrinsics.simd_add(x6_1, s6))
intrinsics.unaligned_store((^u32x4)(dst[23:]), intrinsics.simd_add(x7_1, s7))
intrinsics.unaligned_store((^u32x4)(dst[24:]), intrinsics.simd_add(x8_1, s8))
intrinsics.unaligned_store((^u32x4)(dst[25:]), intrinsics.simd_add(x9_1, s9))
intrinsics.unaligned_store((^u32x4)(dst[26:]), intrinsics.simd_add(x10_1, s10))
intrinsics.unaligned_store((^u32x4)(dst[27:]), intrinsics.simd_add(x11_1, s11))
intrinsics.unaligned_store((^u32x4)(dst[28:]), x12_1)
intrinsics.unaligned_store((^u32x4)(dst[29:]), intrinsics.simd_add(x13_1, s13))
intrinsics.unaligned_store((^u32x4)(dst[30:]), intrinsics.simd_add(x14_1, s14))
intrinsics.unaligned_store((^u32x4)(dst[31:]), intrinsics.simd_add(x15_1, s15))
} else {
intrinsics.unaligned_store((^u32x4)(dst[0:]), _byteswap_u32x4(x0_0))
intrinsics.unaligned_store((^u32x4)(dst[1:]), _byteswap_u32x4(x1_0))
intrinsics.unaligned_store((^u32x4)(dst[2:]), _byteswap_u32x4(x2_0))
intrinsics.unaligned_store((^u32x4)(dst[3:]), _byteswap_u32x4(x3_0))
intrinsics.unaligned_store((^u32x4)(dst[4:]), _byteswap_u32x4(intrinsics.simd_add(x4_0, s4)))
intrinsics.unaligned_store((^u32x4)(dst[5:]), _byteswap_u32x4(intrinsics.simd_add(x5_0, s5)))
intrinsics.unaligned_store((^u32x4)(dst[6:]), _byteswap_u32x4(intrinsics.simd_add(x6_0, s6)))
intrinsics.unaligned_store((^u32x4)(dst[7:]), _byteswap_u32x4(intrinsics.simd_add(x7_0, s7)))
intrinsics.unaligned_store((^u32x4)(dst[8:]), _byteswap_u32x4(intrinsics.simd_add(x8_0, s8)))
intrinsics.unaligned_store((^u32x4)(dst[9:]), _byteswap_u32x4(intrinsics.simd_add(x9_0, s9)))
intrinsics.unaligned_store((^u32x4)(dst[10:]), _byteswap_u32x4(intrinsics.simd_add(x10_0, s10)))
intrinsics.unaligned_store((^u32x4)(dst[11:]), _byteswap_u32x4(intrinsics.simd_add(x11_0, s11)))
intrinsics.unaligned_store((^u32x4)(dst[12:]), _byteswap_u32x4(x12_0))
intrinsics.unaligned_store((^u32x4)(dst[13:]), _byteswap_u32x4(intrinsics.simd_add(x13_0, s13)))
intrinsics.unaligned_store((^u32x4)(dst[14:]), _byteswap_u32x4(intrinsics.simd_add(x14_0, s14)))
intrinsics.unaligned_store((^u32x4)(dst[15:]), _byteswap_u32x4(intrinsics.simd_add(x15_0, s15)))
intrinsics.unaligned_store((^u32x4)(dst[16:]), _byteswap_u32x4(x0_1))
intrinsics.unaligned_store((^u32x4)(dst[17:]), _byteswap_u32x4(x1_1))
intrinsics.unaligned_store((^u32x4)(dst[18:]), _byteswap_u32x4(x2_1))
intrinsics.unaligned_store((^u32x4)(dst[19:]), _byteswap_u32x4(x3_1))
intrinsics.unaligned_store((^u32x4)(dst[20:]), _byteswap_u32x4(intrinsics.simd_add(x4_1, s4)))
intrinsics.unaligned_store((^u32x4)(dst[21:]), _byteswap_u32x4(intrinsics.simd_add(x5_1, s5)))
intrinsics.unaligned_store((^u32x4)(dst[22:]), _byteswap_u32x4(intrinsics.simd_add(x6_1, s6)))
intrinsics.unaligned_store((^u32x4)(dst[23:]), _byteswap_u32x4(intrinsics.simd_add(x7_1, s7)))
intrinsics.unaligned_store((^u32x4)(dst[24:]), _byteswap_u32x4(intrinsics.simd_add(x8_1, s8)))
intrinsics.unaligned_store((^u32x4)(dst[25:]), _byteswap_u32x4(intrinsics.simd_add(x9_1, s9)))
intrinsics.unaligned_store((^u32x4)(dst[26:]), _byteswap_u32x4(intrinsics.simd_add(x10_1, s10)))
intrinsics.unaligned_store((^u32x4)(dst[27:]), _byteswap_u32x4(intrinsics.simd_add(x11_1, s11)))
intrinsics.unaligned_store((^u32x4)(dst[28:]), _byteswap_u32x4(x12_1))
intrinsics.unaligned_store((^u32x4)(dst[29:]), _byteswap_u32x4(intrinsics.simd_add(x13_1, s13)))
intrinsics.unaligned_store((^u32x4)(dst[30:]), _byteswap_u32x4(intrinsics.simd_add(x14_1, s14)))
intrinsics.unaligned_store((^u32x4)(dst[31:]), _byteswap_u32x4(intrinsics.simd_add(x15_1, s15)))
}
s12 = intrinsics.simd_add(s12, _CTR_INC_8)
dst = dst[32:]
}
} else {
for _ in 0..<4 {
x0, x1, x2, x3 := S0, S1, S2, S3
x4, x5, x6, x7 := s4, s5, s6, s7
x8, x9, x10, x11 := s8, s9, s10, s11
x12, x13, x14, x15 := s12, s13, s14, s15
for i := CHACHA_ROUNDS; i > 0; i = i - 2 {
x0, x4, x8, x12 = quarter_round(x0, x4, x8, x12)
x1, x5, x9, x13 = quarter_round(x1, x5, x9, x13)
x2, x6, x10, x14 = quarter_round(x2, x6, x10, x14)
x3, x7, x11, x15 = quarter_round(x3, x7, x11, x15)
x0, x5, x10, x15 = quarter_round(x0, x5, x10, x15)
x1, x6, x11, x12 = quarter_round(x1, x6, x11, x12)
x2, x7, x8, x13 = quarter_round(x2, x7, x8, x13)
x3, x4, x9, x14 = quarter_round(x3, x4, x9, x14)
}
when ODIN_ENDIAN == .Little {
intrinsics.unaligned_store((^u32x4)(dst[0:]), x0)
intrinsics.unaligned_store((^u32x4)(dst[1:]), x1)
intrinsics.unaligned_store((^u32x4)(dst[2:]), x2)
intrinsics.unaligned_store((^u32x4)(dst[3:]), x3)
intrinsics.unaligned_store((^u32x4)(dst[4:]), intrinsics.simd_add(x4, s4))
intrinsics.unaligned_store((^u32x4)(dst[5:]), intrinsics.simd_add(x5, s5))
intrinsics.unaligned_store((^u32x4)(dst[6:]), intrinsics.simd_add(x6, s6))
intrinsics.unaligned_store((^u32x4)(dst[7:]), intrinsics.simd_add(x7, s7))
intrinsics.unaligned_store((^u32x4)(dst[8:]), intrinsics.simd_add(x8, s8))
intrinsics.unaligned_store((^u32x4)(dst[9:]), intrinsics.simd_add(x9, s9))
intrinsics.unaligned_store((^u32x4)(dst[10:]), intrinsics.simd_add(x10, s10))
intrinsics.unaligned_store((^u32x4)(dst[11:]), intrinsics.simd_add(x11, s11))
intrinsics.unaligned_store((^u32x4)(dst[12:]), x12)
intrinsics.unaligned_store((^u32x4)(dst[13:]), intrinsics.simd_add(x13, s13))
intrinsics.unaligned_store((^u32x4)(dst[14:]), intrinsics.simd_add(x14, s14))
intrinsics.unaligned_store((^u32x4)(dst[15:]), intrinsics.simd_add(x15, s15))
} else {
intrinsics.unaligned_store((^u32x4)(dst[0:]), _byteswap_u32x4(x0))
intrinsics.unaligned_store((^u32x4)(dst[1:]), _byteswap_u32x4(x1))
intrinsics.unaligned_store((^u32x4)(dst[2:]), _byteswap_u32x4(x2))
intrinsics.unaligned_store((^u32x4)(dst[3:]), _byteswap_u32x4(x3))
intrinsics.unaligned_store((^u32x4)(dst[4:]), _byteswap_u32x4(intrinsics.simd_add(x4, s4)))
intrinsics.unaligned_store((^u32x4)(dst[5:]), _byteswap_u32x4(intrinsics.simd_add(x5, s5)))
intrinsics.unaligned_store((^u32x4)(dst[6:]), _byteswap_u32x4(intrinsics.simd_add(x6, s6)))
intrinsics.unaligned_store((^u32x4)(dst[7:]), _byteswap_u32x4(intrinsics.simd_add(x7, s7)))
intrinsics.unaligned_store((^u32x4)(dst[8:]), _byteswap_u32x4(intrinsics.simd_add(x8, s8)))
intrinsics.unaligned_store((^u32x4)(dst[9:]), _byteswap_u32x4(intrinsics.simd_add(x9, s9)))
intrinsics.unaligned_store((^u32x4)(dst[10:]), _byteswap_u32x4(intrinsics.simd_add(x10, s10)))
intrinsics.unaligned_store((^u32x4)(dst[11:]), _byteswap_u32x4(intrinsics.simd_add(x11, s11)))
intrinsics.unaligned_store((^u32x4)(dst[12:]), _byteswap_u32x4(x12))
intrinsics.unaligned_store((^u32x4)(dst[13:]), _byteswap_u32x4(intrinsics.simd_add(x13, s13)))
intrinsics.unaligned_store((^u32x4)(dst[14:]), _byteswap_u32x4(intrinsics.simd_add(x14, s14)))
intrinsics.unaligned_store((^u32x4)(dst[15:]), _byteswap_u32x4(intrinsics.simd_add(x15, s15)))
}
s12 = intrinsics.simd_add(s12, _CTR_INC_4)
dst = dst[16:]
}
}
}

View File

@@ -0,0 +1,197 @@
#+build amd64
package runtime
import "base:intrinsics"
#assert(ODIN_ENDIAN == .Little)
@(private = "file")
u32x8 :: #simd[8]u32
@(private = "file")
u32x4 :: #simd[4]u32
@(private = "file")
S0: u32x8 : {
CHACHA_SIGMA_0, CHACHA_SIGMA_0, CHACHA_SIGMA_0, CHACHA_SIGMA_0,
CHACHA_SIGMA_0, CHACHA_SIGMA_0, CHACHA_SIGMA_0, CHACHA_SIGMA_0,
}
@(private = "file")
S1: u32x8 : {
CHACHA_SIGMA_1, CHACHA_SIGMA_1, CHACHA_SIGMA_1, CHACHA_SIGMA_1,
CHACHA_SIGMA_1, CHACHA_SIGMA_1, CHACHA_SIGMA_1, CHACHA_SIGMA_1,
}
@(private = "file")
S2: u32x8 : {
CHACHA_SIGMA_2, CHACHA_SIGMA_2, CHACHA_SIGMA_2, CHACHA_SIGMA_2,
CHACHA_SIGMA_2, CHACHA_SIGMA_2, CHACHA_SIGMA_2, CHACHA_SIGMA_2,
}
@(private = "file")
S3: u32x8 : {
CHACHA_SIGMA_3, CHACHA_SIGMA_3, CHACHA_SIGMA_3, CHACHA_SIGMA_3,
CHACHA_SIGMA_3, CHACHA_SIGMA_3, CHACHA_SIGMA_3, CHACHA_SIGMA_3,
}
@(private = "file")
_ROT_7L: u32x8 : {7, 7, 7, 7, 7, 7, 7, 7}
@(private = "file")
_ROT_7R: u32x8 : {25, 25, 25, 25, 25, 25, 25, 25}
@(private = "file")
_ROT_12L: u32x8 : {12, 12, 12, 12, 12, 12, 12, 12}
@(private = "file")
_ROT_12R: u32x8 : {20, 20, 20, 20, 20, 20, 20, 20}
@(private = "file")
_ROT_8L: u32x8 : {8, 8, 8, 8, 8, 8, 8, 8}
@(private = "file")
_ROT_8R: u32x8 : {24, 24, 24, 24, 24, 24, 24, 24}
@(private = "file")
_ROT_16: u32x8 : {16, 16, 16, 16, 16, 16, 16, 16}
@(private = "file")
_CTR_INC_8: u32x8 : {8, 8, 8, 8, 8, 8, 8, 8}
// To the best of my knowledge this is only really useful on
// modern x86-64 as most ARM silicon is missing support for SVE2.
@(private, enable_target_feature = "avx,avx2")
chacha8rand_refill_simd256 :: proc(r: ^Default_Random_State) {
// Initialize the base state.
k: [^]u32 = (^u32)(raw_data(r._buf[RNG_OUTPUT_PER_ITER:]))
s4_ := k[0]
s5_ := k[1]
s6_ := k[2]
s7_ := k[3]
s8_ := k[4]
s9_ := k[5]
s10_ := k[6]
s11_ := k[7]
// 8-lane ChaCha8.
s4 := u32x8{s4_, s4_, s4_, s4_, s4_, s4_, s4_, s4_}
s5 := u32x8{s5_, s5_, s5_, s5_, s5_, s5_, s5_, s5_}
s6 := u32x8{s6_, s6_, s6_, s6_, s6_, s6_, s6_, s6_}
s7 := u32x8{s7_, s7_, s7_, s7_, s7_, s7_, s7_, s7_}
s8 := u32x8{s8_, s8_, s8_, s8_, s8_, s8_, s8_, s8_}
s9 := u32x8{s9_, s9_, s9_, s9_, s9_, s9_, s9_, s9_}
s10 := u32x8{s10_, s10_, s10_, s10_, s10_, s10_, s10_, s10_}
s11 := u32x8{s11_, s11_, s11_, s11_, s11_, s11_, s11_, s11_}
s12 := u32x8{0, 1, 2, 3, 4, 5, 6, 7}
s13, s14, s15: u32x8
u32x4 :: #simd[4]u32
dst: [^]u32x4 = (^u32x4)(raw_data(r._buf[:]))
quarter_round := #force_inline proc "contextless" (a, b, c, d: u32x8) -> (u32x8, u32x8, u32x8, u32x8) {
a, b, c, d := a, b, c, d
a = intrinsics.simd_add(a, b)
d = intrinsics.simd_bit_xor(d, a)
d = intrinsics.simd_bit_xor(intrinsics.simd_shl(d, _ROT_16), intrinsics.simd_shr(d, _ROT_16))
c = intrinsics.simd_add(c, d)
b = intrinsics.simd_bit_xor(b, c)
b = intrinsics.simd_bit_xor(intrinsics.simd_shl(b, _ROT_12L), intrinsics.simd_shr(b, _ROT_12R))
a = intrinsics.simd_add(a, b)
d = intrinsics.simd_bit_xor(d, a)
d = intrinsics.simd_bit_xor(intrinsics.simd_shl(d, _ROT_8L), intrinsics.simd_shr(d, _ROT_8R))
c = intrinsics.simd_add(c, d)
b = intrinsics.simd_bit_xor(b, c)
b = intrinsics.simd_bit_xor(intrinsics.simd_shl(b, _ROT_7L), intrinsics.simd_shr(b, _ROT_7R))
return a, b, c, d
}
for _ in 0..<2 {
x0, x1, x2, x3 := S0, S1, S2, S3
x4, x5, x6, x7 := s4, s5, s6, s7
x8, x9, x10, x11 := s8, s9, s10, s11
x12, x13, x14, x15 := s12, s13, s14, s15
for i := CHACHA_ROUNDS; i > 0; i = i - 2 {
x0, x4, x8, x12 = quarter_round(x0, x4, x8, x12)
x1, x5, x9, x13 = quarter_round(x1, x5, x9, x13)
x2, x6, x10, x14 = quarter_round(x2, x6, x10, x14)
x3, x7, x11, x15 = quarter_round(x3, x7, x11, x15)
x0, x5, x10, x15 = quarter_round(x0, x5, x10, x15)
x1, x6, x11, x12 = quarter_round(x1, x6, x11, x12)
x2, x7, x8, x13 = quarter_round(x2, x7, x8, x13)
x3, x4, x9, x14 = quarter_round(x3, x4, x9, x14)
}
x4 = intrinsics.simd_add(x4, s4)
x5 = intrinsics.simd_add(x5, s5)
x6 = intrinsics.simd_add(x6, s6)
x7 = intrinsics.simd_add(x7, s7)
x8 = intrinsics.simd_add(x8, s8)
x9 = intrinsics.simd_add(x9, s9)
x10 = intrinsics.simd_add(x10, s10)
x11 = intrinsics.simd_add(x11, s11)
x13 = intrinsics.simd_add(x13, s13)
x14 = intrinsics.simd_add(x14, s14)
x15 = intrinsics.simd_add(x15, s15)
// Ok, now we have x0->x15 with 8 lanes, but we need to
// output the first 4 blocks, then the second 4 blocks.
//
// LLVM appears not to consider "this instruction is totally
// awful on the given microarchitcture", which leads to
// `VPCOMPRESSED` being generated if and only if (⟺) AVX512 support is
// enabled for `intrinsics.simd_masked_compress_store`.
// On Zen 4, this leads to a 50% performance regression vs
// the 128-bit SIMD code.
//
// The fake intrinsic (because LLVM doesn't appear to have
// an amd64 specific one), doesn't generate `VEXTRACTI128`,
// but instead does cleverness without horrible regressions.
intrinsics.unaligned_store((^u32x4)(dst[0:]), _mm_mm256_extracti128_si256(x0, 0))
intrinsics.unaligned_store((^u32x4)(dst[1:]), _mm_mm256_extracti128_si256(x1, 0))
intrinsics.unaligned_store((^u32x4)(dst[2:]), _mm_mm256_extracti128_si256(x2, 0))
intrinsics.unaligned_store((^u32x4)(dst[3:]), _mm_mm256_extracti128_si256(x3, 0))
intrinsics.unaligned_store((^u32x4)(dst[4:]), _mm_mm256_extracti128_si256(x4, 0))
intrinsics.unaligned_store((^u32x4)(dst[5:]), _mm_mm256_extracti128_si256(x5, 0))
intrinsics.unaligned_store((^u32x4)(dst[6:]), _mm_mm256_extracti128_si256(x6, 0))
intrinsics.unaligned_store((^u32x4)(dst[7:]), _mm_mm256_extracti128_si256(x7, 0))
intrinsics.unaligned_store((^u32x4)(dst[8:]), _mm_mm256_extracti128_si256(x8, 0))
intrinsics.unaligned_store((^u32x4)(dst[9:]), _mm_mm256_extracti128_si256(x9, 0))
intrinsics.unaligned_store((^u32x4)(dst[10:]), _mm_mm256_extracti128_si256(x10, 0))
intrinsics.unaligned_store((^u32x4)(dst[11:]), _mm_mm256_extracti128_si256(x11, 0))
intrinsics.unaligned_store((^u32x4)(dst[12:]), _mm_mm256_extracti128_si256(x12, 0))
intrinsics.unaligned_store((^u32x4)(dst[13:]), _mm_mm256_extracti128_si256(x13, 0))
intrinsics.unaligned_store((^u32x4)(dst[14:]), _mm_mm256_extracti128_si256(x14, 0))
intrinsics.unaligned_store((^u32x4)(dst[15:]), _mm_mm256_extracti128_si256(x15, 0))
intrinsics.unaligned_store((^u32x4)(dst[16:]), _mm_mm256_extracti128_si256(x0, 1))
intrinsics.unaligned_store((^u32x4)(dst[17:]), _mm_mm256_extracti128_si256(x1, 1))
intrinsics.unaligned_store((^u32x4)(dst[18:]), _mm_mm256_extracti128_si256(x2, 1))
intrinsics.unaligned_store((^u32x4)(dst[19:]), _mm_mm256_extracti128_si256(x3, 1))
intrinsics.unaligned_store((^u32x4)(dst[20:]), _mm_mm256_extracti128_si256(x4, 1))
intrinsics.unaligned_store((^u32x4)(dst[21:]), _mm_mm256_extracti128_si256(x5, 1))
intrinsics.unaligned_store((^u32x4)(dst[22:]), _mm_mm256_extracti128_si256(x6, 1))
intrinsics.unaligned_store((^u32x4)(dst[23:]), _mm_mm256_extracti128_si256(x7, 1))
intrinsics.unaligned_store((^u32x4)(dst[24:]), _mm_mm256_extracti128_si256(x8, 1))
intrinsics.unaligned_store((^u32x4)(dst[25:]), _mm_mm256_extracti128_si256(x9, 1))
intrinsics.unaligned_store((^u32x4)(dst[26:]), _mm_mm256_extracti128_si256(x10, 1))
intrinsics.unaligned_store((^u32x4)(dst[27:]), _mm_mm256_extracti128_si256(x11, 1))
intrinsics.unaligned_store((^u32x4)(dst[28:]), _mm_mm256_extracti128_si256(x12, 1))
intrinsics.unaligned_store((^u32x4)(dst[29:]), _mm_mm256_extracti128_si256(x13, 1))
intrinsics.unaligned_store((^u32x4)(dst[30:]), _mm_mm256_extracti128_si256(x14, 1))
intrinsics.unaligned_store((^u32x4)(dst[31:]), _mm_mm256_extracti128_si256(x15, 1))
s12 = intrinsics.simd_add(s12, _CTR_INC_8)
dst = dst[32:]
}
}
@(private = "file", require_results, enable_target_feature="avx2")
_mm_mm256_extracti128_si256 :: #force_inline proc "c" (a: u32x8, $OFFSET: int) -> u32x4 {
when OFFSET == 0 {
return intrinsics.simd_shuffle(a, a, 0, 1, 2, 3)
} else when OFFSET == 1 {
return intrinsics.simd_shuffle(a, a, 4, 5, 6, 7)
} else {
#panic("chacha8rand: invalid offset")
}
}

View File

@@ -1,23 +1,13 @@
package runtime
Thread_Local_Cleaner :: #type proc "odin" ()
Thread_Local_Cleaner_Odin :: #type proc "odin" ()
Thread_Local_Cleaner_Contextless :: #type proc "contextless" ()
Thread_Local_Cleaner :: union #shared_nil {Thread_Local_Cleaner_Odin, Thread_Local_Cleaner_Contextless}
@(private="file")
thread_local_cleaners: [8]Thread_Local_Cleaner
// The thread ID is cached here to save time from making syscalls with every
// call.
@(thread_local) local_thread_id: int
// Return the current thread ID.
@(require_results)
get_current_thread_id :: proc "contextless" () -> int {
if local_thread_id == 0 {
local_thread_id = _get_current_thread_id()
assert_contextless(local_thread_id != 0, "_get_current_thread_id returned 0.")
}
return local_thread_id
}
// Add a procedure that will be run at the end of a thread for the purpose of
// deallocating state marked as `thread_local`.
@@ -43,6 +33,9 @@ run_thread_local_cleaners :: proc "odin" () {
if p == nil {
break
}
p()
switch v in p {
case Thread_Local_Cleaner_Odin: v()
case Thread_Local_Cleaner_Contextless: v()
}
}
}

View File

@@ -1,3 +1,4 @@
#+build !bedrock
package runtime
import "base:intrinsics"

View File

@@ -44,7 +44,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
WASM_Allocator :: struct #no_copy {
WASM_Allocator :: struct {
// The minimum alignment of allocations.
alignment: uint,
// A region that contains as payload a single forward linked list of pointers to
@@ -90,10 +90,12 @@ wasm_allocator_init :: proc(a: ^WASM_Allocator, alignment: uint = 8) {
global_default_wasm_allocator_data: WASM_Allocator
@(require_results)
default_wasm_allocator :: proc() -> Allocator {
return wasm_allocator(&global_default_wasm_allocator_data)
}
@(require_results)
wasm_allocator :: proc(a: ^WASM_Allocator) -> Allocator {
return {
data = a,

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,
}

Binary file not shown.

View File

@@ -19,16 +19,27 @@ if "%VSCMD_ARG_TGT_ARCH%" neq "x64" (
)
)
where /Q git.exe || goto skip_git_hash
if not exist .git\ goto skip_git_hash
for /f "tokens=1,2" %%i IN ('git show "--pretty=%%cd %%h" "--date=format:%%Y-%%m-%%d" --no-patch --no-notes HEAD') do (
set CURR_DATE_TIME=%%i
set GIT_SHA=%%j
)
if %ERRORLEVEL% equ 0 (
goto have_git_hash_and_date
)
:skip_git_hash
pushd misc
cl /nologo get-date.c
popd
for /f %%i in ('misc\get-date') do (
for /f %%i in ('get-date') do (
set CURR_DATE_TIME=%%i
rem Don't set GIT_SHA
)
popd
:have_git_hash_and_date
set curr_year=%CURR_DATE_TIME:~0,4%
set curr_month=%CURR_DATE_TIME:~4,2%
set curr_day=%CURR_DATE_TIME:~6,2%
set curr_month=%CURR_DATE_TIME:~5,2%
set curr_day=%CURR_DATE_TIME:~8,2%
:: Make sure this is a decent name and not generic
set exe_name=odin.exe
@@ -61,31 +72,14 @@ if %release_mode% equ 0 (
set V4=0
set odin_version_full="%V1%.%V2%.%V3%.%V4%"
set odin_version_raw="dev-%V1%-%V2%"
set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -EHsc- -GR- -GF
rem Parse source code as utf-8 even on shift-jis and other codepages
rem See https://learn.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170
set compiler_flags= %compiler_flags% /utf-8
set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\"
set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\" -DGIT_SHA=\"%GIT_SHA%\"
rem fileversion is defined as {Major,Minor,Build,Private: u16} so a bit limited
set rc_flags=-nologo ^
-DV1=%V1% -DV2=%V2% -DV3=%V3% -DV4=%V4% ^
-DVF=%odin_version_full% -DNIGHTLY=%nightly%
where /Q git.exe || goto skip_git_hash
if not exist .git\ goto skip_git_hash
for /f "tokens=1,2" %%i IN ('git show "--pretty=%%cd %%h" "--date=format:%%Y-%%m" --no-patch --no-notes HEAD') do (
set odin_version_raw=dev-%%i
set GIT_SHA=%%j
)
if %ERRORLEVEL% equ 0 (
set compiler_defines=%compiler_defines% -DGIT_SHA=\"%GIT_SHA%\"
set rc_flags=%rc_flags% -DGIT_SHA=%GIT_SHA% -DVP=%odin_version_raw%:%GIT_SHA%
) else (
set rc_flags=%rc_flags% -DVP=%odin_version_raw%
)
:skip_git_hash
set rc_flags=-nologo "-DGIT_SHA=%GIT_SHA% -DVP=dev-%V1%-%V2%:%GIT_SHA% nologo -DV1=%V1% -DV2=%V2% -DV3=%V3% -DV4=%V4% -DVF=%odin_version_full% -DNIGHTLY=%nightly%"
if %nightly% equ 1 set compiler_defines=%compiler_defines% -DNIGHTLY
@@ -100,6 +94,7 @@ if %release_mode% EQU 0 ( rem Debug
set compiler_warnings= ^
-W4 -WX ^
-wd4100 -wd4101 -wd4127 -wd4146 ^
-wd4324 ^
-wd4505 ^
-wd4456 -wd4457
@@ -112,16 +107,6 @@ set libs= ^
set odin_res=misc\odin.res
set odin_rc=misc\odin.rc
rem DO NOT TOUCH!
rem THIS TILDE STUFF IS FOR DEVELOPMENT ONLY!
set tilde_backend=0
if %tilde_backend% EQU 1 (
set libs=%libs% src\tilde\tb.lib
set compiler_defines=%compiler_defines% -DODIN_TILDE_BACKEND
)
rem DO NOT TOUCH!
set linker_flags= -incremental:no -opt:ref -subsystem:console -MANIFEST:EMBED
if %release_mode% EQU 0 ( rem Debug
@@ -153,4 +138,4 @@ if %release_mode% EQU 0 echo: & echo Debug compiler built. Note: run "build.bat
del *.obj > NUL 2> NUL
:end_of_build
:end_of_build

View File

@@ -6,7 +6,6 @@ set -eu
: ${LDFLAGS=}
: ${LLVM_CONFIG=}
CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$(date +"%Y-%m")\""
CXXFLAGS="$CXXFLAGS -std=c++14"
DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value"
LDFLAGS="$LDFLAGS -pthread -lm"
@@ -15,47 +14,54 @@ OS_NAME="$(uname -s)"
if [ -d ".git" ] && [ -n "$(command -v git)" ]; then
GIT_SHA=$(git show --pretty='%h' --no-patch --no-notes HEAD)
GIT_DATE=$(git show "--pretty=%cd" "--date=format:%Y-%m" --no-patch --no-notes HEAD)
CPPFLAGS="$CPPFLAGS -DGIT_SHA=\"$GIT_SHA\""
else
GIT_DATE=$(date +"%Y-%m")
fi
CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$GIT_DATE\""
error() {
printf "ERROR: %s\n" "$1"
exit 1
}
SUPPORTED_LLVM_VERSIONS="22 21 20 19 18 17 14"
# Brew advises people not to add llvm to their $PATH, so try and use brew to find it.
if [ -z "$LLVM_CONFIG" ] && [ -n "$(command -v brew)" ]; then
if [ -n "$(command -v $(brew --prefix llvm@20)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@20)/bin/llvm-config"
elif [ -n "$(command -v $(brew --prefix llvm@19)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@19)/bin/llvm-config"
elif [ -n "$(command -v $(brew --prefix llvm@18)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@18)/bin/llvm-config"
elif [ -n "$(command -v $(brew --prefix llvm@17)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@17)/bin/llvm-config"
elif [ -n "$(command -v $(brew --prefix llvm@14)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@14)/bin/llvm-config"
fi
for V in $SUPPORTED_LLVM_VERSIONS; do
if [ -n "$(command -v $(brew --prefix llvm@$V)/bin/llvm-config)" ]; then
LLVM_CONFIG="$(brew --prefix llvm@$V)/bin/llvm-config"
break
fi
done
fi
if [ -z "$LLVM_CONFIG" ]; then
# darwin, linux, openbsd
if [ -n "$(command -v llvm-config-20)" ]; then LLVM_CONFIG="llvm-config-20"
elif [ -n "$(command -v llvm-config-19)" ]; then LLVM_CONFIG="llvm-config-19"
elif [ -n "$(command -v llvm-config-18)" ]; then LLVM_CONFIG="llvm-config-18"
elif [ -n "$(command -v llvm-config-17)" ]; then LLVM_CONFIG="llvm-config-17"
elif [ -n "$(command -v llvm-config-14)" ]; then LLVM_CONFIG="llvm-config-14"
elif [ -n "$(command -v llvm-config-13)" ]; then LLVM_CONFIG="llvm-config-13"
elif [ -n "$(command -v llvm-config-12)" ]; then LLVM_CONFIG="llvm-config-12"
elif [ -n "$(command -v llvm-config-11)" ]; then LLVM_CONFIG="llvm-config-11"
# freebsd
elif [ -n "$(command -v llvm-config20)" ]; then LLVM_CONFIG="llvm-config20"
elif [ -n "$(command -v llvm-config19)" ]; then LLVM_CONFIG="llvm-config19"
elif [ -n "$(command -v llvm-config18)" ]; then LLVM_CONFIG="llvm-config18"
elif [ -n "$(command -v llvm-config17)" ]; then LLVM_CONFIG="llvm-config17"
elif [ -n "$(command -v llvm-config14)" ]; then LLVM_CONFIG="llvm-config14"
elif [ -n "$(command -v llvm-config13)" ]; then LLVM_CONFIG="llvm-config13"
elif [ -n "$(command -v llvm-config12)" ]; then LLVM_CONFIG="llvm-config12"
elif [ -n "$(command -v llvm-config11)" ]; then LLVM_CONFIG="llvm-config11"
# fallback
elif [ -n "$(command -v llvm-config)" ]; then LLVM_CONFIG="llvm-config"
else
error "No llvm-config command found. Set LLVM_CONFIG to proceed."
DEFAULT_VERSION=""
if [ -n "$(command -v llvm-config)" ]; then
DEFAULT_VERSION=$(llvm-config --version | awk -F. '{print $1}')
fi
for V in $SUPPORTED_LLVM_VERSIONS; do
if [ "$DEFAULT_VERSION" = "$V" ]; then
LLVM_CONFIG="llvm-config"
break
# darwin, linux, openbsd
elif [ -n "$(command -v "llvm-config-$V")" ]; then
LLVM_CONFIG="llvm-config-$V"
break
# freebsd
elif [ -n "$(command -v "llvm-config$V")" ]; then
LLVM_CONFIG="llvm-config$V"
break
fi
done
if [ -z "$LLVM_CONFIG" ]; then
error "No supported llvm-config command found. Set LLVM_CONFIG to proceed."
fi
fi
@@ -72,18 +78,12 @@ LLVM_VERSION_MAJOR="$(echo $LLVM_VERSION | awk -F. '{print $1}')"
LLVM_VERSION_MINOR="$(echo $LLVM_VERSION | awk -F. '{print $2}')"
LLVM_VERSION_PATCH="$(echo $LLVM_VERSION | awk -F. '{print $3}')"
if [ $LLVM_VERSION_MAJOR -lt 11 ] || ([ $LLVM_VERSION_MAJOR -gt 14 ] && [ $LLVM_VERSION_MAJOR -lt 17 ]) || [ $LLVM_VERSION_MAJOR -gt 20 ]; then
error "Invalid LLVM version $LLVM_VERSION: must be 11, 12, 13, 14, 17, 18, 19 or 20"
if [ $LLVM_VERSION_MAJOR -lt 14 ] || ([ $LLVM_VERSION_MAJOR -gt 14 ] && [ $LLVM_VERSION_MAJOR -lt 17 ]) || [ $LLVM_VERSION_MAJOR -gt 22 ]; then
error "Invalid LLVM version $LLVM_VERSION: must be 14, 17, 18, 19, 20, 21 or 22"
fi
case "$OS_NAME" in
Darwin)
if [ "$OS_ARCH" = "arm64" ]; then
if [ $LLVM_VERSION_MAJOR -lt 13 ]; then
error "Invalid LLVM version $LLVM_VERSION: Darwin Arm64 requires LLVM 13, 14, 17, 18, 19 or 20"
fi
fi
darwin_sysroot=
if [ $(which xcrun) ]; then
darwin_sysroot="--sysroot $(xcrun --sdk macosx --show-sdk-path)"
@@ -114,12 +114,7 @@ Linux)
;;
OpenBSD)
CXXFLAGS="$CXXFLAGS -I/usr/local/include $($LLVM_CONFIG --cxxflags --ldflags)"
LDFLAGS="$LDFLAGS -lstdc++ -L/usr/local/lib -liconv"
LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
;;
Haiku)
CXXFLAGS="$CXXFLAGS -D_GNU_SOURCE $($LLVM_CONFIG --cxxflags --ldflags) -I/system/develop/headers/private/shared -I/system/develop/headers/private/kernel"
LDFLAGS="$LDFLAGS -lstdc++ -liconv"
LDFLAGS="$LDFLAGS -lstdc++ -L/usr/local/lib -Wl,-rpath,$($LLVM_CONFIG --libdir) -liconv"
LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)"
;;
*)

71
check_all.bat Normal file
View File

@@ -0,0 +1,71 @@
@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 freebsd_i386
odin check examples\all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_i386
)
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
)

74
check_all.sh Executable file
View File

@@ -0,0 +1,74 @@
#!/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 freebsd_i386
odin check examples/all -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -target:freebsd_i386
;;
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

View File

@@ -6,7 +6,14 @@ LLVM_CONFIG="llvm-config-20"
DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value"
CPPFLAGS="-DODIN_VERSION_RAW=\"dev-$(date +"%Y-%m")\""
if [ -d ".git" ] && [ -n "$(command -v git)" ]; then
GIT_SHA=$(git show --pretty='%h' --no-patch --no-notes HEAD)
GIT_DATE=$(git show "--pretty=%cd" "--date=format:%Y-%m" --no-patch --no-notes HEAD)
CPPFLAGS="$CPPFLAGS -DGIT_SHA=\"$GIT_SHA\""
else
GIT_DATE=$(date +"%Y-%m")
fi
CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$GIT_DATE\""
CXXFLAGS="-std=c++14 $($LLVM_CONFIG --cxxflags --ldflags)"
LDFLAGS="-static -lm -lzstd -lz -lffi -pthread -ldl -fuse-ld=mold"

8
ci/remove_windows_binaries.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env sh
find "$1" -type f \(\
-iname "*.exe" \
-o -iname "*.dll" \
-o -iname "*.lib" \
-o -iname "*.pdb" \
\) -delete

6
codecov.yml Normal file
View File

@@ -0,0 +1,6 @@
comment: false
coverage:
status:
project:
default:
threshold: 1%

2
core/bufio/doc.odin Normal file
View File

@@ -0,0 +1,2 @@
// Wraps an `io.Stream` interface to provide buffered I/O.
package bufio

View File

@@ -38,5 +38,5 @@ _read_writer_procedure := proc(stream_data: rawptr, mode: io.Stream_Mode, p: []b
case .Query:
return io.query_utility({.Flush, .Read, .Write, .Query})
}
return 0, .Empty
return 0, .Unsupported
}

View File

@@ -1,14 +1,14 @@
package bufio
import "base:runtime"
import "core:io"
import "core:mem"
import "core:unicode/utf8"
import "core:bytes"
// Reader is a buffered wrapper for an io.Reader
Reader :: struct {
buf: []byte,
buf_allocator: mem.Allocator,
buf_allocator: runtime.Allocator,
rd: io.Reader, // reader
r, w: int, // read and write positions for buf
@@ -29,6 +29,7 @@ MIN_READ_BUFFER_SIZE :: 16
@(private)
DEFAULT_MAX_CONSECUTIVE_EMPTY_READS :: 128
// reader_init initializes using an `allocator`
reader_init :: proc(b: ^Reader, rd: io.Reader, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator, loc := #caller_location) {
size := size
size = max(size, MIN_READ_BUFFER_SIZE)
@@ -37,22 +38,25 @@ reader_init :: proc(b: ^Reader, rd: io.Reader, size: int = DEFAULT_BUF_SIZE, all
b.buf = make([]byte, size, allocator, loc)
}
// reader_init initializes using a user provided bytes buffer `buf`
reader_init_with_buf :: proc(b: ^Reader, rd: io.Reader, buf: []byte) {
reader_reset(b, rd)
b.buf_allocator = {}
b.buf = buf
}
// reader_destroy destroys the underlying buffer with its associated allocator IFF that allocator has been set
// reader_destroy destroys the underlying buffer with its associated allocator if and only if (⟺) that allocator has been set
reader_destroy :: proc(b: ^Reader) {
delete(b.buf, b.buf_allocator)
b^ = {}
}
// reader_size returns the number of bytes in the backing buffer
reader_size :: proc(b: ^Reader) -> int {
return len(b.buf)
}
// reader_reset resets the read and write positions, and the error values
reader_reset :: proc(b: ^Reader, r: io.Reader) {
b.rd = r
b.r, b.w = 0, 0
@@ -343,7 +347,7 @@ _reader_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offse
case .Query:
return io.query_utility({.Read, .Destroy, .Query})
}
return 0, .Empty
return 0, .Unsupported
}
//
@@ -351,16 +355,15 @@ _reader_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offse
//
// reader_read_slice reads until the first occurrence of delim from the reader
// It returns a slice pointing at the bytes in the buffer
// The bytes stop being valid at the next read
// If reader_read_slice encounters an error before finding a delimiter
// reader_read_slice fails with error .Buffer_Full if the buffer fills without a delim
// Because the data returned from reader_read_slice will be overwritten on the
// next IO operation, reader_read_bytes or reader_read_string is usually preferred
//
// reader_read_slice returns err != nil if and only if line does not end in delim
// reader_read_slice reads until the first occurrence of delim in the input,
// returning a slice pointing at the bytes in the internal buffer.
// The returned slice is only valid until the next read call.
// If the buffer fills without finding delim, it returns .Buffer_Full.
// If the underlying reader returns an error before finding delim, that error is returned.
// Because the returned data will be overwritten by the next I/O operation,
// reader_read_bytes or reader_read_string is usually preferred.
//
// reader_read_slice returns err != nil if and only if line does not end in delim.
reader_read_slice :: proc(b: ^Reader, delim: byte) -> (line: []byte, err: io.Error) {
s := 0
for {

View File

@@ -1,10 +1,10 @@
package bufio
import "core:bytes"
import "core:io"
import "core:mem"
import "core:unicode/utf8"
import "base:runtime"
import "base:intrinsics"
import "core:io"
import "core:bytes"
import "core:unicode/utf8"
// Extra errors returns by scanning procedures
Scanner_Extra_Error :: enum i32 {
@@ -46,6 +46,7 @@ DEFAULT_MAX_SCAN_TOKEN_SIZE :: 1<<16
@(private)
_INIT_BUF_SIZE :: 4096
// Initializes a Scanner buffer an allocator `buf_allocator`
scanner_init :: proc(s: ^Scanner, r: io.Reader, buf_allocator := context.allocator) -> ^Scanner {
s.r = r
s.split = scan_lines
@@ -53,11 +54,21 @@ scanner_init :: proc(s: ^Scanner, r: io.Reader, buf_allocator := context.allocat
s.buf.allocator = buf_allocator
return s
}
// Initializes a Scanner buffer a user provided bytes buffer `buf`
scanner_init_with_buffer :: proc(s: ^Scanner, r: io.Reader, buf: []byte) -> ^Scanner {
s.r = r
s.split = scan_lines
s.max_token_size = DEFAULT_MAX_SCAN_TOKEN_SIZE
s.buf = mem.buffer_from_slice(buf)
s.buf = transmute([dynamic]byte)runtime.Raw_Dynamic_Array{
data = raw_data(buf),
len = 0,
cap = len(buf),
allocator = runtime.Allocator{
procedure = runtime.nil_allocator_proc,
data = nil,
},
}
resize(&s.buf, cap(s.buf))
return s
}
@@ -75,24 +86,27 @@ scanner_error :: proc(s: ^Scanner) -> Scanner_Error {
return s._err
}
// Returns the most recent token created by scanner_scan.
// Returns the most recent token created by 'scan'.
// The underlying array may point to data that may be overwritten
// by another call to scanner_scan.
// by another call to 'scan'.
// Treat the returned value as if it is immutable.
scanner_bytes :: proc(s: ^Scanner) -> []byte {
return s.token
}
// Returns the most recent token created by scanner_scan.
// Returns the most recent token created by 'scan'.
// The underlying array may point to data that may be overwritten
// by another call to scanner_scan.
// by another call to 'scan'.
// Treat the returned value as if it is immutable.
scanner_text :: proc(s: ^Scanner) -> string {
return string(s.token)
}
// scanner_scan advances the scanner
scanner_scan :: proc(s: ^Scanner) -> bool {
// scanner_scan is an alias of scan
scanner_scan :: scan
// scan advances the Scanner
scan :: proc(s: ^Scanner) -> bool {
set_err :: proc(s: ^Scanner, err: Scanner_Error) {
switch s._err {
case nil, .EOF:
@@ -229,6 +243,7 @@ scanner_scan :: proc(s: ^Scanner) -> bool {
}
}
// scan_bytes is a splitting procedure that returns each byte as a token
scan_bytes :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, err: Scanner_Error, final_token: bool) {
if at_eof && len(data) == 0 {
return
@@ -236,6 +251,10 @@ scan_bytes :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte,
return 1, data[0:1], nil, false
}
// scan_runes is a splitting procedure that returns each UTF-8 encoded rune as a token.
// The lsit of runes return is equivalent to that of iterating over a string in a 'for in' loop, meaning any
// erroneous UTF-8 encodings will be returned as U+FFFD. Unfortunately this means it is impossible for the "client"
// to know whether a U+FFFD is an expected replacement rune or an encoding of an error.
scan_runes :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, err: Scanner_Error, final_token: bool) {
if at_eof && len(data) == 0 {
return
@@ -264,7 +283,8 @@ scan_runes :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte,
token = ERROR_RUNE
return
}
// scan_words is a splitting procedure that returns each Unicode-space-separated word of text, excluding the surrounded spaces.
// It will never return return an empty string.
scan_words :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, err: Scanner_Error, final_token: bool) {
is_space :: proc "contextless" (r: rune) -> bool {
switch r {
@@ -312,6 +332,8 @@ scan_words :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte,
return
}
// scan_lines is a splitting procedure that returns each line of text stripping of any trailing newline and an optional preceding carriage return (\r?\n).
// A new line is allowed to be empty.
scan_lines :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, err: Scanner_Error, final_token: bool) {
trim_carriage_return :: proc "contextless" (data: []byte) -> []byte {
if len(data) > 0 && data[len(data)-1] == '\r' {

View File

@@ -1,14 +1,14 @@
package bufio
import "base:runtime"
import "core:io"
import "core:mem"
import "core:unicode/utf8"
// import "core:bytes"
// Writer is a buffered wrapper for an io.Writer
Writer :: struct {
buf: []byte,
buf_allocator: mem.Allocator,
buf_allocator: runtime.Allocator,
wr: io.Writer,
n: int,
@@ -19,6 +19,7 @@ Writer :: struct {
}
// Initialized a Writer with an `allocator`
writer_init :: proc(b: ^Writer, wr: io.Writer, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator) {
size := size
size = max(size, MIN_READ_BUFFER_SIZE)
@@ -27,13 +28,14 @@ writer_init :: proc(b: ^Writer, wr: io.Writer, size: int = DEFAULT_BUF_SIZE, all
b.buf = make([]byte, size, allocator)
}
// Initialized a Writer with a user provided buffer `buf`
writer_init_with_buf :: proc(b: ^Writer, wr: io.Writer, buf: []byte) {
writer_reset(b, wr)
b.buf_allocator = {}
b.buf = buf
}
// writer_destroy destroys the underlying buffer with its associated allocator IFF that allocator has been set
// writer_destroy destroys the underlying buffer with its associated allocator if and only if (⟺) that allocator has been set
writer_destroy :: proc(b: ^Writer) {
delete(b.buf, b.buf_allocator)
b^ = {}
@@ -247,5 +249,5 @@ _writer_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offse
case .Query:
return io.query_utility({.Flush, .Write, .Destroy, .Query})
}
return 0, .Empty
return 0, .Unsupported
}

View File

@@ -176,6 +176,11 @@ buffer_write_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int, loc := #caller_loca
return buffer_write(b, ([^]byte)(ptr)[:size], loc=loc)
}
buffer_write_slice :: proc(b: ^Buffer, slice: $S/[]$T, loc := #caller_location) -> (n: int, err: io.Error) {
size := len(slice)*size_of(T)
return buffer_write(b, ([^]byte)(raw_data(slice))[:size], loc=loc)
}
buffer_write_string :: proc(b: ^Buffer, s: string, loc := #caller_location) -> (n: int, err: io.Error) {
b.last_read = .Invalid
m, ok := _buffer_try_grow(b, len(s), loc=loc)
@@ -248,6 +253,12 @@ buffer_read_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int) -> (n: int, err: io.
return buffer_read(b, ([^]byte)(ptr)[:size])
}
buffer_read_slice :: proc(b: ^Buffer, slice: $S/[]$T) -> (n: int, err: io.Error) {
size := len(slice)*size_of(T)
return buffer_read(b, ([^]byte)(raw_data(slice))[:size])
}
buffer_read_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.Error) {
if len(p) == 0 {
return 0, nil
@@ -434,5 +445,5 @@ _buffer_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offse
case .Query:
return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Destroy, .Query})
}
return 0, .Empty
return 0, .Unsupported
}

View File

@@ -1,7 +1,8 @@
// Procedures for manipulation of `[]byte` slices.
package bytes
import "base:runtime"
import "base:intrinsics"
import "core:mem"
import "core:simd"
import "core:unicode"
import "core:unicode/utf8"
@@ -34,20 +35,20 @@ SCANNER_SENTINEL_MIN_128: simd.u8x16 : u8(0xff)
SIMD_REG_SIZE_128 :: 16
clone :: proc(s: []byte, allocator := context.allocator, loc := #caller_location) -> []byte {
c := make([]byte, len(s), allocator, loc)
c := make([]byte, len(s), allocator, loc=loc)
copy(c, s)
return c[:len(s)]
}
clone_safe :: proc(s: []byte, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: mem.Allocator_Error) {
c := make([]byte, len(s), allocator, loc) or_return
clone_safe :: proc(s: []byte, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: runtime.Allocator_Error) {
c := make([]byte, len(s), allocator, loc=loc) or_return
copy(c, s)
return c[:len(s)], nil
}
ptr_from_slice :: ptr_from_bytes
ptr_from_bytes :: proc(str: []byte) -> ^byte {
d := transmute(mem.Raw_String)str
d := transmute(runtime.Raw_String)str
return d.data
}
@@ -66,10 +67,16 @@ truncate_to_rune :: proc(str: []byte, r: rune) -> []byte {
return str[:n]
}
// Compares two strings, returning a value representing which one comes first lexiographically.
// -1 for `a`; 1 for `b`, or 0 if they are equal.
// Compares two []byte, returning a value representing which one comes first lexiographically.
// Returns: -1 for `lhs`, 1 for `rhs`, or 0 if they are equal.
compare :: proc(lhs, rhs: []byte) -> int {
return mem.compare(lhs, rhs)
res := runtime.memory_compare(raw_data(lhs), raw_data(rhs), min(len(lhs), len(rhs)))
if res == 0 && len(lhs) != len(rhs) {
return len(lhs) <= len(rhs) ? -1 : +1
} else if len(lhs) == 0 && len(rhs) == 0 {
return 0
}
return res
}
contains_rune :: proc(s: []byte, r: rune) -> int {
@@ -134,8 +141,13 @@ equal_fold :: proc(u, v: []byte) -> bool {
return false
}
// TODO(bill): Unicode folding
r := unicode.simple_fold(sr)
for r != sr && r < tr {
r = unicode.simple_fold(sr)
}
if r == tr {
continue loop
}
return false
}
@@ -151,7 +163,7 @@ has_suffix :: proc(s, suffix: []byte) -> bool {
}
join :: proc(a: [][]byte, sep: []byte, allocator := context.allocator) -> []byte {
join :: proc(a: [][]byte, sep: []byte, allocator := context.allocator, loc := #caller_location) -> []byte {
if len(a) == 0 {
return nil
}
@@ -161,7 +173,7 @@ join :: proc(a: [][]byte, sep: []byte, allocator := context.allocator) -> []byte
n += len(s)
}
b := make([]byte, n, allocator)
b := make([]byte, n, allocator, loc=loc)
i := copy(b, a[0])
for s in a[1:] {
i += copy(b[i:], sep)
@@ -170,7 +182,7 @@ join :: proc(a: [][]byte, sep: []byte, allocator := context.allocator) -> []byte
return b
}
join_safe :: proc(a: [][]byte, sep: []byte, allocator := context.allocator) -> (data: []byte, err: mem.Allocator_Error) {
join_safe :: proc(a: [][]byte, sep: []byte, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: runtime.Allocator_Error) {
if len(a) == 0 {
return nil, nil
}
@@ -180,7 +192,7 @@ join_safe :: proc(a: [][]byte, sep: []byte, allocator := context.allocator) -> (
n += len(s)
}
b := make([]byte, n, allocator) or_return
b := make([]byte, n, allocator, loc=loc) or_return
i := copy(b, a[0])
for s in a[1:] {
i += copy(b[i:], sep)
@@ -189,7 +201,7 @@ join_safe :: proc(a: [][]byte, sep: []byte, allocator := context.allocator) -> (
return b, nil
}
concatenate :: proc(a: [][]byte, allocator := context.allocator) -> []byte {
concatenate :: proc(a: [][]byte, allocator := context.allocator, loc := #caller_location) -> []byte {
if len(a) == 0 {
return nil
}
@@ -198,7 +210,7 @@ concatenate :: proc(a: [][]byte, allocator := context.allocator) -> []byte {
for s in a {
n += len(s)
}
b := make([]byte, n, allocator)
b := make([]byte, n, allocator, loc=loc)
i := 0
for s in a {
i += copy(b[i:], s)
@@ -206,7 +218,7 @@ concatenate :: proc(a: [][]byte, allocator := context.allocator) -> []byte {
return b
}
concatenate_safe :: proc(a: [][]byte, allocator := context.allocator) -> (data: []byte, err: mem.Allocator_Error) {
concatenate_safe :: proc(a: [][]byte, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: runtime.Allocator_Error) {
if len(a) == 0 {
return nil, nil
}
@@ -215,7 +227,7 @@ concatenate_safe :: proc(a: [][]byte, allocator := context.allocator) -> (data:
for s in a {
n += len(s)
}
b := make([]byte, n, allocator) or_return
b := make([]byte, n, allocator, loc=loc) or_return
i := 0
for s in a {
i += copy(b[i:], s)
@@ -225,7 +237,7 @@ concatenate_safe :: proc(a: [][]byte, allocator := context.allocator) -> (data:
@private
_split :: proc(s, sep: []byte, sep_save, n: int, allocator := context.allocator) -> [][]byte {
_split :: proc(s, sep: []byte, sep_save, n: int, allocator := context.allocator, loc := #caller_location) -> [][]byte {
s, n := s, n
if n == 0 {
@@ -238,7 +250,7 @@ _split :: proc(s, sep: []byte, sep_save, n: int, allocator := context.allocator)
n = l
}
res := make([dynamic][]byte, n, allocator)
res := make([dynamic][]byte, n, allocator, loc=loc)
for i := 0; i < n-1; i += 1 {
_, w := utf8.decode_rune(s)
res[i] = s[:w]
@@ -254,7 +266,7 @@ _split :: proc(s, sep: []byte, sep_save, n: int, allocator := context.allocator)
n = count(s, sep) + 1
}
res := make([dynamic][]byte, n, allocator)
res := make([dynamic][]byte, n, allocator, loc=loc)
n -= 1
@@ -272,20 +284,20 @@ _split :: proc(s, sep: []byte, sep_save, n: int, allocator := context.allocator)
return res[:i+1]
}
split :: proc(s, sep: []byte, allocator := context.allocator) -> [][]byte {
return _split(s, sep, 0, -1, allocator)
split :: proc(s, sep: []byte, allocator := context.allocator, loc := #caller_location) -> [][]byte {
return _split(s, sep, 0, -1, allocator, loc)
}
split_n :: proc(s, sep: []byte, n: int, allocator := context.allocator) -> [][]byte {
return _split(s, sep, 0, n, allocator)
split_n :: proc(s, sep: []byte, n: int, allocator := context.allocator, loc := #caller_location) -> [][]byte {
return _split(s, sep, 0, n, allocator, loc)
}
split_after :: proc(s, sep: []byte, allocator := context.allocator) -> [][]byte {
return _split(s, sep, len(sep), -1, allocator)
split_after :: proc(s, sep: []byte, allocator := context.allocator, loc := #caller_location) -> [][]byte {
return _split(s, sep, len(sep), -1, allocator, loc)
}
split_after_n :: proc(s, sep: []byte, n: int, allocator := context.allocator) -> [][]byte {
return _split(s, sep, len(sep), n, allocator)
split_after_n :: proc(s, sep: []byte, n: int, allocator := context.allocator, loc := #caller_location) -> [][]byte {
return _split(s, sep, len(sep), n, allocator, loc)
}
@@ -350,7 +362,7 @@ index_byte :: proc "contextless" (s: []byte, c: byte) -> (index: int) #no_bounds
}
c_vec: simd.u8x16 = c
when !simd.IS_EMULATED {
when simd.HAS_HARDWARE_SIMD {
// Note: While this is something that could also logically take
// advantage of AVX512, the various downclocking and power
// consumption related woes make premature to have a dedicated
@@ -485,7 +497,7 @@ last_index_byte :: proc "contextless" (s: []byte, c: byte) -> int #no_bounds_che
}
c_vec: simd.u8x16 = c
when !simd.IS_EMULATED {
when simd.HAS_HARDWARE_SIMD {
// Note: While this is something that could also logically take
// advantage of AVX512, the various downclocking and power
// consumption related woes make premature to have a dedicated
@@ -762,14 +774,14 @@ count :: proc(s, substr: []byte) -> int {
}
repeat :: proc(s: []byte, count: int, allocator := context.allocator) -> []byte {
repeat :: proc(s: []byte, count: int, allocator := context.allocator, loc := #caller_location) -> []byte {
if count < 0 {
panic("bytes: negative repeat count")
} else if count > 0 && (len(s)*count)/count != len(s) {
panic("bytes: repeat count will cause an overflow")
}
b := make([]byte, len(s)*count, allocator)
b := make([]byte, len(s)*count, allocator, loc=loc)
i := copy(b, s)
for i < len(b) { // 2^N trick to reduce the need to copy
copy(b[i:], b[:i])
@@ -778,12 +790,12 @@ repeat :: proc(s: []byte, count: int, allocator := context.allocator) -> []byte
return b
}
replace_all :: proc(s, old, new: []byte, allocator := context.allocator) -> (output: []byte, was_allocation: bool) {
return replace(s, old, new, -1, allocator)
replace_all :: proc(s, old, new: []byte, allocator := context.allocator, loc := #caller_location) -> (output: []byte, was_allocation: bool) {
return replace(s, old, new, -1, allocator, loc)
}
// if n < 0, no limit on the number of replacements
replace :: proc(s, old, new: []byte, n: int, allocator := context.allocator) -> (output: []byte, was_allocation: bool) {
replace :: proc(s, old, new: []byte, n: int, allocator := context.allocator, loc := #caller_location) -> (output: []byte, was_allocation: bool) {
if string(old) == string(new) || n == 0 {
was_allocation = false
output = s
@@ -799,7 +811,7 @@ replace :: proc(s, old, new: []byte, n: int, allocator := context.allocator) ->
}
t := make([]byte, len(s) + byte_count*(len(new) - len(old)), allocator)
t := make([]byte, len(s) + byte_count*(len(new) - len(old)), allocator, loc=loc)
was_allocation = true
w := 0
@@ -823,12 +835,12 @@ replace :: proc(s, old, new: []byte, n: int, allocator := context.allocator) ->
return
}
remove :: proc(s, key: []byte, n: int, allocator := context.allocator) -> (output: []byte, was_allocation: bool) {
return replace(s, key, {}, n, allocator)
remove :: proc(s, key: []byte, n: int, allocator := context.allocator, loc := #caller_location) -> (output: []byte, was_allocation: bool) {
return replace(s, key, {}, n, allocator, loc)
}
remove_all :: proc(s, key: []byte, allocator := context.allocator) -> (output: []byte, was_allocation: bool) {
return remove(s, key, -1, allocator)
remove_all :: proc(s, key: []byte, allocator := context.allocator, loc := #caller_location) -> (output: []byte, was_allocation: bool) {
return remove(s, key, -1, allocator, loc)
}
@(private) _ascii_space := [256]u8{'\t' = 1, '\n' = 1, '\v' = 1, '\f' = 1, '\r' = 1, ' ' = 1}
@@ -985,16 +997,18 @@ trim_left :: proc(s: []byte, cutset: []byte) -> []byte {
if s == nil || cutset == nil {
return s
}
state := cutset
return trim_left_proc_with_state(s, is_in_cutset, &state)
begin := 0; end := len(s)
for ; begin < end && index_byte(cutset, s[begin]) >= 0; begin += 1 {}
return s[begin:]
}
trim_right :: proc(s: []byte, cutset: []byte) -> []byte {
if s == nil || cutset == nil {
return s
}
state := cutset
return trim_right_proc_with_state(s, is_in_cutset, &state)
begin := 0; end := len(s)
for ; end > begin && index_byte(cutset, s[end - 1]) >= 0; end -= 1 {}
return s[:end]
}
trim :: proc(s: []byte, cutset: []byte) -> []byte {
@@ -1040,7 +1054,7 @@ trim_suffix :: proc(s, suffix: []byte) -> []byte {
return s
}
split_multi :: proc(s: []byte, substrs: [][]byte, skip_empty := false, allocator := context.allocator) -> [][]byte #no_bounds_check {
split_multi :: proc(s: []byte, substrs: [][]byte, skip_empty := false, allocator := context.allocator, loc := #caller_location) -> [][]byte #no_bounds_check {
if s == nil || len(substrs) <= 0 {
return nil
}
@@ -1088,7 +1102,7 @@ split_multi :: proc(s: []byte, substrs: [][]byte, skip_empty := false, allocator
return nil
}
buf := make([][]byte, n, allocator)
buf := make([][]byte, n, allocator, loc=loc)
n, i, l = 0, 0, 0
@@ -1175,10 +1189,10 @@ split_multi_iterator :: proc(s: ^[]byte, substrs: [][]byte, skip_empty := false)
// Scrubs invalid utf-8 characters and replaces them with the replacement string
// Adjacent invalid bytes are only replaced once
scrub :: proc(s: []byte, replacement: []byte, allocator := context.allocator) -> []byte {
scrub :: proc(s: []byte, replacement: []byte, allocator := context.allocator, loc := #caller_location) -> []byte {
str := s
b: Buffer
buffer_init_allocator(&b, 0, len(s), allocator)
buffer_init_allocator(&b, 0, len(s), allocator, loc)
has_error := false
cursor := 0
@@ -1190,11 +1204,11 @@ scrub :: proc(s: []byte, replacement: []byte, allocator := context.allocator) ->
if r == utf8.RUNE_ERROR {
if !has_error {
has_error = true
buffer_write(&b, origin[:cursor])
buffer_write(&b, origin[:cursor], loc)
}
} else if has_error {
has_error = false
buffer_write(&b, replacement)
buffer_write(&b, replacement, loc)
origin = origin[cursor:]
cursor = 0
@@ -1208,10 +1222,10 @@ scrub :: proc(s: []byte, replacement: []byte, allocator := context.allocator) ->
}
reverse :: proc(s: []byte, allocator := context.allocator) -> []byte {
reverse :: proc(s: []byte, allocator := context.allocator, loc := #caller_location) -> []byte {
str := s
n := len(str)
buf := make([]byte, n)
buf := make([]byte, n, loc=loc)
i := n
for len(str) > 0 {
@@ -1223,7 +1237,7 @@ reverse :: proc(s: []byte, allocator := context.allocator) -> []byte {
return buf
}
expand_tabs :: proc(s: []byte, tab_size: int, allocator := context.allocator) -> []byte {
expand_tabs :: proc(s: []byte, tab_size: int, allocator := context.allocator, loc := #caller_location) -> []byte {
if tab_size <= 0 {
panic("tab size must be positive")
}
@@ -1234,7 +1248,7 @@ expand_tabs :: proc(s: []byte, tab_size: int, allocator := context.allocator) ->
}
b: Buffer
buffer_init_allocator(&b, 0, len(s), allocator)
buffer_init_allocator(&b, 0, len(s), allocator, loc=loc)
str := s
column: int
@@ -1246,7 +1260,7 @@ expand_tabs :: proc(s: []byte, tab_size: int, allocator := context.allocator) ->
expand := tab_size - column%tab_size
for i := 0; i < expand; i += 1 {
buffer_write_byte(&b, ' ')
buffer_write_byte(&b, ' ', loc)
}
column += expand
@@ -1257,7 +1271,7 @@ expand_tabs :: proc(s: []byte, tab_size: int, allocator := context.allocator) ->
column += w
}
buffer_write_rune(&b, r)
buffer_write_rune(&b, r, loc)
}
str = str[w:]
@@ -1282,59 +1296,59 @@ partition :: proc(str, sep: []byte) -> (head, match, tail: []byte) {
center_justify :: centre_justify // NOTE(bill): Because Americans exist
// centre_justify returns a byte slice with a pad byte slice at boths sides if the str's rune length is smaller than length
centre_justify :: proc(str: []byte, length: int, pad: []byte, allocator := context.allocator) -> []byte {
centre_justify :: proc(str: []byte, length: int, pad: []byte, allocator := context.allocator, loc := #caller_location) -> []byte {
n := rune_count(str)
if n >= length || pad == nil {
return clone(str, allocator)
return clone(str, allocator, loc)
}
remains := length-1
pad_len := rune_count(pad)
b: Buffer
buffer_init_allocator(&b, 0, len(str) + (remains/pad_len + 1)*len(pad), allocator)
buffer_init_allocator(&b, 0, len(str) + (remains/pad_len + 1)*len(pad), allocator, loc)
write_pad_string(&b, pad, pad_len, remains/2)
buffer_write(&b, str)
buffer_write(&b, str, loc)
write_pad_string(&b, pad, pad_len, (remains+1)/2)
return buffer_to_bytes(&b)
}
// left_justify returns a byte slice with a pad byte slice at left side if the str's rune length is smaller than length
left_justify :: proc(str: []byte, length: int, pad: []byte, allocator := context.allocator) -> []byte {
left_justify :: proc(str: []byte, length: int, pad: []byte, allocator := context.allocator, loc := #caller_location) -> []byte {
n := rune_count(str)
if n >= length || pad == nil {
return clone(str, allocator)
return clone(str, allocator, loc)
}
remains := length-1
pad_len := rune_count(pad)
b: Buffer
buffer_init_allocator(&b, 0, len(str) + (remains/pad_len + 1)*len(pad), allocator)
buffer_init_allocator(&b, 0, len(str) + (remains/pad_len + 1)*len(pad), allocator, loc)
buffer_write(&b, str)
buffer_write(&b, str, loc)
write_pad_string(&b, pad, pad_len, remains)
return buffer_to_bytes(&b)
}
// right_justify returns a byte slice with a pad byte slice at right side if the str's rune length is smaller than length
right_justify :: proc(str: []byte, length: int, pad: []byte, allocator := context.allocator) -> []byte {
right_justify :: proc(str: []byte, length: int, pad: []byte, allocator := context.allocator, loc := #caller_location) -> []byte {
n := rune_count(str)
if n >= length || pad == nil {
return clone(str, allocator)
return clone(str, allocator, loc)
}
remains := length-1
pad_len := rune_count(pad)
b: Buffer
buffer_init_allocator(&b, 0, len(str) + (remains/pad_len + 1)*len(pad), allocator)
buffer_init_allocator(&b, 0, len(str) + (remains/pad_len + 1)*len(pad), allocator, loc)
write_pad_string(&b, pad, pad_len, remains)
buffer_write(&b, str)
buffer_write(&b, str, loc)
return buffer_to_bytes(&b)
}
@@ -1343,11 +1357,11 @@ right_justify :: proc(str: []byte, length: int, pad: []byte, allocator := contex
@private
write_pad_string :: proc(b: ^Buffer, pad: []byte, pad_len, remains: int) {
write_pad_string :: proc(b: ^Buffer, pad: []byte, pad_len, remains: int, loc := #caller_location) {
repeats := remains / pad_len
for i := 0; i < repeats; i += 1 {
buffer_write(b, pad)
buffer_write(b, pad, loc)
}
n := remains % pad_len
@@ -1355,7 +1369,7 @@ write_pad_string :: proc(b: ^Buffer, pad: []byte, pad_len, remains: int) {
for i := 0; i < n; i += 1 {
r, width := utf8.decode_rune(p)
buffer_write_rune(b, r)
buffer_write_rune(b, r, loc)
p = p[width:]
}
}
@@ -1363,7 +1377,7 @@ write_pad_string :: proc(b: ^Buffer, pad: []byte, pad_len, remains: int) {
// fields splits the byte slice s around each instance of one or more consecutive white space character, defined by unicode.is_space
// returning a slice of subslices of s or an empty slice if s only contains white space
fields :: proc(s: []byte, allocator := context.allocator) -> [][]byte #no_bounds_check {
fields :: proc(s: []byte, allocator := context.allocator, loc := #caller_location) -> [][]byte #no_bounds_check {
n := 0
was_space := 1
set_bits := u8(0)
@@ -1378,14 +1392,14 @@ fields :: proc(s: []byte, allocator := context.allocator) -> [][]byte #no_bounds
}
if set_bits >= utf8.RUNE_SELF {
return fields_proc(s, unicode.is_space, allocator)
return fields_proc(s, unicode.is_space, allocator, loc)
}
if n == 0 {
return nil
}
a := make([][]byte, n, allocator)
a := make([][]byte, n, allocator, loc=loc)
na := 0
field_start := 0
i := 0
@@ -1419,15 +1433,15 @@ fields :: proc(s: []byte, allocator := context.allocator) -> [][]byte #no_bounds
//
// fields_proc makes no guarantee about the order in which it calls f(ch)
// it assumes that `f` always returns the same value for a given ch
fields_proc :: proc(s: []byte, f: proc(rune) -> bool, allocator := context.allocator) -> [][]byte #no_bounds_check {
subslices := make([dynamic][]byte, 0, 32, allocator)
fields_proc :: proc(s: []byte, f: proc(rune) -> bool, allocator := context.allocator, loc := #caller_location) -> [][]byte #no_bounds_check {
subslices := make([dynamic][]byte, 0, 32, allocator, loc=loc)
start, end := -1, -1
for r, offset in string(s) {
end = offset
if f(r) {
if start >= 0 {
append(&subslices, s[start : end])
append(&subslices, s[start : end], loc=loc)
// -1 could be used, but just speed it up through bitwise not
// gotta love 2's complement
start = ~start
@@ -1440,13 +1454,13 @@ fields_proc :: proc(s: []byte, f: proc(rune) -> bool, allocator := context.alloc
}
if start >= 0 {
append(&subslices, s[start : len(s)])
append(&subslices, s[start : len(s)], loc=loc)
}
return subslices[:]
}
// alias returns true iff a and b have a non-zero length, and any part of
// alias returns true if and only if (⟺) a and b have a non-zero length, and any part of
// a overlaps with b.
alias :: proc "contextless" (a, b: []byte) -> bool {
a_len, b_len := len(a), len(b)
@@ -1460,7 +1474,7 @@ alias :: proc "contextless" (a, b: []byte) -> bool {
return a_start <= b_end && b_start <= a_end
}
// alias_inexactly returns true iff a and b have a non-zero length,
// alias_inexactly returns true if and only if (⟺) a and b have a non-zero length,
// the base pointer of a and b are NOT equal, and any part of a overlaps
// with b (ie: `alias(a, b)` with an exception that returns false for
// `a == b`, `b = a[:len(a)-69]` and similar conditions).

View File

@@ -61,6 +61,27 @@ reader_read_at :: proc(r: ^Reader, p: []byte, off: i64) -> (n: int, err: io.Erro
}
return
}
reader_read_slice :: proc(r: ^Reader, slice: $T/[]$S) -> (n: int, err: io.Error) {
b := ([^]byte)(raw_data(slice))[:len(slice)*size_of(S)]
return reader_read(r, b)
}
reader_read_slice_at :: proc(r: ^Reader, slice: $T/[]$S, off: i64) -> (n: int, err: io.Error) {
b := ([^]byte)(raw_data(slice))[:len(slice)*size_of(S)]
return reader_read_at(r, b, off)
}
reader_read_ptr :: proc(r: ^Reader, data: rawptr, len: int) -> (n: int, err: io.Error) {
b := ([^]byte)(data)[:len]
return reader_read(r, b)
}
reader_read_ptr_at :: proc(r: ^Reader, data: rawptr, len: int, off: i64) -> (n: int, err: io.Error) {
b := ([^]byte)(data)[:len]
return reader_read_at(r, b, off)
}
reader_read_byte :: proc(r: ^Reader) -> (byte, io.Error) {
r.prev_rune = -1
if r.i >= i64(len(r.s)) {
@@ -160,6 +181,6 @@ _reader_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offse
case .Query:
return io.query_utility({.Read, .Read_At, .Seek, .Size, .Query})
}
return 0, .Empty
return 0, .Unsupported
}

View File

@@ -1,6 +1,8 @@
// Defines the basic types used by `C` programs for foreign function and data structure interop.
package c
import builtin "base:builtin"
import builtin "base:builtin"
import intrinsics "base:intrinsics"
char :: builtin.u8 // assuming -funsigned-char
@@ -48,7 +50,7 @@ int_least64_t :: builtin.i64
uint_least64_t :: builtin.u64
// Same on Windows, Linux, and FreeBSD
when ODIN_ARCH == .i386 || ODIN_ARCH == .amd64 {
when ODIN_ARCH == .i386 {
int_fast8_t :: builtin.i8
uint_fast8_t :: builtin.u8
int_fast16_t :: builtin.i32
@@ -57,6 +59,15 @@ when ODIN_ARCH == .i386 || ODIN_ARCH == .amd64 {
uint_fast32_t :: builtin.u32
int_fast64_t :: builtin.i64
uint_fast64_t :: builtin.u64
} else when ODIN_ARCH == .amd64 {
int_fast8_t :: builtin.i8
uint_fast8_t :: builtin.u8
int_fast16_t :: long
uint_fast16_t :: ulong
int_fast32_t :: long
uint_fast32_t :: ulong
int_fast64_t :: builtin.i64
uint_fast64_t :: builtin.u64
} else {
int_fast8_t :: builtin.i8
uint_fast8_t :: builtin.u8
@@ -105,14 +116,6 @@ NDEBUG :: !ODIN_DEBUG
CHAR_BIT :: 8
// Since there are no types in C with an alignment larger than that of
// max_align_t, which cannot be larger than sizeof(long double) as any other
// exposed type wouldn't be valid C, the maximum alignment possible in a
// strictly conformant C implementation is 16 on the platforms we care about.
// The choice of 4096 bytes for storage of this type is more than enough on all
// relevant platforms.
va_list :: struct #align(16) {
_: [4096]u8,
}
va_list :: intrinsics.c_va_list
FILE :: struct {}

View File

@@ -5,7 +5,7 @@ package libc
when ODIN_OS == .Windows {
foreign import libc "system:libucrt.lib"
} else when ODIN_OS == .Darwin {
foreign import libc "system:System.framework"
foreign import libc "system:System"
} else {
foreign import libc "system:c"
}

View File

@@ -3,7 +3,7 @@ package libc
when ODIN_OS == .Windows {
foreign import libc "system:libucrt.lib"
} else when ODIN_OS == .Darwin {
foreign import libc "system:System.framework"
foreign import libc "system:System"
} else {
foreign import libc "system:c"
}

2
core/c/libc/doc.odin Normal file
View File

@@ -0,0 +1,2 @@
// Declares the commonly used things in `libc` (`C` standard library).
package libc

View File

@@ -5,7 +5,7 @@ package libc
when ODIN_OS == .Windows {
foreign import libc "system:libucrt.lib"
} else when ODIN_OS == .Darwin {
foreign import libc "system:System.framework"
foreign import libc "system:System"
} else {
foreign import libc "system:c"
}
@@ -80,25 +80,6 @@ when ODIN_OS == .Darwin {
ERANGE :: 34
}
when ODIN_OS == .Haiku {
@(private="file")
@(default_calling_convention="c")
foreign libc {
@(link_name="_errnop")
_get_errno :: proc() -> ^int ---
}
_HAIKU_USE_POSITIVE_POSIX_ERRORS :: #config(HAIKU_USE_POSITIVE_POSIX_ERRORS, false)
_POSIX_ERROR_FACTOR :: -1 when _HAIKU_USE_POSITIVE_POSIX_ERRORS else 1
@(private="file") _GENERAL_ERROR_BASE :: min(int)
@(private="file") _POSIX_ERROR_BASE :: _GENERAL_ERROR_BASE + 0x7000
EDOM :: _POSIX_ERROR_FACTOR * (_POSIX_ERROR_BASE + 16)
EILSEQ :: _POSIX_ERROR_FACTOR * (_POSIX_ERROR_BASE + 38)
ERANGE :: _POSIX_ERROR_FACTOR * (_POSIX_ERROR_BASE + 17)
}
when ODIN_OS == .JS {
_ :: libc
_get_errno :: proc "c" () -> ^int {

View File

@@ -5,7 +5,7 @@ import "core:c"
when ODIN_OS == .Windows {
foreign import libc "system:libucrt.lib"
} else when ODIN_OS == .Darwin {
foreign import libc "system:System.framework"
foreign import libc "system:System"
} else {
foreign import libc "system:c"
}
@@ -72,14 +72,14 @@ when ODIN_OS == .Windows {
n_sep_by_space: c.char,
p_sign_posn: c.char,
n_sign_posn: c.char,
_W_decimal_point: [^]u16 `fmt:"s,0"`,
_W_thousands_sep: [^]u16 `fmt:"s,0"`,
_W_int_curr_symbol: [^]u16 `fmt:"s,0"`,
_W_currency_symbol: [^]u16 `fmt:"s,0"`,
_W_mon_decimal_point: [^]u16 `fmt:"s,0"`,
_W_mon_thousands_sep: [^]u16 `fmt:"s,0"`,
_W_positive_sign: [^]u16 `fmt:"s,0"`,
_W_negative_sign: [^]u16 `fmt:"s,0"`,
_W_decimal_point: cstring16,
_W_thousands_sep: cstring16,
_W_int_curr_symbol: cstring16,
_W_currency_symbol: cstring16,
_W_mon_decimal_point: cstring16,
_W_mon_thousands_sep: cstring16,
_W_positive_sign: cstring16,
_W_negative_sign: cstring16,
}
} else {
lconv :: struct {
@@ -110,7 +110,7 @@ when ODIN_OS == .Windows {
}
}
when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Haiku || ODIN_OS == .Windows {
when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Windows {
LC_ALL :: 0
LC_COLLATE :: 1

View File

@@ -7,7 +7,7 @@ import "base:intrinsics"
when ODIN_OS == .Windows {
foreign import libc "system:libucrt.lib"
} else when ODIN_OS == .Darwin {
foreign import libc "system:System.framework"
foreign import libc "system:System"
} else {
foreign import libc "system:c"
}
@@ -154,12 +154,12 @@ _nan_bit_pattern := ~u64(0)
// On amd64 Windows and Linux, float_t and double_t are respectively both
// their usual types. On x86 it's not possible to define these types correctly
// since they would be long double which Odin does have support for.
// since they would be long double which Odin does NOT have support for.
float_t :: float
double_t :: double
NAN := transmute(double)(_nan_bit_pattern)
INFINITY :: 1e5000
INFINITY :: 0h7ff00000_00000000 // +Inf
HUGE_VALF :: INFINITY
HUGE_VAL :: double(INFINITY)

View File

@@ -5,7 +5,7 @@ package libc
when ODIN_OS == .Windows {
foreign import libc "system:libucrt.lib"
} else when ODIN_OS == .Darwin {
foreign import libc "system:System.framework"
foreign import libc "system:System"
} else {
foreign import libc "system:c"
}

View File

@@ -5,7 +5,7 @@ package libc
when ODIN_OS == .Windows {
foreign import libc "system:libucrt.lib"
} else when ODIN_OS == .Darwin {
foreign import libc "system:System.framework"
foreign import libc "system:System"
} else {
foreign import libc "system:c"
}
@@ -34,7 +34,7 @@ when ODIN_OS == .Windows {
SIGTERM :: 15
}
when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Haiku || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Darwin {
when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Darwin {
SIG_ERR :: rawptr(~uintptr(0))
SIG_DFL :: rawptr(uintptr(0))
SIG_IGN :: rawptr(uintptr(1))

View File

@@ -4,29 +4,12 @@ package libc
import "base:intrinsics"
import "core:c"
va_list :: intrinsics.c_va_list
@(private="file")
@(default_calling_convention="none")
foreign _ {
@(link_name="llvm.va_start") _va_start :: proc(arglist: ^i8) ---
@(link_name="llvm.va_end") _va_end :: proc(arglist: ^i8) ---
@(link_name="llvm.va_copy") _va_copy :: proc(dst, src: ^i8) ---
}
va_start :: intrinsics.c_va_start
va_end :: intrinsics.c_va_end
va_copy :: intrinsics.c_va_copy
va_list :: c.va_list
va_start :: #force_inline proc(ap: ^va_list, _: any) {
_va_start(cast(^i8)ap)
}
va_end :: #force_inline proc(ap: ^va_list) {
_va_end(cast(^i8)ap)
}
va_copy :: #force_inline proc(dst, src: ^va_list) {
_va_copy(cast(^i8)dst, cast(^i8)src)
}
// We cannot provide va_arg as there is no way to create "C" style procedures
// in Odin which take variable arguments the C way. The #c_vararg attribute only

View File

@@ -9,7 +9,7 @@ when ODIN_OS == .Windows {
"system:legacy_stdio_definitions.lib",
}
} else when ODIN_OS == .Darwin {
foreign import libc "system:System.framework"
foreign import libc "system:System"
} else {
foreign import libc "system:c"
}
@@ -200,36 +200,6 @@ when ODIN_OS == .Darwin {
}
}
when ODIN_OS == .Haiku {
fpos_t :: distinct i64
_IOFBF :: 0
_IOLBF :: 1
_IONBF :: 2
BUFSIZ :: 8192
EOF :: int(-1)
FOPEN_MAX :: 128
FILENAME_MAX :: 256
L_tmpnam :: 512
SEEK_SET :: 0
SEEK_CUR :: 1
SEEK_END :: 2
TMP_MAX :: 32768
foreign libc {
stderr: ^FILE
stdin: ^FILE
stdout: ^FILE
}
}
when ODIN_OS == .NetBSD {
@(private) LRENAME :: "__posix_rename"
@(private) LFGETPOS :: "__fgetpos50"
@@ -275,7 +245,7 @@ foreign libc {
// 7.21.7 Character input/output functions
fgetc :: proc(stream: ^FILE) -> int ---
fgets :: proc(s: [^]char, n: int, stream: ^FILE) -> [^]char ---
fputc :: proc(s: cstring, stream: ^FILE) -> int ---
fputc :: proc(s: c.int, stream: ^FILE) -> int ---
getc :: proc(stream: ^FILE) -> int ---
getchar :: proc() -> int ---
putc :: proc(c: int, stream: ^FILE) -> int ---
@@ -390,7 +360,7 @@ to_stream :: proc(file: ^FILE) -> io.Stream {
}
case .Destroy:
return 0, .Empty
return 0, .Unsupported
case .Query:
return io.query_utility({ .Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query })

View File

@@ -5,7 +5,7 @@ package libc
when ODIN_OS == .Windows {
foreign import libc "system:libucrt.lib"
} else when ODIN_OS == .Darwin {
foreign import libc "system:System.framework"
foreign import libc "system:System"
} else {
foreign import libc "system:c"
}
@@ -42,22 +42,6 @@ when ODIN_OS == .Linux {
}
}
when ODIN_OS == .Haiku {
RAND_MAX :: 0x7fffffff
// GLIBC and MUSL only
@(private="file")
@(default_calling_convention="c")
foreign libc {
__ctype_get_mb_cur_max :: proc() -> ushort ---
}
MB_CUR_MAX :: #force_inline proc() -> size_t {
return size_t(__ctype_get_mb_cur_max())
}
}
when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD {
RAND_MAX :: 0x7fffffff

View File

@@ -7,7 +7,7 @@ import "base:runtime"
when ODIN_OS == .Windows {
foreign import libc "system:libucrt.lib"
} else when ODIN_OS == .Darwin {
foreign import libc "system:System.framework"
foreign import libc "system:System"
} else {
foreign import libc "system:c"
}

View File

@@ -5,7 +5,7 @@ package libc
when ODIN_OS == .Windows {
foreign import libc "system:libucrt.lib"
} else when ODIN_OS == .Darwin {
foreign import libc "system:System.framework"
foreign import libc "system:System"
} else {
foreign import libc "system:c"
}
@@ -45,7 +45,7 @@ when ODIN_OS == .Windows {
}
}
when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku || ODIN_OS == .JS {
when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .JS {
@(default_calling_convention="c")
foreign libc {
// 7.27.2 Time manipulation functions
@@ -95,7 +95,7 @@ when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Darwin || ODIN_OS =
time_t :: distinct i64
when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku {
when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD {
clock_t :: distinct int32_t
} else {
clock_t :: distinct long

View File

@@ -5,7 +5,7 @@ package libc
when ODIN_OS == .Windows {
foreign import libc "system:libucrt.lib"
} else when ODIN_OS == .Darwin {
foreign import libc "system:System.framework"
foreign import libc "system:System"
} else {
foreign import libc "system:c"
}

View File

@@ -5,7 +5,7 @@ package libc
when ODIN_OS == .Windows {
foreign import libc "system:libucrt.lib"
} else when ODIN_OS == .Darwin {
foreign import libc "system:System.framework"
foreign import libc "system:System"
} else {
foreign import libc "system:c"
}
@@ -17,12 +17,12 @@ foreign libc {
fwscanf :: proc(stream: ^FILE, format: [^]wchar_t, #c_vararg arg: ..any) -> int ---
swprintf :: proc(stream: ^FILE, n: size_t, format: [^]wchar_t, #c_vararg arg: ..any) -> int ---
swscanf :: proc(s, format: [^]wchar_t, #c_vararg arg: ..any) -> int ---
vfwprintf :: proc(stream: ^FILE, format: [^]wchar_t, arg: va_list) -> int ---
vfwscanf :: proc(stream: ^FILE, format: [^]wchar_t, arg: va_list) -> int ---
vswprintf :: proc(s: [^]wchar_t, n: size_t, format: [^]wchar_t, arg: va_list) -> int ---
vswscanf :: proc(s, format: [^]wchar_t, arg: va_list) -> int ---
vwprintf :: proc(format: [^]wchar_t, arg: va_list) -> int ---
vwscanf :: proc(format: [^]wchar_t, arg: va_list) -> int ---
vfwprintf :: proc(stream: ^FILE, format: [^]wchar_t, arg: ^va_list) -> int ---
vfwscanf :: proc(stream: ^FILE, format: [^]wchar_t, arg: ^va_list) -> int ---
vswprintf :: proc(s: [^]wchar_t, n: size_t, format: [^]wchar_t, arg: ^va_list) -> int ---
vswscanf :: proc(s, format: [^]wchar_t, arg: ^va_list) -> int ---
vwprintf :: proc(format: [^]wchar_t, arg: ^va_list) -> int ---
vwscanf :: proc(format: [^]wchar_t, arg: ^va_list) -> int ---
wprintf :: proc(format: [^]wchar_t, #c_vararg arg: ..any) -> int ---
wscanf :: proc(format: [^]wchar_t, #c_vararg arg: ..any) -> int ---

View File

@@ -5,7 +5,7 @@ package libc
when ODIN_OS == .Windows {
foreign import libc "system:libucrt.lib"
} else when ODIN_OS == .Darwin {
foreign import libc "system:System.framework"
foreign import libc "system:System"
} else {
foreign import libc "system:c"
}
@@ -30,10 +30,6 @@ when ODIN_OS == .Windows {
wctrans_t :: distinct int
wctype_t :: distinct ulong
} else when ODIN_OS == .Haiku {
wctrans_t :: distinct i32
wctype_t :: distinct i32
}
@(default_calling_convention="c")

View File

@@ -1,15 +1,14 @@
// A collection of utilities to aid with other `compress`ion packages.
package compress
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
Made available under Odin's license.
List of contributors:
Jeroen van Rijn: Initial implementation, optimization.
*/
// package compress is a collection of utilities to aid with other compression packages
package compress
import "core:io"
import "core:bytes"
import "base:runtime"
@@ -139,9 +138,6 @@ Context_Memory_Input :: struct #packed {
}
when size_of(rawptr) == 8 {
#assert(size_of(Context_Memory_Input) == 64)
} else {
// e.g. `-target:windows_i386`
#assert(size_of(Context_Memory_Input) == 52)
}
Context_Stream_Input :: struct #packed {
@@ -301,7 +297,7 @@ peek_data_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid
curr := z.input->impl_seek(0, .Current) or_return
r, e1 := io.to_reader_at(z.input)
if !e1 {
return T{}, .Empty
return T{}, .Unsupported
}
when size <= 128 {
b: [size]u8
@@ -310,7 +306,7 @@ peek_data_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid
}
_, e2 := io.read_at(r, b[:], curr)
if e2 != .None {
return T{}, .Empty
return T{}, .Unsupported
}
res = (^T)(&b[0])^
@@ -328,7 +324,7 @@ peek_data_at_offset_from_stream :: #force_inline proc(z: ^Context_Stream_Input,
r, e3 := io.to_reader_at(z.input)
if !e3 {
return T{}, .Empty
return T{}, .Unsupported
}
when size <= 128 {
b: [size]u8
@@ -337,7 +333,7 @@ peek_data_at_offset_from_stream :: #force_inline proc(z: ^Context_Stream_Input,
}
_, e4 := io.read_at(r, b[:], pos)
if e4 != .None {
return T{}, .Empty
return T{}, .Unsupported
}
// Return read head to original position.
@@ -364,23 +360,26 @@ refill_lsb_from_memory :: #force_inline proc(z: ^Context_Memory_Input, width :=
refill := u64(width)
b := u64(0)
if z.num_bits > refill {
return
}
for {
if z.num_bits > refill {
break
}
if z.code_buffer == 0 && z.num_bits > 63 {
z.num_bits = 0
}
if z.code_buffer >= 1 << uint(z.num_bits) {
// Code buffer is malformed.
z.num_bits = max(u64)
return
}
if len(z.input_data) != 0 {
b = u64(z.input_data[0])
z.input_data = z.input_data[1:]
} else {
b = 0
return
}
z.code_buffer |= b << u8(z.num_bits)
z.num_bits += 8
if z.num_bits > refill {
break
}
}
}

View File

@@ -1,19 +1,11 @@
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
A small `GZIP` unpacker.
List of contributors:
Jeroen van Rijn: Initial implementation.
Ginger Bill: Cosmetic changes.
A small GZIP implementation as an example.
*/
/*
Example:
import "core:bytes"
import "core:os"
import "core:compress"
import "core:compress/gzip"
import "core:fmt"
// Small GZIP file with fextra, fname and fcomment present.
@@ -31,7 +23,8 @@ Example:
main :: proc() {
// Set up output buffer.
buf := bytes.Buffer{}
buf: bytes.Buffer
defer bytes.buffer_destroy(&buf)
stdout :: proc(s: string) {
os.write_string(os.stdout, s)
@@ -40,15 +33,13 @@ Example:
os.write_string(os.stderr, s)
}
args := os.args
if len(args) < 2 {
if len(os.args) < 2 {
stderr("No input file specified.\n")
err := load(data=TEST, buf=&buf, known_gzip_size=len(TEST))
err := gzip.load(data=TEST, buf=&buf, known_gzip_size=len(TEST))
if err == nil {
stdout("Displaying test vector: ")
stdout("Displaying test vector: \"")
stdout(bytes.buffer_to_string(&buf))
stdout("\n")
stdout("\"\n")
} else {
fmt.printf("gzip.load returned %v\n", err)
}
@@ -56,35 +47,42 @@ Example:
os.exit(0)
}
// The rest are all files.
args = args[1:]
err: Error
for file in os.args[1:] {
err: gzip.Error
for file in args {
if file == "-" {
// Read from stdin
s := os.stream_from_handle(os.stdin)
ctx := &compress.Context_Stream_Input{
input = s,
input = os.stdin.stream,
}
err = load(ctx, &buf)
err = gzip.load(ctx, &buf)
} else {
err = load(file, &buf)
err = gzip.load(file, &buf)
}
if err != nil {
if err != E_General.File_Not_Found {
stderr("File not found: ")
stderr(file)
stderr("\n")
os.exit(1)
}
switch err {
case nil:
stdout(bytes.buffer_to_string(&buf))
case gzip.E_General.File_Not_Found:
stderr("File not found: ")
stderr(file)
stderr("\n")
os.exit(1)
case:
stderr("GZIP returned an error.\n")
bytes.buffer_destroy(&buf)
os.exit(2)
}
stdout(bytes.buffer_to_string(&buf))
}
bytes.buffer_destroy(&buf)
}
*/
package compress_gzip
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's license.
List of contributors:
Jeroen van Rijn: Initial implementation.
Ginger Bill: Cosmetic changes.
A small GZIP implementation as an example.
*/

View File

@@ -2,7 +2,7 @@ package compress_gzip
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
Made available under Odin's license.
List of contributors:
Jeroen van Rijn: Initial implementation.
@@ -107,14 +107,10 @@ load :: proc{load_from_bytes, load_from_file, load_from_context}
load_from_file :: proc(filename: string, buf: ^bytes.Buffer, expected_output_size := -1, allocator := context.allocator) -> (err: Error) {
context.allocator = allocator
data, ok := os.read_entire_file(filename)
defer delete(data)
file_data, file_err := os.read_entire_file(filename, allocator)
defer delete(file_data)
err = E_General.File_Not_Found
if ok {
err = load_from_bytes(data, buf, len(data), expected_output_size)
}
return
return load_from_bytes(file_data, buf, len(file_data), expected_output_size) if file_err == nil else E_General.File_Not_Found
}
load_from_bytes :: proc(data: []byte, buf: ^bytes.Buffer, known_gzip_size := -1, expected_output_size := -1, allocator := context.allocator) -> (err: Error) {

View File

@@ -1,11 +1,11 @@
package compress_shoco
/*
This file was generated, so don't edit this by hand.
Transliterated from https://github.com/Ed-von-Schleck/shoco/blob/master/shoco_model.h,
which is an English word model.
*/
package compress_shoco
DEFAULT_MODEL :: Shoco_Model {
min_char = 39,
max_char = 122,

View File

@@ -1,6 +1,9 @@
// `Shoco` short string compression and decompression.
package compress_shoco
/*
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
Made available under Odin's license.
List of contributors:
Jeroen van Rijn: Initial implementation.
@@ -8,9 +11,6 @@
An implementation of [shoco](https://github.com/Ed-von-Schleck/shoco) by Christian Schramm.
*/
// package shoco is an implementation of the shoco short string compressor.
package compress_shoco
import "base:intrinsics"
import "core:compress"

View File

@@ -1,14 +1,6 @@
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
`Deflate` decompression of raw and `ZLIB`-type streams.
List of contributors:
Jeroen van Rijn: Initial implementation.
An example of how to use `zlib.inflate`.
*/
/*
Example:
package main
@@ -49,3 +41,13 @@ Example:
}
*/
package compress_zlib
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's license.
List of contributors:
Jeroen van Rijn: Initial implementation.
An example of how to use `zlib.inflate`.
*/

View File

@@ -1,9 +1,9 @@
#+vet !using-param
#+feature using-stmt
package compress_zlib
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
Made available under Odin's license.
List of contributors:
Jeroen van Rijn: Initial implementation, optimization.
@@ -322,9 +322,6 @@ decode_huffman_slowpath :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Erro
@(optimization_mode="favor_size")
decode_huffman :: proc(z: ^$C, t: ^Huffman_Table) -> (r: u16, err: Error) #no_bounds_check {
if z.num_bits < 16 {
if z.num_bits > 63 {
return 0, .Code_Buffer_Malformed
}
compress.refill_lsb(z)
if z.num_bits > 63 {
return 0, .Stream_Too_Short

View File

@@ -1,8 +1,4 @@
/*
package avl implements an AVL tree.
The implementation is non-intrusive, and non-recursive.
*/
// A non-intrusive and non-recursive implementation of `AVL` trees.
package container_avl
@(require) import "base:intrinsics"
@@ -104,20 +100,20 @@ len :: proc "contextless" (t: ^$T/Tree($Value)) -> int {
return t._size
}
// first returns the first node in the tree (in-order) or nil iff
// first returns the first node in the tree (in-order) or nil if and only if (⟺)
// the tree is empty.
first :: proc "contextless" (t: ^$T/Tree($Value)) -> ^Node(Value) {
return tree_first_or_last_in_order(t, Direction.Backward)
}
// last returns the last element in the tree (in-order) or nil iff
// last returns the last element in the tree (in-order) or nil if and only if (⟺)
// the tree is empty.
last :: proc "contextless" (t: ^$T/Tree($Value)) -> ^Node(Value) {
return tree_first_or_last_in_order(t, Direction.Forward)
}
// find finds the value in the tree, and returns the corresponding
// node or nil iff the value is not present.
// node or nil if and only if (⟺) the value is not present.
find :: proc(t: ^$T/Tree($Value), value: Value) -> ^Node(Value) {
cur := t._root
descend_loop: for cur != nil {
@@ -172,7 +168,7 @@ find_or_insert :: proc(
return
}
// remove removes a node or value from the tree, and returns true iff the
// remove removes a node or value from the tree, and returns true if and only if (⟺) the
// removal was successful. While the node's value will be left intact,
// the node itself will be freed via the tree's node allocator.
remove :: proc {
@@ -180,7 +176,7 @@ remove :: proc {
remove_node,
}
// remove_value removes a value from the tree, and returns true iff the
// remove_value removes a value from the tree, and returns true if and only if (⟺) the
// removal was successful. While the node's value will be left intact,
// the node itself will be freed via the tree's node allocator.
remove_value :: proc(t: ^$T/Tree($Value), value: Value, call_on_remove: bool = true) -> bool {
@@ -191,7 +187,7 @@ remove_value :: proc(t: ^$T/Tree($Value), value: Value, call_on_remove: bool = t
return remove_node(t, n, call_on_remove)
}
// remove_node removes a node from the tree, and returns true iff the
// remove_node removes a node from the tree, and returns true if and only if (⟺) the
// removal was successful. While the node's value will be left intact,
// the node itself will be freed via the tree's node allocator.
remove_node :: proc(t: ^$T/Tree($Value), node: ^Node(Value), call_on_remove: bool = true) -> bool {
@@ -285,14 +281,14 @@ iterator_from_pos :: proc "contextless" (
}
// iterator_get returns the node currently pointed to by the iterator,
// or nil iff the node has been removed, the tree is empty, or the end
// or nil if and only if (⟺) the node has been removed, the tree is empty, or the end
// of the tree has been reached.
iterator_get :: proc "contextless" (it: ^$I/Iterator($Value)) -> ^Node(Value) {
return it._cur
}
// iterator_remove removes the node currently pointed to by the iterator,
// and returns true iff the removal was successful. Semantics are the
// and returns true if and only if (⟺) the removal was successful. Semantics are the
// same as the Tree remove.
iterator_remove :: proc(it: ^$I/Iterator($Value), call_on_remove: bool = true) -> bool {
if it._cur == nil {
@@ -308,7 +304,7 @@ iterator_remove :: proc(it: ^$I/Iterator($Value), call_on_remove: bool = true) -
}
// iterator_next advances the iterator and returns the (node, true) or
// or (nil, false) iff the end of the tree has been reached.
// or (nil, false) if and only if (⟺) the end of the tree has been reached.
//
// Note: The first call to iterator_next will return the first node instead
// of advancing the iterator.

View File

@@ -2,7 +2,6 @@ package container_dynamic_bit_array
import "base:builtin"
import "base:intrinsics"
import "core:mem"
/*
Note that these constants are dependent on the backing being a u64.
@@ -329,7 +328,7 @@ Inputs:
*/
clear :: proc(ba: ^Bit_Array) {
if ba == nil { return }
mem.zero_slice(ba.bits[:])
intrinsics.mem_zero(raw_data(ba.bits), builtin.len(ba.bits) * NUM_BITS / 8)
}
/*
Gets the length of set and unset valid bits in the Bit_Array.

View File

@@ -1,7 +1,9 @@
/*
A dynamically-sized array of bits.
The Bit Array can be used in several ways:
By default you don't need to instantiate a Bit Array.
By default you don't need to instantiate a `Bit_Array`.
Example:
package test
@@ -18,11 +20,11 @@ Example:
// returns `false`, `false`, because this Bit Array wasn't created to allow negative indices.
was_set, was_retrieved := get(&bits, -1)
fmt.println(was_set, was_retrieved)
fmt.println(was_set, was_retrieved)
destroy(&bits)
}
A Bit Array can optionally allow for negative indices, if the minimum value was given during creation.
A `Bit_Array` can optionally allow for negative indices, if the minimum value was given during creation.
Example:
package test
@@ -49,4 +51,4 @@ Example:
fmt.printf("Freed.\n")
}
*/
package container_dynamic_bit_array
package container_dynamic_bit_array

View File

@@ -0,0 +1,60 @@
/*
Handle-based map using either fixed-length arrays, or exponential arrays from "core:container/xar".
Example:
import hm "core:container/handle_map"
Handle :: hm.Handle32
Entity :: struct {
handle: Handle,
pos: [2]f32,
}
{ // static map
entities: hm.Static_Handle_Map(1024, Entity, Handle)
h1 := hm.add(&entities, Entity{pos = {1, 4}})
h2 := hm.add(&entities, Entity{pos = {9, 16}})
if e, ok := hm.get(&entities, h2); ok {
e.pos.x += 32
}
hm.remove(&entities, h1)
h3 := hm.add(&entities, Entity{pos = {6, 7}})
assert(hm.is_valid(entities, h3))
it := hm.iterator_make(&entities)
for e, h in hm.iterate(&it) {
assert(hm.is_valid(entities, h))
e.pos += {1, 2}
}
}
{ // dynamic map
entities: hm.Dynamic_Handle_Map(Entity, Handle)
hm.dynamic_init(&entities, context.allocator)
defer hm.dynamic_destroy(&entities)
h1 := hm.add(&entities, Entity{pos = {1, 4}})
h2 := hm.add(&entities, Entity{pos = {9, 16}})
if e, ok := hm.get(&entities, h2); ok {
e.pos.x += 32
}
hm.remove(&entities, h1)
h3 := hm.add(&entities, Entity{pos = {6, 7}})
assert(hm.is_valid(entities, h3))
it := hm.iterator_make(&entities)
for e, h in hm.iterate(&it) {
assert(hm.is_valid(entities, h))
e.pos += {1, 2}
}
}
*/
package container_handle_map

View File

@@ -0,0 +1,142 @@
package container_handle_map
import "base:runtime"
import "base:builtin"
import "base:intrinsics"
@(require) import "core:container/xar"
Dynamic_Handle_Map :: struct($T: typeid, $Handle_Type: typeid)
where
intrinsics.type_has_field(Handle_Type, "idx"),
intrinsics.type_has_field(Handle_Type, "gen"),
intrinsics.type_is_unsigned(intrinsics.type_field_type(Handle_Type, "idx")),
intrinsics.type_is_unsigned(intrinsics.type_field_type(Handle_Type, "gen")),
intrinsics.type_field_type(Handle_Type, "idx") == intrinsics.type_field_type(Handle_Type, "gen"),
intrinsics.type_has_field (T, "handle"),
intrinsics.type_field_type(T, "handle") == Handle_Type {
items: xar.Array(T, 4),
unused_items: xar.Array(u32, 4),
}
dynamic_init :: proc(m: ^$D/Dynamic_Handle_Map($T, $Handle_Type), allocator: runtime.Allocator) {
xar.init(&m.items, allocator)
xar.init(&m.unused_items, allocator)
}
dynamic_destroy :: proc(m: ^$D/Dynamic_Handle_Map($T, $Handle_Type)) {
xar.destroy(&m.unused_items)
xar.destroy(&m.items)
}
@(require_results)
dynamic_add :: proc(m: ^$D/Dynamic_Handle_Map($T, $Handle_Type), item: T, loc := #caller_location) -> (handle: Handle_Type, err: runtime.Allocator_Error) #optional_allocator_error {
if xar.len(m.unused_items) > 0 {
i := xar.pop(&m.unused_items)
ptr := xar.get_ptr_unsafe(&m.items, i)
prev_gen := ptr.handle.gen
ptr^ = item
ptr.handle.idx = auto_cast i
ptr.handle.gen = auto_cast (prev_gen + 1)
return ptr.handle, nil
}
if xar.len(m.items) == 0 {
// initialize the zero-value sentinel
xar.append(&m.items, T{}, loc) or_return
}
_ = xar.append(&m.items, item, loc) or_return
i := xar.len(m.items)-1
ptr := xar.get_ptr_unsafe(&m.items, i)
ptr.handle.idx = auto_cast i
ptr.handle.gen = 1
return ptr.handle, nil
}
@(require_results)
dynamic_get :: proc "contextless" (m: ^$D/Dynamic_Handle_Map($T, $Handle_Type), h: Handle_Type) -> (^T, bool) #optional_ok {
if h.idx <= 0 || int(u32(h.idx)) >= xar.len(m.items) {
return nil, false
}
if e := xar.get_ptr_unsafe(&m.items, h.idx); e.handle == h {
return e, true
}
return nil, false
}
dynamic_remove :: proc(m: ^$D/Dynamic_Handle_Map($T, $Handle_Type), h: Handle_Type, loc := #caller_location) -> (found: bool, err: runtime.Allocator_Error) {
if h.idx <= 0 || int(u32(h.idx)) >= xar.len(m.items) {
return false, nil
}
if item := xar.get_ptr(&m.items, h.idx); item.handle == h {
xar.append(&m.unused_items, u32(h.idx), loc) or_return
item.handle.idx = 0
return true, nil
}
return false, nil
}
@(require_results)
dynamic_is_valid :: proc "contextless" (m: ^$D/Dynamic_Handle_Map($T, $Handle_Type), h: Handle_Type) -> bool {
return h.idx > 0 && int(u32(h.idx)) < xar.len(m.items) && xar.get_ptr_unsafe(&m.items, h.idx).handle == h
}
// Returns the number of possibly valid items in the handle map.
@(require_results)
dynamic_len :: proc "contextless" (m: $D/Dynamic_Handle_Map($T, $Handle_Type)) -> uint {
n := xar.len(m.items) - xar.len(m.unused_items)
return uint(n-1 if n > 0 else 0)
}
@(require_results)
dynamic_cap :: proc "contextless" (m: $D/Dynamic_Handle_Map($T, $Handle_Type)) -> uint {
n := xar.cap(m.items)
return uint(n-1 if n > 0 else 0)
}
dynamic_clear :: proc "contextless" (m: ^$D/Dynamic_Handle_Map($T, $Handle_Type)) {
xar.clear(&m.items)
xar.clear(&m.unused_items)
}
// An iterator for a handle map.
Dynamic_Handle_Map_Iterator :: struct($D: typeid) {
m: ^D,
index: int,
}
// Makes an iterator from a handle map.
@(require_results)
dynamic_iterator_make :: proc "contextless" (m: ^$D/Dynamic_Handle_Map($T, $Handle_Type)) -> Dynamic_Handle_Map_Iterator(D) {
return {m, 1}
}
/*
Iterate over a handle map. It will skip over unused item slots (e.g. handle.idx == 0).
Usage:
it := hm.dynamic_iterator_make(&the_dynamic_handle_map)
for item, handle in hm.iterate(&it) {
...
}
*/
@(require_results)
dynamic_iterate :: proc "contextless" (it: ^$DHI/Dynamic_Handle_Map_Iterator($D/Dynamic_Handle_Map($T, $Handle_Type))) -> (val: ^T, h: Handle_Type, ok: bool) {
for _ in it.index..<xar.len(it.m.items) {
e := xar.get_ptr_unsafe(&it.m.items, it.index)
it.index += 1
if e.handle.idx != 0 {
return e, e.handle, true
}
}
it.index = 0
return
}

View File

@@ -0,0 +1,222 @@
package container_handle_map
import "base:builtin"
import "base:intrinsics"
// Default 16-bit Handle type which can be used for handle maps which only need a maximum of 254 (1<<8 - 2) items
Handle16 :: struct {
idx: u8,
gen: u8,
}
// Default 32-bit Handle type which can be used for handle maps which only need a maximum of 65534 (1<<16 - 2) items
Handle32 :: struct {
idx: u16,
gen: u16,
}
// Default 64-bit Handle type which can be used for handle maps which only need a maximum of 4294967294 (1<<32 - 2) items
Handle64 :: struct {
idx: u32,
gen: u32,
}
Static_Handle_Map :: struct($N: uint, $T: typeid, $Handle_Type: typeid)
where
0 < N, N < uint(1<<31 - 1),
intrinsics.type_has_field(Handle_Type, "idx"),
intrinsics.type_has_field(Handle_Type, "gen"),
intrinsics.type_is_unsigned(intrinsics.type_field_type(Handle_Type, "idx")),
intrinsics.type_is_unsigned(intrinsics.type_field_type(Handle_Type, "gen")),
intrinsics.type_field_type(Handle_Type, "idx") == intrinsics.type_field_type(Handle_Type, "gen"),
N < uint(max(intrinsics.type_field_type(Handle_Type, "idx"))),
intrinsics.type_has_field (T, "handle"),
intrinsics.type_field_type(T, "handle") == Handle_Type {
// The zero element represent a zero-value sentinel (dummy value), allowing for `idx == 0` to mean a no-handle.
// This means the capacity is actually N-1 items.
items: [N]T,
used_len: u32, // How many of the items are in use
unused_len: u32, // Use to calculate the number of valid items
unused_items: [N]u32,
next_unused: u32,
}
// `add` a value of type `T` to the handle map. This will return a pointer to the item and an optional boolean to check for validity.
@(require_results)
static_add :: proc "contextless" (m: ^$H/Static_Handle_Map($N, $T, $Handle_Type), item: T) -> (handle: Handle_Type, ok: bool) #optional_ok {
if i := m.next_unused; i != 0 {
ptr := &m.items[i]
m.next_unused = m.unused_items[i]
m.unused_items[i] = 0
prev_gen := ptr.handle.gen
ptr^ = item
ptr.handle.idx = auto_cast i
ptr.handle.gen = auto_cast (prev_gen + 1)
m.unused_len -= 1
return ptr.handle, true
}
if m.used_len == 0 {
// initialize the zero-value sentinel
m.items[0] = {}
m.used_len += 1
}
if m.used_len == builtin.len(m.items) {
return {}, false
}
ptr := &m.items[m.used_len]
ptr^ = item
ptr.handle.idx = auto_cast m.used_len
ptr.handle.gen = 1
m.used_len += 1
return ptr.handle, true
}
// `get` a stable pointer of type `^T` by resolving the handle `h`. If the handle is not valid, then `nil, false` is returned.
@(require_results)
static_get :: proc "contextless" (m: ^$H/Static_Handle_Map($N, $T, $Handle_Type), h: Handle_Type) -> (^T, bool) #optional_ok {
if h.idx <= 0 || u32(h.idx) >= m.used_len {
return nil, false
}
if e := &m.items[h.idx]; e.handle == h {
return e, true
}
return nil, false
}
// `remove` an item from the handle map from the handle `h`.
static_remove :: proc "contextless" (m: ^$H/Static_Handle_Map($N, $T, $Handle_Type), h: Handle_Type) -> bool {
if h.idx <= 0 || u32(h.idx) >= m.used_len {
return false
}
if item := &m.items[h.idx]; item.handle == h {
m.unused_items[h.idx] = m.next_unused
m.next_unused = u32(h.idx)
m.unused_len += 1
item.handle.idx = 0
return true
}
return false
}
// Returns true when the handle `h` is valid relating to the handle map.
@(require_results)
static_is_valid :: proc "contextless" (m: $H/Static_Handle_Map($N, $T, $Handle_Type), h: Handle_Type) -> bool {
return h.idx > 0 && u32(h.idx) < m.used_len && m.items[h.idx].handle == h
}
// Returns the number of possibly valid items in the handle map.
@(require_results)
static_len :: proc "contextless" (m: $H/Static_Handle_Map($N, $T, $Handle_Type)) -> uint {
n := uint(m.used_len) - uint(m.unused_len)
return n-1 if n > 0 else 0
}
// Returns the capacity of the items in a handle map.
// This is equivalent to `N-1` as the zero value is reserved for the zero-value sentinel.
@(require_results)
static_cap :: proc "contextless" (m: $H/Static_Handle_Map($N, $T, $Handle_Type)) -> uint {
// We could just return `N` but I am doing this for clarity
return builtin.len(m.items)-1
}
// `clear` the handle map by zeroing all of the memory.
// Internally this does not do `m^ = {}` but rather uses `intrinsics.mem_zero` explicitly improve performance.
static_clear :: proc "contextless" (m: ^$H/Static_Handle_Map($N, $T, $Handle_Type)) {
intrinsics.mem_zero(m, size_of(m^))
}
// An iterator for a handle map.
Static_Handle_Map_Iterator :: struct($H: typeid) {
m: ^H,
index: u32,
}
// Makes an iterator from a handle map.
@(require_results)
static_iterator_make :: proc "contextless" (m: ^$H/Static_Handle_Map($N, $T, $Handle_Type)) -> Static_Handle_Map_Iterator(H) {
return {m, 1}
}
/*
Iterate over a handle map. It will skip over unused item slots (e.g. handle.idx == 0).
Usage:
it := hm.iterator_make(&the_handle_map)
for item, handle in hm.iterate(&it) {
...
}
*/
@(require_results)
static_iterate :: proc "contextless" (it: ^$HI/Static_Handle_Map_Iterator($H/Static_Handle_Map($N, $T, $Handle_Type))) -> (val: ^T, h: Handle_Type, ok: bool) {
for _ in it.index..<it.m.used_len {
e := &it.m.items[it.index]
it.index += 1
if e.handle.idx != 0 {
return e, e.handle, true
}
}
it.index = 0
return
}
add :: proc{
static_add,
dynamic_add,
}
get :: proc{
static_get,
dynamic_get,
}
remove :: proc{
static_remove,
dynamic_remove,
}
is_valid :: proc{
static_is_valid,
dynamic_is_valid,
}
len :: proc{
static_len,
dynamic_len,
}
cap :: proc{
static_cap,
dynamic_cap,
}
clear :: proc{
static_clear,
dynamic_clear,
}
iterator_make :: proc{
static_iterator_make,
dynamic_iterator_make,
}
iterate :: proc{
static_iterate,
dynamic_iterate,
}

View File

@@ -1,7 +1,7 @@
/*
Package list implements an intrusive doubly-linked list.
An intrusive doubly-linked list.
An intrusive container requires a `Node` to be embedded in your own structure, like this.
The intrusive container requires a `Node` to be embedded in your own structure, like this.
Example:
My_String :: struct {
node: list.Node,
@@ -46,4 +46,4 @@ Output:
Hello
World
*/
package container_intrusive_list
package container_intrusive_list

View File

@@ -1,3 +1,4 @@
// A least-recently-used (`LRU`) cache. It automatically removes older entries if its capacity is reached.
package container_lru
import "base:runtime"

View File

@@ -0,0 +1,146 @@
package container_pool
import "base:intrinsics"
import "base:runtime"
import "base:sanitizer"
import "core:sync"
_ :: sanitizer
_ :: sync
DEFAULT_BLOCK_SIZE :: _DEFAULT_BLOCK_SIZE
Pool_Arena :: _Pool_Arena
/*
A thread-safe (between init and destroy) object pool backed by virtual growing arena returning stable pointers.
The element type requires an intrusive link node.
Example:
Elem :: struct {
link: ^Elem,
}
p: pool.Pool(Elem)
pool.init(&p, "link")
*/
Pool :: struct($T: typeid) {
arena: Pool_Arena,
num_outstanding: int,
num_ready: int,
link_off: uintptr,
// Guards free_list. An untagged Treiber stack is vulnerable to ABA:
// between get's head load and its CAS, another thread can pop the head,
// reuse it, and push it back — the CAS then succeeds and installs a
// stale next pointer, handing an in-flight element to two owners.
// A tagged head would keep this lock-free but requires a double-width
// CAS, which is not portably available.
mu: sync.Mutex,
free_list: ^T,
}
@(require_results)
init :: proc(p: ^Pool($T), $link_field: string, block_size: uint = DEFAULT_BLOCK_SIZE) -> (err: runtime.Allocator_Error)
where intrinsics.type_has_field(T, link_field),
intrinsics.type_field_type(T, link_field) == ^T {
p.link_off = offset_of_by_string(T, link_field)
return _pool_arena_init(&p.arena, block_size)
}
destroy :: proc(p: ^Pool($T)) {
elem := sync.atomic_exchange_explicit(&p.free_list, nil, .Acquire)
sync.atomic_store_explicit(&p.num_ready, 0, .Relaxed)
when .Address in ODIN_SANITIZER_FLAGS {
for ; elem != nil; elem = _get_next(p, elem) {
_unpoison_elem(p, elem)
}
} else {
_ = elem
}
_pool_arena_destroy(&p.arena)
p.arena = {}
}
@(require_results)
get :: proc(p: ^Pool($T)) -> (elem: ^T, err: runtime.Allocator_Error) #optional_allocator_error {
defer sync.atomic_add_explicit(&p.num_outstanding, 1, .Relaxed)
sync.mutex_lock(&p.mu)
elem = p.free_list
if elem == nil {
sync.mutex_unlock(&p.mu)
// NOTE: pool arena has an internal lock.
return new(T, _pool_arena_allocator(&p.arena))
}
p.free_list = _get_next(p, elem)
sync.mutex_unlock(&p.mu)
_set_next(p, elem, nil)
_unpoison_elem(p, elem)
sync.atomic_sub_explicit(&p.num_ready, 1, .Relaxed)
return
}
put :: proc(p: ^Pool($T), elem: ^T) {
intrinsics.mem_zero(elem, size_of(T))
_poison_elem(p, elem)
defer sync.atomic_sub_explicit(&p.num_outstanding, 1, .Relaxed)
defer sync.atomic_add_explicit(&p.num_ready, 1, .Relaxed)
sync.mutex_lock(&p.mu)
_set_next(p, elem, p.free_list)
p.free_list = elem
sync.mutex_unlock(&p.mu)
}
num_outstanding :: proc(p: ^Pool($T)) -> int {
return sync.atomic_load(&p.num_outstanding)
}
num_ready :: proc(p: ^Pool($T)) -> int {
return sync.atomic_load(&p.num_ready)
}
cap :: proc(p: ^Pool($T)) -> int {
return sync.atomic_load(&p.num_ready) + sync.atomic_load(&p.num_outstanding)
}
_get_next :: proc(p: ^Pool($T), elem: ^T) -> ^T {
return (^^T)(uintptr(elem) + p.link_off)^
}
_set_next :: proc(p: ^Pool($T), elem: ^T, next: ^T) {
(^^T)(uintptr(elem) + p.link_off)^ = next
}
_poison_elem :: proc(p: ^Pool($T), elem: ^T) {
when .Address in ODIN_SANITIZER_FLAGS {
if p.link_off > 0 {
sanitizer.address_poison_rawptr(elem, int(p.link_off))
}
len := size_of(T) - p.link_off - size_of(rawptr)
if len > 0 {
ptr := rawptr(uintptr(elem) + p.link_off + size_of(rawptr))
sanitizer.address_poison_rawptr(ptr, int(len))
}
}
}
_unpoison_elem :: proc(p: ^Pool($T), elem: ^T) {
when .Address in ODIN_SANITIZER_FLAGS {
if p.link_off > 0 {
sanitizer.address_unpoison_rawptr(elem, int(p.link_off))
}
len := size_of(T) - p.link_off - size_of(rawptr)
if len > 0 {
ptr := rawptr(uintptr(elem) + p.link_off + size_of(rawptr))
sanitizer.address_unpoison_rawptr(ptr, int(len))
}
}
}

View File

@@ -0,0 +1,27 @@
#+build !darwin
#+build !freebsd
#+build !openbsd
#+build !netbsd
#+build !linux
#+build !windows
#+private
package container_pool
import "base:runtime"
_Pool_Arena :: runtime.Arena
_DEFAULT_BLOCK_SIZE :: runtime.Megabyte
_pool_arena_init :: proc(arena: ^Pool_Arena, block_size: uint = DEFAULT_BLOCK_SIZE) -> (err: runtime.Allocator_Error) {
runtime.arena_init(arena, block_size, runtime.default_allocator()) or_return
return
}
_pool_arena_allocator :: proc(arena: ^Pool_Arena) -> runtime.Allocator {
return runtime.arena_allocator(arena)
}
_pool_arena_destroy :: proc(arena: ^Pool_Arena) {
runtime.arena_destroy(arena)
}

View File

@@ -0,0 +1,22 @@
#+build darwin, freebsd, openbsd, netbsd, linux, windows
package container_pool
import "base:runtime"
import "core:mem/virtual"
_Pool_Arena :: virtual.Arena
_DEFAULT_BLOCK_SIZE :: runtime.Gigabyte
_pool_arena_init :: proc(arena: ^Pool_Arena, block_size: uint = DEFAULT_BLOCK_SIZE) -> (err: runtime.Allocator_Error) {
virtual.arena_init_growing(arena, block_size) or_return
return
}
_pool_arena_allocator :: proc(arena: ^Pool_Arena) -> runtime.Allocator {
return virtual.arena_allocator(arena)
}
_pool_arena_destroy :: proc(arena: ^Pool_Arena) {
virtual.arena_destroy(arena)
}

View File

@@ -0,0 +1,53 @@
/*
A priority queue data structure.
Important: It needs to be initialized with `less` and `swap` procedures, see `init` and `init_from_dynamic_array`.
Example:
import "base:runtime"
import pq "core:container/priority_queue"
main :: proc() {
Printer_Job :: struct {
user_id: u64,
weight: enum u8 {Highest, High, Normal, Low, Idle},
}
q: pq.Priority_Queue(Printer_Job)
pq.init(
pq = &q,
less = proc(a, b: Printer_Job) -> bool {
// Jobs will be sorted in order of increasing weight
return a.weight < b.weight
},
swap = pq.default_swap_proc(Printer_Job),
)
defer pq.destroy(&q)
// Add jobs with random weights
for _ in 0..<100 {
job: Printer_Job = ---
assert(runtime.random_generator_read_ptr(context.random_generator, &job, size_of(job)))
pq.push(&q, job)
}
// Drain jobs in order of importance
last: Printer_Job
for pq.len(q) > 0 {
v := pq.pop(&q)
assert(v.weight >= last.weight)
last = v
}
// Queue empty?
assert(pq.len(q) == 0)
// Add one more job
pq.push(&q, Printer_Job{user_id = 42, weight = .Idle})
// Cancel all jobs
pq.clear(&q)
assert(pq.len(q) == 0)
}
*/
package container_priority_queue

View File

@@ -3,6 +3,10 @@ package container_priority_queue
import "base:builtin"
import "base:runtime"
// Priority Queue.
//
// Important: It needs to be initialized with `less` and `swap` procedures, see `init` and `init_from_dynamic_array`.
// See `doc.odin` for an example.
Priority_Queue :: struct($T: typeid) {
queue: [dynamic]T,
@@ -133,12 +137,10 @@ pop_safe :: proc(pq: ^$Q/Priority_Queue($T), loc := #caller_location) -> (value:
remove :: proc(pq: ^$Q/Priority_Queue($T), i: int) -> (value: T, ok: bool) {
n := builtin.len(pq.queue)
if 0 <= i && i < n {
if n != i {
pq.swap(pq.queue[:], i, n)
_shift_down(pq, i, n)
_shift_up(pq, i)
}
value, ok = builtin.pop_safe(&pq.queue)
pq.swap(pq.queue[:], i, n-1)
_shift_down(pq, i, n-1)
_shift_up(pq, i)
value, ok = builtin.pop(&pq.queue), true
}
return
}

View File

@@ -1,10 +1,17 @@
// A dynamically resizable double-ended queue/ring-buffer.
package container_queue
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 +20,31 @@ Queue :: struct($T: typeid) {
DEFAULT_CAPACITY :: 16
// Procedure to initialize a queue
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
}
/*
Initialize a `Queue` with a starting `capacity` and an `allocator`.
*/
init :: proc(q: ^$Q/Queue($T), capacity := DEFAULT_CAPACITY, allocator := context.allocator, loc := #caller_location) -> runtime.Allocator_Error {
clear(q)
return reserve(q, capacity)
q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{
data = nil,
len = 0,
cap = 0,
allocator = allocator,
}
return reserve(q, capacity, loc)
}
// 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 +56,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,87 +76,193 @@ 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 :: proc(q: ^$Q/Queue($T), capacity: int) -> runtime.Allocator_Error {
/*
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, loc := #caller_location) -> runtime.Allocator_Error {
if capacity > space(q^) {
return _grow(q, uint(capacity))
return _grow(q, uint(capacity), loc)
}
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]
}
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]
}
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]
}
/*
Push an element to the back of the queue.
// Push an element to the back of the queue
push_back :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error) {
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, loc := #caller_location) -> (ok: bool, err: runtime.Allocator_Error) {
if space(q^) == 0 {
_grow(q) or_return
_grow(q, loc = loc) or_return
}
idx := (q.offset+uint(q.len))%builtin.len(q.data)
q.data[idx] = elem
@@ -138,27 +270,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_front :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error) {
/*
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, loc := #caller_location) -> (ok: bool, err: runtime.Allocator_Error) {
if space(q^) == 0 {
_grow(q) or_return
}
_grow(q, loc = loc) 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 +352,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 +381,18 @@ pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) {
return
}
// Push multiple elements to the back of the queue
push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime.Allocator_Error) {
/*
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, loc := #caller_location) -> (ok: bool, err: runtime.Allocator_Error) {
n := uint(builtin.len(elems))
if space(q^) < int(n) {
_grow(q, q.len + n) or_return
_grow(q, q.len + n, loc) or_return
}
sz := uint(builtin.len(q.data))
insert_from := (q.offset + q.len) % sz
insert_to := n
@@ -207,19 +405,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 +441,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
@@ -241,10 +456,10 @@ clear :: proc(q: ^$Q/Queue($T)) {
// Internal growing procedure
_grow :: proc(q: ^$Q/Queue($T), min_capacity: uint = 0) -> runtime.Allocator_Error {
_grow :: proc(q: ^$Q/Queue($T), min_capacity: uint = 0, loc := #caller_location) -> runtime.Allocator_Error {
new_capacity := max(min_capacity, uint(8), uint(builtin.len(q.data))*2)
n := uint(builtin.len(q.data))
builtin.resize(&q.data, int(new_capacity)) or_return
builtin.resize(&q.data, int(new_capacity), loc) or_return
if q.offset + q.len > n {
diff := n - q.offset
copy(q.data[new_capacity-diff:], q.data[q.offset:][:diff])

View File

@@ -1,4 +1,4 @@
// This package implements a red-black tree
// A red-black tree with the same API as our AVL tree.
package container_rbtree
@(require) import "base:intrinsics"
@@ -91,24 +91,24 @@ destroy :: proc(t: ^$T/Tree($Key, $Value), call_on_remove: bool = true) {
}
}
len :: proc "contextless" (t: ^$T/Tree($Key, $Value)) -> (node_count: int) {
len :: proc "contextless" (t: $T/Tree($Key, $Value)) -> (node_count: int) {
return t._size
}
// first returns the first node in the tree (in-order) or nil iff
// first returns the first node in the tree (in-order) or nil if and only if (⟺)
// the tree is empty.
first :: proc "contextless" (t: ^$T/Tree($Key, $Value)) -> ^Node(Key, Value) {
return tree_first_or_last_in_order(t, Direction.Backward)
}
// last returns the last element in the tree (in-order) or nil iff
// last returns the last element in the tree (in-order) or nil if and only if (⟺)
// the tree is empty.
last :: proc "contextless" (t: ^$T/Tree($Key, $Value)) -> ^Node(Key, Value) {
return tree_first_or_last_in_order(t, Direction.Forward)
}
// find finds the key in the tree, and returns the corresponding node, or nil iff the value is not present.
find :: proc(t: ^$T/Tree($Key, $Value), key: Key) -> (node: ^Node(Key, Value)) {
// find finds the key in the tree, and returns the corresponding node, or nil if and only if (⟺) the value is not present.
find :: proc(t: $T/Tree($Key, $Value), key: Key) -> (node: ^Node(Key, Value)) {
node = t._root
for node != nil {
switch t._cmp_fn(key, node.key) {
@@ -120,17 +120,17 @@ find :: proc(t: ^$T/Tree($Key, $Value), key: Key) -> (node: ^Node(Key, Value)) {
return node
}
// find_value finds the key in the tree, and returns the corresponding value, or nil iff the value is not present.
find_value :: proc(t: ^$T/Tree($Key, $Value), key: Key) -> (value: Value, ok: bool) #optional_ok {
// find_value finds the key in the tree, and returns the corresponding value, or nil if and only if (⟺) the value is not present.
find_value :: proc(t: $T/Tree($Key, $Value), key: Key) -> (value: Value, ok: bool) #optional_ok {
if n := find(t, key); n != nil {
return n.value, true
}
return
}
// find_or_insert attempts to insert the value into the tree, and returns
// the node, a boolean indicating if the value was inserted, and the
// node allocator error if relevant. If the value is already present, the existing node is updated.
// find_or_insert attempts to insert the key-value pair into the tree, and returns
// the node, a boolean indicating if a new node was inserted, and the
// node allocator error if relevant. If the key is already present, the existing node is updated and returned.
find_or_insert :: proc(t: ^$T/Tree($Key, $Value), key: Key, value: Value) -> (n: ^Node(Key, Value), inserted: bool, err: runtime.Allocator_Error) {
n_ptr := &t._root
for n_ptr^ != nil {
@@ -141,6 +141,7 @@ find_or_insert :: proc(t: ^$T/Tree($Key, $Value), key: Key, value: Value) -> (n:
case .Greater:
n_ptr = &n._right
case .Equal:
n.value = value
return
}
}
@@ -153,7 +154,7 @@ find_or_insert :: proc(t: ^$T/Tree($Key, $Value), key: Key, value: Value) -> (n:
return n, true, nil
}
// remove removes a node or value from the tree, and returns true iff the
// remove removes a node or value from the tree, and returns true if and only if (⟺) the
// removal was successful. While the node's value will be left intact,
// the node itself will be freed via the tree's node allocator.
remove :: proc {
@@ -161,18 +162,18 @@ remove :: proc {
remove_node,
}
// remove_value removes a value from the tree, and returns true iff the
// remove_value removes a value from the tree, and returns true if and only if (⟺) the
// removal was successful. While the node's key + value will be left intact,
// the node itself will be freed via the tree's node allocator.
remove_key :: proc(t: ^$T/Tree($Key, $Value), key: Key, call_on_remove := true) -> bool {
n := find(t, key)
n := find(t^, key)
if n == nil {
return false // Key not found, nothing to do
}
return remove_node(t, n, call_on_remove)
}
// remove_node removes a node from the tree, and returns true iff the
// remove_node removes a node from the tree, and returns true if and only if (⟺) the
// removal was successful. While the node's key + value will be left intact,
// the node itself will be freed via the tree's node allocator.
remove_node :: proc(t: ^$T/Tree($Key, $Value), node: ^$N/Node(Key, Value), call_on_remove := true) -> (found: bool) {
@@ -234,14 +235,14 @@ iterator_from_pos :: proc "contextless" (t: ^$T/Tree($Key, $Value), pos: ^Node(K
}
// iterator_get returns the node currently pointed to by the iterator,
// or nil iff the node has been removed, the tree is empty, or the end
// or nil if and only if (⟺) the node has been removed, the tree is empty, or the end
// of the tree has been reached.
iterator_get :: proc "contextless" (it: ^$I/Iterator($Key, $Value)) -> ^Node(Key, Value) {
return it._cur
}
// iterator_remove removes the node currently pointed to by the iterator,
// and returns true iff the removal was successful. Semantics are the
// and returns true if and only if (⟺) the removal was successful. Semantics are the
// same as the Tree remove.
iterator_remove :: proc(it: ^$I/Iterator($Key, $Value), call_on_remove: bool = true) -> bool {
if it._cur == nil {
@@ -257,7 +258,7 @@ iterator_remove :: proc(it: ^$I/Iterator($Key, $Value), call_on_remove: bool = t
}
// iterator_next advances the iterator and returns the (node, true) or
// or (nil, false) iff the end of the tree has been reached.
// or (nil, false) if and only if (⟺) the end of the tree has been reached.
//
// Note: The first call to iterator_next will return the first node instead
// of advancing the iterator.

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