Merge branch 'master' into macharena

This commit is contained in:
Colin Davidson
2025-06-08 16:17:32 -07:00
279 changed files with 18301 additions and 4272 deletions
+64 -56
View File
@@ -32,8 +32,8 @@ jobs:
gmake -C vendor/miniaudio/src gmake -C vendor/miniaudio/src
./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_amd64 ./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_amd64
./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_arm64 ./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 examples/all/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/sdl3 -vet -strict-style -disallow-do -target:netbsd_arm64 -no-entry-point
./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true ./odin test tests/core/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/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/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
@@ -64,7 +64,7 @@ jobs:
gmake -C vendor/cgltf/src gmake -C vendor/cgltf/src
gmake -C vendor/miniaudio/src gmake -C vendor/miniaudio/src
./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64 ./odin check examples/all -vet -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/sdl3 -vet -strict-style -disallow-do -target:freebsd_amd64 -no-entry-point
./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true ./odin test tests/core/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/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/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
@@ -75,32 +75,35 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
# MacOS 13 runs on Intel, 14 runs on ARM # MacOS 13 runs on Intel, 14 runs on ARM
os: [ubuntu-latest, macos-13, macos-14] os: [macos-13, macos-14, ubuntu-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel' || 'Ubuntu') }} Build, Check, and Test name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel') || (matrix.os == 'ubuntu-latest' && 'Ubuntu') }} Build, Check, and Test
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- uses: actions/checkout@v4
- name: Download LLVM (Linux) - uses: actions/checkout@v4
if: matrix.os == 'ubuntu-latest'
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 20
echo "/usr/lib/llvm-20/bin" >> $GITHUB_PATH
- name: Download LLVM (MacOS Intel) - name: Download LLVM (MacOS Intel)
if: matrix.os == 'macos-13' if: matrix.os == 'macos-13'
run: | run: |
brew update brew update
brew install llvm@20 lua@5.4 lld brew install llvm@20 lua@5.4 lld
echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH
- name: Download LLVM (MacOS ARM) - name: Download LLVM (MacOS ARM)
if: matrix.os == 'macos-14' if: matrix.os == 'macos-14'
run: | run: |
brew update brew update
brew install llvm@20 wasmtime lua@5.4 lld brew install llvm@20 wasmtime lua@5.4 lld
echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH
- name: Download LLVM (Ubuntu)
if: matrix.os == 'ubuntu-latest'
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 20
echo "/usr/lib/llvm-20/bin" >> $GITHUB_PATH
- name: Build Odin - name: Build Odin
run: ./build_odin.sh release run: ./build_odin.sh release
@@ -121,56 +124,61 @@ jobs:
run: ./odin run examples/demo -debug run: ./odin run examples/demo -debug
- name: Odin check examples/all - name: Odin check examples/all
run: ./odin check examples/all -strict-style -vet -disallow-do run: ./odin check examples/all -strict-style -vet -disallow-do
- name: Odin check vendor/sdl3 - name: Odin check examples/all/sdl3
run: ./odin check vendor/sdl3 -strict-style -vet -disallow-do -no-entry-point run: ./odin check examples/all/sdl3 -strict-style -vet -disallow-do -no-entry-point
- name: Normal Core library tests - name: Normal Core library tests
run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Optimized Core library tests - name: Optimized Core library tests
run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Vendor library tests - name: Vendor library tests
run: ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true run: ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Internals tests - name: Internals tests
run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: GitHub Issue tests - name: GitHub Issue tests
run: | run: |
cd tests/issues cd tests/issues
./run.sh ./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 - name: Run demo on WASI WASM32
run: | run: |
./odin build examples/demo -target:wasi_wasm32 -vet -strict-style -disallow-do -out:demo ./odin build examples/demo -target:wasi_wasm32 -vet -strict-style -disallow-do -out:demo
wasmtime ./demo.wasm wasmtime ./demo.wasm
if: matrix.os == 'macos-14' if: matrix.os == 'macos-14'
- name: Check benchmarks
run: ./odin check tests/benchmark -vet -strict-style -no-entry-point
- name: Odin check examples/all for Linux i386
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_i386
- name: Odin check examples/all for Linux arm64
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_arm64
- name: Odin check examples/all for FreeBSD amd64
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
- name: Odin check examples/all for OpenBSD amd64
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64
- name: Odin check examples/all for js_wasm32
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -no-entry-point -target:js_wasm32
- name: Odin check examples/all for js_wasm64p32
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all -vet -strict-style -disallow-do -no-entry-point -target:js_wasm64p32
- name: Odin check examples/all/sdl3 for Linux i386
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_i386
- name: Odin check examples/all/sdl3 for Linux arm64
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_arm64
- name: Odin check examples/all/sdl3 for FreeBSD amd64
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:freebsd_amd64
- name: Odin check examples/all/sdl3 for OpenBSD amd64
if: matrix.os == 'ubuntu-latest'
run: ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:openbsd_amd64
build_windows: build_windows:
name: Windows Build, Check, and Test name: Windows Build, Check, and Test
runs-on: windows-2022 runs-on: windows-2022
@@ -206,32 +214,32 @@ jobs:
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin check examples/all -vet -strict-style -disallow-do odin check examples/all -vet -strict-style -disallow-do
- name: Odin check vendor/sdl3 - name: Odin check examples/all/sdl3
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point
- name: Core library tests - name: Core library tests
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Optimized core library tests - name: Optimized core library tests
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Vendor library tests - name: Vendor library tests
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
copy vendor\lua\5.4\windows\*.dll . copy vendor\lua\5.4\windows\*.dll .
odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Odin internals tests - name: Odin internals tests
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Check issues - name: Check issues
shell: cmd shell: cmd
run: | run: |
@@ -293,8 +301,8 @@ jobs:
- name: Odin check examples/all - name: Odin check examples/all
run: ./odin check examples/all -target:linux_riscv64 -vet -strict-style -disallow-do run: ./odin check examples/all -target:linux_riscv64 -vet -strict-style -disallow-do
- name: Odin check vendor/sdl3 - name: Odin check examples/all/sdl3
run: ./odin check vendor/sdl3 -target:linux_riscv64 -vet -strict-style -disallow-do -no-entry-point run: ./odin check examples/all/sdl3 -target:linux_riscv64 -vet -strict-style -disallow-do -no-entry-point
- name: Install riscv64 toolchain and qemu - name: Install riscv64 toolchain and qemu
run: sudo apt-get install -y qemu-user qemu-user-static gcc-12-riscv64-linux-gnu libc6-riscv64-cross run: sudo apt-get install -y qemu-user qemu-user-static gcc-12-riscv64-linux-gnu libc6-riscv64-cross
+2 -1
View File
@@ -293,5 +293,6 @@ build.sh
# RAD debugger project file # RAD debugger project file
*.raddbg *.raddbg
*.rdi
tests/issues/build/*
misc/featuregen/featuregen misc/featuregen/featuregen
+231 -8
View File
@@ -7,13 +7,232 @@ nil :: nil
false :: 0!=0 false :: 0!=0
true :: 0==0 true :: 0==0
ODIN_OS :: ODIN_OS // The following constants are added in `checker.cpp`'s `init_universal` procedure.
ODIN_ARCH :: ODIN_ARCH
ODIN_ENDIAN :: ODIN_ENDIAN /*
ODIN_VENDOR :: ODIN_VENDOR An `enum` value indicating the target's CPU architecture.
ODIN_VERSION :: ODIN_VERSION Possible values are: `.amd64`, `.i386`, `.arm32`, `.arm64`, `.wasm32`, `.wasm64p32`, and `.riscv64`.
ODIN_ROOT :: ODIN_ROOT */
ODIN_DEBUG :: ODIN_DEBUG 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` `.iOS`, 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 byte :: u8 // alias
@@ -119,7 +338,8 @@ jmag :: proc(value: Quaternion) -> Float ---
kmag :: proc(value: Quaternion) -> Float --- kmag :: proc(value: Quaternion) -> Float ---
conj :: proc(value: Complex_Or_Quaternion) -> Complex_Or_Quaternion --- 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 --- min :: proc(values: ..T) -> T ---
max :: 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: ...) --- soa_unzip :: proc(value: $S/#soa[]$E) -> (slices: ...) ---
unreachable :: proc() -> ! --- unreachable :: proc() -> ! ---
// Where T is a string, slice, dynamic array, or pointer to an array type
raw_data :: proc(t: $T) -> rawptr
+13 -2
View File
@@ -169,6 +169,7 @@ type_is_union :: proc($T: typeid) -> bool ---
type_is_enum :: proc($T: typeid) -> bool --- type_is_enum :: proc($T: typeid) -> bool ---
type_is_proc :: proc($T: typeid) -> bool --- type_is_proc :: proc($T: typeid) -> bool ---
type_is_bit_set :: 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_simd_vector :: proc($T: typeid) -> bool ---
type_is_matrix :: proc($T: typeid) -> bool --- type_is_matrix :: proc($T: typeid) -> bool ---
@@ -221,6 +222,9 @@ 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_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_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) --- type_has_shared_fields :: proc($U, $V: typeid) -> bool where type_is_struct(U), type_is_struct(V) ---
constant_utf16_cstring :: proc($literal: string) -> [^]u16 --- constant_utf16_cstring :: proc($literal: string) -> [^]u16 ---
@@ -274,8 +278,12 @@ simd_lanes_ge :: proc(a, b: #simd[N]T) -> #simd[N]Integer ---
simd_extract :: proc(a: #simd[N]T, idx: uint) -> T --- simd_extract :: proc(a: #simd[N]T, idx: uint) -> T ---
simd_replace :: proc(a: #simd[N]T, idx: uint, elem: T) -> #simd[N]T --- simd_replace :: proc(a: #simd[N]T, idx: uint, elem: T) -> #simd[N]T ---
simd_reduce_add_bisect :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_mul_bisect :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_add_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- simd_reduce_add_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_mul_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- simd_reduce_mul_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_add_pairs :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_mul_pairs :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_min :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- simd_reduce_min :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_max :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- simd_reduce_max :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_and :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)--- simd_reduce_and :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
@@ -298,7 +306,7 @@ simd_masked_store :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U)
simd_masked_expand_load :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) -> #simd[N]T where type_is_integer(U) || type_is_boolean(U) --- simd_masked_expand_load :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) -> #simd[N]T where type_is_integer(U) || type_is_boolean(U) ---
simd_masked_compress_store :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) where type_is_integer(U) || type_is_boolean(U) --- simd_masked_compress_store :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) where type_is_integer(U) || type_is_boolean(U) ---
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_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_select :: proc(cond: #simd[N]boolean_or_integer, true, false: #simd[N]T) -> #simd[N]T ---
@@ -353,15 +361,18 @@ x86_xgetbv :: proc(cx: u32) -> (eax, edx: u32) ---
objc_object :: struct{} objc_object :: struct{}
objc_selector :: struct{} objc_selector :: struct{}
objc_class :: struct{} objc_class :: struct{}
objc_ivar :: struct{}
objc_id :: ^objc_object objc_id :: ^objc_object
objc_SEL :: ^objc_selector objc_SEL :: ^objc_selector
objc_Class :: ^objc_class objc_Class :: ^objc_class
objc_Ivar :: ^objc_ivar
objc_find_selector :: proc($name: string) -> objc_SEL --- objc_find_selector :: proc($name: string) -> objc_SEL ---
objc_register_selector :: proc($name: string) -> objc_SEL --- objc_register_selector :: proc($name: string) -> objc_SEL ---
objc_find_class :: proc($name: string) -> objc_Class --- objc_find_class :: proc($name: string) -> objc_Class ---
objc_register_class :: proc($name: string) -> objc_Class --- objc_register_class :: proc($name: string) -> objc_Class ---
objc_ivar_get :: proc(self: ^$T) -> ^$U ---
valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr --- valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr ---
+9
View File
@@ -648,6 +648,9 @@ append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: i
@builtin @builtin
inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
when !ODIN_NO_BOUNDS_CHECK {
ensure(index >= 0, "Index must be positive.", loc)
}
if array == nil { if array == nil {
return return
} }
@@ -666,6 +669,9 @@ inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcas
@builtin @builtin
inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
when !ODIN_NO_BOUNDS_CHECK {
ensure(index >= 0, "Index must be positive.", loc)
}
if array == nil { if array == nil {
return return
} }
@@ -689,6 +695,9 @@ inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadca
@builtin @builtin
inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error {
when !ODIN_NO_BOUNDS_CHECK {
ensure(index >= 0, "Index must be positive.", loc)
}
if array == nil { if array == nil {
return return
} }
@@ -1,6 +1,7 @@
package runtime package runtime
import "base:intrinsics" import "base:intrinsics"
import "base:sanitizer"
DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE) DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE)
@@ -43,6 +44,8 @@ memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint
block.base = ([^]byte)(uintptr(block) + base_offset) block.base = ([^]byte)(uintptr(block) + base_offset)
block.capacity = uint(end - uintptr(block.base)) block.capacity = uint(end - uintptr(block.base))
sanitizer.address_poison(block.base, block.capacity)
// Should be zeroed // Should be zeroed
assert(block.used == 0) assert(block.used == 0)
assert(block.prev == nil) assert(block.prev == nil)
@@ -52,6 +55,7 @@ memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint
memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) { memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) {
if block_to_free != nil { if block_to_free != nil {
allocator := block_to_free.allocator allocator := block_to_free.allocator
sanitizer.address_unpoison(block_to_free.base, block_to_free.capacity)
mem_free(block_to_free, allocator, loc) mem_free(block_to_free, allocator, loc)
} }
} }
@@ -83,6 +87,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint)
return return
} }
data = block.base[block.used+alignment_offset:][:min_size] data = block.base[block.used+alignment_offset:][:min_size]
sanitizer.address_unpoison(block.base[block.used:block.used+size])
block.used += size block.used += size
return return
} }
@@ -162,6 +167,7 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
if arena.curr_block != nil { if arena.curr_block != nil {
intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used) intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used)
arena.curr_block.used = 0 arena.curr_block.used = 0
sanitizer.address_poison(arena.curr_block.base, arena.curr_block.capacity)
} }
arena.total_used = 0 arena.total_used = 0
} }
@@ -226,6 +232,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
// grow data in-place, adjusting next allocation // grow data in-place, adjusting next allocation
block.used = uint(new_end) block.used = uint(new_end)
data = block.base[start:new_end] data = block.base[start:new_end]
sanitizer.address_unpoison(data)
return return
} }
} }
@@ -299,6 +306,7 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
assert(block.used >= temp.used, "out of order use of arena_temp_end", loc) assert(block.used >= temp.used, "out of order use of arena_temp_end", loc)
amount_to_zero := block.used-temp.used amount_to_zero := block.used-temp.used
intrinsics.mem_zero(block.base[temp.used:], amount_to_zero) intrinsics.mem_zero(block.base[temp.used:], amount_to_zero)
sanitizer.address_poison(block.base[temp.used:block.capacity])
block.used = temp.used block.used = temp.used
arena.total_used -= amount_to_zero arena.total_used -= amount_to_zero
} }
+10 -2
View File
@@ -1,5 +1,7 @@
package runtime package runtime
import "../sanitizer"
foreign import kernel32 "system:Kernel32.lib" foreign import kernel32 "system:Kernel32.lib"
@(private="file") @(private="file")
@@ -16,7 +18,10 @@ foreign kernel32 {
_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr { _heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
HEAP_ZERO_MEMORY :: 0x00000008 HEAP_ZERO_MEMORY :: 0x00000008
return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size)) ptr := HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size))
// NOTE(lucas): asan not guarunteed to unpoison win32 heap out of the box, do it ourselves
sanitizer.address_unpoison(ptr, size)
return ptr
} }
_heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr { _heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
if new_size == 0 { if new_size == 0 {
@@ -28,7 +33,10 @@ _heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
} }
HEAP_ZERO_MEMORY :: 0x00000008 HEAP_ZERO_MEMORY :: 0x00000008
return HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size)) new_ptr := HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size))
// NOTE(lucas): asan not guarunteed to unpoison win32 heap out of the box, do it ourselves
sanitizer.address_unpoison(new_ptr, new_size)
return new_ptr
} }
_heap_free :: proc "contextless" (ptr: rawptr) { _heap_free :: proc "contextless" (ptr: rawptr) {
if ptr == nil { if ptr == nil {
+216 -111
View File
@@ -16,6 +16,12 @@ RUNTIME_REQUIRE :: false // !ODIN_TILDE
@(private) @(private)
__float16 :: f16 when __ODIN_LLVM_F16_SUPPORTED else u16 __float16 :: f16 when __ODIN_LLVM_F16_SUPPORTED else u16
HAS_HARDWARE_SIMD :: false when (ODIN_ARCH == .amd64 || ODIN_ARCH == .i386) && !intrinsics.has_target_feature("sse2") else
false when (ODIN_ARCH == .arm64 || ODIN_ARCH == .arm32) && !intrinsics.has_target_feature("neon") else
false when (ODIN_ARCH == .wasm64p32 || ODIN_ARCH == .wasm32) && !intrinsics.has_target_feature("simd128") else
false when (ODIN_ARCH == .riscv64) && !intrinsics.has_target_feature("v") else
true
@(private) @(private)
byte_slice :: #force_inline proc "contextless" (data: rawptr, len: int) -> []byte #no_bounds_check { byte_slice :: #force_inline proc "contextless" (data: rawptr, len: int) -> []byte #no_bounds_check {
@@ -229,151 +235,242 @@ memory_equal :: proc "contextless" (x, y: rawptr, n: int) -> bool {
case n == 0: return true case n == 0: return true
case x == y: return true case x == y: return true
} }
a, b := ([^]byte)(x), ([^]byte)(y) a, b := cast([^]byte)x, cast([^]byte)y
length := uint(n)
for i := uint(0); i < length; i += 1 { n := uint(n)
i := uint(0)
m := uint(0)
if n >= 8 {
when HAS_HARDWARE_SIMD {
// Avoid using 256-bit SIMD on platforms where its emulation is
// likely to be less than ideal.
when ODIN_ARCH == .amd64 && intrinsics.has_target_feature("avx2") {
m = n / 32 * 32
for /**/; i < m; i += 32 {
load_a := intrinsics.unaligned_load(cast(^#simd[32]u8)&a[i])
load_b := intrinsics.unaligned_load(cast(^#simd[32]u8)&b[i])
ne := intrinsics.simd_lanes_ne(load_a, load_b)
if intrinsics.simd_reduce_or(ne) != 0 {
return false
}
}
}
}
m = (n-i) / 16 * 16
for /**/; i < m; i += 16 {
load_a := intrinsics.unaligned_load(cast(^#simd[16]u8)&a[i])
load_b := intrinsics.unaligned_load(cast(^#simd[16]u8)&b[i])
ne := intrinsics.simd_lanes_ne(load_a, load_b)
if intrinsics.simd_reduce_or(ne) != 0 {
return false
}
}
m = (n-i) / 8 * 8
for /**/; i < m; i += 8 {
if intrinsics.unaligned_load(cast(^uintptr)&a[i]) != intrinsics.unaligned_load(cast(^uintptr)&b[i]) {
return false
}
}
}
for /**/; i < n; i += 1 {
if a[i] != b[i] { if a[i] != b[i] {
return false return false
} }
} }
return true return true
/*
when size_of(uint) == 8 {
if word_length := length >> 3; word_length != 0 {
for _ in 0..<word_length {
if intrinsics.unaligned_load((^u64)(a)) != intrinsics.unaligned_load((^u64)(b)) {
return false
}
a = a[size_of(u64):]
b = b[size_of(u64):]
}
}
if length & 4 != 0 {
if intrinsics.unaligned_load((^u32)(a)) != intrinsics.unaligned_load((^u32)(b)) {
return false
}
a = a[size_of(u32):]
b = b[size_of(u32):]
}
if length & 2 != 0 {
if intrinsics.unaligned_load((^u16)(a)) != intrinsics.unaligned_load((^u16)(b)) {
return false
}
a = a[size_of(u16):]
b = b[size_of(u16):]
}
if length & 1 != 0 && a[0] != b[0] {
return false
}
return true
} else {
if word_length := length >> 2; word_length != 0 {
for _ in 0..<word_length {
if intrinsics.unaligned_load((^u32)(a)) != intrinsics.unaligned_load((^u32)(b)) {
return false
}
a = a[size_of(u32):]
b = b[size_of(u32):]
}
}
length &= 3
if length != 0 {
for i in 0..<length {
if a[i] != b[i] {
return false
}
}
}
return true
}
*/
} }
memory_compare :: proc "contextless" (a, b: rawptr, n: int) -> int #no_bounds_check {
memory_compare :: proc "contextless" (x, y: rawptr, n: int) -> int #no_bounds_check {
switch { switch {
case a == b: return 0 case x == y: return 0
case a == nil: return -1 case x == nil: return -1
case b == nil: return +1 case y == nil: return +1
} }
a, b := cast([^]byte)x, cast([^]byte)y
x := uintptr(a) n := uint(n)
y := uintptr(b) i := uint(0)
n := uintptr(n) m := uint(0)
SU :: size_of(uintptr) when HAS_HARDWARE_SIMD {
fast := n/SU + 1 when ODIN_ARCH == .amd64 && intrinsics.has_target_feature("avx2") {
offset := (fast-1)*SU m = n / 32 * 32
curr_block := uintptr(0) for /**/; i < m; i += 32 {
if n < SU { load_a := intrinsics.unaligned_load(cast(^#simd[32]u8)&a[i])
fast = 0 load_b := intrinsics.unaligned_load(cast(^#simd[32]u8)&b[i])
} comparison := intrinsics.simd_lanes_ne(load_a, load_b)
if intrinsics.simd_reduce_or(comparison) != 0 {
for /**/; curr_block < fast; curr_block += 1 { sentinel: #simd[32]u8 = u8(0xFF)
va := (^uintptr)(x + curr_block * size_of(uintptr))^ indices := intrinsics.simd_indices(#simd[32]u8)
vb := (^uintptr)(y + curr_block * size_of(uintptr))^ index_select := intrinsics.simd_select(comparison, indices, sentinel)
if va ~ vb != 0 { index_reduce := cast(uint)intrinsics.simd_reduce_min(index_select)
for pos := curr_block*SU; pos < n; pos += 1 { return -1 if a[i+index_reduce] < b[i+index_reduce] else +1
a := (^byte)(x+pos)^
b := (^byte)(y+pos)^
if a ~ b != 0 {
return -1 if (int(a) - int(b)) < 0 else +1
} }
} }
} }
} }
for /**/; offset < n; offset += 1 { m = (n-i) / 16 * 16
a := (^byte)(x+offset)^ for /**/; i < m; i += 16 {
b := (^byte)(y+offset)^ load_a := intrinsics.unaligned_load(cast(^#simd[16]u8)&a[i])
if a ~ b != 0 { load_b := intrinsics.unaligned_load(cast(^#simd[16]u8)&b[i])
return -1 if (int(a) - int(b)) < 0 else +1 comparison := intrinsics.simd_lanes_ne(load_a, load_b)
if intrinsics.simd_reduce_or(comparison) != 0 {
sentinel: #simd[16]u8 = u8(0xFF)
indices := intrinsics.simd_indices(#simd[16]u8)
index_select := intrinsics.simd_select(comparison, indices, sentinel)
index_reduce := cast(uint)intrinsics.simd_reduce_min(index_select)
return -1 if a[i+index_reduce] < b[i+index_reduce] else +1
} }
} }
// 64-bit SIMD is faster than using a `uintptr` to detect a difference then
// re-iterating with the byte-by-byte loop, at least on AMD64.
m = (n-i) / 8 * 8
for /**/; i < m; i += 8 {
load_a := intrinsics.unaligned_load(cast(^#simd[8]u8)&a[i])
load_b := intrinsics.unaligned_load(cast(^#simd[8]u8)&b[i])
comparison := intrinsics.simd_lanes_ne(load_a, load_b)
if intrinsics.simd_reduce_or(comparison) != 0 {
sentinel: #simd[8]u8 = u8(0xFF)
indices := intrinsics.simd_indices(#simd[8]u8)
index_select := intrinsics.simd_select(comparison, indices, sentinel)
index_reduce := cast(uint)intrinsics.simd_reduce_min(index_select)
return -1 if a[i+index_reduce] < b[i+index_reduce] else +1
}
}
for /**/; i < n; i += 1 {
if a[i] ~ b[i] != 0 {
return -1 if int(a[i]) - int(b[i]) < 0 else +1
}
}
return 0 return 0
} }
memory_compare_zero :: proc "contextless" (a: rawptr, n: int) -> int #no_bounds_check { memory_compare_zero :: proc "contextless" (a: rawptr, n: int) -> int #no_bounds_check {
x := uintptr(a) n := uint(n)
n := uintptr(n) i := uint(0)
m := uint(0)
SU :: size_of(uintptr) // Because we're comparing against zero, we never return -1, as that would
fast := n/SU + 1 // indicate the compared value is less than zero.
offset := (fast-1)*SU //
curr_block := uintptr(0) // Note that a zero return value here means equality.
if n < SU {
fast = 0 bytes := ([^]u8)(a)
if n >= 8 {
when HAS_HARDWARE_SIMD {
when ODIN_ARCH == .amd64 && intrinsics.has_target_feature("avx2") {
scanner32: #simd[32]u8
m = n / 32 * 32
for /**/; i < m; i += 32 {
load := intrinsics.unaligned_load(cast(^#simd[32]u8)&bytes[i])
ne := intrinsics.simd_lanes_ne(scanner32, load)
if intrinsics.simd_reduce_or(ne) > 0 {
return 1
}
}
}
}
scanner16: #simd[16]u8
m = (n-i) / 16 * 16
for /**/; i < m; i += 16 {
load := intrinsics.unaligned_load(cast(^#simd[16]u8)&bytes[i])
ne := intrinsics.simd_lanes_ne(scanner16, load)
if intrinsics.simd_reduce_or(ne) != 0 {
return 1
}
}
m = (n-i) / 8 * 8
for /**/; i < m; i += 8 {
if intrinsics.unaligned_load(cast(^uintptr)&bytes[i]) != 0 {
return 1
}
}
} }
for /**/; curr_block < fast; curr_block += 1 { for /**/; i < n; i += 1 {
va := (^uintptr)(x + curr_block * size_of(uintptr))^ if bytes[i] != 0 {
if va ~ 0 != 0 { return 1
for pos := curr_block*SU; pos < n; pos += 1 { }
a := (^byte)(x+pos)^ }
if a ~ 0 != 0 { return 0
return -1 if int(a) < 0 else +1 }
memory_prefix_length :: proc "contextless" (x, y: rawptr, n: int) -> (idx: int) #no_bounds_check {
switch {
case x == y: return n
case x == nil: return 0
case y == nil: return 0
}
a, b := cast([^]byte)x, cast([^]byte)y
n := uint(n)
i := uint(0)
m := uint(0)
when HAS_HARDWARE_SIMD {
when ODIN_ARCH == .amd64 && intrinsics.has_target_feature("avx2") {
m = n / 32 * 32
for /**/; i < m; i += 32 {
load_a := intrinsics.unaligned_load(cast(^#simd[32]u8)&a[i])
load_b := intrinsics.unaligned_load(cast(^#simd[32]u8)&b[i])
comparison := intrinsics.simd_lanes_ne(load_a, load_b)
if intrinsics.simd_reduce_or(comparison) != 0 {
sentinel: #simd[32]u8 = u8(0xFF)
indices := intrinsics.simd_indices(#simd[32]u8)
index_select := intrinsics.simd_select(comparison, indices, sentinel)
index_reduce := cast(uint)intrinsics.simd_reduce_min(index_select)
return int(i + index_reduce)
} }
} }
} }
} }
for /**/; offset < n; offset += 1 { m = (n-i) / 16 * 16
a := (^byte)(x+offset)^ for /**/; i < m; i += 16 {
if a ~ 0 != 0 { load_a := intrinsics.unaligned_load(cast(^#simd[16]u8)&a[i])
return -1 if int(a) < 0 else +1 load_b := intrinsics.unaligned_load(cast(^#simd[16]u8)&b[i])
comparison := intrinsics.simd_lanes_ne(load_a, load_b)
if intrinsics.simd_reduce_or(comparison) != 0 {
sentinel: #simd[16]u8 = u8(0xFF)
indices := intrinsics.simd_indices(#simd[16]u8)
index_select := intrinsics.simd_select(comparison, indices, sentinel)
index_reduce := cast(uint)intrinsics.simd_reduce_min(index_select)
return int(i + index_reduce)
} }
} }
return 0 // 64-bit SIMD is faster than using a `uintptr` to detect a difference then
// re-iterating with the byte-by-byte loop, at least on AMD64.
m = (n-i) / 8 * 8
for /**/; i < m; i += 8 {
load_a := intrinsics.unaligned_load(cast(^#simd[8]u8)&a[i])
load_b := intrinsics.unaligned_load(cast(^#simd[8]u8)&b[i])
comparison := intrinsics.simd_lanes_ne(load_a, load_b)
if intrinsics.simd_reduce_or(comparison) != 0 {
sentinel: #simd[8]u8 = u8(0xFF)
indices := intrinsics.simd_indices(#simd[8]u8)
index_select := intrinsics.simd_select(comparison, indices, sentinel)
index_reduce := cast(uint)intrinsics.simd_reduce_min(index_select)
return int(i + index_reduce)
}
}
for /**/; i < n; i += 1 {
if a[i] ~ b[i] != 0 {
return int(i)
}
}
return int(n)
} }
string_eq :: proc "contextless" (lhs, rhs: string) -> bool { string_eq :: proc "contextless" (lhs, rhs: string) -> bool {
@@ -1106,3 +1203,11 @@ __read_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uin
dst[j>>3] |= the_bit<<(j&7) dst[j>>3] |= the_bit<<(j&7)
} }
} }
when .Address in ODIN_SANITIZER_FLAGS {
foreign {
@(require)
__asan_unpoison_memory_region :: proc "system" (address: rawptr, size: uint) ---
}
}
+19 -6
View File
@@ -2,21 +2,34 @@
package runtime package runtime
@(priority_index=-1e6) @(priority_index=-1e6)
foreign import "system:Foundation.framework" foreign import ObjC "system:objc"
import "base:intrinsics" import "base:intrinsics"
objc_id :: ^intrinsics.objc_object objc_id :: ^intrinsics.objc_object
objc_Class :: ^intrinsics.objc_class objc_Class :: ^intrinsics.objc_class
objc_SEL :: ^intrinsics.objc_selector objc_SEL :: ^intrinsics.objc_selector
objc_Ivar :: ^intrinsics.objc_ivar
objc_BOOL :: bool
foreign Foundation {
objc_lookUpClass :: proc "c" (name: cstring) -> 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 --- sel_registerName :: proc "c" (name: cstring) -> objc_SEL ---
objc_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class ---
objc_msgSend :: 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_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_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_stret :: proc "c" (self: objc_id, 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 ---
objc_registerClassPair :: proc "c" (cls : objc_Class) ---
class_addMethod :: proc "c" (cls: objc_Class, name: objc_SEL, imp: objc_IMP, types: cstring) -> objc_BOOL ---
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 ---
ivar_getOffset :: proc "c" (v: objc_Ivar) -> uintptr ---
} }
+309 -7
View File
@@ -40,9 +40,10 @@ Address_Access_Type :: enum {
write, write,
} }
Address_Located_Address_String :: struct { Address_Located_Address :: struct {
category: string, category: string,
name: string, name: string,
region: []byte,
} }
Address_Shadow_Mapping :: struct { Address_Shadow_Mapping :: struct {
@@ -50,30 +51,81 @@ Address_Shadow_Mapping :: struct {
offset: uint, offset: uint,
} }
/*
Marks a slice as unaddressable
Code instrumented with `-sanitize:address` is forbidden from accessing any address
within the slice. This procedure is not thread-safe because no two threads can
poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_poison_slice :: proc "contextless" (region: $T/[]$E) { address_poison_slice :: proc "contextless" (region: $T/[]$E) {
when ASAN_ENABLED { when ASAN_ENABLED {
__asan_poison_memory_region(raw_data(region), size_of(E) * len(region)) __asan_poison_memory_region(raw_data(region), size_of(E) * len(region))
} }
} }
/*
Marks a slice as addressable
Code instrumented with `-sanitize:address` is allowed to access any address
within the slice again. This procedure is not thread-safe because no two threads
can poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_unpoison_slice :: proc "contextless" (region: $T/[]$E) { address_unpoison_slice :: proc "contextless" (region: $T/[]$E) {
when ASAN_ENABLED { when ASAN_ENABLED {
__asan_unpoison_memory_region(raw_data(region), size_of(E) * len(region)) __asan_unpoison_memory_region(raw_data(region), size_of(E) * len(region))
} }
} }
/*
Marks a pointer as unaddressable
Code instrumented with `-sanitize:address` is forbidden from accessing any address
within the region the pointer points to. This procedure is not thread-safe because no
two threads can poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_poison_ptr :: proc "contextless" (ptr: ^$T) { address_poison_ptr :: proc "contextless" (ptr: ^$T) {
when ASAN_ENABLED { when ASAN_ENABLED {
__asan_poison_memory_region(ptr, size_of(T)) __asan_poison_memory_region(ptr, size_of(T))
} }
} }
/*
Marks a pointer as addressable
Code instrumented with `-sanitize:address` is allowed to access any address
within the region the pointer points to again. This procedure is not thread-safe
because no two threads can poison or unpoison memory in the same memory region
region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_unpoison_ptr :: proc "contextless" (ptr: ^$T) { address_unpoison_ptr :: proc "contextless" (ptr: ^$T) {
when ASAN_ENABLED { when ASAN_ENABLED {
__asan_unpoison_memory_region(ptr, size_of(T)) __asan_unpoison_memory_region(ptr, size_of(T))
} }
} }
/*
Marks the region covering `[ptr, ptr+len)` as unaddressable
Code instrumented with `-sanitize:address` is forbidden from accessing any address
within the region. This procedure is not thread-safe because no two threads can
poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_poison_rawptr :: proc "contextless" (ptr: rawptr, len: int) { address_poison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
when ASAN_ENABLED { when ASAN_ENABLED {
assert_contextless(len >= 0) assert_contextless(len >= 0)
@@ -81,6 +133,32 @@ address_poison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
} }
} }
/*
Marks the region covering `[ptr, ptr+len)` as unaddressable
Code instrumented with `-sanitize:address` is forbidden from accessing any address
within the region. This procedure is not thread-safe because no two threads can
poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_poison_rawptr_uint :: proc "contextless" (ptr: rawptr, len: uint) {
when ASAN_ENABLED {
__asan_poison_memory_region(ptr, len)
}
}
/*
Marks the region covering `[ptr, ptr+len)` as addressable
Code instrumented with `-sanitize:address` is allowed to access any address
within the region again. This procedure is not thread-safe because no two
threads can poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) { address_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
when ASAN_ENABLED { when ASAN_ENABLED {
assert_contextless(len >= 0) assert_contextless(len >= 0)
@@ -88,25 +166,61 @@ address_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
} }
} }
/*
Marks the region covering `[ptr, ptr+len)` as addressable
Code instrumented with `-sanitize:address` is allowed to access any address
within the region again. This procedure is not thread-safe because no two
threads can poison or unpoison memory in the same memory region region simultaneously.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_unpoison_rawptr_uint :: proc "contextless" (ptr: rawptr, len: uint) {
when ASAN_ENABLED {
__asan_unpoison_memory_region(ptr, len)
}
}
address_poison :: proc { address_poison :: proc {
address_poison_slice, address_poison_slice,
address_poison_ptr, address_poison_ptr,
address_poison_rawptr, address_poison_rawptr,
address_poison_rawptr_uint,
} }
address_unpoison :: proc { address_unpoison :: proc {
address_unpoison_slice, address_unpoison_slice,
address_unpoison_ptr, address_unpoison_ptr,
address_unpoison_rawptr, address_unpoison_rawptr,
address_unpoison_rawptr_uint,
} }
/*
Registers a callback to be run when asan detects a memory error right before terminating
the process.
This can be used for logging and/or debugging purposes.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_set_death_callback :: proc "contextless" (callback: Address_Death_Callback) { address_set_death_callback :: proc "contextless" (callback: Address_Death_Callback) {
when ASAN_ENABLED { when ASAN_ENABLED {
__sanitizer_set_death_callback(callback) __sanitizer_set_death_callback(callback)
} }
} }
address_region_is_poisoned_slice :: proc "contextless" (region: []$T/$E) -> rawptr { /*
Checks if the memory region covered by the slice is poisoned.
If it is poisoned this procedure returns the address which would result
in an asan error.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_region_is_poisoned_slice :: proc "contextless" (region: $T/[]$E) -> rawptr {
when ASAN_ENABLED { when ASAN_ENABLED {
return __asan_region_is_poisoned(raw_data(region), size_of(E) * len(region)) return __asan_region_is_poisoned(raw_data(region), size_of(E) * len(region))
} else { } else {
@@ -114,6 +228,15 @@ address_region_is_poisoned_slice :: proc "contextless" (region: []$T/$E) -> rawp
} }
} }
/*
Checks if the memory region pointed to by the pointer is poisoned.
If it is poisoned this procedure returns the address which would result
in an asan error.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_region_is_poisoned_ptr :: proc "contextless" (ptr: ^$T) -> rawptr { address_region_is_poisoned_ptr :: proc "contextless" (ptr: ^$T) -> rawptr {
when ASAN_ENABLED { when ASAN_ENABLED {
return __asan_region_is_poisoned(ptr, size_of(T)) return __asan_region_is_poisoned(ptr, size_of(T))
@@ -122,6 +245,15 @@ address_region_is_poisoned_ptr :: proc "contextless" (ptr: ^$T) -> rawptr {
} }
} }
/*
Checks if the memory region covered by `[ptr, ptr+len)` is poisoned.
If it is poisoned this procedure returns the address which would result
in an asan error.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_region_is_poisoned_rawptr :: proc "contextless" (region: rawptr, len: int) -> rawptr { address_region_is_poisoned_rawptr :: proc "contextless" (region: rawptr, len: int) -> rawptr {
when ASAN_ENABLED { when ASAN_ENABLED {
assert_contextless(len >= 0) assert_contextless(len >= 0)
@@ -131,13 +263,41 @@ address_region_is_poisoned_rawptr :: proc "contextless" (region: rawptr, len: in
} }
} }
/*
Checks if the memory region covered by `[ptr, ptr+len)` is poisoned.
If it is poisoned this procedure returns the address which would result
in an asan error.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_region_is_poisoned_rawptr_uint :: proc "contextless" (region: rawptr, len: uint) -> rawptr {
when ASAN_ENABLED {
return __asan_region_is_poisoned(region, len)
} else {
return nil
}
}
address_region_is_poisoned :: proc { address_region_is_poisoned :: proc {
address_region_is_poisoned_slice, address_region_is_poisoned_slice,
address_region_is_poisoned_ptr, address_region_is_poisoned_ptr,
address_region_is_poisoned_rawptr, address_region_is_poisoned_rawptr,
address_region_is_poisoned_rawptr_uint,
} }
address_address_is_poisoned :: proc "contextless" (address: rawptr) -> bool { /*
Checks if the address is poisoned.
If it is poisoned this procedure returns `true`, otherwise it returns
`false`.
When asan is not enabled this procedure returns `false`.
*/
@(no_sanitize_address)
address_is_poisoned :: proc "contextless" (address: rawptr) -> bool {
when ASAN_ENABLED { when ASAN_ENABLED {
return __asan_address_is_poisoned(address) != 0 return __asan_address_is_poisoned(address) != 0
} else { } else {
@@ -145,12 +305,27 @@ address_address_is_poisoned :: proc "contextless" (address: rawptr) -> bool {
} }
} }
/*
Describes the sanitizer state for an address.
This procedure prints the description out to `stdout`.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_describe_address :: proc "contextless" (address: rawptr) { address_describe_address :: proc "contextless" (address: rawptr) {
when ASAN_ENABLED { when ASAN_ENABLED {
__asan_describe_address(address) __asan_describe_address(address)
} }
} }
/*
Returns `true` if an asan error has occured, otherwise it returns
`false`.
When asan is not enabled this procedure returns `false`.
*/
@(no_sanitize_address)
address_report_present :: proc "contextless" () -> bool { address_report_present :: proc "contextless" () -> bool {
when ASAN_ENABLED { when ASAN_ENABLED {
return __asan_report_present() != 0 return __asan_report_present() != 0
@@ -159,6 +334,14 @@ address_report_present :: proc "contextless" () -> bool {
} }
} }
/*
Returns the program counter register value of an asan error.
If no asan error has occurd `nil` is returned.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_get_report_pc :: proc "contextless" () -> rawptr { address_get_report_pc :: proc "contextless" () -> rawptr {
when ASAN_ENABLED { when ASAN_ENABLED {
return __asan_get_report_pc() return __asan_get_report_pc()
@@ -167,6 +350,14 @@ address_get_report_pc :: proc "contextless" () -> rawptr {
} }
} }
/*
Returns the base pointer register value of an asan error.
If no asan error has occurd `nil` is returned.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_get_report_bp :: proc "contextless" () -> rawptr { address_get_report_bp :: proc "contextless" () -> rawptr {
when ASAN_ENABLED { when ASAN_ENABLED {
return __asan_get_report_bp() return __asan_get_report_bp()
@@ -175,6 +366,14 @@ address_get_report_bp :: proc "contextless" () -> rawptr {
} }
} }
/*
Returns the stack pointer register value of an asan error.
If no asan error has occurd `nil` is returned.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_get_report_sp :: proc "contextless" () -> rawptr { address_get_report_sp :: proc "contextless" () -> rawptr {
when ASAN_ENABLED { when ASAN_ENABLED {
return __asan_get_report_sp() return __asan_get_report_sp()
@@ -183,6 +382,14 @@ address_get_report_sp :: proc "contextless" () -> rawptr {
} }
} }
/*
Returns the report buffer address of an asan error.
If no asan error has occurd `nil` is returned.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_get_report_address :: proc "contextless" () -> rawptr { address_get_report_address :: proc "contextless" () -> rawptr {
when ASAN_ENABLED { when ASAN_ENABLED {
return __asan_get_report_address() return __asan_get_report_address()
@@ -191,14 +398,33 @@ address_get_report_address :: proc "contextless" () -> rawptr {
} }
} }
/*
Returns the address access type of an asan error.
If no asan error has occurd `.none` is returned.
When asan is not enabled this procedure returns `.none`.
*/
@(no_sanitize_address)
address_get_report_access_type :: proc "contextless" () -> Address_Access_Type { address_get_report_access_type :: proc "contextless" () -> Address_Access_Type {
when ASAN_ENABLED { when ASAN_ENABLED {
if ! address_report_present() {
return .none
}
return __asan_get_report_access_type() == 0 ? .read : .write return __asan_get_report_access_type() == 0 ? .read : .write
} else { } else {
return .none return .none
} }
} }
/*
Returns the access size of an asan error.
If no asan error has occurd `0` is returned.
When asan is not enabled this procedure returns `0`.
*/
@(no_sanitize_address)
address_get_report_access_size :: proc "contextless" () -> uint { address_get_report_access_size :: proc "contextless" () -> uint {
when ASAN_ENABLED { when ASAN_ENABLED {
return __asan_get_report_access_size() return __asan_get_report_access_size()
@@ -207,25 +433,52 @@ address_get_report_access_size :: proc "contextless" () -> uint {
} }
} }
/*
Returns the bug description of an asan error.
If no asan error has occurd an empty string is returned.
When asan is not enabled this procedure returns an empty string.
*/
@(no_sanitize_address)
address_get_report_description :: proc "contextless" () -> string { address_get_report_description :: proc "contextless" () -> string {
when ASAN_ENABLED { when ASAN_ENABLED {
return string(__asan_get_report_description()) return string(__asan_get_report_description())
} else { } else {
return "unknown" return ""
} }
} }
address_locate_address :: proc "contextless" (addr: rawptr, data: []byte) -> (Address_Located_Address_String, []byte) { /*
Returns asan information about the address provided, writing the category into `data`.
The information provided include:
* The category of the address, i.e. stack, global, heap, etc.
* The name of the variable this address belongs to
* The memory region of the address
When asan is not enabled this procedure returns zero initialised values.
*/
@(no_sanitize_address)
address_locate_address :: proc "contextless" (addr: rawptr, data: []byte) -> Address_Located_Address {
when ASAN_ENABLED { when ASAN_ENABLED {
out_addr: rawptr out_addr: rawptr
out_size: uint out_size: uint
str := __asan_locate_address(addr, raw_data(data), len(data), &out_addr, &out_size) str := __asan_locate_address(addr, raw_data(data), len(data), &out_addr, &out_size)
return { string(str), string(cstring(raw_data(data))) }, (cast([^]byte)out_addr)[:out_size] return { string(str), string(cstring(raw_data(data))), (cast([^]byte)out_addr)[:out_size] },
} else { } else {
return { "", "" }, {} return { "", "", {} }
} }
} }
/*
Returns the allocation stack trace and thread id for a heap address.
The stack trace is filled into the `data` slice.
When asan is not enabled this procedure returns a zero initialised value.
*/
@(no_sanitize_address)
address_get_alloc_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) { address_get_alloc_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) {
when ASAN_ENABLED { when ASAN_ENABLED {
out_thread: i32 out_thread: i32
@@ -236,6 +489,14 @@ address_get_alloc_stack_trace :: proc "contextless" (addr: rawptr, data: []rawpt
} }
} }
/*
Returns the free stack trace and thread id for a heap address.
The stack trace is filled into the `data` slice.
When asan is not enabled this procedure returns zero initialised values.
*/
@(no_sanitize_address)
address_get_free_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) { address_get_free_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) {
when ASAN_ENABLED { when ASAN_ENABLED {
out_thread: i32 out_thread: i32
@@ -246,6 +507,12 @@ address_get_free_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr
} }
} }
/*
Returns the current asan shadow memory mapping.
When asan is not enabled this procedure returns a zero initialised value.
*/
@(no_sanitize_address)
address_get_shadow_mapping :: proc "contextless" () -> Address_Shadow_Mapping { address_get_shadow_mapping :: proc "contextless" () -> Address_Shadow_Mapping {
when ASAN_ENABLED { when ASAN_ENABLED {
result: Address_Shadow_Mapping result: Address_Shadow_Mapping
@@ -256,12 +523,26 @@ address_get_shadow_mapping :: proc "contextless" () -> Address_Shadow_Mapping {
} }
} }
/*
Prints asan statistics to `stderr`
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_print_accumulated_stats :: proc "contextless" () { address_print_accumulated_stats :: proc "contextless" () {
when ASAN_ENABLED { when ASAN_ENABLED {
__asan_print_accumulated_stats() __asan_print_accumulated_stats()
} }
} }
/*
Returns the address of the current fake stack used by asan.
This pointer can be then used for `address_is_in_fake_stack`.
When asan is not enabled this procedure returns `nil`.
*/
@(no_sanitize_address)
address_get_current_fake_stack :: proc "contextless" () -> rawptr { address_get_current_fake_stack :: proc "contextless" () -> rawptr {
when ASAN_ENABLED { when ASAN_ENABLED {
return __asan_get_current_fake_stack() return __asan_get_current_fake_stack()
@@ -270,6 +551,12 @@ address_get_current_fake_stack :: proc "contextless" () -> rawptr {
} }
} }
/*
Returns if an address belongs to a given fake stack and if so the region of the fake frame.
When asan is not enabled this procedure returns zero initialised values.
*/
@(no_sanitize_address)
address_is_in_fake_stack :: proc "contextless" (fake_stack: rawptr, addr: rawptr) -> ([]byte, bool) { address_is_in_fake_stack :: proc "contextless" (fake_stack: rawptr, addr: rawptr) -> ([]byte, bool) {
when ASAN_ENABLED { when ASAN_ENABLED {
begin: rawptr begin: rawptr
@@ -283,12 +570,27 @@ address_is_in_fake_stack :: proc "contextless" (fake_stack: rawptr, addr: rawptr
} }
} }
/*
Performs shadow memory cleanup for the current thread before a procedure with no return is called
i.e. a procedure such as `panic` and `os.exit`.
When asan is not enabled this procedure does nothing.
*/
@(no_sanitize_address)
address_handle_no_return :: proc "contextless" () { address_handle_no_return :: proc "contextless" () {
when ASAN_ENABLED { when ASAN_ENABLED {
__asan_handle_no_return() __asan_handle_no_return()
} }
} }
/*
Updates the allocation stack trace for the given address.
Returns `true` if successful, otherwise it returns `false`.
When asan is not enabled this procedure returns `false`.
*/
@(no_sanitize_address)
address_update_allocation_context :: proc "contextless" (addr: rawptr) -> bool { address_update_allocation_context :: proc "contextless" (addr: rawptr) -> bool {
when ASAN_ENABLED { when ASAN_ENABLED {
return __asan_update_allocation_context(addr) != 0 return __asan_update_allocation_context(addr) != 0
+38
View File
@@ -0,0 +1,38 @@
/*
The `sanitizer` package implements various procedures for interacting with sanitizers
from user code.
An odin project can be linked with various sanitizers to help identify various different
bugs. These sanitizers are:
## Address
Enabled with `-sanitize:address` when building an odin project.
The address sanitizer (asan) is a runtime memory error detector used to help find common memory
related bugs. Typically asan interacts with libc but Odin code can be marked up to interact
with the asan runtime to extend the memory error detection outside of libc using this package.
For more information about asan see: https://clang.llvm.org/docs/AddressSanitizer.html
Procedures can be made exempt from asan when marked up with @(no_sanitize_address)
## Memory
Enabled with `-sanitize:memory` when building an odin project.
The memory sanitizer is another runtime memory error detector with the sole purpose to catch the
use of uninitialized memory. This is not a very common bug in Odin as by default everything is
set to zero when initialised (ZII).
For more information about the memory sanitizer see: https://clang.llvm.org/docs/MemorySanitizer.html
## Thread
Enabled with `-sanitize:thread` when building an odin project.
The thread sanitizer is a runtime data race detector. It can be used to detect if multiple threads
are concurrently writing and accessing a memory location without proper syncronisation.
For more information about the thread sanitizer see: https://clang.llvm.org/docs/ThreadSanitizer.html
*/
package sanitizer
+18 -24
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 pushd misc
cl /nologo get-date.c cl /nologo get-date.c
popd for /f %%i in ('get-date') do (
for /f %%i in ('misc\get-date') do (
set CURR_DATE_TIME=%%i 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_year=%CURR_DATE_TIME:~0,4%
set curr_month=%CURR_DATE_TIME:~4,2% set curr_month=%CURR_DATE_TIME:~5,2%
set curr_day=%CURR_DATE_TIME:~6,2% set curr_day=%CURR_DATE_TIME:~8,2%
:: Make sure this is a decent name and not generic :: Make sure this is a decent name and not generic
set exe_name=odin.exe set exe_name=odin.exe
@@ -61,31 +72,14 @@ if %release_mode% equ 0 (
set V4=0 set V4=0
set odin_version_full="%V1%.%V2%.%V3%.%V4%" set odin_version_full="%V1%.%V2%.%V3%.%V4%"
set odin_version_raw="dev-%V1%-%V2%" set odin_version_raw="dev-%V1%-%V2%"
set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -EHsc- -GR- -GF 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 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 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_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 rem fileversion is defined as {Major,Minor,Build,Private: u16} so a bit limited
set rc_flags=-nologo ^ set rc_flags="-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%"
-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
if %nightly% equ 1 set compiler_defines=%compiler_defines% -DNIGHTLY if %nightly% equ 1 set compiler_defines=%compiler_defines% -DNIGHTLY
+4 -1
View File
@@ -6,7 +6,6 @@ set -eu
: ${LDFLAGS=} : ${LDFLAGS=}
: ${LLVM_CONFIG=} : ${LLVM_CONFIG=}
CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$(date +"%Y-%m")\""
CXXFLAGS="$CXXFLAGS -std=c++14" CXXFLAGS="$CXXFLAGS -std=c++14"
DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value" DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value"
LDFLAGS="$LDFLAGS -pthread -lm" LDFLAGS="$LDFLAGS -pthread -lm"
@@ -15,8 +14,12 @@ OS_NAME="$(uname -s)"
if [ -d ".git" ] && [ -n "$(command -v git)" ]; then if [ -d ".git" ] && [ -n "$(command -v git)" ]; then
GIT_SHA=$(git show --pretty='%h' --no-patch --no-notes HEAD) 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\"" CPPFLAGS="$CPPFLAGS -DGIT_SHA=\"$GIT_SHA\""
else
GIT_DATE=$(date +"%Y-%m")
fi fi
CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$GIT_DATE\""
error() { error() {
printf "ERROR: %s\n" "$1" printf "ERROR: %s\n" "$1"
+1 -1
View File
@@ -257,7 +257,7 @@ reader_read_rune :: proc(b: ^Reader) -> (r: rune, size: int, err: io.Error) {
for b.r+utf8.UTF_MAX > b.w && for b.r+utf8.UTF_MAX > b.w &&
!utf8.full_rune(b.buf[b.r:b.w]) && !utf8.full_rune(b.buf[b.r:b.w]) &&
b.err == nil && b.err == nil &&
b.w-b.w < len(b.buf) { b.w-b.r < len(b.buf) {
_reader_read_new_chunk(b) or_return _reader_read_new_chunk(b) or_return
} }
+2 -2
View File
@@ -350,7 +350,7 @@ index_byte :: proc "contextless" (s: []byte, c: byte) -> (index: int) #no_bounds
} }
c_vec: simd.u8x16 = c c_vec: simd.u8x16 = c
when !simd.IS_EMULATED { when simd.HAS_HARDWARE_SIMD {
// Note: While this is something that could also logically take // Note: While this is something that could also logically take
// advantage of AVX512, the various downclocking and power // advantage of AVX512, the various downclocking and power
// consumption related woes make premature to have a dedicated // consumption related woes make premature to have a dedicated
@@ -485,7 +485,7 @@ last_index_byte :: proc "contextless" (s: []byte, c: byte) -> int #no_bounds_che
} }
c_vec: simd.u8x16 = c c_vec: simd.u8x16 = c
when !simd.IS_EMULATED { when simd.HAS_HARDWARE_SIMD {
// Note: While this is something that could also logically take // Note: While this is something that could also logically take
// advantage of AVX512, the various downclocking and power // advantage of AVX512, the various downclocking and power
// consumption related woes make premature to have a dedicated // consumption related woes make premature to have a dedicated
-3
View File
@@ -139,9 +139,6 @@ Context_Memory_Input :: struct #packed {
} }
when size_of(rawptr) == 8 { when size_of(rawptr) == 8 {
#assert(size_of(Context_Memory_Input) == 64) #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 { Context_Stream_Input :: struct #packed {
+1 -1
View File
@@ -129,7 +129,7 @@ remove :: proc(c: ^$C/Cache($Key, $Value), key: Key) -> bool {
return false return false
} }
_remove_node(c, e) _remove_node(c, e)
free(node, c.node_allocator) free(e, c.node_allocator)
c.count -= 1 c.count -= 1
return true return true
} }
@@ -133,12 +133,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) { remove :: proc(pq: ^$Q/Priority_Queue($T), i: int) -> (value: T, ok: bool) {
n := builtin.len(pq.queue) n := builtin.len(pq.queue)
if 0 <= i && i < n { if 0 <= i && i < n {
if n != i { pq.swap(pq.queue[:], i, n-1)
pq.swap(pq.queue[:], i, n) _shift_down(pq, i, n-1)
_shift_down(pq, i, n) _shift_up(pq, i)
_shift_up(pq, i) value, ok = builtin.pop(&pq.queue), true
}
value, ok = builtin.pop_safe(&pq.queue)
} }
return return
} }
+1 -1
View File
@@ -6,7 +6,7 @@ import "core:sys/info"
// is_supported returns true iff hardware accelerated AES // is_supported returns true iff hardware accelerated AES
// is supported. // is supported.
is_supported :: proc "contextless" () -> bool { is_supported :: proc "contextless" () -> bool {
features, ok := info.cpu_features.? features, ok := info.cpu.features.?
if !ok { if !ok {
return false return false
} }
@@ -39,7 +39,7 @@ when ODIN_ARCH == .arm64 || ODIN_ARCH == .arm32 {
// Some targets lack runtime feature detection, and will flat out refuse // Some targets lack runtime feature detection, and will flat out refuse
// to load binaries that have unknown instructions. This is distinct from // to load binaries that have unknown instructions. This is distinct from
// `simd.IS_EMULATED` as actually good designs support runtime feature // `simd.HAS_HARDWARE_SIMD` as actually good designs support runtime feature
// detection and that constant establishes a baseline. // detection and that constant establishes a baseline.
// //
// See: // See:
@@ -227,7 +227,7 @@ is_performant :: proc "contextless" () -> bool {
req_features :: info.CPU_Features{.V} req_features :: info.CPU_Features{.V}
} }
features, ok := info.cpu_features.? features, ok := info.cpu.features.?
if !ok { if !ok {
return false return false
} }
@@ -41,7 +41,7 @@ _VEC_TWO: simd.u64x4 : {2, 0, 2, 0}
is_performant :: proc "contextless" () -> bool { is_performant :: proc "contextless" () -> bool {
req_features :: info.CPU_Features{.avx, .avx2} req_features :: info.CPU_Features{.avx, .avx2}
features, ok := info.cpu_features.? features, ok := info.cpu.features.?
if !ok { if !ok {
return false return false
} }
+1 -1
View File
@@ -52,7 +52,7 @@ K_15 :: simd.u64x2{0xa4506ceb90befffa, 0xc67178f2bef9a3f7}
// is_hardware_accelerated_256 returns true iff hardware accelerated // is_hardware_accelerated_256 returns true iff hardware accelerated
// SHA-224/SHA-256 is supported. // SHA-224/SHA-256 is supported.
is_hardware_accelerated_256 :: proc "contextless" () -> bool { is_hardware_accelerated_256 :: proc "contextless" () -> bool {
features, ok := info.cpu_features.? features, ok := info.cpu.features.?
if !ok { if !ok {
return false return false
} }
+3 -3
View File
@@ -385,17 +385,17 @@ to_diagnostic_format_writer :: proc(w: io.Writer, val: Value, padding := 0) -> i
// which we want for the diagnostic format. // which we want for the diagnostic format.
case f16: case f16:
buf: [64]byte buf: [64]byte
str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f16), 8*size_of(f16)) str := strconv.write_float(buf[:], f64(v), 'f', 2*size_of(f16), 8*size_of(f16))
if str[0] == '+' && str != "+Inf" { str = str[1:] } if str[0] == '+' && str != "+Inf" { str = str[1:] }
io.write_string(w, str) or_return io.write_string(w, str) or_return
case f32: case f32:
buf: [128]byte buf: [128]byte
str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f32), 8*size_of(f32)) str := strconv.write_float(buf[:], f64(v), 'f', 2*size_of(f32), 8*size_of(f32))
if str[0] == '+' && str != "+Inf" { str = str[1:] } if str[0] == '+' && str != "+Inf" { str = str[1:] }
io.write_string(w, str) or_return io.write_string(w, str) or_return
case f64: case f64:
buf: [256]byte buf: [256]byte
str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f64), 8*size_of(f64)) str := strconv.write_float(buf[:], f64(v), 'f', 2*size_of(f64), 8*size_of(f64))
if str[0] == '+' && str != "+Inf" { str = str[1:] } if str[0] == '+' && str != "+Inf" { str = str[1:] }
io.write_string(w, str) or_return io.write_string(w, str) or_return
+36
View File
@@ -612,6 +612,42 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er
case: case:
panic("unknown bit_size size") panic("unknown bit_size size")
} }
case runtime.Type_Info_Matrix:
count := info.column_count * info.elem_stride
err_conv(_encode_u64(e, u64(count), .Array)) or_return
if impl, ok := _tag_implementations_type[info.elem.id]; ok {
for i in 0..<count {
data := uintptr(v.data) + uintptr(i*info.elem_size)
impl->marshal(e, any{rawptr(data), info.elem.id}) or_return
}
return
}
elem_ti := runtime.type_info_core(type_info_of(info.elem.id))
for i in 0..<count {
data := uintptr(v.data) + uintptr(i*info.elem_size)
_marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return
}
return
case runtime.Type_Info_Simd_Vector:
err_conv(_encode_u64(e, u64(info.count), .Array)) or_return
if impl, ok := _tag_implementations_type[info.elem.id]; ok {
for i in 0..<info.count {
data := uintptr(v.data) + uintptr(i*info.elem_size)
impl->marshal(e, any{rawptr(data), info.elem.id}) or_return
}
return
}
elem_ti := runtime.type_info_core(type_info_of(info.elem.id))
for i in 0..<info.count {
data := uintptr(v.data) + uintptr(i*info.elem_size)
_marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return
}
return
} }
return _unsupported(v.id, nil) return _unsupported(v.id, nil)
+32 -1
View File
@@ -29,6 +29,7 @@ an input.
unmarshal :: proc { unmarshal :: proc {
unmarshal_from_reader, unmarshal_from_reader,
unmarshal_from_string, unmarshal_from_string,
unmarshal_from_bytes,
} }
unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) { unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
@@ -51,6 +52,11 @@ unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, all
return return
} }
// Unmarshals from a slice of bytes, see docs on the proc group `Unmarshal` for more info.
unmarshal_from_bytes :: proc(bytes: []byte, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
return unmarshal_from_string(string(bytes), ptr, flags, allocator, temp_allocator, loc)
}
unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) { unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
d := d d := d
@@ -487,7 +493,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, allocator=allocator, loc=loc) or_return data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, allocator=allocator, loc=loc) or_return
defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) } defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) }
da := mem.Raw_Dynamic_Array{raw_data(data), 0, length, context.allocator } da := mem.Raw_Dynamic_Array{raw_data(data), 0, scap, context.allocator }
assign_array(d, &da, t.elem, length) or_return assign_array(d, &da, t.elem, length) or_return
@@ -585,6 +591,31 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
if out_of_space { return _unsupported(v, hdr) } if out_of_space { return _unsupported(v, hdr) }
return return
case reflect.Type_Info_Matrix:
count := t.column_count * t.elem_stride
length, _ := err_conv(_decode_len_container(d, add)) or_return
if length > count {
return _unsupported(v, hdr)
}
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator }
out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
if out_of_space { return _unsupported(v, hdr) }
return
case reflect.Type_Info_Simd_Vector:
length, _ := err_conv(_decode_len_container(d, add)) or_return
if length > t.count {
return _unsupported(v, hdr)
}
da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator }
out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return
if out_of_space { return _unsupported(v, hdr) }
return
case: return _unsupported(v, hdr) case: return _unsupported(v, hdr)
} }
} }
-2
View File
@@ -63,8 +63,6 @@ Example:
read_csv_from_string :: proc(filename: string) { read_csv_from_string :: proc(filename: string) {
r: csv.Reader r: csv.Reader
r.trim_leading_space = true r.trim_leading_space = true
r.reuse_record = true // Without it you have to delete(record)
r.reuse_record_buffer = true // Without it you have to each of the fields within it
defer csv.reader_destroy(&r) defer csv.reader_destroy(&r)
csv_data, ok := os.read_entire_file(filename) csv_data, ok := os.read_entire_file(filename)
+1 -1
View File
@@ -130,7 +130,7 @@ reader_destroy :: proc(r: ^Reader) {
for record, row_idx in csv.iterator_next(&r) { ... } for record, row_idx in csv.iterator_next(&r) { ... }
TIP: If you process the results within the loop and don't need to own the results, TIP: If you process the results within the loop and don't need to own the results,
you can set the Reader's `reuse_record` and `reuse_record_reuse_record_buffer` to true; you can set the Reader's `reuse_record` and `reuse_record_buffer` to true;
you won't need to delete the record or its fields. you won't need to delete the record or its fields.
*/ */
iterator_next :: proc(r: ^Reader) -> (record: []string, idx: int, err: Error, more: bool) { iterator_next :: proc(r: ^Reader) -> (record: []string, idx: int, err: Error, more: bool) {
+4 -4
View File
@@ -108,13 +108,13 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
if opt.write_uint_as_hex && (opt.spec == .JSON5 || opt.spec == .MJSON) { if opt.write_uint_as_hex && (opt.spec == .JSON5 || opt.spec == .MJSON) {
switch i in a { switch i in a {
case u8, u16, u32, u64, u128: case u8, u16, u32, u64, u128:
s = strconv.append_bits_128(buf[:], u, 16, info.signed, 8*ti.size, "0123456789abcdef", { .Prefix }) s = strconv.write_bits_128(buf[:], u, 16, info.signed, 8*ti.size, "0123456789abcdef", { .Prefix })
case: case:
s = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil) s = strconv.write_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil)
} }
} else { } else {
s = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil) s = strconv.write_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil)
} }
io.write_string(w, s) or_return io.write_string(w, s) or_return
@@ -286,7 +286,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
case runtime.Type_Info_Integer: case runtime.Type_Info_Integer:
buf: [40]byte buf: [40]byte
u := cast_any_int_to_u128(ka) u := cast_any_int_to_u128(ka)
name = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*kti.size, "0123456789", nil) name = strconv.write_bits_128(buf[:], u, 10, info.signed, 8*kti.size, "0123456789", nil)
opt_write_key(w, opt, name) or_return opt_write_key(w, opt, name) or_return
case: return .Unsupported_Type case: return .Unsupported_Type
+2 -2
View File
@@ -101,7 +101,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
} }
} }
scan_espace :: proc(t: ^Tokenizer) -> bool { scan_escape :: proc(t: ^Tokenizer) -> bool {
switch t.r { switch t.r {
case '"', '\'', '\\', '/', 'b', 'n', 'r', 't', 'f': case '"', '\'', '\\', '/', 'b', 'n', 'r', 't', 'f':
next_rune(t) next_rune(t)
@@ -310,7 +310,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
break break
} }
if r == '\\' { if r == '\\' {
scan_espace(t) scan_escape(t)
} }
} }
+14 -8
View File
@@ -406,6 +406,9 @@ unmarshal_expect_token :: proc(p: ^Parser, kind: Token_Kind, loc := #caller_loca
return prev return prev
} }
// Struct tags can include not only the name of the JSON key, but also a tag such as `omitempty`.
// Example: `json:"key_name,omitempty"`
// This returns the first field as `json_name`, and the rest are returned as `extra`.
@(private) @(private)
json_name_from_tag_value :: proc(value: string) -> (json_name, extra: string) { json_name_from_tag_value :: proc(value: string) -> (json_name, extra: string) {
json_name = value json_name = value
@@ -442,12 +445,6 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
unmarshal_expect_token(p, .Colon) unmarshal_expect_token(p, .Colon)
field_test :: #force_inline proc "contextless" (field_used: [^]byte, offset: uintptr) -> bool {
prev_set := field_used[offset/8] & byte(offset&7) != 0
field_used[offset/8] |= byte(offset&7)
return prev_set
}
field_used_bytes := (reflect.size_of_typeid(ti.id)+7)/8 field_used_bytes := (reflect.size_of_typeid(ti.id)+7)/8
field_used := intrinsics.alloca(field_used_bytes + 1, 1) // + 1 to not overflow on size_of 0 types. field_used := intrinsics.alloca(field_used_bytes + 1, 1) // + 1 to not overflow on size_of 0 types.
intrinsics.mem_zero(field_used, field_used_bytes) intrinsics.mem_zero(field_used, field_used_bytes)
@@ -465,7 +462,9 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
if use_field_idx < 0 { if use_field_idx < 0 {
for field, field_idx in fields { for field, field_idx in fields {
if key == field.name { tag_value := reflect.struct_tag_get(field.tag, "json")
json_name, _ := json_name_from_tag_value(tag_value)
if json_name == "" && key == field.name {
use_field_idx = field_idx use_field_idx = field_idx
break break
} }
@@ -486,7 +485,9 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
} }
} }
if field.name == key || (field.tag != "" && reflect.struct_tag_get(field.tag, "json") == key) { tag_value := reflect.struct_tag_get(field.tag, "json")
json_name, _ := json_name_from_tag_value(tag_value)
if (json_name == "" && field.name == key) || json_name == key {
offset = field.offset offset = field.offset
type = field.type type = field.type
found = true found = true
@@ -508,6 +509,11 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
} }
if field_found { if field_found {
field_test :: #force_inline proc "contextless" (field_used: [^]byte, offset: uintptr) -> bool {
prev_set := field_used[offset/8] & byte(offset&7) != 0
field_used[offset/8] |= byte(offset&7)
return prev_set
}
if field_test(field_used, offset) { if field_test(field_used, offset) {
return .Multiple_Use_Field return .Multiple_Use_Field
} }
+4 -4
View File
@@ -1122,7 +1122,7 @@ _fmt_int :: proc(fi: ^Info, u: u64, base: int, is_signed: bool, bit_size: int, d
flags: strconv.Int_Flags flags: strconv.Int_Flags
if fi.hash && !fi.zero && start == 0 { flags += {.Prefix} } if fi.hash && !fi.zero && start == 0 { flags += {.Prefix} }
if fi.plus { flags += {.Plus} } if fi.plus { flags += {.Plus} }
s := strconv.append_bits(buf[start:], u, base, is_signed, bit_size, digits, flags) s := strconv.write_bits(buf[start:], u, base, is_signed, bit_size, digits, flags)
prev_zero := fi.zero prev_zero := fi.zero
defer fi.zero = prev_zero defer fi.zero = prev_zero
fi.zero = false fi.zero = false
@@ -1207,7 +1207,7 @@ _fmt_int_128 :: proc(fi: ^Info, u: u128, base: int, is_signed: bool, bit_size: i
flags: strconv.Int_Flags flags: strconv.Int_Flags
if fi.hash && !fi.zero && start == 0 { flags += {.Prefix} } if fi.hash && !fi.zero && start == 0 { flags += {.Prefix} }
if fi.plus { flags += {.Plus} } if fi.plus { flags += {.Plus} }
s := strconv.append_bits_128(buf[start:], u, base, is_signed, bit_size, digits, flags) s := strconv.write_bits_128(buf[start:], u, base, is_signed, bit_size, digits, flags)
if fi.hash && fi.zero && fi.indent == 0 { if fi.hash && fi.zero && fi.indent == 0 {
c: byte = 0 c: byte = 0
@@ -1272,7 +1272,7 @@ _fmt_memory :: proc(fi: ^Info, u: u64, is_signed: bool, bit_size: int, units: st
} }
buf: [256]byte buf: [256]byte
str := strconv.append_float(buf[:], amt, 'f', prec, 64) str := strconv.write_float(buf[:], amt, 'f', prec, 64)
// Add the unit at the end. // Add the unit at the end.
copy(buf[len(str):], units[off:off+unit_len]) copy(buf[len(str):], units[off:off+unit_len])
@@ -1424,7 +1424,7 @@ _fmt_float_as :: proc(fi: ^Info, v: f64, bit_size: int, verb: rune, float_fmt: b
buf: [386]byte buf: [386]byte
// Can return "NaN", "+Inf", "-Inf", "+<value>", "-<value>". // Can return "NaN", "+Inf", "-Inf", "+<value>", "-<value>".
str := strconv.append_float(buf[:], v, float_fmt, prec, bit_size) str := strconv.write_float(buf[:], v, float_fmt, prec, bit_size)
if !fi.plus { if !fi.plus {
// Strip sign from "+<value>" but not "+Inf". // Strip sign from "+<value>" but not "+Inf".
-81
View File
@@ -318,84 +318,3 @@ crc32_table := [8][256]u32{
0x2c8e0fff, 0xe0240f61, 0x6eab0882, 0xa201081c, 0xa8c40105, 0x646e019b, 0xeae10678, 0x264b06e6, 0x2c8e0fff, 0xe0240f61, 0x6eab0882, 0xa201081c, 0xa8c40105, 0x646e019b, 0xeae10678, 0x264b06e6,
}, },
} }
/*
@(optimization_mode="speed")
crc32 :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 {
result := ~u32(seed);
#no_bounds_check for b in data {
result = result>>8 ~ _crc32_table[(result ~ u32(b)) & 0xff];
}
return ~result;
}
@private _crc32_table := [256]u32{
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
};
*/
-3
View File
@@ -1212,7 +1212,6 @@ Filter_Params :: struct #packed {
depth_scale_table :: []u8{0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01} depth_scale_table :: []u8{0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01}
// @(optimization_mode="speed")
defilter_8 :: proc(params: ^Filter_Params) -> (ok: bool) { defilter_8 :: proc(params: ^Filter_Params) -> (ok: bool) {
using params using params
@@ -1273,7 +1272,6 @@ defilter_8 :: proc(params: ^Filter_Params) -> (ok: bool) {
return return
} }
// @(optimization_mode="speed")
defilter_less_than_8 :: proc(params: ^Filter_Params) -> bool #no_bounds_check { defilter_less_than_8 :: proc(params: ^Filter_Params) -> bool #no_bounds_check {
using params using params
@@ -1436,7 +1434,6 @@ defilter_less_than_8 :: proc(params: ^Filter_Params) -> bool #no_bounds_check {
return true return true
} }
// @(optimization_mode="speed")
defilter_16 :: proc(params: ^Filter_Params) -> bool { defilter_16 :: proc(params: ^Filter_Params) -> bool {
using params using params
+8 -8
View File
@@ -22,12 +22,12 @@ write_ptr_at :: proc(w: Writer_At, p: rawptr, byte_size: int, offset: i64, n_wri
write_u64 :: proc(w: Writer, i: u64, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) { write_u64 :: proc(w: Writer, i: u64, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
buf: [32]byte buf: [32]byte
s := strconv.append_bits(buf[:], i, base, false, 64, strconv.digits, nil) s := strconv.write_bits(buf[:], i, base, false, 64, strconv.digits, nil)
return write_string(w, s, n_written) return write_string(w, s, n_written)
} }
write_i64 :: proc(w: Writer, i: i64, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) { write_i64 :: proc(w: Writer, i: i64, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
buf: [32]byte buf: [32]byte
s := strconv.append_bits(buf[:], u64(i), base, true, 64, strconv.digits, nil) s := strconv.write_bits(buf[:], u64(i), base, true, 64, strconv.digits, nil)
return write_string(w, s, n_written) return write_string(w, s, n_written)
} }
@@ -40,18 +40,18 @@ write_int :: proc(w: Writer, i: int, base: int = 10, n_written: ^int = nil) -> (
write_u128 :: proc(w: Writer, i: u128, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) { write_u128 :: proc(w: Writer, i: u128, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
buf: [39]byte buf: [39]byte
s := strconv.append_bits_128(buf[:], i, base, false, 128, strconv.digits, nil) s := strconv.write_bits_128(buf[:], i, base, false, 128, strconv.digits, nil)
return write_string(w, s, n_written) return write_string(w, s, n_written)
} }
write_i128 :: proc(w: Writer, i: i128, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) { write_i128 :: proc(w: Writer, i: i128, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) {
buf: [40]byte buf: [40]byte
s := strconv.append_bits_128(buf[:], u128(i), base, true, 128, strconv.digits, nil) s := strconv.write_bits_128(buf[:], u128(i), base, true, 128, strconv.digits, nil)
return write_string(w, s, n_written) return write_string(w, s, n_written)
} }
write_f16 :: proc(w: Writer, val: f16, n_written: ^int = nil) -> (n: int, err: Error) { write_f16 :: proc(w: Writer, val: f16, n_written: ^int = nil) -> (n: int, err: Error) {
buf: [386]byte buf: [386]byte
str := strconv.append_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val)) str := strconv.write_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val))
s := buf[:len(str)+1] s := buf[:len(str)+1]
if s[1] == '+' || s[1] == '-' { if s[1] == '+' || s[1] == '-' {
s = s[1:] s = s[1:]
@@ -67,7 +67,7 @@ write_f16 :: proc(w: Writer, val: f16, n_written: ^int = nil) -> (n: int, err: E
write_f32 :: proc(w: Writer, val: f32, n_written: ^int = nil) -> (n: int, err: Error) { write_f32 :: proc(w: Writer, val: f32, n_written: ^int = nil) -> (n: int, err: Error) {
buf: [386]byte buf: [386]byte
str := strconv.append_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val)) str := strconv.write_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val))
s := buf[:len(str)+1] s := buf[:len(str)+1]
if s[1] == '+' || s[1] == '-' { if s[1] == '+' || s[1] == '-' {
s = s[1:] s = s[1:]
@@ -83,7 +83,7 @@ write_f32 :: proc(w: Writer, val: f32, n_written: ^int = nil) -> (n: int, err: E
write_f64 :: proc(w: Writer, val: f64, n_written: ^int = nil) -> (n: int, err: Error) { write_f64 :: proc(w: Writer, val: f64, n_written: ^int = nil) -> (n: int, err: Error) {
buf: [386]byte buf: [386]byte
str := strconv.append_float(buf[1:], val, 'f', 2*size_of(val), 8*size_of(val)) str := strconv.write_float(buf[1:], val, 'f', 2*size_of(val), 8*size_of(val))
s := buf[:len(str)+1] s := buf[:len(str)+1]
if s[1] == '+' || s[1] == '-' { if s[1] == '+' || s[1] == '-' {
s = s[1:] s = s[1:]
@@ -130,7 +130,7 @@ write_encoded_rune :: proc(w: Writer, r: rune, write_quote := true, n_written: ^
write_string(w, `\x`, &n) or_return write_string(w, `\x`, &n) or_return
buf: [2]byte buf: [2]byte
s := strconv.append_bits(buf[:], u64(r), 16, true, 64, strconv.digits, nil) s := strconv.write_bits(buf[:], u64(r), 16, true, 64, strconv.digits, nil)
switch len(s) { switch len(s) {
case 0: case 0:
write_string(w, "00", &n) or_return write_string(w, "00", &n) or_return
+53 -11
View File
@@ -2,10 +2,12 @@
#+build !orca #+build !orca
package log package log
import "core:encoding/ansi" import "base:runtime"
import "core:fmt" import "core:fmt"
import "core:strings" import "core:strings"
import "core:os" import "core:os"
import "core:terminal"
import "core:terminal/ansi"
import "core:time" import "core:time"
Level_Headers := [?]string{ Level_Headers := [?]string{
@@ -37,11 +39,36 @@ File_Console_Logger_Data :: struct {
ident: string, ident: string,
} }
@(private) global_subtract_stdout_options: Options
@(private) global_subtract_stderr_options: Options
@(init, private)
init_standard_stream_status :: proc() {
// NOTE(Feoramund): While it is technically possible for these streams to
// be redirected during the runtime of the program, the cost of checking on
// every single log message is not worth it to support such an
// uncommonly-used feature.
if terminal.color_enabled {
// This is done this way because it's possible that only one of these
// streams could be redirected to a file.
if !terminal.is_terminal(os.stdout) {
global_subtract_stdout_options = {.Terminal_Color}
}
if !terminal.is_terminal(os.stderr) {
global_subtract_stderr_options = {.Terminal_Color}
}
} else {
// Override any terminal coloring.
global_subtract_stdout_options = {.Terminal_Color}
global_subtract_stderr_options = {.Terminal_Color}
}
}
create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger { create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger {
data := new(File_Console_Logger_Data, allocator) data := new(File_Console_Logger_Data, allocator)
data.file_handle = h data.file_handle = h
data.ident = ident data.ident = ident
return Logger{file_console_logger_proc, data, lowest, opt} return Logger{file_logger_proc, data, lowest, opt}
} }
destroy_file_logger :: proc(log: Logger, allocator := context.allocator) { destroy_file_logger :: proc(log: Logger, allocator := context.allocator) {
@@ -56,19 +83,15 @@ create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logg
data := new(File_Console_Logger_Data, allocator) data := new(File_Console_Logger_Data, allocator)
data.file_handle = os.INVALID_HANDLE data.file_handle = os.INVALID_HANDLE
data.ident = ident data.ident = ident
return Logger{file_console_logger_proc, data, lowest, opt} return Logger{console_logger_proc, data, lowest, opt}
} }
destroy_console_logger :: proc(log: Logger, allocator := context.allocator) { destroy_console_logger :: proc(log: Logger, allocator := context.allocator) {
free(log.data, allocator) free(log.data, allocator)
} }
file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) { @(private)
data := cast(^File_Console_Logger_Data)logger_data _file_console_logger_proc :: proc(h: os.Handle, ident: string, level: Level, text: string, options: Options, location: runtime.Source_Code_Location) {
h: os.Handle = os.stdout if level <= Level.Error else os.stderr
if data.file_handle != os.INVALID_HANDLE {
h = data.file_handle
}
backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths. backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths.
buf := strings.builder_from_bytes(backing[:]) buf := strings.builder_from_bytes(backing[:])
@@ -86,13 +109,32 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string
fmt.sbprintf(&buf, "[{}] ", os.current_thread_id()) fmt.sbprintf(&buf, "[{}] ", os.current_thread_id())
} }
if data.ident != "" { if ident != "" {
fmt.sbprintf(&buf, "[%s] ", data.ident) fmt.sbprintf(&buf, "[%s] ", ident)
} }
//TODO(Hoej): When we have better atomics and such, make this thread-safe //TODO(Hoej): When we have better atomics and such, make this thread-safe
fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text) fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text)
} }
file_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
data := cast(^File_Console_Logger_Data)logger_data
_file_console_logger_proc(data.file_handle, data.ident, level, text, options, location)
}
console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
options := options
data := cast(^File_Console_Logger_Data)logger_data
h: os.Handle = ---
if level < Level.Error {
h = os.stdout
options -= global_subtract_stdout_options
} else {
h = os.stderr
options -= global_subtract_stderr_options
}
_file_console_logger_proc(h, data.ident, level, text, options, location)
}
do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) { do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) {
RESET :: ansi.CSI + ansi.RESET + ansi.SGR RESET :: ansi.CSI + ansi.RESET + ansi.SGR
+1 -1
View File
@@ -1370,8 +1370,8 @@ _private_int_div_recursive :: proc(quotient, remainder, a, b: ^Int, allocator :=
/* /*
Slower bit-bang division... also smaller. Slower bit-bang division... also smaller.
Prefer `_int_div_school` for speed.
*/ */
@(deprecated="Use `_int_div_school`, it's 3.5x faster.")
_private_int_div_small :: proc(quotient, remainder, numerator, denominator: ^Int) -> (err: Error) { _private_int_div_small :: proc(quotient, remainder, numerator, denominator: ^Int) -> (err: Error) {
ta, tb, tq, q := &Int{}, &Int{}, &Int{}, &Int{} ta, tb, tq, q := &Int{}, &Int{}, &Int{}, &Int{}
+1 -1
View File
@@ -280,7 +280,7 @@ int_atoi :: proc(res: ^Int, input: string, radix := i8(10), allocator := context
} }
pos := ch - '+' pos := ch - '+'
if RADIX_TABLE_REVERSE_SIZE <= pos { if RADIX_TABLE_REVERSE_SIZE <= u32(pos) {
break break
} }
y := RADIX_TABLE_REVERSE[pos] y := RADIX_TABLE_REVERSE[pos]
+10 -5
View File
@@ -103,7 +103,7 @@ round :: proc(x: $T/Fixed($Backing, $Fraction_Width)) -> Backing {
} }
@(require_results) @(require_results)
append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string { write :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
Integer_Width :: 8*size_of(Backing) - Fraction_Width Integer_Width :: 8*size_of(Backing) - Fraction_Width
x := x x := x
@@ -124,16 +124,16 @@ append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
when size_of(Backing) < 16 { when size_of(Backing) < 16 {
T :: u64 T :: u64
append_uint :: strconv.append_uint write_uint :: strconv.write_uint
} else { } else {
T :: u128 T :: u128
append_uint :: strconv.append_u128 write_uint :: strconv.write_u128
} }
integer := T(x.i) >> Fraction_Width integer := T(x.i) >> Fraction_Width
fraction := T(x.i) & (1<<Fraction_Width - 1) fraction := T(x.i) & (1<<Fraction_Width - 1)
s := append_uint(buf[i:], integer, 10) s := write_uint(buf[i:], integer, 10)
i += len(s) i += len(s)
if fraction != 0 { if fraction != 0 {
buf[i] = '.' buf[i] = '.'
@@ -155,7 +155,7 @@ append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
@(require_results) @(require_results)
to_string :: proc(x: $T/Fixed($Backing, $Fraction_Width), allocator := context.allocator) -> string { to_string :: proc(x: $T/Fixed($Backing, $Fraction_Width), allocator := context.allocator) -> string {
buf: [48]byte buf: [48]byte
s := append(buf[:], x) s := write(buf[:], x)
str := make([]byte, len(s), allocator) str := make([]byte, len(s), allocator)
copy(str, s) copy(str, s)
return string(str) return string(str)
@@ -294,3 +294,8 @@ _power_of_two_table := [129]string{
"85070591730234615865843651857942052864", "85070591730234615865843651857942052864",
"170141183460469231731687303715884105728", "170141183460469231731687303715884105728",
} }
@(deprecated="Use write instead")
append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string {
return write(dst, x)
}
+1 -1
View File
@@ -350,7 +350,7 @@ Example:
Possible Output: Possible Output:
6 6
500 13
*/ */
@(require_results) @(require_results)
+25 -5
View File
@@ -315,18 +315,38 @@ check_zero_ptr :: proc(ptr: rawptr, len: int) -> bool {
Offset a given pointer by a given amount. Offset a given pointer by a given amount.
This procedure offsets the pointer `ptr` to an object of type `T`, by the amount This procedure offsets the pointer `ptr` to an object of type `T`, by the amount
of bytes specified by `offset*size_of(T)`, and returns the pointer `ptr`. of bytes specified by `offset * size_of(T)`, and returns the pointer `ptr`.
**Note**: Prefer to use multipointer types, if possible. **Note**: Prefer to use multipointer types, if possible.
*/ */
ptr_offset :: intrinsics.ptr_offset ptr_offset :: intrinsics.ptr_offset
/* /*
Offset a given pointer by a given amount backwards. Subtract two pointers of the same type, and return the number of `T` between them.
This procedure offsets the pointer `ptr` to an object of type `T`, by the amount This procedure subtracts pointer `b` from pointer `a`, both of type `^T`,
of bytes specified by `offset*size_of(T)` in the negative direction, and and returns an integer count of the `T` between them.
returns the pointer `ptr`.
**Inputs**
- `a`: A pointer to a type T
- `b`: A pointer to a type T
**Returns**
- `b` - `a` in items of T as an `int`.
Example:
import "core:mem"
import "core:fmt"
ptr_sub_example :: proc() {
arr: [2]int
fmt.println(mem.ptr_sub(&arr[1], &arr[0]))
}
Output:
1
*/ */
ptr_sub :: intrinsics.ptr_sub ptr_sub :: intrinsics.ptr_sub
+31 -19
View File
@@ -1,6 +1,7 @@
package mem package mem
import "base:runtime" import "base:runtime"
import "base:sanitizer"
/* /*
Rollback stack default block size. Rollback stack default block size.
@@ -47,14 +48,14 @@ Rollback_Stack :: struct {
block_allocator: Allocator, block_allocator: Allocator,
} }
@(private="file", require_results) @(private="file", require_results, no_sanitize_address)
rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool { rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool {
start := raw_data(block.buffer) start := raw_data(block.buffer)
end := start[block.offset:] end := start[block.offset:]
return start < ptr && ptr <= end return start < ptr && ptr <= end
} }
@(private="file", require_results) @(private="file", require_results, no_sanitize_address)
rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
parent: ^Rollback_Stack_Block, parent: ^Rollback_Stack_Block,
block: ^Rollback_Stack_Block, block: ^Rollback_Stack_Block,
@@ -71,7 +72,7 @@ rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
return nil, nil, nil, .Invalid_Pointer return nil, nil, nil, .Invalid_Pointer
} }
@(private="file", require_results) @(private="file", require_results, no_sanitize_address)
rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
block: ^Rollback_Stack_Block, block: ^Rollback_Stack_Block,
header: ^Rollback_Stack_Header, header: ^Rollback_Stack_Header,
@@ -86,9 +87,10 @@ rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
return nil, nil, false return nil, nil, false
} }
@(private="file") @(private="file", no_sanitize_address)
rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_Header) { rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_Header) {
header := header header := header
for block.offset > 0 && header.is_free { for block.offset > 0 && header.is_free {
block.offset = header.prev_offset block.offset = header.prev_offset
block.last_alloc = raw_data(block.buffer)[header.prev_ptr:] block.last_alloc = raw_data(block.buffer)[header.prev_ptr:]
@@ -99,9 +101,10 @@ rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_
/* /*
Free memory to a rollback stack allocator. Free memory to a rollback stack allocator.
*/ */
@(private="file", require_results) @(private="file", require_results, no_sanitize_address)
rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error { rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error {
parent, block, header := rb_find_ptr(stack, ptr) or_return parent, block, header := rb_find_ptr(stack, ptr) or_return
if header.is_free { if header.is_free {
return .Invalid_Pointer return .Invalid_Pointer
} }
@@ -120,7 +123,7 @@ rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error {
/* /*
Free all memory owned by the rollback stack allocator. Free all memory owned by the rollback stack allocator.
*/ */
@(private="file") @(private="file", no_sanitize_address)
rb_free_all :: proc(stack: ^Rollback_Stack) { rb_free_all :: proc(stack: ^Rollback_Stack) {
for block := stack.head.next_block; block != nil; /**/ { for block := stack.head.next_block; block != nil; /**/ {
next_block := block.next_block next_block := block.next_block
@@ -131,12 +134,13 @@ rb_free_all :: proc(stack: ^Rollback_Stack) {
stack.head.next_block = nil stack.head.next_block = nil
stack.head.last_alloc = nil stack.head.last_alloc = nil
stack.head.offset = 0 stack.head.offset = 0
sanitizer.address_poison(stack.head.buffer)
} }
/* /*
Allocate memory using the rollback stack allocator. Allocate memory using the rollback stack allocator.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_alloc :: proc( rb_alloc :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
size: int, size: int,
@@ -153,7 +157,7 @@ rb_alloc :: proc(
/* /*
Allocate memory using the rollback stack allocator. Allocate memory using the rollback stack allocator.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_alloc_bytes :: proc( rb_alloc_bytes :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
size: int, size: int,
@@ -170,7 +174,7 @@ rb_alloc_bytes :: proc(
/* /*
Allocate non-initialized memory using the rollback stack allocator. Allocate non-initialized memory using the rollback stack allocator.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_alloc_non_zeroed :: proc( rb_alloc_non_zeroed :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
size: int, size: int,
@@ -184,7 +188,7 @@ rb_alloc_non_zeroed :: proc(
/* /*
Allocate non-initialized memory using the rollback stack allocator. Allocate non-initialized memory using the rollback stack allocator.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_alloc_bytes_non_zeroed :: proc( rb_alloc_bytes_non_zeroed :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
size: int, size: int,
@@ -194,6 +198,7 @@ rb_alloc_bytes_non_zeroed :: proc(
assert(size >= 0, "Size must be positive or zero.", loc) assert(size >= 0, "Size must be positive or zero.", loc)
assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", loc) assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", loc)
parent: ^Rollback_Stack_Block parent: ^Rollback_Stack_Block
for block := stack.head; /**/; block = block.next_block { for block := stack.head; /**/; block = block.next_block {
when !ODIN_DISABLE_ASSERT { when !ODIN_DISABLE_ASSERT {
allocated_new_block: bool allocated_new_block: bool
@@ -235,7 +240,9 @@ rb_alloc_bytes_non_zeroed :: proc(
// Prevent any further allocations on it. // Prevent any further allocations on it.
block.offset = cast(uintptr)len(block.buffer) block.offset = cast(uintptr)len(block.buffer)
} }
#no_bounds_check return ptr[:size], nil res := ptr[:size]
sanitizer.address_unpoison(res)
return res, nil
} }
return nil, .Out_Of_Memory return nil, .Out_Of_Memory
} }
@@ -243,7 +250,7 @@ rb_alloc_bytes_non_zeroed :: proc(
/* /*
Resize an allocation owned by rollback stack allocator. Resize an allocation owned by rollback stack allocator.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_resize :: proc( rb_resize :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
old_ptr: rawptr, old_ptr: rawptr,
@@ -266,7 +273,7 @@ rb_resize :: proc(
/* /*
Resize an allocation owned by rollback stack allocator. Resize an allocation owned by rollback stack allocator.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_resize_bytes :: proc( rb_resize_bytes :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
old_memory: []byte, old_memory: []byte,
@@ -289,7 +296,7 @@ rb_resize_bytes :: proc(
Resize an allocation owned by rollback stack allocator without explicit Resize an allocation owned by rollback stack allocator without explicit
zero-initialization. zero-initialization.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_resize_non_zeroed :: proc( rb_resize_non_zeroed :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
old_ptr: rawptr, old_ptr: rawptr,
@@ -306,7 +313,7 @@ rb_resize_non_zeroed :: proc(
Resize an allocation owned by rollback stack allocator without explicit Resize an allocation owned by rollback stack allocator without explicit
zero-initialization. zero-initialization.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rb_resize_bytes_non_zeroed :: proc( rb_resize_bytes_non_zeroed :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
old_memory: []byte, old_memory: []byte,
@@ -330,7 +337,9 @@ rb_resize_bytes_non_zeroed :: proc(
if len(block.buffer) <= stack.block_size { if len(block.buffer) <= stack.block_size {
block.offset += cast(uintptr)size - cast(uintptr)old_size block.offset += cast(uintptr)size - cast(uintptr)old_size
} }
#no_bounds_check return (ptr)[:size], nil res := (ptr)[:size]
sanitizer.address_unpoison(res)
#no_bounds_check return res, nil
} }
} }
} }
@@ -340,7 +349,7 @@ rb_resize_bytes_non_zeroed :: proc(
return return
} }
@(private="file", require_results) @(private="file", require_results, no_sanitize_address)
rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) { rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) {
buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return
block = cast(^Rollback_Stack_Block)raw_data(buffer) block = cast(^Rollback_Stack_Block)raw_data(buffer)
@@ -351,6 +360,7 @@ rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stac
/* /*
Initialize the rollback stack allocator using a fixed backing buffer. Initialize the rollback stack allocator using a fixed backing buffer.
*/ */
@(no_sanitize_address)
rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, location := #caller_location) { rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, location := #caller_location) {
MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr) MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr)
assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.", location) assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.", location)
@@ -365,6 +375,7 @@ rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, loc
/* /*
Initialize the rollback stack alocator using a backing block allocator. Initialize the rollback stack alocator using a backing block allocator.
*/ */
@(no_sanitize_address)
rollback_stack_init_dynamic :: proc( rollback_stack_init_dynamic :: proc(
stack: ^Rollback_Stack, stack: ^Rollback_Stack,
block_size : int = ROLLBACK_STACK_DEFAULT_BLOCK_SIZE, block_size : int = ROLLBACK_STACK_DEFAULT_BLOCK_SIZE,
@@ -396,6 +407,7 @@ rollback_stack_init :: proc {
/* /*
Destroy a rollback stack. Destroy a rollback stack.
*/ */
@(no_sanitize_address)
rollback_stack_destroy :: proc(stack: ^Rollback_Stack) { rollback_stack_destroy :: proc(stack: ^Rollback_Stack) {
if stack.block_allocator.procedure != nil { if stack.block_allocator.procedure != nil {
rb_free_all(stack) rb_free_all(stack)
@@ -435,7 +447,7 @@ from the last allocation backwards.
Each allocation has an overhead of 8 bytes and any extra bytes to satisfy Each allocation has an overhead of 8 bytes and any extra bytes to satisfy
the requested alignment. the requested alignment.
*/ */
@(require_results) @(require_results, no_sanitize_address)
rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator { rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator {
return Allocator { return Allocator {
data = stack, data = stack,
@@ -443,7 +455,7 @@ rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator {
} }
} }
@(require_results) @(require_results, no_sanitize_address)
rollback_stack_allocator_proc :: proc( rollback_stack_allocator_proc :: proc(
allocator_data: rawptr, allocator_data: rawptr,
mode: Allocator_Mode, mode: Allocator_Mode,
+50 -42
View File
@@ -10,6 +10,7 @@
package mem_tlsf package mem_tlsf
import "base:intrinsics" import "base:intrinsics"
import "base:sanitizer"
import "base:runtime" import "base:runtime"
// log2 of number of linear subdivisions of block sizes. // log2 of number of linear subdivisions of block sizes.
@@ -209,6 +210,8 @@ alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) ->
return nil, .Out_Of_Memory return nil, .Out_Of_Memory
} }
sanitizer.address_poison(new_pool_buf)
// Allocate a new link in the `control.pool` tracking structure. // Allocate a new link in the `control.pool` tracking structure.
new_pool := new_clone(Pool{ new_pool := new_clone(Pool{
data = new_pool_buf, data = new_pool_buf,
@@ -254,7 +257,7 @@ alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) ->
return block_prepare_used(control, block, adjust) return block_prepare_used(control, block, adjust)
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) { alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) {
res, err = alloc_bytes_non_zeroed(control, size, align) res, err = alloc_bytes_non_zeroed(control, size, align)
if err == nil { if err == nil {
@@ -273,6 +276,7 @@ free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) {
block := block_from_ptr(ptr) block := block_from_ptr(ptr)
assert(!block_is_free(block), "block already marked as free") // double free assert(!block_is_free(block), "block already marked as free") // double free
sanitizer.address_poison(ptr, block.size)
block_mark_as_free(block) block_mark_as_free(block)
block = block_merge_prev(control, block) block = block_merge_prev(control, block)
block = block_merge_next(control, block) block = block_merge_next(control, block)
@@ -316,6 +320,7 @@ resize :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, align
block_trim_used(control, block, adjust) block_trim_used(control, block, adjust)
res = ([^]byte)(ptr)[:new_size] res = ([^]byte)(ptr)[:new_size]
sanitizer.address_unpoison(res)
if min_size < new_size { if min_size < new_size {
to_zero := ([^]byte)(ptr)[min_size:new_size] to_zero := ([^]byte)(ptr)[min_size:new_size]
@@ -374,95 +379,96 @@ resize_non_zeroed :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size:
NOTE: TLSF spec relies on ffs/fls returning a value in the range 0..31. NOTE: TLSF spec relies on ffs/fls returning a value in the range 0..31.
*/ */
@(private, require_results) @(private, require_results, no_sanitize_address)
block_size :: proc "contextless" (block: ^Block_Header) -> (size: uint) { block_size :: proc "contextless" (block: ^Block_Header) -> (size: uint) {
return block.size &~ (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE) return block.size &~ (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE)
} }
@(private) @(private, no_sanitize_address)
block_set_size :: proc "contextless" (block: ^Block_Header, size: uint) { block_set_size :: proc "contextless" (block: ^Block_Header, size: uint) {
old_size := block.size old_size := block.size
block.size = size | (old_size & (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE)) block.size = size | (old_size & (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE))
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
block_is_last :: proc "contextless" (block: ^Block_Header) -> (is_last: bool) { block_is_last :: proc "contextless" (block: ^Block_Header) -> (is_last: bool) {
return block_size(block) == 0 return block_size(block) == 0
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
block_is_free :: proc "contextless" (block: ^Block_Header) -> (is_free: bool) { block_is_free :: proc "contextless" (block: ^Block_Header) -> (is_free: bool) {
return (block.size & BLOCK_HEADER_FREE) == BLOCK_HEADER_FREE return (block.size & BLOCK_HEADER_FREE) == BLOCK_HEADER_FREE
} }
@(private) @(private, no_sanitize_address)
block_set_free :: proc "contextless" (block: ^Block_Header) { block_set_free :: proc "contextless" (block: ^Block_Header) {
block.size |= BLOCK_HEADER_FREE block.size |= BLOCK_HEADER_FREE
} }
@(private) @(private, no_sanitize_address)
block_set_used :: proc "contextless" (block: ^Block_Header) { block_set_used :: proc "contextless" (block: ^Block_Header) {
block.size &~= BLOCK_HEADER_FREE block.size &~= BLOCK_HEADER_FREE
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
block_is_prev_free :: proc "contextless" (block: ^Block_Header) -> (is_prev_free: bool) { block_is_prev_free :: proc "contextless" (block: ^Block_Header) -> (is_prev_free: bool) {
return (block.size & BLOCK_HEADER_PREV_FREE) == BLOCK_HEADER_PREV_FREE return (block.size & BLOCK_HEADER_PREV_FREE) == BLOCK_HEADER_PREV_FREE
} }
@(private) @(private, no_sanitize_address)
block_set_prev_free :: proc "contextless" (block: ^Block_Header) { block_set_prev_free :: proc "contextless" (block: ^Block_Header) {
block.size |= BLOCK_HEADER_PREV_FREE block.size |= BLOCK_HEADER_PREV_FREE
} }
@(private) @(private, no_sanitize_address)
block_set_prev_used :: proc "contextless" (block: ^Block_Header) { block_set_prev_used :: proc "contextless" (block: ^Block_Header) {
block.size &~= BLOCK_HEADER_PREV_FREE block.size &~= BLOCK_HEADER_PREV_FREE
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
block_from_ptr :: proc(ptr: rawptr) -> (block_ptr: ^Block_Header) { block_from_ptr :: proc(ptr: rawptr) -> (block_ptr: ^Block_Header) {
return (^Block_Header)(uintptr(ptr) - BLOCK_START_OFFSET) return (^Block_Header)(uintptr(ptr) - BLOCK_START_OFFSET)
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
block_to_ptr :: proc(block: ^Block_Header) -> (ptr: rawptr) { block_to_ptr :: proc(block: ^Block_Header) -> (ptr: rawptr) {
return rawptr(uintptr(block) + BLOCK_START_OFFSET) return rawptr(uintptr(block) + BLOCK_START_OFFSET)
} }
// Return location of next block after block of given size. // Return location of next block after block of given size.
@(private, require_results) @(private, require_results, no_sanitize_address)
offset_to_block :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) { offset_to_block :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) {
return (^Block_Header)(uintptr(ptr) + uintptr(size)) return (^Block_Header)(uintptr(ptr) + uintptr(size))
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
offset_to_block_backwards :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) { offset_to_block_backwards :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) {
return (^Block_Header)(uintptr(ptr) - uintptr(size)) return (^Block_Header)(uintptr(ptr) - uintptr(size))
} }
// Return location of previous block. // Return location of previous block.
@(private, require_results) @(private, require_results, no_sanitize_address)
block_prev :: proc(block: ^Block_Header) -> (prev: ^Block_Header) { block_prev :: proc(block: ^Block_Header) -> (prev: ^Block_Header) {
assert(block_is_prev_free(block), "previous block must be free") assert(block_is_prev_free(block), "previous block must be free")
return block.prev_phys_block return block.prev_phys_block
} }
// Return location of next existing block. // Return location of next existing block.
@(private, require_results) @(private, require_results, no_sanitize_address)
block_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) { block_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) {
return offset_to_block(block_to_ptr(block), block_size(block) - BLOCK_HEADER_OVERHEAD) return offset_to_block(block_to_ptr(block), block_size(block) - BLOCK_HEADER_OVERHEAD)
} }
// Link a new block with its physical neighbor, return the neighbor. // Link a new block with its physical neighbor, return the neighbor.
@(private, require_results) @(private, require_results, no_sanitize_address)
block_link_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) { block_link_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) {
next = block_next(block) next = block_next(block)
next.prev_phys_block = block next.prev_phys_block = block
return return
} }
@(private) @(private, no_sanitize_address)
block_mark_as_free :: proc(block: ^Block_Header) { block_mark_as_free :: proc(block: ^Block_Header) {
// Link the block to the next block, first. // Link the block to the next block, first.
next := block_link_next(block) next := block_link_next(block)
@@ -470,26 +476,26 @@ block_mark_as_free :: proc(block: ^Block_Header) {
block_set_free(block) block_set_free(block)
} }
@(private) @(private, no_sanitize_address)
block_mark_as_used :: proc(block: ^Block_Header) { block_mark_as_used :: proc(block: ^Block_Header) {
next := block_next(block) next := block_next(block)
block_set_prev_used(next) block_set_prev_used(next)
block_set_used(block) block_set_used(block)
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
align_up :: proc(x, align: uint) -> (aligned: uint) { align_up :: proc(x, align: uint) -> (aligned: uint) {
assert(0 == (align & (align - 1)), "must align to a power of two") assert(0 == (align & (align - 1)), "must align to a power of two")
return (x + (align - 1)) &~ (align - 1) return (x + (align - 1)) &~ (align - 1)
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
align_down :: proc(x, align: uint) -> (aligned: uint) { align_down :: proc(x, align: uint) -> (aligned: uint) {
assert(0 == (align & (align - 1)), "must align to a power of two") assert(0 == (align & (align - 1)), "must align to a power of two")
return x - (x & (align - 1)) return x - (x & (align - 1))
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) { align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) {
assert(0 == (align & (align - 1)), "must align to a power of two") assert(0 == (align & (align - 1)), "must align to a power of two")
align_mask := uintptr(align) - 1 align_mask := uintptr(align) - 1
@@ -499,7 +505,7 @@ align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) {
} }
// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum. // Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
@(private, require_results) @(private, require_results, no_sanitize_address)
adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) { adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) {
if size == 0 { if size == 0 {
return 0 return 0
@@ -513,7 +519,7 @@ adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) {
} }
// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum. // Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
@(private, require_results) @(private, require_results, no_sanitize_address)
adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: runtime.Allocator_Error) { adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: runtime.Allocator_Error) {
if size == 0 { if size == 0 {
return 0, nil return 0, nil
@@ -531,7 +537,7 @@ adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err:
// TLSF utility functions. In most cases these are direct translations of // TLSF utility functions. In most cases these are direct translations of
// the documentation in the research paper. // the documentation in the research paper.
@(optimization_mode="favor_size", private, require_results) @(optimization_mode="favor_size", private, require_results, no_sanitize_address)
mapping_insert :: proc(size: uint) -> (fl, sl: i32) { mapping_insert :: proc(size: uint) -> (fl, sl: i32) {
if size < SMALL_BLOCK_SIZE { if size < SMALL_BLOCK_SIZE {
// Store small blocks in first list. // Store small blocks in first list.
@@ -544,7 +550,7 @@ mapping_insert :: proc(size: uint) -> (fl, sl: i32) {
return return
} }
@(optimization_mode="favor_size", private, require_results) @(optimization_mode="favor_size", private, require_results, no_sanitize_address)
mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) { mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) {
rounded = size rounded = size
if size >= SMALL_BLOCK_SIZE { if size >= SMALL_BLOCK_SIZE {
@@ -555,12 +561,12 @@ mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) {
} }
// This version rounds up to the next block size (for allocations) // This version rounds up to the next block size (for allocations)
@(optimization_mode="favor_size", private, require_results) @(optimization_mode="favor_size", private, require_results, no_sanitize_address)
mapping_search :: proc(size: uint) -> (fl, sl: i32) { mapping_search :: proc(size: uint) -> (fl, sl: i32) {
return mapping_insert(mapping_round(size)) return mapping_insert(mapping_round(size))
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
search_suitable_block :: proc(control: ^Allocator, fli, sli: ^i32) -> (block: ^Block_Header) { search_suitable_block :: proc(control: ^Allocator, fli, sli: ^i32) -> (block: ^Block_Header) {
// First, search for a block in the list associated with the given fl/sl index. // First, search for a block in the list associated with the given fl/sl index.
fl := fli^; sl := sli^ fl := fli^; sl := sli^
@@ -587,7 +593,7 @@ search_suitable_block :: proc(control: ^Allocator, fli, sli: ^i32) -> (block: ^B
} }
// Remove a free block from the free list. // Remove a free block from the free list.
@(private) @(private, no_sanitize_address)
remove_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) { remove_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) {
prev := block.prev_free prev := block.prev_free
next := block.next_free next := block.next_free
@@ -613,7 +619,7 @@ remove_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl
} }
// Insert a free block into the free block list. // Insert a free block into the free block list.
@(private) @(private, no_sanitize_address)
insert_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) { insert_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) {
current := control.blocks[fl][sl] current := control.blocks[fl][sl]
assert(current != nil, "free lists cannot have a nil entry") assert(current != nil, "free lists cannot have a nil entry")
@@ -631,26 +637,26 @@ insert_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl
} }
// Remove a given block from the free list. // Remove a given block from the free list.
@(private) @(private, no_sanitize_address)
block_remove :: proc(control: ^Allocator, block: ^Block_Header) { block_remove :: proc(control: ^Allocator, block: ^Block_Header) {
fl, sl := mapping_insert(block_size(block)) fl, sl := mapping_insert(block_size(block))
remove_free_block(control, block, fl, sl) remove_free_block(control, block, fl, sl)
} }
// Insert a given block into the free list. // Insert a given block into the free list.
@(private) @(private, no_sanitize_address)
block_insert :: proc(control: ^Allocator, block: ^Block_Header) { block_insert :: proc(control: ^Allocator, block: ^Block_Header) {
fl, sl := mapping_insert(block_size(block)) fl, sl := mapping_insert(block_size(block))
insert_free_block(control, block, fl, sl) insert_free_block(control, block, fl, sl)
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
block_can_split :: proc(block: ^Block_Header, size: uint) -> (can_split: bool) { block_can_split :: proc(block: ^Block_Header, size: uint) -> (can_split: bool) {
return block_size(block) >= size_of(Block_Header) + size return block_size(block) >= size_of(Block_Header) + size
} }
// Split a block into two, the second of which is free. // Split a block into two, the second of which is free.
@(private, require_results) @(private, require_results, no_sanitize_address)
block_split :: proc(block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) { block_split :: proc(block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) {
// Calculate the amount of space left in the remaining block. // Calculate the amount of space left in the remaining block.
remaining = offset_to_block(block_to_ptr(block), size - BLOCK_HEADER_OVERHEAD) remaining = offset_to_block(block_to_ptr(block), size - BLOCK_HEADER_OVERHEAD)
@@ -671,9 +677,10 @@ block_split :: proc(block: ^Block_Header, size: uint) -> (remaining: ^Block_Head
} }
// Absorb a free block's storage into an adjacent previous free block. // Absorb a free block's storage into an adjacent previous free block.
@(private, require_results) @(private, require_results, no_sanitize_address)
block_absorb :: proc(prev: ^Block_Header, block: ^Block_Header) -> (absorbed: ^Block_Header) { block_absorb :: proc(prev: ^Block_Header, block: ^Block_Header) -> (absorbed: ^Block_Header) {
assert(!block_is_last(prev), "previous block can't be last") assert(!block_is_last(prev), "previous block can't be last")
// Note: Leaves flags untouched. // Note: Leaves flags untouched.
prev.size += block_size(block) + BLOCK_HEADER_OVERHEAD prev.size += block_size(block) + BLOCK_HEADER_OVERHEAD
_ = block_link_next(prev) _ = block_link_next(prev)
@@ -681,7 +688,7 @@ block_absorb :: proc(prev: ^Block_Header, block: ^Block_Header) -> (absorbed: ^B
} }
// Merge a just-freed block with an adjacent previous free block. // Merge a just-freed block with an adjacent previous free block.
@(private, require_results) @(private, require_results, no_sanitize_address)
block_merge_prev :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) { block_merge_prev :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) {
merged = block merged = block
if (block_is_prev_free(block)) { if (block_is_prev_free(block)) {
@@ -695,7 +702,7 @@ block_merge_prev :: proc(control: ^Allocator, block: ^Block_Header) -> (merged:
} }
// Merge a just-freed block with an adjacent free block. // Merge a just-freed block with an adjacent free block.
@(private, require_results) @(private, require_results, no_sanitize_address)
block_merge_next :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) { block_merge_next :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) {
merged = block merged = block
next := block_next(block) next := block_next(block)
@@ -710,7 +717,7 @@ block_merge_next :: proc(control: ^Allocator, block: ^Block_Header) -> (merged:
} }
// Trim any trailing block space off the end of a free block, return to pool. // Trim any trailing block space off the end of a free block, return to pool.
@(private) @(private, no_sanitize_address)
block_trim_free :: proc(control: ^Allocator, block: ^Block_Header, size: uint) { block_trim_free :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
assert(block_is_free(block), "block must be free") assert(block_is_free(block), "block must be free")
if (block_can_split(block, size)) { if (block_can_split(block, size)) {
@@ -722,7 +729,7 @@ block_trim_free :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
} }
// Trim any trailing block space off the end of a used block, return to pool. // Trim any trailing block space off the end of a used block, return to pool.
@(private) @(private, no_sanitize_address)
block_trim_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) { block_trim_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
assert(!block_is_free(block), "Block must be used") assert(!block_is_free(block), "Block must be used")
if (block_can_split(block, size)) { if (block_can_split(block, size)) {
@@ -736,7 +743,7 @@ block_trim_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
} }
// Trim leading block space, return to pool. // Trim leading block space, return to pool.
@(private, require_results) @(private, require_results, no_sanitize_address)
block_trim_free_leading :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) { block_trim_free_leading :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) {
remaining = block remaining = block
if block_can_split(block, size) { if block_can_split(block, size) {
@@ -750,7 +757,7 @@ block_trim_free_leading :: proc(control: ^Allocator, block: ^Block_Header, size:
return remaining return remaining
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
block_locate_free :: proc(control: ^Allocator, size: uint) -> (block: ^Block_Header) { block_locate_free :: proc(control: ^Allocator, size: uint) -> (block: ^Block_Header) {
fl, sl: i32 fl, sl: i32
if size != 0 { if size != 0 {
@@ -774,13 +781,14 @@ block_locate_free :: proc(control: ^Allocator, size: uint) -> (block: ^Block_Hea
return block return block
} }
@(private, require_results) @(private, require_results, no_sanitize_address)
block_prepare_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (res: []byte, err: runtime.Allocator_Error) { block_prepare_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (res: []byte, err: runtime.Allocator_Error) {
if block != nil { if block != nil {
assert(size != 0, "Size must be non-zero") assert(size != 0, "Size must be non-zero")
block_trim_free(control, block, size) block_trim_free(control, block, size)
block_mark_as_used(block) block_mark_as_used(block)
res = ([^]byte)(block_to_ptr(block))[:size] res = ([^]byte)(block_to_ptr(block))[:size]
sanitizer.address_unpoison(res)
} }
return return
} }
+10 -1
View File
@@ -64,6 +64,7 @@ This procedure initializes the tracking allocator `t` with a backing allocator
specified with `backing_allocator`. The `internals_allocator` will used to specified with `backing_allocator`. The `internals_allocator` will used to
allocate the tracked data. allocate the tracked data.
*/ */
@(no_sanitize_address)
tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) { tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) {
t.backing = backing_allocator t.backing = backing_allocator
t.allocation_map.allocator = internals_allocator t.allocation_map.allocator = internals_allocator
@@ -77,6 +78,7 @@ tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Alloc
/* /*
Destroy the tracking allocator. Destroy the tracking allocator.
*/ */
@(no_sanitize_address)
tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) { tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) {
delete(t.allocation_map) delete(t.allocation_map)
delete(t.bad_free_array) delete(t.bad_free_array)
@@ -90,6 +92,7 @@ This procedure clears the tracked data from a tracking allocator.
**Note**: This procedure clears only the current allocation data while keeping **Note**: This procedure clears only the current allocation data while keeping
the totals intact. the totals intact.
*/ */
@(no_sanitize_address)
tracking_allocator_clear :: proc(t: ^Tracking_Allocator) { tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
sync.mutex_lock(&t.mutex) sync.mutex_lock(&t.mutex)
clear(&t.allocation_map) clear(&t.allocation_map)
@@ -103,6 +106,7 @@ Reset the tracking allocator.
Reset all of a Tracking Allocator's allocation data back to zero. Reset all of a Tracking Allocator's allocation data back to zero.
*/ */
@(no_sanitize_address)
tracking_allocator_reset :: proc(t: ^Tracking_Allocator) { tracking_allocator_reset :: proc(t: ^Tracking_Allocator) {
sync.mutex_lock(&t.mutex) sync.mutex_lock(&t.mutex)
clear(&t.allocation_map) clear(&t.allocation_map)
@@ -124,6 +128,7 @@ Override Tracking_Allocator.bad_free_callback to have something else happen. For
example, you can use tracking_allocator_bad_free_callback_add_to_array to return example, you can use tracking_allocator_bad_free_callback_add_to_array to return
the tracking allocator to the old behavior, where the bad_free_array was used. the tracking allocator to the old behavior, where the bad_free_array was used.
*/ */
@(no_sanitize_address)
tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) { tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) {
runtime.print_caller_location(location) runtime.print_caller_location(location)
runtime.print_string(" Tracking allocator error: Bad free of pointer ") runtime.print_string(" Tracking allocator error: Bad free of pointer ")
@@ -136,6 +141,7 @@ tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memor
Alternative behavior for a bad free: Store in `bad_free_array`. If you use this, Alternative behavior for a bad free: Store in `bad_free_array`. If you use this,
then you must make sure to check Tracking_Allocator.bad_free_array at some point. then you must make sure to check Tracking_Allocator.bad_free_array at some point.
*/ */
@(no_sanitize_address)
tracking_allocator_bad_free_callback_add_to_array :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) { tracking_allocator_bad_free_callback_add_to_array :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) {
append(&t.bad_free_array, Tracking_Allocator_Bad_Free_Entry { append(&t.bad_free_array, Tracking_Allocator_Bad_Free_Entry {
memory = memory, memory = memory,
@@ -175,7 +181,7 @@ Example:
} }
} }
*/ */
@(require_results) @(require_results, no_sanitize_address)
tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator { tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
return Allocator{ return Allocator{
data = data, data = data,
@@ -183,6 +189,7 @@ tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
} }
} }
@(no_sanitize_address)
tracking_allocator_proc :: proc( tracking_allocator_proc :: proc(
allocator_data: rawptr, allocator_data: rawptr,
mode: Allocator_Mode, mode: Allocator_Mode,
@@ -191,6 +198,7 @@ tracking_allocator_proc :: proc(
old_size: int, old_size: int,
loc := #caller_location, loc := #caller_location,
) -> (result: []byte, err: Allocator_Error) { ) -> (result: []byte, err: Allocator_Error) {
@(no_sanitize_address)
track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
data.total_memory_allocated += i64(entry.size) data.total_memory_allocated += i64(entry.size)
data.total_allocation_count += 1 data.total_allocation_count += 1
@@ -200,6 +208,7 @@ tracking_allocator_proc :: proc(
} }
} }
@(no_sanitize_address)
track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
data.total_memory_freed += i64(entry.size) data.total_memory_freed += i64(entry.size)
data.total_free_count += 1 data.total_free_count += 1
+32 -11
View File
@@ -3,6 +3,8 @@ package mem_virtual
import "core:mem" import "core:mem"
import "core:sync" import "core:sync"
import "base:sanitizer"
Arena_Kind :: enum uint { Arena_Kind :: enum uint {
Growing = 0, // Chained memory blocks (singly linked list). Growing = 0, // Chained memory blocks (singly linked list).
Static = 1, // Fixed reservation sized. Static = 1, // Fixed reservation sized.
@@ -43,7 +45,7 @@ DEFAULT_ARENA_STATIC_RESERVE_SIZE :: mem.Gigabyte when size_of(uintptr) == 8 els
// Initialization of an `Arena` to be a `.Growing` variant. // Initialization of an `Arena` to be a `.Growing` variant.
// A growing arena is a linked list of `Memory_Block`s allocated with virtual memory. // A growing arena is a linked list of `Memory_Block`s allocated with virtual memory.
@(require_results) @(require_results, no_sanitize_address)
arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (err: Allocator_Error) { arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (err: Allocator_Error) {
arena.kind = .Growing arena.kind = .Growing
arena.curr_block = memory_block_alloc(0, reserved, {}) or_return arena.curr_block = memory_block_alloc(0, reserved, {}) or_return
@@ -53,24 +55,26 @@ arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING
if arena.minimum_block_size == 0 { if arena.minimum_block_size == 0 {
arena.minimum_block_size = reserved arena.minimum_block_size = reserved
} }
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
return return
} }
// Initialization of an `Arena` to be a `.Static` variant. // Initialization of an `Arena` to be a `.Static` variant.
// A static arena contains a single `Memory_Block` allocated with virtual memory. // A static arena contains a single `Memory_Block` allocated with virtual memory.
@(require_results) @(require_results, no_sanitize_address)
arena_init_static :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_STATIC_RESERVE_SIZE, commit_size: uint = DEFAULT_ARENA_STATIC_COMMIT_SIZE) -> (err: Allocator_Error) { arena_init_static :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_STATIC_RESERVE_SIZE, commit_size: uint = DEFAULT_ARENA_STATIC_COMMIT_SIZE) -> (err: Allocator_Error) {
arena.kind = .Static arena.kind = .Static
arena.curr_block = memory_block_alloc(commit_size, reserved, {}) or_return arena.curr_block = memory_block_alloc(commit_size, reserved, {}) or_return
arena.total_used = 0 arena.total_used = 0
arena.total_reserved = arena.curr_block.reserved arena.total_reserved = arena.curr_block.reserved
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
return return
} }
// Initialization of an `Arena` to be a `.Buffer` variant. // Initialization of an `Arena` to be a `.Buffer` variant.
// A buffer arena contains single `Memory_Block` created from a user provided []byte. // A buffer arena contains single `Memory_Block` created from a user provided []byte.
@(require_results) @(require_results, no_sanitize_address)
arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Error) { arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Error) {
if len(buffer) < size_of(Memory_Block) { if len(buffer) < size_of(Memory_Block) {
return .Out_Of_Memory return .Out_Of_Memory
@@ -78,7 +82,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
arena.kind = .Buffer arena.kind = .Buffer
mem.zero_slice(buffer) sanitizer.address_poison(buffer[:])
block_base := raw_data(buffer) block_base := raw_data(buffer)
block := (^Memory_Block)(block_base) block := (^Memory_Block)(block_base)
@@ -94,7 +98,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
} }
// Allocates memory from the provided arena. // Allocates memory from the provided arena.
@(require_results) @(require_results, no_sanitize_address)
arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc) assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc)
@@ -158,10 +162,13 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l
data, err = alloc_from_memory_block(arena.curr_block, size, alignment, default_commit_size=0) data, err = alloc_from_memory_block(arena.curr_block, size, alignment, default_commit_size=0)
arena.total_used = arena.curr_block.used arena.total_used = arena.curr_block.used
} }
sanitizer.address_unpoison(data)
return return
} }
// Resets the memory of a Static or Buffer arena to a specific `position` (offset) and zeroes the previously used memory. // Resets the memory of a Static or Buffer arena to a specific `position` (offset) and zeroes the previously used memory.
@(no_sanitize_address)
arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) -> bool { arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) -> bool {
sync.mutex_guard(&arena.mutex) sync.mutex_guard(&arena.mutex)
@@ -175,6 +182,7 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location)
mem.zero_slice(arena.curr_block.base[arena.curr_block.used:][:prev_pos-pos]) mem.zero_slice(arena.curr_block.base[arena.curr_block.used:][:prev_pos-pos])
} }
arena.total_used = arena.curr_block.used arena.total_used = arena.curr_block.used
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
return true return true
} else if pos == 0 { } else if pos == 0 {
arena.total_used = 0 arena.total_used = 0
@@ -184,6 +192,7 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location)
} }
// Frees the last memory block of a Growing Arena // Frees the last memory block of a Growing Arena
@(no_sanitize_address)
arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) { arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) {
if free_block := arena.curr_block; free_block != nil { if free_block := arena.curr_block; free_block != nil {
assert(arena.kind == .Growing, "expected a .Growing arena", loc) assert(arena.kind == .Growing, "expected a .Growing arena", loc)
@@ -191,11 +200,13 @@ arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_locat
arena.total_reserved -= free_block.reserved arena.total_reserved -= free_block.reserved
arena.curr_block = free_block.prev arena.curr_block = free_block.prev
sanitizer.address_poison(free_block.base[:free_block.committed])
memory_block_dealloc(free_block) memory_block_dealloc(free_block)
} }
} }
// Deallocates all but the first memory block of the arena and resets the allocator's usage to 0. // Deallocates all but the first memory block of the arena and resets the allocator's usage to 0.
@(no_sanitize_address)
arena_free_all :: proc(arena: ^Arena, loc := #caller_location) { arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
switch arena.kind { switch arena.kind {
case .Growing: case .Growing:
@@ -208,7 +219,9 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
if arena.curr_block != nil { if arena.curr_block != nil {
curr_block_used := int(arena.curr_block.used) curr_block_used := int(arena.curr_block.used)
arena.curr_block.used = 0 arena.curr_block.used = 0
sanitizer.address_unpoison(arena.curr_block.base[:curr_block_used])
mem.zero(arena.curr_block.base, curr_block_used) mem.zero(arena.curr_block.base, curr_block_used)
sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
} }
arena.total_used = 0 arena.total_used = 0
case .Static, .Buffer: case .Static, .Buffer:
@@ -219,6 +232,7 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
// Frees all of the memory allocated by the arena and zeros all of the values of an arena. // Frees all of the memory allocated by the arena and zeros all of the values of an arena.
// A buffer based arena does not `delete` the provided `[]byte` bufffer. // A buffer based arena does not `delete` the provided `[]byte` bufffer.
@(no_sanitize_address)
arena_destroy :: proc(arena: ^Arena, loc := #caller_location) { arena_destroy :: proc(arena: ^Arena, loc := #caller_location) {
sync.mutex_guard(&arena.mutex) sync.mutex_guard(&arena.mutex)
switch arena.kind { switch arena.kind {
@@ -250,7 +264,7 @@ arena_static_bootstrap_new :: proc{
} }
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy. // Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
@(require_results) @(require_results, no_sanitize_address)
arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) { arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
bootstrap: Arena bootstrap: Arena
bootstrap.kind = .Growing bootstrap.kind = .Growing
@@ -266,13 +280,13 @@ arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintp
} }
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy. // Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
@(require_results) @(require_results, no_sanitize_address)
arena_growing_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) { arena_growing_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
return arena_growing_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), minimum_block_size) return arena_growing_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), minimum_block_size)
} }
// Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy. // Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy.
@(require_results) @(require_results, no_sanitize_address)
arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, reserved: uint) -> (ptr: ^T, err: Allocator_Error) { arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
bootstrap: Arena bootstrap: Arena
bootstrap.kind = .Static bootstrap.kind = .Static
@@ -288,19 +302,20 @@ arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintpt
} }
// Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy. // Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy.
@(require_results) @(require_results, no_sanitize_address)
arena_static_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, reserved: uint) -> (ptr: ^T, err: Allocator_Error) { arena_static_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
return arena_static_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), reserved) return arena_static_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), reserved)
} }
// Create an `Allocator` from the provided `Arena` // Create an `Allocator` from the provided `Arena`
@(require_results) @(require_results, no_sanitize_address)
arena_allocator :: proc(arena: ^Arena) -> mem.Allocator { arena_allocator :: proc(arena: ^Arena) -> mem.Allocator {
return mem.Allocator{arena_allocator_proc, arena} return mem.Allocator{arena_allocator_proc, arena}
} }
// The allocator procedure used by an `Allocator` produced by `arena_allocator` // The allocator procedure used by an `Allocator` produced by `arena_allocator`
@(no_sanitize_address)
arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
size, alignment: int, size, alignment: int,
old_memory: rawptr, old_size: int, old_memory: rawptr, old_size: int,
@@ -334,6 +349,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
if size < old_size { if size < old_size {
// shrink data in-place // shrink data in-place
data = old_data[:size] data = old_data[:size]
sanitizer.address_poison(old_data[size:old_size])
return return
} }
@@ -347,6 +363,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
_ = alloc_from_memory_block(block, new_end - old_end, 1, default_commit_size=arena.default_commit_size) or_return _ = alloc_from_memory_block(block, new_end - old_end, 1, default_commit_size=arena.default_commit_size) or_return
arena.total_used += block.used - prev_used arena.total_used += block.used - prev_used
data = block.base[start:new_end] data = block.base[start:new_end]
sanitizer.address_unpoison(data)
return return
} }
} }
@@ -357,6 +374,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
return return
} }
copy(new_memory, old_data[:old_size]) copy(new_memory, old_data[:old_size])
sanitizer.address_poison(old_data[:old_size])
return new_memory, nil return new_memory, nil
case .Query_Features: case .Query_Features:
set := (^mem.Allocator_Mode_Set)(old_memory) set := (^mem.Allocator_Mode_Set)(old_memory)
@@ -382,7 +400,7 @@ Arena_Temp :: struct {
} }
// Begins the section of temporary arena memory. // Begins the section of temporary arena memory.
@(require_results) @(require_results, no_sanitize_address)
arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) { arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) {
assert(arena != nil, "nil arena", loc) assert(arena != nil, "nil arena", loc)
sync.mutex_guard(&arena.mutex) sync.mutex_guard(&arena.mutex)
@@ -397,6 +415,7 @@ arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena
} }
// Ends the section of temporary arena memory by resetting the memory to the stored position. // Ends the section of temporary arena memory by resetting the memory to the stored position.
@(no_sanitize_address)
arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
assert(temp.arena != nil, "nil arena", loc) assert(temp.arena != nil, "nil arena", loc)
arena := temp.arena arena := temp.arena
@@ -432,6 +451,7 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
} }
// Ignore the use of a `arena_temp_begin` entirely by __not__ resetting to the stored position. // Ignore the use of a `arena_temp_begin` entirely by __not__ resetting to the stored position.
@(no_sanitize_address)
arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) { arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
assert(temp.arena != nil, "nil arena", loc) assert(temp.arena != nil, "nil arena", loc)
arena := temp.arena arena := temp.arena
@@ -442,6 +462,7 @@ arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
} }
// Asserts that all uses of `Arena_Temp` has been used by an `Arena` // Asserts that all uses of `Arena_Temp` has been used by an `Arena`
@(no_sanitize_address)
arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) { arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) {
assert(arena.temp_count == 0, "Arena_Temp not been ended", loc) assert(arena.temp_count == 0, "Arena_Temp not been ended", loc)
} }
+17 -8
View File
@@ -2,6 +2,7 @@ package mem_virtual
import "core:mem" import "core:mem"
import "base:intrinsics" import "base:intrinsics"
import "base:sanitizer"
import "base:runtime" import "base:runtime"
_ :: runtime _ :: runtime
@@ -14,27 +15,33 @@ platform_memory_init :: proc() {
Allocator_Error :: mem.Allocator_Error Allocator_Error :: mem.Allocator_Error
@(require_results) @(require_results, no_sanitize_address)
reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
return _reserve(size) return _reserve(size)
} }
@(no_sanitize_address)
commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error { commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
sanitizer.address_unpoison(data, size)
return _commit(data, size) return _commit(data, size)
} }
@(require_results) @(require_results, no_sanitize_address)
reserve_and_commit :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { reserve_and_commit :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
data = reserve(size) or_return data = reserve(size) or_return
commit(raw_data(data), size) or_return commit(raw_data(data), size) or_return
return return
} }
@(no_sanitize_address)
decommit :: proc "contextless" (data: rawptr, size: uint) { decommit :: proc "contextless" (data: rawptr, size: uint) {
sanitizer.address_poison(data, size)
_decommit(data, size) _decommit(data, size)
} }
@(no_sanitize_address)
release :: proc "contextless" (data: rawptr, size: uint) { release :: proc "contextless" (data: rawptr, size: uint) {
sanitizer.address_unpoison(data, size)
_release(data, size) _release(data, size)
} }
@@ -46,13 +53,11 @@ Protect_Flag :: enum u32 {
Protect_Flags :: distinct bit_set[Protect_Flag; u32] Protect_Flags :: distinct bit_set[Protect_Flag; u32]
Protect_No_Access :: Protect_Flags{} Protect_No_Access :: Protect_Flags{}
@(no_sanitize_address)
protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool { protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool {
return _protect(data, size, flags) return _protect(data, size, flags)
} }
Memory_Block :: struct { Memory_Block :: struct {
prev: ^Memory_Block, prev: ^Memory_Block,
base: [^]byte, base: [^]byte,
@@ -66,13 +71,13 @@ Memory_Block_Flag :: enum u32 {
Memory_Block_Flags :: distinct bit_set[Memory_Block_Flag; u32] Memory_Block_Flags :: distinct bit_set[Memory_Block_Flag; u32]
@(private="file", require_results) @(private="file", require_results, no_sanitize_address)
align_formula :: #force_inline proc "contextless" (size, align: uint) -> uint { align_formula :: #force_inline proc "contextless" (size, align: uint) -> uint {
result := size + align-1 result := size + align-1
return result - result%align return result - result%align
} }
@(require_results) @(require_results, no_sanitize_address)
memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags: Memory_Block_Flags = {}) -> (block: ^Memory_Block, err: Allocator_Error) { memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags: Memory_Block_Flags = {}) -> (block: ^Memory_Block, err: Allocator_Error) {
page_size := DEFAULT_PAGE_SIZE page_size := DEFAULT_PAGE_SIZE
assert(mem.is_power_of_two(uintptr(page_size))) assert(mem.is_power_of_two(uintptr(page_size)))
@@ -116,8 +121,9 @@ memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags
return &pmblock.block, nil return &pmblock.block, nil
} }
@(require_results) @(require_results, no_sanitize_address)
alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint, default_commit_size: uint = 0) -> (data: []byte, err: Allocator_Error) { alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint, default_commit_size: uint = 0) -> (data: []byte, err: Allocator_Error) {
@(no_sanitize_address)
calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint { calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint {
alignment_offset := uint(0) alignment_offset := uint(0)
ptr := uintptr(block.base[block.used:]) ptr := uintptr(block.base[block.used:])
@@ -128,6 +134,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint,
return alignment_offset return alignment_offset
} }
@(no_sanitize_address)
do_commit_if_necessary :: proc(block: ^Memory_Block, size: uint, default_commit_size: uint) -> (err: Allocator_Error) { do_commit_if_necessary :: proc(block: ^Memory_Block, size: uint, default_commit_size: uint) -> (err: Allocator_Error) {
if block.committed - block.used < size { if block.committed - block.used < size {
pmblock := (^Platform_Memory_Block)(block) pmblock := (^Platform_Memory_Block)(block)
@@ -172,10 +179,12 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint,
data = block.base[block.used+alignment_offset:][:min_size] data = block.base[block.used+alignment_offset:][:min_size]
block.used += size block.used += size
sanitizer.address_unpoison(data)
return return
} }
@(no_sanitize_address)
memory_block_dealloc :: proc(block_to_free: ^Memory_Block) { memory_block_dealloc :: proc(block_to_free: ^Memory_Block) {
if block := (^Platform_Memory_Block)(block_to_free); block != nil { if block := (^Platform_Memory_Block)(block_to_free); block != nil {
platform_memory_free(block) platform_memory_free(block)
+3
View File
@@ -7,6 +7,7 @@ Platform_Memory_Block :: struct {
reserved: uint, reserved: uint,
} }
@(no_sanitize_address)
platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (block: ^Platform_Memory_Block, err: Allocator_Error) { platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (block: ^Platform_Memory_Block, err: Allocator_Error) {
to_commit, to_reserve := to_commit, to_reserve to_commit, to_reserve := to_commit, to_reserve
to_reserve = max(to_commit, to_reserve) to_reserve = max(to_commit, to_reserve)
@@ -26,12 +27,14 @@ platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (bl
} }
@(no_sanitize_address)
platform_memory_free :: proc "contextless" (block: ^Platform_Memory_Block) { platform_memory_free :: proc "contextless" (block: ^Platform_Memory_Block) {
if block != nil { if block != nil {
release(block, block.reserved) release(block, block.reserved)
} }
} }
@(no_sanitize_address)
platform_memory_commit :: proc "contextless" (block: ^Platform_Memory_Block, to_commit: uint) -> (err: Allocator_Error) { platform_memory_commit :: proc "contextless" (block: ^Platform_Memory_Block, to_commit: uint) -> (err: Allocator_Error) {
if to_commit < block.committed { if to_commit < block.committed {
return nil return nil
+11 -1
View File
@@ -83,6 +83,8 @@ foreign Kernel32 {
dwNumberOfBytesToMap: uint, dwNumberOfBytesToMap: uint,
) -> rawptr --- ) -> rawptr ---
} }
@(no_sanitize_address)
_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
result := VirtualAlloc(nil, size, MEM_RESERVE, PAGE_READWRITE) result := VirtualAlloc(nil, size, MEM_RESERVE, PAGE_READWRITE)
if result == nil { if result == nil {
@@ -93,6 +95,7 @@ _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Err
return return
} }
@(no_sanitize_address)
_commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error { _commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
result := VirtualAlloc(data, size, MEM_COMMIT, PAGE_READWRITE) result := VirtualAlloc(data, size, MEM_COMMIT, PAGE_READWRITE)
if result == nil { if result == nil {
@@ -107,12 +110,18 @@ _commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
} }
return nil return nil
} }
@(no_sanitize_address)
_decommit :: proc "contextless" (data: rawptr, size: uint) { _decommit :: proc "contextless" (data: rawptr, size: uint) {
VirtualFree(data, size, MEM_DECOMMIT) VirtualFree(data, size, MEM_DECOMMIT)
} }
@(no_sanitize_address)
_release :: proc "contextless" (data: rawptr, size: uint) { _release :: proc "contextless" (data: rawptr, size: uint) {
VirtualFree(data, 0, MEM_RELEASE) VirtualFree(data, 0, MEM_RELEASE)
} }
@(no_sanitize_address)
_protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool { _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool {
pflags: u32 pflags: u32
pflags = PAGE_NOACCESS pflags = PAGE_NOACCESS
@@ -136,7 +145,7 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags)
} }
@(no_sanitize_address)
_platform_memory_init :: proc() { _platform_memory_init :: proc() {
sys_info: SYSTEM_INFO sys_info: SYSTEM_INFO
GetSystemInfo(&sys_info) GetSystemInfo(&sys_info)
@@ -147,6 +156,7 @@ _platform_memory_init :: proc() {
} }
@(no_sanitize_address)
_map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
page_flags: u32 page_flags: u32
if flags == {.Read} { if flags == {.Read} {
+1 -1
View File
@@ -125,7 +125,7 @@ percent_encode :: proc(s: string, allocator := context.allocator) -> string {
bytes, n := utf8.encode_rune(ch) bytes, n := utf8.encode_rune(ch)
for byte in bytes[:n] { for byte in bytes[:n] {
buf: [2]u8 = --- buf: [2]u8 = ---
t := strconv.append_int(buf[:], i64(byte), 16) t := strconv.write_int(buf[:], i64(byte), 16)
strings.write_rune(&b, '%') strings.write_rune(&b, '%')
strings.write_string(&b, t) strings.write_string(&b, t)
} }
+17 -17
View File
@@ -1276,28 +1276,28 @@ parse_unrolled_for_loop :: proc(p: ^Parser, inline_tok: tokenizer.Token) -> ^ast
args = make([dynamic]^ast.Expr) args = make([dynamic]^ast.Expr)
for p.curr_tok.kind != .Close_Paren && for p.curr_tok.kind != .Close_Paren &&
p.curr_tok.kind != .EOF { p.curr_tok.kind != .EOF {
arg := parse_value(p) arg := parse_value(p)
if p.curr_tok.kind == .Eq { if p.curr_tok.kind == .Eq {
eq := expect_token(p, .Eq) eq := expect_token(p, .Eq)
if arg != nil { if arg != nil {
if _, ok := arg.derived.(^ast.Ident); !ok { if _, ok := arg.derived.(^ast.Ident); !ok {
error(p, arg.pos, "expected an identifier for 'key=value'") error(p, arg.pos, "expected an identifier for 'key=value'")
} }
} }
value := parse_value(p) value := parse_value(p)
fv := ast.new(ast.Field_Value, arg.pos, value) fv := ast.new(ast.Field_Value, arg.pos, value)
fv.field = arg fv.field = arg
fv.sep = eq.pos fv.sep = eq.pos
fv.value = value fv.value = value
arg = fv arg = fv
} }
append(&args, arg) append(&args, arg)
allow_token(p, .Comma) or_break allow_token(p, .Comma) or_break
} }
} }
p.expr_level -= 1 p.expr_level -= 1
+1 -1
View File
@@ -57,7 +57,7 @@ write_encoded_rune :: proc(f: Handle, r: rune) -> (n: int, err: Error) {
if r < 32 { if r < 32 {
if wrap(write_string(f, "\\x"), &n, &err) { return } if wrap(write_string(f, "\\x"), &n, &err) { return }
b: [2]byte b: [2]byte
s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil) s := strconv.write_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil)
switch len(s) { switch len(s) {
case 0: if wrap(write_string(f, "00"), &n, &err) { return } case 0: if wrap(write_string(f, "00"), &n, &err) { return }
case 1: if wrap(write_rune(f, '0'), &n, &err) { return } case 1: if wrap(write_rune(f, '0'), &n, &err) { return }
+42 -41
View File
@@ -8,43 +8,13 @@ file_allocator :: proc() -> runtime.Allocator {
return heap_allocator() return heap_allocator()
} }
temp_allocator_proc :: runtime.arena_allocator_proc
@(private="file") @(private="file")
MAX_TEMP_ARENA_COUNT :: 2 MAX_TEMP_ARENA_COUNT :: 2
@(private="file")
MAX_TEMP_ARENA_COLLISIONS :: MAX_TEMP_ARENA_COUNT - 1
@(private="file", thread_local) @(private="file", thread_local)
global_default_temp_allocator_arenas: [MAX_TEMP_ARENA_COUNT]runtime.Arena global_default_temp_allocator_arenas: [MAX_TEMP_ARENA_COUNT]runtime.Arena
@(private="file", thread_local)
global_default_temp_allocator_index: uint
@(require_results)
temp_allocator :: proc() -> runtime.Allocator {
arena := &global_default_temp_allocator_arenas[global_default_temp_allocator_index]
if arena.backing_allocator.procedure == nil {
arena.backing_allocator = heap_allocator()
}
return runtime.Allocator{
procedure = temp_allocator_proc,
data = arena,
}
}
@(require_results)
temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: runtime.Arena_Temp) {
temp = runtime.arena_temp_begin(&global_default_temp_allocator_arenas[global_default_temp_allocator_index], loc)
return
}
temp_allocator_temp_end :: proc(temp: runtime.Arena_Temp, loc := #caller_location) {
runtime.arena_temp_end(temp, loc)
}
@(fini, private) @(fini, private)
temp_allocator_fini :: proc() { temp_allocator_fini :: proc() {
for &arena in global_default_temp_allocator_arenas { for &arena in global_default_temp_allocator_arenas {
@@ -53,18 +23,49 @@ temp_allocator_fini :: proc() {
global_default_temp_allocator_arenas = {} global_default_temp_allocator_arenas = {}
} }
TEMP_ALLOCATOR_GUARD_END :: proc(temp: runtime.Arena_Temp, loc := #caller_location) { Temp_Allocator :: struct {
runtime.arena_temp_end(temp, loc) using arena: ^runtime.Arena,
if temp.arena != nil { using allocator: runtime.Allocator,
global_default_temp_allocator_index = (global_default_temp_allocator_index-1)%MAX_TEMP_ARENA_COUNT tmp: runtime.Arena_Temp,
} loc: runtime.Source_Code_Location,
}
TEMP_ALLOCATOR_GUARD_END :: proc(temp: Temp_Allocator) {
runtime.arena_temp_end(temp.tmp, temp.loc)
} }
@(deferred_out=TEMP_ALLOCATOR_GUARD_END) @(deferred_out=TEMP_ALLOCATOR_GUARD_END)
TEMP_ALLOCATOR_GUARD :: #force_inline proc(loc := #caller_location) -> (runtime.Arena_Temp, runtime.Source_Code_Location) { TEMP_ALLOCATOR_GUARD :: #force_inline proc(collisions: []runtime.Allocator, loc := #caller_location) -> Temp_Allocator {
global_default_temp_allocator_index = (global_default_temp_allocator_index+1)%MAX_TEMP_ARENA_COUNT assert(len(collisions) <= MAX_TEMP_ARENA_COLLISIONS, "Maximum collision count exceeded. MAX_TEMP_ARENA_COUNT must be increased!")
tmp := temp_allocator_temp_begin(loc) good_arena: ^runtime.Arena
return tmp, loc for i in 0..<MAX_TEMP_ARENA_COUNT {
good_arena = &global_default_temp_allocator_arenas[i]
for c in collisions {
if good_arena == c.data {
good_arena = nil
}
}
if good_arena != nil {
break
}
}
assert(good_arena != nil)
if good_arena.backing_allocator.procedure == nil {
good_arena.backing_allocator = heap_allocator()
}
tmp := runtime.arena_temp_begin(good_arena, loc)
return { good_arena, runtime.arena_allocator(good_arena), tmp, loc }
}
temp_allocator_begin :: runtime.arena_temp_begin
temp_allocator_end :: runtime.arena_temp_end
@(deferred_out=_temp_allocator_end)
temp_allocator_scope :: proc(tmp: Temp_Allocator) -> (runtime.Arena_Temp) {
return temp_allocator_begin(tmp.arena)
}
@(private="file")
_temp_allocator_end :: proc(tmp: runtime.Arena_Temp) {
temp_allocator_end(tmp)
} }
@(init, private) @(init, private)
+43 -16
View File
@@ -2,6 +2,7 @@ package os2
import "base:runtime" import "base:runtime"
import "core:slice" import "core:slice"
import "core:strings"
read_dir :: read_directory read_dir :: read_directory
@@ -18,12 +19,12 @@ read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files
size = 100 size = 100
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
it := read_directory_iterator_create(f) it := read_directory_iterator_create(f)
defer _read_directory_iterator_destroy(&it) defer _read_directory_iterator_destroy(&it)
dfi := make([dynamic]File_Info, 0, size, temp_allocator()) dfi := make([dynamic]File_Info, 0, size, temp_allocator)
defer if err != nil { defer if err != nil {
for fi in dfi { for fi in dfi {
file_info_delete(fi, allocator) file_info_delete(fi, allocator)
@@ -194,28 +195,54 @@ read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info,
} }
// Recursively copies a directory to `dst` from `src` // Recursively copies a directory to `dst` from `src`
copy_directory :: proc(dst, src: string, dst_perm := 0o755) -> Error { copy_directory_all :: proc(dst, src: string, dst_perm := 0o755) -> Error {
switch err := make_directory_all(dst, dst_perm); err { when #defined(_copy_directory_all_native) {
case nil, .Exist: return _copy_directory_all_native(dst, src, dst_perm)
// okay } else {
case: return _copy_directory_all(dst, src, dst_perm)
}
}
@(private)
_copy_directory_all :: proc(dst, src: string, dst_perm := 0o755) -> Error {
err := make_directory(dst, dst_perm)
if err != nil && err != .Exist {
return err return err
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
file_infos := read_all_directory_by_path(src, temp_allocator()) or_return abs_src := get_absolute_path(src, temp_allocator) or_return
for fi in file_infos { abs_dst := get_absolute_path(dst, temp_allocator) or_return
TEMP_ALLOCATOR_GUARD()
dst_path := join_path({dst, fi.name}, temp_allocator()) or_return dst_buf := make([dynamic]byte, 0, len(abs_dst) + 256, temp_allocator) or_return
src_path := fi.fullpath
if fi.type == .Directory { w: Walker
copy_directory(dst_path, src_path) or_return walker_init_path(&w, src)
defer walker_destroy(&w)
for info in walker_walk(&w) {
_ = walker_error(&w) or_break
rel := strings.trim_prefix(info.fullpath, abs_src)
non_zero_resize(&dst_buf, 0)
reserve(&dst_buf, len(abs_dst) + len(Path_Separator_String) + len(rel)) or_return
append(&dst_buf, abs_dst)
append(&dst_buf, Path_Separator_String)
append(&dst_buf, rel)
if info.type == .Directory {
err = make_directory(string(dst_buf[:]), dst_perm)
if err != nil && err != .Exist {
return err
}
} else { } else {
copy_file(dst_path, src_path) or_return copy_file(string(dst_buf[:]), info.fullpath) or_return
} }
} }
_ = walker_error(&w) or_return
return nil return nil
} }
+2 -1
View File
@@ -78,7 +78,8 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
it.impl.prev_fi = fi it.impl.prev_fi = fi
if err != nil { if err != nil {
path, _ := _get_full_path(entry_fd, temp_allocator()) temp_allocator := TEMP_ALLOCATOR_GUARD({})
path, _ := _get_full_path(entry_fd, temp_allocator)
read_directory_iterator_set_error(it, path, err) read_directory_iterator_set_error(it, path, err)
} }
+17
View File
@@ -0,0 +1,17 @@
#+private
package os2
import "core:sys/darwin"
_copy_directory_all_native :: proc(dst, src: string, dst_perm := 0o755) -> (err: Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
csrc := clone_to_cstring(src, temp_allocator) or_return
cdst := clone_to_cstring(dst, temp_allocator) or_return
if darwin.copyfile(csrc, cdst, nil, darwin.COPYFILE_ALL + {.RECURSIVE}) < 0 {
err = _get_platform_error()
}
return
}
+5 -5
View File
@@ -14,7 +14,9 @@ find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, al
if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 { if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 {
return return
} }
path := concatenate({base_path, `\`, win32_utf16_to_utf8(d.cFileName[:], temp_allocator()) or_else ""}, allocator) or_return
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
path := concatenate({base_path, `\`, win32_wstring_to_utf8(raw_data(d.cFileName[:]), temp_allocator) or_else ""}, allocator) or_return
handle := win32.HANDLE(_open_internal(path, {.Read}, 0o666) or_else 0) handle := win32.HANDLE(_open_internal(path, {.Read}, 0o666) or_else 0)
defer win32.CloseHandle(handle) defer win32.CloseHandle(handle)
@@ -49,8 +51,6 @@ Read_Directory_Iterator_Impl :: struct {
@(require_results) @(require_results)
_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
TEMP_ALLOCATOR_GUARD()
for !it.impl.no_more_files { for !it.impl.no_more_files {
err: Error err: Error
file_info_delete(it.impl.prev_fi, file_allocator()) file_info_delete(it.impl.prev_fi, file_allocator())
@@ -116,9 +116,9 @@ _read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
wpath = impl.wname[:i] wpath = impl.wname[:i]
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
wpath_search := make([]u16, len(wpath)+3, temp_allocator()) wpath_search := make([]u16, len(wpath)+3, temp_allocator)
copy(wpath_search, wpath) copy(wpath_search, wpath)
wpath_search[len(wpath)+0] = '\\' wpath_search[len(wpath)+0] = '\\'
wpath_search[len(wpath)+1] = '*' wpath_search[len(wpath)+1] = '*'
+7 -7
View File
@@ -12,9 +12,9 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
return return
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
ckey := strings.clone_to_cstring(key, temp_allocator()) ckey := strings.clone_to_cstring(key, temp_allocator)
cval := posix.getenv(ckey) cval := posix.getenv(ckey)
if cval == nil { if cval == nil {
return return
@@ -27,10 +27,10 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
} }
_set_env :: proc(key, value: string) -> (err: Error) { _set_env :: proc(key, value: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
ckey := strings.clone_to_cstring(key, temp_allocator()) or_return ckey := strings.clone_to_cstring(key, temp_allocator) or_return
cval := strings.clone_to_cstring(key, temp_allocator()) or_return cval := strings.clone_to_cstring(value, temp_allocator) or_return
if posix.setenv(ckey, cval, true) != nil { if posix.setenv(ckey, cval, true) != nil {
err = _get_platform_error_from_errno() err = _get_platform_error_from_errno()
@@ -39,9 +39,9 @@ _set_env :: proc(key, value: string) -> (err: Error) {
} }
_unset_env :: proc(key: string) -> (ok: bool) { _unset_env :: proc(key: string) -> (ok: bool) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
ckey := strings.clone_to_cstring(key, temp_allocator()) ckey := strings.clone_to_cstring(key, temp_allocator)
ok = posix.unsetenv(ckey) == .OK ok = posix.unsetenv(ckey) == .OK
return return
+2 -2
View File
@@ -39,9 +39,9 @@ build_env :: proc() -> (err: Error) {
g_env_buf = make([]byte, size_of_envs, file_allocator()) or_return g_env_buf = make([]byte, size_of_envs, file_allocator()) or_return
defer if err != nil { delete(g_env_buf, file_allocator()) } defer if err != nil { delete(g_env_buf, file_allocator()) }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
envs := make([]cstring, num_envs, temp_allocator()) or_return envs := make([]cstring, num_envs, temp_allocator) or_return
_err = wasi.environ_get(raw_data(envs), raw_data(g_env_buf)) _err = wasi.environ_get(raw_data(envs), raw_data(g_env_buf))
if _err != nil { if _err != nil {
+10 -10
View File
@@ -8,8 +8,8 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
if key == "" { if key == "" {
return return
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
wkey, _ := win32_utf8_to_wstring(key, temp_allocator()) wkey, _ := win32_utf8_to_wstring(key, temp_allocator)
n := win32.GetEnvironmentVariableW(wkey, nil, 0) n := win32.GetEnvironmentVariableW(wkey, nil, 0)
if n == 0 { if n == 0 {
@@ -20,7 +20,7 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
return "", true return "", true
} }
b := make([]u16, n+1, temp_allocator()) b := make([]u16, n+1, temp_allocator)
n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b))) n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b)))
if n == 0 { if n == 0 {
@@ -37,9 +37,9 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
} }
_set_env :: proc(key, value: string) -> Error { _set_env :: proc(key, value: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
k := win32_utf8_to_wstring(key, temp_allocator()) or_return k := win32_utf8_to_wstring(key, temp_allocator) or_return
v := win32_utf8_to_wstring(value, temp_allocator()) or_return v := win32_utf8_to_wstring(value, temp_allocator) or_return
if !win32.SetEnvironmentVariableW(k, v) { if !win32.SetEnvironmentVariableW(k, v) {
return _get_platform_error() return _get_platform_error()
@@ -48,14 +48,14 @@ _set_env :: proc(key, value: string) -> Error {
} }
_unset_env :: proc(key: string) -> bool { _unset_env :: proc(key: string) -> bool {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
k, _ := win32_utf8_to_wstring(key, temp_allocator()) k, _ := win32_utf8_to_wstring(key, temp_allocator)
return bool(win32.SetEnvironmentVariableW(k, nil)) return bool(win32.SetEnvironmentVariableW(k, nil))
} }
_clear_env :: proc() { _clear_env :: proc() {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
envs, _ := environ(temp_allocator()) envs, _ := environ(temp_allocator)
for env in envs { for env in envs {
for j in 1..<len(env) { for j in 1..<len(env) {
if env[j] == '=' { if env[j] == '=' {
+21 -16
View File
@@ -27,6 +27,9 @@ General_Error :: enum u32 {
Pattern_Has_Separator, Pattern_Has_Separator,
No_HOME_Variable,
Wordexp_Failed,
Unsupported, Unsupported,
} }
@@ -59,20 +62,22 @@ error_string :: proc(ferr: Error) -> string {
case General_Error: case General_Error:
switch e { switch e {
case .None: return "" case .None: return ""
case .Permission_Denied: return "permission denied" case .Permission_Denied: return "permission denied"
case .Exist: return "file already exists" case .Exist: return "file already exists"
case .Not_Exist: return "file does not exist" case .Not_Exist: return "file does not exist"
case .Closed: return "file already closed" case .Closed: return "file already closed"
case .Timeout: return "i/o timeout" case .Timeout: return "i/o timeout"
case .Broken_Pipe: return "Broken pipe" case .Broken_Pipe: return "Broken pipe"
case .No_Size: return "file has no definite size" case .No_Size: return "file has no definite size"
case .Invalid_File: return "invalid file" case .Invalid_File: return "invalid file"
case .Invalid_Dir: return "invalid directory" case .Invalid_Dir: return "invalid directory"
case .Invalid_Path: return "invalid path" case .Invalid_Path: return "invalid path"
case .Invalid_Callback: return "invalid callback" case .Invalid_Callback: return "invalid callback"
case .Invalid_Command: return "invalid command" case .Invalid_Command: return "invalid command"
case .Unsupported: return "unsupported" case .Unsupported: return "unsupported"
case .Pattern_Has_Separator: return "pattern has separator" case .Pattern_Has_Separator: return "pattern has separator"
case .No_HOME_Variable: return "no $HOME variable"
case .Wordexp_Failed: return "posix.wordexp was unable to expand"
} }
case io.Error: case io.Error:
switch e { switch e {
@@ -108,12 +113,12 @@ error_string :: proc(ferr: Error) -> string {
} }
print_error :: proc(f: ^File, ferr: Error, msg: string) { print_error :: proc(f: ^File, ferr: Error, msg: string) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
err_str := error_string(ferr) err_str := error_string(ferr)
// msg + ": " + err_str + '\n' // msg + ": " + err_str + '\n'
length := len(msg) + 2 + len(err_str) + 1 length := len(msg) + 2 + len(err_str) + 1
buf := make([]u8, length, temp_allocator()) buf := make([]u8, length, temp_allocator)
copy(buf, msg) copy(buf, msg)
buf[len(msg)] = ':' buf[len(msg)] = ':'
+13 -4
View File
@@ -291,8 +291,8 @@ exists :: proc(path: string) -> bool {
@(require_results) @(require_results)
is_file :: proc(path: string) -> bool { is_file :: proc(path: string) -> bool {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
fi, err := stat(path, temp_allocator()) fi, err := stat(path, temp_allocator)
if err != nil { if err != nil {
return false return false
} }
@@ -303,8 +303,8 @@ is_dir :: is_directory
@(require_results) @(require_results)
is_directory :: proc(path: string) -> bool { is_directory :: proc(path: string) -> bool {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
fi, err := stat(path, temp_allocator()) fi, err := stat(path, temp_allocator)
if err != nil { if err != nil {
return false return false
} }
@@ -313,6 +313,15 @@ is_directory :: proc(path: string) -> bool {
copy_file :: proc(dst_path, src_path: string) -> Error { copy_file :: proc(dst_path, src_path: string) -> Error {
when #defined(_copy_file_native) {
return _copy_file_native(dst_path, src_path)
} else {
return _copy_file(dst_path, src_path)
}
}
@(private)
_copy_file :: proc(dst_path, src_path: string) -> Error {
src := open(src_path) or_return src := open(src_path) or_return
defer close(src) defer close(src)
+29 -29
View File
@@ -66,8 +66,8 @@ _standard_stream_init :: proc() {
} }
_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) { _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
// Just default to using O_NOCTTY because needing to open a controlling // Just default to using O_NOCTTY because needing to open a controlling
// terminal would be incredibly rare. This has no effect on files while // terminal would be incredibly rare. This has no effect on files while
@@ -299,8 +299,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
} }
_remove :: proc(name: string) -> Error { _remove :: proc(name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
if fd, errno := linux.open(name_cstr, _OPENDIR_FLAGS + {.NOFOLLOW}); errno == .NONE { if fd, errno := linux.open(name_cstr, _OPENDIR_FLAGS + {.NOFOLLOW}); errno == .NONE {
linux.close(fd) linux.close(fd)
@@ -311,25 +311,25 @@ _remove :: proc(name: string) -> Error {
} }
_rename :: proc(old_name, new_name: string) -> Error { _rename :: proc(old_name, new_name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
old_name_cstr := temp_cstring(old_name) or_return old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return
new_name_cstr := temp_cstring(new_name) or_return new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return
return _get_platform_error(linux.rename(old_name_cstr, new_name_cstr)) return _get_platform_error(linux.rename(old_name_cstr, new_name_cstr))
} }
_link :: proc(old_name, new_name: string) -> Error { _link :: proc(old_name, new_name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
old_name_cstr := temp_cstring(old_name) or_return old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return
new_name_cstr := temp_cstring(new_name) or_return new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return
return _get_platform_error(linux.link(old_name_cstr, new_name_cstr)) return _get_platform_error(linux.link(old_name_cstr, new_name_cstr))
} }
_symlink :: proc(old_name, new_name: string) -> Error { _symlink :: proc(old_name, new_name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
old_name_cstr := temp_cstring(old_name) or_return old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return
new_name_cstr := temp_cstring(new_name) or_return new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return
return _get_platform_error(linux.symlink(old_name_cstr, new_name_cstr)) return _get_platform_error(linux.symlink(old_name_cstr, new_name_cstr))
} }
@@ -352,14 +352,14 @@ _read_link_cstr :: proc(name_cstr: cstring, allocator: runtime.Allocator) -> (st
} }
_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, e: Error) { _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, e: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _read_link_cstr(name_cstr, allocator) return _read_link_cstr(name_cstr, allocator)
} }
_chdir :: proc(name: string) -> Error { _chdir :: proc(name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _get_platform_error(linux.chdir(name_cstr)) return _get_platform_error(linux.chdir(name_cstr))
} }
@@ -369,8 +369,8 @@ _fchdir :: proc(f: ^File) -> Error {
} }
_chmod :: proc(name: string, mode: int) -> Error { _chmod :: proc(name: string, mode: int) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode)))) return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode))))
} }
@@ -381,15 +381,15 @@ _fchmod :: proc(f: ^File, mode: int) -> Error {
// NOTE: will throw error without super user priviledges // NOTE: will throw error without super user priviledges
_chown :: proc(name: string, uid, gid: int) -> Error { _chown :: proc(name: string, uid, gid: int) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _get_platform_error(linux.chown(name_cstr, linux.Uid(uid), linux.Gid(gid))) return _get_platform_error(linux.chown(name_cstr, linux.Uid(uid), linux.Gid(gid)))
} }
// NOTE: will throw error without super user priviledges // NOTE: will throw error without super user priviledges
_lchown :: proc(name: string, uid, gid: int) -> Error { _lchown :: proc(name: string, uid, gid: int) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _get_platform_error(linux.lchown(name_cstr, linux.Uid(uid), linux.Gid(gid))) return _get_platform_error(linux.lchown(name_cstr, linux.Uid(uid), linux.Gid(gid)))
} }
@@ -400,8 +400,8 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
} }
_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
times := [2]linux.Time_Spec { times := [2]linux.Time_Spec {
{ {
uint(atime._nsec) / uint(time.Second), uint(atime._nsec) / uint(time.Second),
@@ -431,8 +431,8 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
} }
_exists :: proc(name: string) -> bool { _exists :: proc(name: string) -> bool {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
name_cstr, _ := temp_cstring(name) name_cstr, _ := clone_to_cstring(name, temp_allocator)
return linux.access(name_cstr, linux.F_OK) == .NONE return linux.access(name_cstr, linux.F_OK) == .NONE
} }
@@ -440,8 +440,8 @@ _exists :: proc(name: string) -> bool {
_read_entire_pseudo_file :: proc { _read_entire_pseudo_file_string, _read_entire_pseudo_file_cstring } _read_entire_pseudo_file :: proc { _read_entire_pseudo_file_string, _read_entire_pseudo_file_cstring }
_read_entire_pseudo_file_string :: proc(name: string, allocator: runtime.Allocator) -> (b: []u8, e: Error) { _read_entire_pseudo_file_string :: proc(name: string, allocator: runtime.Allocator) -> (b: []u8, e: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
name_cstr := clone_to_cstring(name, temp_allocator()) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _read_entire_pseudo_file_cstring(name_cstr, allocator) return _read_entire_pseudo_file_cstring(name_cstr, allocator)
} }
+36 -35
View File
@@ -69,8 +69,8 @@ _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Err
if .Trunc in flags { sys_flags += {.TRUNC} } if .Trunc in flags { sys_flags += {.TRUNC} }
if .Inheritable in flags { sys_flags -= {.CLOEXEC} } if .Inheritable in flags { sys_flags -= {.CLOEXEC} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
fd := posix.open(cname, sys_flags, transmute(posix.mode_t)posix._mode_t(perm)) fd := posix.open(cname, sys_flags, transmute(posix.mode_t)posix._mode_t(perm))
if fd < 0 { if fd < 0 {
@@ -183,39 +183,39 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
return nil return nil
} }
_remove :: proc(name: string) -> Error { _remove :: proc(name: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
if posix.remove(cname) != 0 { if posix.remove(cname) != 0 {
return _get_platform_error() return _get_platform_error()
} }
return nil return nil
} }
_rename :: proc(old_path, new_path: string) -> Error { _rename :: proc(old_path, new_path: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cold := temp_cstring(old_path) cold := clone_to_cstring(old_path, temp_allocator) or_return
cnew := temp_cstring(new_path) cnew := clone_to_cstring(new_path, temp_allocator) or_return
if posix.rename(cold, cnew) != 0 { if posix.rename(cold, cnew) != 0 {
return _get_platform_error() return _get_platform_error()
} }
return nil return nil
} }
_link :: proc(old_name, new_name: string) -> Error { _link :: proc(old_name, new_name: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cold := temp_cstring(old_name) cold := clone_to_cstring(old_name, temp_allocator) or_return
cnew := temp_cstring(new_name) cnew := clone_to_cstring(new_name, temp_allocator) or_return
if posix.link(cold, cnew) != .OK { if posix.link(cold, cnew) != .OK {
return _get_platform_error() return _get_platform_error()
} }
return nil return nil
} }
_symlink :: proc(old_name, new_name: string) -> Error { _symlink :: proc(old_name, new_name: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cold := temp_cstring(old_name) cold := clone_to_cstring(old_name, temp_allocator) or_return
cnew := temp_cstring(new_name) cnew := clone_to_cstring(new_name, temp_allocator) or_return
if posix.symlink(cold, cnew) != .OK { if posix.symlink(cold, cnew) != .OK {
return _get_platform_error() return _get_platform_error()
} }
@@ -223,8 +223,8 @@ _symlink :: proc(old_name, new_name: string) -> Error {
} }
_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) { _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
buf: [dynamic]byte buf: [dynamic]byte
buf.allocator = allocator buf.allocator = allocator
@@ -268,9 +268,9 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
} }
} }
_chdir :: proc(name: string) -> Error { _chdir :: proc(name: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
if posix.chdir(cname) != .OK { if posix.chdir(cname) != .OK {
return _get_platform_error() return _get_platform_error()
} }
@@ -291,9 +291,9 @@ _fchmod :: proc(f: ^File, mode: int) -> Error {
return nil return nil
} }
_chmod :: proc(name: string, mode: int) -> Error { _chmod :: proc(name: string, mode: int) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
if posix.chmod(cname, transmute(posix.mode_t)posix._mode_t(mode)) != .OK { if posix.chmod(cname, transmute(posix.mode_t)posix._mode_t(mode)) != .OK {
return _get_platform_error() return _get_platform_error()
} }
@@ -307,9 +307,9 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
return nil return nil
} }
_chown :: proc(name: string, uid, gid: int) -> Error { _chown :: proc(name: string, uid, gid: int) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
if posix.chown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK { if posix.chown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK {
return _get_platform_error() return _get_platform_error()
} }
@@ -317,15 +317,15 @@ _chown :: proc(name: string, uid, gid: int) -> Error {
} }
_lchown :: proc(name: string, uid, gid: int) -> Error { _lchown :: proc(name: string, uid, gid: int) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
if posix.lchown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK { if posix.lchown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK {
return _get_platform_error() return _get_platform_error()
} }
return nil return nil
} }
_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { _chtimes :: proc(name: string, atime, mtime: time.Time) -> (err: Error) {
times := [2]posix.timeval{ times := [2]posix.timeval{
{ {
tv_sec = posix.time_t(atime._nsec/1e9), /* seconds */ tv_sec = posix.time_t(atime._nsec/1e9), /* seconds */
@@ -337,8 +337,8 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
}, },
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
if posix.utimes(cname, &times) != .OK { if posix.utimes(cname, &times) != .OK {
return _get_platform_error() return _get_platform_error()
@@ -365,8 +365,9 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
} }
_exists :: proc(path: string) -> bool { _exists :: proc(path: string) -> bool {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cpath := temp_cstring(path) cpath, err := clone_to_cstring(path, temp_allocator)
if err != nil { return false }
return posix.access(cpath) == .OK return posix.access(cpath) == .OK
} }
+28
View File
@@ -3,6 +3,7 @@ package os2
import "base:runtime" import "base:runtime"
import "core:sys/darwin"
import "core:sys/posix" import "core:sys/posix"
_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) {
@@ -16,3 +17,30 @@ _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allo
return clone_to_cstring(string(cstring(&buf[0])), allocator) return clone_to_cstring(string(cstring(&buf[0])), allocator)
} }
_copy_file_native :: proc(dst_path, src_path: string) -> (err: Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
csrc := clone_to_cstring(src_path, temp_allocator) or_return
cdst := clone_to_cstring(dst_path, temp_allocator) or_return
// Disallow directories, as specified by the generic implementation.
stat: posix.stat_t
if posix.stat(csrc, &stat) != .OK {
err = _get_platform_error()
return
}
if posix.S_ISDIR(stat.st_mode) {
err = .Invalid_File
return
}
ret := darwin.copyfile(csrc, cdst, nil, darwin.COPYFILE_ALL)
if ret < 0 {
err = _get_platform_error()
}
return
}
+2 -2
View File
@@ -7,8 +7,8 @@ import "base:runtime"
import "core:sys/posix" import "core:sys/posix"
_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator)
buf: [posix.PATH_MAX]byte buf: [posix.PATH_MAX]byte
path = posix.realpath(cname, raw_data(buf[:])) path = posix.realpath(cname, raw_data(buf[:]))
+1 -1
View File
@@ -59,7 +59,7 @@ write_encoded_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) {
if r < 32 { if r < 32 {
if wrap(write_string(f, "\\x"), &n, &err) { return } if wrap(write_string(f, "\\x"), &n, &err) { return }
b: [2]byte b: [2]byte
s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil) s := strconv.write_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil)
switch len(s) { switch len(s) {
case 0: if wrap(write_string(f, "00"), &n, &err) { return } case 0: if wrap(write_string(f, "00"), &n, &err) { return }
case 1: if wrap(write_rune(f, '0'), &n, &err) { return } case 1: if wrap(write_rune(f, '0'), &n, &err) { return }
+27 -34
View File
@@ -86,9 +86,9 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: u
err = .Not_Exist err = .Not_Exist
return return
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
path := _fix_long_path(name, temp_allocator()) or_return path := _fix_long_path(name, temp_allocator) or_return
access: u32 access: u32
switch flags & {.Read, .Write} { switch flags & {.Read, .Write} {
case {.Read}: access = win32.FILE_GENERIC_READ case {.Read}: access = win32.FILE_GENERIC_READ
@@ -508,11 +508,12 @@ _file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) {
length: win32.LARGE_INTEGER length: win32.LARGE_INTEGER
handle := _handle(&f.file) handle := _handle(&f.file)
if f.kind == .Pipe { if f.kind == .Pipe {
bytesAvail: u32 bytes_available: u32
if win32.PeekNamedPipe(handle, nil, 0, nil, &bytesAvail, nil) { if win32.PeekNamedPipe(handle, nil, 0, nil, &bytes_available, nil) {
return i64(bytesAvail), nil return i64(bytes_available), nil
} else { } else {
return 0, .No_Size err = _get_platform_error()
return
} }
} }
if !win32.GetFileSizeEx(handle, &length) { if !win32.GetFileSizeEx(handle, &length) {
@@ -556,8 +557,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
} }
_remove :: proc(name: string) -> Error { _remove :: proc(name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
p := _fix_long_path(name, temp_allocator()) or_return p := _fix_long_path(name, temp_allocator) or_return
err, err1: Error err, err1: Error
if !win32.DeleteFileW(p) { if !win32.DeleteFileW(p) {
err = _get_platform_error() err = _get_platform_error()
@@ -594,9 +595,9 @@ _remove :: proc(name: string) -> Error {
} }
_rename :: proc(old_path, new_path: string) -> Error { _rename :: proc(old_path, new_path: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
from := _fix_long_path(old_path, temp_allocator()) or_return from := _fix_long_path(old_path, temp_allocator) or_return
to := _fix_long_path(new_path, temp_allocator()) or_return to := _fix_long_path(new_path, temp_allocator) or_return
if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) { if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) {
return nil return nil
} }
@@ -605,9 +606,9 @@ _rename :: proc(old_path, new_path: string) -> Error {
} }
_link :: proc(old_name, new_name: string) -> Error { _link :: proc(old_name, new_name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
o := _fix_long_path(old_name, temp_allocator()) or_return o := _fix_long_path(old_name, temp_allocator) or_return
n := _fix_long_path(new_name, temp_allocator()) or_return n := _fix_long_path(new_name, temp_allocator) or_return
if win32.CreateHardLinkW(n, o, nil) { if win32.CreateHardLinkW(n, o, nil) {
return nil return nil
} }
@@ -668,9 +669,9 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st
return "", _get_platform_error() return "", _get_platform_error()
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buf := make([]u16, n+1, temp_allocator()) buf := make([]u16, n+1, temp_allocator)
n = win32.GetFinalPathNameByHandleW(handle, raw_data(buf), u32(len(buf)), win32.VOLUME_NAME_DOS) n = win32.GetFinalPathNameByHandleW(handle, raw_data(buf), u32(len(buf)), win32.VOLUME_NAME_DOS)
if n == 0 { if n == 0 {
return "", _get_platform_error() return "", _get_platform_error()
@@ -694,9 +695,9 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
@thread_local @thread_local
rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
p := _fix_long_path(name, temp_allocator()) or_return p := _fix_long_path(name, temp_allocator) or_return
handle := _open_sym_link(p) or_return handle := _open_sym_link(p) or_return
defer win32.CloseHandle(handle) defer win32.CloseHandle(handle)
@@ -771,8 +772,8 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
} }
_chdir :: proc(name: string) -> Error { _chdir :: proc(name: string) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
p := _fix_long_path(name, temp_allocator()) or_return p := _fix_long_path(name, temp_allocator) or_return
if !win32.SetCurrentDirectoryW(p) { if !win32.SetCurrentDirectoryW(p) {
return _get_platform_error() return _get_platform_error()
} }
@@ -799,19 +800,11 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
defer close(f) defer close(f)
return _fchtimes(f, atime, mtime) return _fchtimes(f, atime, mtime)
} }
_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
if f == nil || f.impl == nil { if f == nil || f.impl == nil {
return nil return nil
} }
d: win32.BY_HANDLE_FILE_INFORMATION
if !win32.GetFileInformationByHandle(_handle(f), &d) {
return _get_platform_error()
}
to_windows_time :: #force_inline proc(t: time.Time) -> win32.LARGE_INTEGER {
// a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC)
return win32.LARGE_INTEGER(time.time_to_unix_nano(t) * 100 + 116444736000000000)
}
atime, mtime := atime, mtime atime, mtime := atime, mtime
if time.time_to_unix_nano(atime) < time.time_to_unix_nano(mtime) { if time.time_to_unix_nano(atime) < time.time_to_unix_nano(mtime) {
@@ -819,17 +812,17 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
} }
info: win32.FILE_BASIC_INFO info: win32.FILE_BASIC_INFO
info.LastAccessTime = to_windows_time(atime) info.LastAccessTime = time_as_filetime(atime)
info.LastWriteTime = to_windows_time(mtime) info.LastWriteTime = time_as_filetime(mtime)
if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(d)) { if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(info)) {
return _get_platform_error() return _get_platform_error()
} }
return nil return nil
} }
_exists :: proc(path: string) -> bool { _exists :: proc(path: string) -> bool {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
wpath, _ := _fix_long_path(path, temp_allocator()) wpath, _ := _fix_long_path(path, temp_allocator)
attribs := win32.GetFileAttributesW(wpath) attribs := win32.GetFileAttributesW(wpath)
return attribs != win32.INVALID_FILE_ATTRIBUTES return attribs != win32.INVALID_FILE_ATTRIBUTES
} }
-5
View File
@@ -43,11 +43,6 @@ clone_to_cstring :: proc(s: string, allocator: runtime.Allocator) -> (res: cstri
return cstring(&buf[0]), nil return cstring(&buf[0]), nil
} }
@(require_results)
temp_cstring :: proc(s: string) -> (cstring, runtime.Allocator_Error) #optional_allocator_error {
return clone_to_cstring(s, temp_allocator())
}
@(require_results) @(require_results)
string_from_null_terminated_bytes :: proc(b: []byte) -> (res: string) { string_from_null_terminated_bytes :: proc(b: []byte) -> (res: string) {
s := string(b) s := string(b)
+4 -4
View File
@@ -119,11 +119,11 @@ clean_path :: proc(path: string, allocator: runtime.Allocator) -> (cleaned: stri
return strings.clone(".", allocator) return strings.clone(".", allocator)
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
// The extra byte is to simplify appending path elements by letting the // The extra byte is to simplify appending path elements by letting the
// loop to end each with a separator. We'll trim the last one when we're done. // loop to end each with a separator. We'll trim the last one when we're done.
buffer := make([]u8, len(path) + 1, temp_allocator()) or_return buffer := make([]u8, len(path) + 1, temp_allocator) or_return
// This is the only point where Windows and POSIX differ, as Windows has // This is the only point where Windows and POSIX differ, as Windows has
// alphabet-based volumes for root paths. // alphabet-based volumes for root paths.
@@ -326,8 +326,8 @@ For example, `join_path({"/home", "foo", "bar.txt"})` will result in `"/home/foo
join_path :: proc(elems: []string, allocator: runtime.Allocator) -> (joined: string, err: Error) { join_path :: proc(elems: []string, allocator: runtime.Allocator) -> (joined: string, err: Error) {
for e, i in elems { for e, i in elems {
if e != "" { if e != "" {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
p := strings.join(elems[i:], Path_Separator_String, temp_allocator()) or_return p := strings.join(elems[i:], Path_Separator_String, temp_allocator) or_return
return clean_path(p, allocator) return clean_path(p, allocator)
} }
} }
+29 -9
View File
@@ -18,8 +18,8 @@ _is_path_separator :: proc(c: byte) -> bool {
} }
_mkdir :: proc(path: string, perm: int) -> Error { _mkdir :: proc(path: string, perm: int) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
path_cstr := temp_cstring(path) or_return path_cstr := clone_to_cstring(path, temp_allocator) or_return
return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)u32(perm))) return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)u32(perm)))
} }
@@ -52,9 +52,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
} }
return _get_platform_error(errno) return _get_platform_error(errno)
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
// need something we can edit, and use to generate cstrings // need something we can edit, and use to generate cstrings
path_bytes := make([]u8, len(path) + 1, temp_allocator()) path_bytes := make([]u8, len(path) + 1, temp_allocator)
// zero terminate the byte slice to make it a valid cstring // zero terminate the byte slice to make it a valid cstring
copy(path_bytes, path) copy(path_bytes, path)
@@ -129,8 +129,8 @@ _remove_all :: proc(path: string) -> Error {
return nil return nil
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
path_cstr := temp_cstring(path) or_return path_cstr := clone_to_cstring(path, temp_allocator) or_return
fd, errno := linux.open(path_cstr, _OPENDIR_FLAGS) fd, errno := linux.open(path_cstr, _OPENDIR_FLAGS)
#partial switch errno { #partial switch errno {
@@ -168,14 +168,16 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (string, Error)
} }
_set_working_directory :: proc(dir: string) -> Error { _set_working_directory :: proc(dir: string) -> Error {
dir_cstr := temp_cstring(dir) or_return temp_allocator := TEMP_ALLOCATOR_GUARD({})
dir_cstr := clone_to_cstring(dir, temp_allocator) or_return
return _get_platform_error(linux.chdir(dir_cstr)) return _get_platform_error(linux.chdir(dir_cstr))
} }
_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buf := make([dynamic]byte, 1024, temp_allocator()) or_return buf := make([dynamic]byte, 1024, temp_allocator) or_return
for { for {
n, errno := linux.readlink("/proc/self/exe", buf[:]) n, errno := linux.readlink("/proc/self/exe", buf[:])
if errno != .NONE { if errno != .NONE {
@@ -205,3 +207,21 @@ _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath:
} }
return return
} }
_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
rel := path
if rel == "" {
rel = "."
}
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
fd, errno := linux.open(clone_to_cstring(path, temp_allocator) or_return, {})
if errno != nil {
err = _get_platform_error(errno)
return
}
defer linux.close(fd)
return _get_full_path(fd, allocator)
}
+2 -2
View File
@@ -5,9 +5,9 @@ import "base:runtime"
import "core:sys/posix" import "core:sys/posix"
_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buf := make([dynamic]byte, 1024, temp_allocator()) or_return buf := make([dynamic]byte, 1024, temp_allocator) or_return
for { for {
n := posix.readlink("/proc/curproc/exe", raw_data(buf), len(buf)) n := posix.readlink("/proc/curproc/exe", raw_data(buf), len(buf))
if n < 0 { if n < 0 {
+3 -3
View File
@@ -35,11 +35,11 @@ _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err
return real(arg, allocator) return real(arg, allocator)
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buf := strings.builder_make(temp_allocator()) buf := strings.builder_make(temp_allocator)
paths := get_env("PATH", temp_allocator()) paths := get_env("PATH", temp_allocator)
for dir in strings.split_iterator(&paths, ":") { for dir in strings.split_iterator(&paths, ":") {
strings.builder_reset(&buf) strings.builder_reset(&buf)
strings.write_string(&buf, dir) strings.write_string(&buf, dir)
+30 -13
View File
@@ -14,9 +14,9 @@ _is_path_separator :: proc(c: byte) -> bool {
return c == _Path_Separator return c == _Path_Separator
} }
_mkdir :: proc(name: string, perm: int) -> Error { _mkdir :: proc(name: string, perm: int) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cname := temp_cstring(name) cname := clone_to_cstring(name, temp_allocator) or_return
if posix.mkdir(cname, transmute(posix.mode_t)posix._mode_t(perm)) != .OK { if posix.mkdir(cname, transmute(posix.mode_t)posix._mode_t(perm)) != .OK {
return _get_platform_error() return _get_platform_error()
} }
@@ -28,13 +28,13 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
return .Invalid_Path return .Invalid_Path
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
if exists(path) { if exists(path) {
return .Exist return .Exist
} }
clean_path := clean_path(path, temp_allocator()) or_return clean_path := clean_path(path, temp_allocator) or_return
return internal_mkdir_all(clean_path, perm) return internal_mkdir_all(clean_path, perm)
internal_mkdir_all :: proc(path: string, perm: int) -> Error { internal_mkdir_all :: proc(path: string, perm: int) -> Error {
@@ -52,9 +52,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
} }
} }
_remove_all :: proc(path: string) -> Error { _remove_all :: proc(path: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cpath := temp_cstring(path) cpath := clone_to_cstring(path, temp_allocator) or_return
dir := posix.opendir(cpath) dir := posix.opendir(cpath)
if dir == nil { if dir == nil {
@@ -78,7 +78,7 @@ _remove_all :: proc(path: string) -> Error {
continue continue
} }
fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator()) fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator)
if entry.d_type == .DIR { if entry.d_type == .DIR {
_remove_all(fullpath[:len(fullpath)-1]) or_return _remove_all(fullpath[:len(fullpath)-1]) or_return
} else { } else {
@@ -95,10 +95,10 @@ _remove_all :: proc(path: string) -> Error {
} }
_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buf: [dynamic]byte buf: [dynamic]byte
buf.allocator = temp_allocator() buf.allocator = temp_allocator
size := uint(posix.PATH_MAX) size := uint(posix.PATH_MAX)
cwd: cstring cwd: cstring
@@ -116,10 +116,27 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, er
} }
_set_working_directory :: proc(dir: string) -> (err: Error) { _set_working_directory :: proc(dir: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
cdir := temp_cstring(dir) cdir := clone_to_cstring(dir, temp_allocator) or_return
if posix.chdir(cdir) != .OK { if posix.chdir(cdir) != .OK {
err = _get_platform_error() err = _get_platform_error()
} }
return return
} }
_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
rel := path
if rel == "" {
rel = "."
}
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
rel_cstr := clone_to_cstring(rel, temp_allocator) or_return
path_ptr := posix.realpath(rel_cstr, nil)
if path_ptr == nil {
return "", Platform_Error(posix.errno())
}
defer posix.free(path_ptr)
path_str := clone_string(string(path_ptr), allocator) or_return
return path_str, nil
}
-21
View File
@@ -4,10 +4,6 @@ package os2
// This implementation is for all systems that have POSIX-compliant filesystem paths. // This implementation is for all systems that have POSIX-compliant filesystem paths.
import "base:runtime"
import "core:strings"
import "core:sys/posix"
_are_paths_identical :: proc(a, b: string) -> (identical: bool) { _are_paths_identical :: proc(a, b: string) -> (identical: bool) {
return a == b return a == b
} }
@@ -26,23 +22,6 @@ _is_absolute_path :: proc(path: string) -> bool {
return len(path) > 0 && _is_path_separator(path[0]) return len(path) > 0 && _is_path_separator(path[0])
} }
_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
rel := path
if rel == "" {
rel = "."
}
TEMP_ALLOCATOR_GUARD()
rel_cstr := strings.clone_to_cstring(rel, temp_allocator())
path_ptr := posix.realpath(rel_cstr, nil)
if path_ptr == nil {
return "", Platform_Error(posix.errno())
}
defer posix.free(path_ptr)
path_str := strings.clone(string(path_ptr), allocator)
return path_str, nil
}
_get_relative_path_handle_start :: proc(base, target: string) -> bool { _get_relative_path_handle_start :: proc(base, target: string) -> bool {
base_rooted := len(base) > 0 && _is_path_separator(base[0]) base_rooted := len(base) > 0 && _is_path_separator(base[0])
target_rooted := len(target) > 0 && _is_path_separator(target[0]) target_rooted := len(target) > 0 && _is_path_separator(target[0])
+2 -2
View File
@@ -28,13 +28,13 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
return .Invalid_Path return .Invalid_Path
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
if exists(path) { if exists(path) {
return .Exist return .Exist
} }
clean_path := clean_path(path, temp_allocator()) clean_path := clean_path(path, temp_allocator)
return internal_mkdir_all(clean_path) return internal_mkdir_all(clean_path)
internal_mkdir_all :: proc(path: string) -> Error { internal_mkdir_all :: proc(path: string) -> Error {
+23 -19
View File
@@ -14,8 +14,8 @@ _is_path_separator :: proc(c: byte) -> bool {
} }
_mkdir :: proc(name: string, perm: int) -> Error { _mkdir :: proc(name: string, perm: int) -> Error {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator()) or_return, nil) { if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator) or_return, nil) {
return _get_platform_error() return _get_platform_error()
} }
return nil return nil
@@ -33,9 +33,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
return p, false, nil return p, false, nil
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
dir_stat, err := stat(path, temp_allocator()) dir_stat, err := stat(path, temp_allocator)
if err == nil { if err == nil {
if dir_stat.type == .Directory { if dir_stat.type == .Directory {
return nil return nil
@@ -63,7 +63,7 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
err = mkdir(path, perm) err = mkdir(path, perm)
if err != nil { if err != nil {
new_dir_stat, err1 := lstat(path, temp_allocator()) new_dir_stat, err1 := lstat(path, temp_allocator)
if err1 == nil && new_dir_stat.type == .Directory { if err1 == nil && new_dir_stat.type == .Directory {
return nil return nil
} }
@@ -82,8 +82,8 @@ _remove_all :: proc(path: string) -> Error {
return nil return nil
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
dir := win32_utf8_to_wstring(path, temp_allocator()) or_return dir := win32_utf8_to_wstring(path, temp_allocator) or_return
empty: [1]u16 empty: [1]u16
@@ -109,10 +109,10 @@ _remove_all :: proc(path: string) -> Error {
_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
win32.AcquireSRWLockExclusive(&cwd_lock) win32.AcquireSRWLockExclusive(&cwd_lock)
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
sz_utf16 := win32.GetCurrentDirectoryW(0, nil) sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
dir_buf_wstr := make([]u16, sz_utf16, temp_allocator()) or_return dir_buf_wstr := make([]u16, sz_utf16, temp_allocator) or_return
sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr)) sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr))
assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL. assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL.
@@ -123,8 +123,8 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, er
} }
_set_working_directory :: proc(dir: string) -> (err: Error) { _set_working_directory :: proc(dir: string) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
wstr := win32_utf8_to_wstring(dir, temp_allocator()) or_return wstr := win32_utf8_to_wstring(dir, temp_allocator) or_return
win32.AcquireSRWLockExclusive(&cwd_lock) win32.AcquireSRWLockExclusive(&cwd_lock)
@@ -138,9 +138,9 @@ _set_working_directory :: proc(dir: string) -> (err: Error) {
} }
_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buf := make([dynamic]u16, 512, temp_allocator()) or_return buf := make([dynamic]u16, 512, temp_allocator) or_return
for { for {
ret := win32.GetModuleFileNameW(nil, raw_data(buf), win32.DWORD(len(buf))) ret := win32.GetModuleFileNameW(nil, raw_data(buf), win32.DWORD(len(buf)))
if ret == 0 { if ret == 0 {
@@ -187,7 +187,6 @@ init_long_path_support :: proc() {
if value == 1 { if value == 1 {
can_use_long_paths = true can_use_long_paths = true
} }
} }
@(require_results) @(require_results)
@@ -222,10 +221,10 @@ _fix_long_path_internal :: proc(path: string) -> string {
return path return path
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
PREFIX :: `\\?` PREFIX :: `\\?`
path_buf := make([]byte, len(PREFIX)+len(path)+1, temp_allocator()) path_buf := make([]byte, len(PREFIX)+len(path)+1, temp_allocator)
copy(path_buf, PREFIX) copy(path_buf, PREFIX)
n := len(path) n := len(path)
r, w := 0, len(PREFIX) r, w := 0, len(PREFIX)
@@ -271,6 +270,11 @@ _clean_path_handle_start :: proc(path: string, buffer: []u8) -> (rooted: bool, s
start += 1 start += 1
} }
copy(buffer, path[:start]) copy(buffer, path[:start])
for n in 0..<start {
if _is_path_separator(buffer[n]) {
buffer[n] = _Path_Separator
}
}
} }
return return
} }
@@ -297,14 +301,14 @@ _get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absol
if rel == "" { if rel == "" {
rel = "." rel = "."
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
rel_utf16 := win32.utf8_to_utf16(rel, temp_allocator()) rel_utf16 := win32.utf8_to_utf16(rel, temp_allocator)
n := win32.GetFullPathNameW(raw_data(rel_utf16), 0, nil, nil) n := win32.GetFullPathNameW(raw_data(rel_utf16), 0, nil, nil)
if n == 0 { if n == 0 {
return "", Platform_Error(win32.GetLastError()) return "", Platform_Error(win32.GetLastError())
} }
buf := make([]u16, n, temp_allocator()) or_return buf := make([]u16, n, temp_allocator) or_return
n = win32.GetFullPathNameW(raw_data(rel_utf16), u32(n), raw_data(buf), nil) n = win32.GetFullPathNameW(raw_data(rel_utf16), u32(n), raw_data(buf), nil)
if n == 0 { if n == 0 {
return "", Platform_Error(win32.GetLastError()) return "", Platform_Error(win32.GetLastError())
+1 -12
View File
@@ -264,7 +264,7 @@ specific process, even after it has died.
**Note(linux)**: The `handle` will be referring to pidfd. **Note(linux)**: The `handle` will be referring to pidfd.
*/ */
Process :: struct { Process :: struct {
pid: int, pid: int,
handle: uintptr, handle: uintptr,
} }
@@ -290,21 +290,10 @@ process_open :: proc(pid: int, flags := Process_Open_Flags {}) -> (Process, Erro
return _process_open(pid, flags) return _process_open(pid, flags)
} }
/*
OS-specific process attributes.
*/
Process_Attributes :: struct {
sys_attr: _Sys_Process_Attributes,
}
/* /*
The description of how a process should be created. The description of how a process should be created.
*/ */
Process_Desc :: struct { Process_Desc :: struct {
// OS-specific attributes.
sys_attr: Process_Attributes,
// The working directory of the process. If the string has length 0, the // The working directory of the process. If the string has length 0, the
// working directory is assumed to be the current working directory of the // working directory is assumed to be the current working directory of the
// current process. // current process.
+50 -41
View File
@@ -50,7 +50,7 @@ _get_ppid :: proc() -> int {
@(private="package") @(private="package")
_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
dir_fd, errno := linux.open("/proc/", _OPENDIR_FLAGS) dir_fd, errno := linux.open("/proc/", _OPENDIR_FLAGS)
#partial switch errno { #partial switch errno {
@@ -68,9 +68,9 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error)
} }
defer linux.close(dir_fd) defer linux.close(dir_fd)
dynamic_list := make([dynamic]int, temp_allocator()) or_return dynamic_list := make([dynamic]int, temp_allocator) or_return
buf := make([dynamic]u8, 128, 128, temp_allocator()) or_return buf := make([dynamic]u8, 128, 128, temp_allocator) or_return
loop: for { loop: for {
buflen: int buflen: int
buflen, errno = linux.getdents(dir_fd, buf[:]) buflen, errno = linux.getdents(dir_fd, buf[:])
@@ -100,7 +100,7 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error)
@(private="package") @(private="package")
_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
info.pid = pid info.pid = pid
@@ -126,7 +126,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
passwd_bytes: []u8 passwd_bytes: []u8
passwd_err: Error passwd_err: Error
passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator()) passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator)
if passwd_err != nil { if passwd_err != nil {
err = passwd_err err = passwd_err
break username_if break username_if
@@ -162,13 +162,13 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
} }
} }
cmdline_if: if selection & {.Working_Dir, .Command_Line, .Command_Args, .Executable_Path} != {} { cmdline_if: if selection & {.Working_Dir, .Command_Line, .Command_Args} != {} {
strings.builder_reset(&path_builder) strings.builder_reset(&path_builder)
strings.write_string(&path_builder, "/proc/") strings.write_string(&path_builder, "/proc/")
strings.write_int(&path_builder, pid) strings.write_int(&path_builder, pid)
strings.write_string(&path_builder, "/cmdline") strings.write_string(&path_builder, "/cmdline")
cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator()) cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
if cmdline_err != nil || len(cmdline_bytes) == 0 { if cmdline_err != nil || len(cmdline_bytes) == 0 {
err = cmdline_err err = cmdline_err
break cmdline_if break cmdline_if
@@ -178,18 +178,18 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
terminator := strings.index_byte(cmdline, 0) terminator := strings.index_byte(cmdline, 0)
assert(terminator > 0) assert(terminator > 0)
command_line_exec := cmdline[:terminator] // command_line_exec := cmdline[:terminator]
// Still need cwd if the execution on the command line is relative. // Still need cwd if the execution on the command line is relative.
cwd: string cwd: string
cwd_err: Error cwd_err: Error
if .Working_Dir in selection || (.Executable_Path in selection && command_line_exec[0] != '/') { if .Working_Dir in selection {
strings.builder_reset(&path_builder) strings.builder_reset(&path_builder)
strings.write_string(&path_builder, "/proc/") strings.write_string(&path_builder, "/proc/")
strings.write_int(&path_builder, pid) strings.write_int(&path_builder, pid)
strings.write_string(&path_builder, "/cwd") strings.write_string(&path_builder, "/cwd")
cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder) or_return, temp_allocator()) // allowed to fail cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder) or_return, temp_allocator) // allowed to fail
if cwd_err == nil && .Working_Dir in selection { if cwd_err == nil && .Working_Dir in selection {
info.working_dir = strings.clone(cwd, allocator) or_return info.working_dir = strings.clone(cwd, allocator) or_return
info.fields += {.Working_Dir} info.fields += {.Working_Dir}
@@ -199,18 +199,6 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
} }
} }
if .Executable_Path in selection {
if cmdline[0] == '/' {
info.executable_path = strings.clone(cmdline[:terminator], allocator) or_return
info.fields += {.Executable_Path}
} else if cwd_err == nil {
info.executable_path = join_path({ cwd, cmdline[:terminator] }, allocator) or_return
info.fields += {.Executable_Path}
} else {
break cmdline_if
}
}
if selection & {.Command_Line, .Command_Args} != {} { if selection & {.Command_Line, .Command_Args} != {} {
// skip to first arg // skip to first arg
//cmdline = cmdline[terminator + 1:] //cmdline = cmdline[terminator + 1:]
@@ -257,7 +245,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
strings.write_int(&path_builder, pid) strings.write_int(&path_builder, pid)
strings.write_string(&path_builder, "/stat") strings.write_string(&path_builder, "/stat")
proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator()) proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
if stat_err != nil { if stat_err != nil {
err = stat_err err = stat_err
break stat_if break stat_if
@@ -296,7 +284,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
Nice, Nice,
//... etc, //... etc,
} }
stat_fields := strings.split(stats, " ", temp_allocator()) or_return stat_fields := strings.split(stats, " ", temp_allocator) or_return
if len(stat_fields) <= int(Fields.Nice) { if len(stat_fields) <= int(Fields.Nice) {
break stat_if break stat_if
@@ -323,13 +311,37 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
} }
} }
if .Executable_Path in selection {
/*
NOTE(Jeroen):
The old version returned the wrong executable path for things like `bash` or `sh`,
for whom `/proc/<pid>/cmdline` will just report "bash" or "sh",
resulting in misleading paths like `$PWD/sh`, even though that executable doesn't exist there.
Thanks to Yawning for suggesting `/proc/self/exe`.
*/
strings.builder_reset(&path_builder)
strings.write_string(&path_builder, "/proc/")
strings.write_int(&path_builder, pid)
strings.write_string(&path_builder, "/exe")
if exe_bytes, exe_err := _read_link(strings.to_string(path_builder), temp_allocator); exe_err == nil {
info.executable_path = strings.clone(string(exe_bytes), allocator) or_return
info.fields += {.Executable_Path}
} else {
err = exe_err
}
}
if .Environment in selection { if .Environment in selection {
strings.builder_reset(&path_builder) strings.builder_reset(&path_builder)
strings.write_string(&path_builder, "/proc/") strings.write_string(&path_builder, "/proc/")
strings.write_int(&path_builder, pid) strings.write_int(&path_builder, pid)
strings.write_string(&path_builder, "/environ") strings.write_string(&path_builder, "/environ")
if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator()); env_err == nil { if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator); env_err == nil {
env := string(env_bytes) env := string(env_bytes)
env_list := make([dynamic]string, allocator) or_return env_list := make([dynamic]string, allocator) or_return
@@ -378,12 +390,9 @@ _process_open :: proc(pid: int, _: Process_Open_Flags) -> (process: Process, err
return return
} }
@(private="package")
_Sys_Process_Attributes :: struct {}
@(private="package") @(private="package")
_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
if len(desc.command) == 0 { if len(desc.command) == 0 {
return process, .Invalid_Command return process, .Invalid_Command
@@ -392,7 +401,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
dir_fd := linux.AT_FDCWD dir_fd := linux.AT_FDCWD
errno: linux.Errno errno: linux.Errno
if desc.working_dir != "" { if desc.working_dir != "" {
dir_cstr := temp_cstring(desc.working_dir) or_return dir_cstr := clone_to_cstring(desc.working_dir, temp_allocator) or_return
if dir_fd, errno = linux.open(dir_cstr, _OPENDIR_FLAGS); errno != .NONE { if dir_fd, errno = linux.open(dir_cstr, _OPENDIR_FLAGS); errno != .NONE {
return process, _get_platform_error(errno) return process, _get_platform_error(errno)
} }
@@ -405,10 +414,10 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
exe_path: cstring exe_path: cstring
executable_name := desc.command[0] executable_name := desc.command[0]
if strings.index_byte(executable_name, '/') < 0 { if strings.index_byte(executable_name, '/') < 0 {
path_env := get_env("PATH", temp_allocator()) path_env := get_env("PATH", temp_allocator)
path_dirs := split_path_list(path_env, temp_allocator()) or_return path_dirs := split_path_list(path_env, temp_allocator) or_return
exe_builder := strings.builder_make(temp_allocator()) or_return exe_builder := strings.builder_make(temp_allocator) or_return
found: bool found: bool
for dir in path_dirs { for dir in path_dirs {
@@ -435,7 +444,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
} }
} }
} else { } else {
exe_path = temp_cstring(executable_name) or_return exe_path = clone_to_cstring(executable_name, temp_allocator) or_return
if linux.access(exe_path, linux.X_OK) != .NONE { if linux.access(exe_path, linux.X_OK) != .NONE {
return process, .Not_Exist return process, .Not_Exist
} }
@@ -443,20 +452,20 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
// args and environment need to be a list of cstrings // args and environment need to be a list of cstrings
// that are terminated by a nil pointer. // that are terminated by a nil pointer.
cargs := make([]cstring, len(desc.command) + 1, temp_allocator()) or_return cargs := make([]cstring, len(desc.command) + 1, temp_allocator) or_return
for command, i in desc.command { for command, i in desc.command {
cargs[i] = temp_cstring(command) or_return cargs[i] = clone_to_cstring(command, temp_allocator) or_return
} }
// Use current process' environment if description didn't provide it. // Use current process' environment if description didn't provide it.
env: [^]cstring env: [^]cstring
if desc.env == nil { if desc.env == nil {
// take this process's current environment // take this process's current environment
env = raw_data(export_cstring_environment(temp_allocator())) env = raw_data(export_cstring_environment(temp_allocator))
} else { } else {
cenv := make([]cstring, len(desc.env) + 1, temp_allocator()) or_return cenv := make([]cstring, len(desc.env) + 1, temp_allocator) or_return
for env, i in desc.env { for env, i in desc.env {
cenv[i] = temp_cstring(env) or_return cenv[i] = clone_to_cstring(env, temp_allocator) or_return
} }
env = &cenv[0] env = &cenv[0]
} }
@@ -584,7 +593,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
} }
_process_state_update_times :: proc(state: ^Process_State) -> (err: Error) { _process_state_update_times :: proc(state: ^Process_State) -> (err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
stat_path_buf: [48]u8 stat_path_buf: [48]u8
path_builder := strings.builder_from_bytes(stat_path_buf[:]) path_builder := strings.builder_from_bytes(stat_path_buf[:])
@@ -593,7 +602,7 @@ _process_state_update_times :: proc(state: ^Process_State) -> (err: Error) {
strings.write_string(&path_builder, "/stat") strings.write_string(&path_builder, "/stat")
stat_buf: []u8 stat_buf: []u8
stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator()) stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
if err != nil { if err != nil {
return return
} }
+9 -11
View File
@@ -46,22 +46,20 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime
return _process_info_by_pid(_get_pid(), selection, allocator) return _process_info_by_pid(_get_pid(), selection, allocator)
} }
_Sys_Process_Attributes :: struct {}
_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
if len(desc.command) == 0 { if len(desc.command) == 0 {
err = .Invalid_Path err = .Invalid_Path
return return
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
// search PATH if just a plain name is provided. // search PATH if just a plain name is provided.
exe_builder := strings.builder_make(temp_allocator()) exe_builder := strings.builder_make(temp_allocator)
exe_name := desc.command[0] exe_name := desc.command[0]
if strings.index_byte(exe_name, '/') < 0 { if strings.index_byte(exe_name, '/') < 0 {
path_env := get_env("PATH", temp_allocator()) path_env := get_env("PATH", temp_allocator)
path_dirs := split_path_list(path_env, temp_allocator()) or_return path_dirs := split_path_list(path_env, temp_allocator) or_return
found: bool found: bool
for dir in path_dirs { for dir in path_dirs {
@@ -110,12 +108,12 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
} }
cwd: cstring; if desc.working_dir != "" { cwd: cstring; if desc.working_dir != "" {
cwd = temp_cstring(desc.working_dir) cwd = clone_to_cstring(desc.working_dir, temp_allocator) or_return
} }
cmd := make([]cstring, len(desc.command) + 1, temp_allocator()) cmd := make([]cstring, len(desc.command) + 1, temp_allocator)
for part, i in desc.command { for part, i in desc.command {
cmd[i] = temp_cstring(part) cmd[i] = clone_to_cstring(part, temp_allocator) or_return
} }
env: [^]cstring env: [^]cstring
@@ -123,9 +121,9 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
// take this process's current environment // take this process's current environment
env = posix.environ env = posix.environ
} else { } else {
cenv := make([]cstring, len(desc.env) + 1, temp_allocator()) cenv := make([]cstring, len(desc.env) + 1, temp_allocator)
for env, i in desc.env { for env, i in desc.env {
cenv[i] = temp_cstring(env) cenv[i] = clone_to_cstring(env, temp_allocator) or_return
} }
env = raw_data(cenv) env = raw_data(cenv)
} }
+4 -3
View File
@@ -50,6 +50,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
} }
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
info.pid = pid info.pid = pid
// Thought on errors is: allocation failures return immediately (also why the non-allocation stuff is done first), // Thought on errors is: allocation failures return immediately (also why the non-allocation stuff is done first),
@@ -127,7 +128,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
break args break args
} }
buf := runtime.make_aligned([]byte, length, 4, temp_allocator()) buf := runtime.make_aligned([]byte, length, 4, temp_allocator)
if sysctl(raw_data(mib), 3, raw_data(buf), &length, nil, 0) != .OK { if sysctl(raw_data(mib), 3, raw_data(buf), &length, nil, 0) != .OK {
if err == nil { if err == nil {
err = _get_platform_error() err = _get_platform_error()
@@ -239,9 +240,9 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error)
return return
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buffer := make([]i32, ret, temp_allocator()) buffer := make([]i32, ret, temp_allocator)
ret = darwin.proc_listallpids(raw_data(buffer), ret*size_of(i32)) ret = darwin.proc_listallpids(raw_data(buffer), ret*size_of(i32))
if ret < 0 { if ret < 0 {
err = _get_platform_error() err = _get_platform_error()
-2
View File
@@ -44,8 +44,6 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime
return return
} }
_Sys_Process_Attributes :: struct {}
_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
err = .Unsupported err = .Unsupported
return return
+63 -32
View File
@@ -162,9 +162,10 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
if err != nil { if err != nil {
break read_peb break read_peb
} }
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
if selection >= {.Command_Line, .Command_Args} { if selection >= {.Command_Line, .Command_Args} {
TEMP_ALLOCATOR_GUARD() temp_allocator_scope(temp_allocator)
cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) _, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
if err != nil { if err != nil {
break read_peb break read_peb
@@ -179,9 +180,9 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
} }
} }
if .Environment in selection { if .Environment in selection {
TEMP_ALLOCATOR_GUARD() temp_allocator_scope(temp_allocator)
env_len := process_params.EnvironmentSize / 2 env_len := process_params.EnvironmentSize / 2
envs_w := make([]u16, env_len, temp_allocator()) or_return envs_w := make([]u16, env_len, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.Environment, envs_w) _, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
if err != nil { if err != nil {
break read_peb break read_peb
@@ -190,8 +191,8 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
info.fields += {.Environment} info.fields += {.Environment}
} }
if .Working_Dir in selection { if .Working_Dir in selection {
TEMP_ALLOCATOR_GUARD() temp_allocator_scope(temp_allocator)
cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) _, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
if err != nil { if err != nil {
break read_peb break read_peb
@@ -272,9 +273,10 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
if err != nil { if err != nil {
break read_peb break read_peb
} }
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
if selection >= {.Command_Line, .Command_Args} { if selection >= {.Command_Line, .Command_Args} {
TEMP_ALLOCATOR_GUARD() temp_allocator_scope(temp_allocator)
cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) _, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
if err != nil { if err != nil {
break read_peb break read_peb
@@ -289,9 +291,9 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
} }
} }
if .Environment in selection { if .Environment in selection {
TEMP_ALLOCATOR_GUARD() temp_allocator_scope(temp_allocator)
env_len := process_params.EnvironmentSize / 2 env_len := process_params.EnvironmentSize / 2
envs_w := make([]u16, env_len, temp_allocator()) or_return envs_w := make([]u16, env_len, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.Environment, envs_w) _, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
if err != nil { if err != nil {
break read_peb break read_peb
@@ -300,8 +302,8 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
info.fields += {.Environment} info.fields += {.Environment}
} }
if .Working_Dir in selection { if .Working_Dir in selection {
TEMP_ALLOCATOR_GUARD() temp_allocator_scope(temp_allocator)
cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) _, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
if err != nil { if err != nil {
break read_peb break read_peb
@@ -417,35 +419,64 @@ _process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process,
return return
} }
@(private="package")
_Sys_Process_Attributes :: struct {}
@(private="package") @(private="package")
_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
command_line := _build_command_line(desc.command, temp_allocator()) command_line := _build_command_line(desc.command, temp_allocator)
command_line_w := win32_utf8_to_wstring(command_line, temp_allocator()) or_return command_line_w := win32_utf8_to_wstring(command_line, temp_allocator) or_return
environment := desc.env environment := desc.env
if desc.env == nil { if desc.env == nil {
environment = environ(temp_allocator()) or_return environment = environ(temp_allocator) or_return
} }
environment_block := _build_environment_block(environment, temp_allocator()) environment_block := _build_environment_block(environment, temp_allocator)
environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator()) or_return environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator) or_return
stderr_handle := win32.GetStdHandle(win32.STD_ERROR_HANDLE)
stdout_handle := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE)
stdin_handle := win32.GetStdHandle(win32.STD_INPUT_HANDLE)
if desc.stdout != nil { stderr_handle: win32.HANDLE
stdout_handle: win32.HANDLE
stdin_handle: win32.HANDLE
null_handle: win32.HANDLE
if desc.stdout == nil || desc.stderr == nil || desc.stdin == nil {
null_handle = win32.CreateFileW(
win32.L("NUL"),
win32.GENERIC_READ|win32.GENERIC_WRITE,
win32.FILE_SHARE_READ|win32.FILE_SHARE_WRITE,
&win32.SECURITY_ATTRIBUTES{
nLength = size_of(win32.SECURITY_ATTRIBUTES),
bInheritHandle = true,
},
win32.OPEN_EXISTING,
win32.FILE_ATTRIBUTE_NORMAL,
nil,
)
// Opening NUL should always succeed.
assert(null_handle != nil)
}
// NOTE(laytan): I believe it is fine to close this handle right after CreateProcess,
// and we don't have to hold onto this until the process exits.
defer if null_handle != nil {
win32.CloseHandle(null_handle)
}
if desc.stdout == nil {
stdout_handle = null_handle
} else {
stdout_handle = win32.HANDLE((^File_Impl)(desc.stdout.impl).fd) stdout_handle = win32.HANDLE((^File_Impl)(desc.stdout.impl).fd)
} }
if desc.stderr != nil {
if desc.stderr == nil {
stderr_handle = null_handle
} else {
stderr_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd) stderr_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd)
} }
if desc.stdin != nil {
if desc.stdin == nil {
stdin_handle = null_handle
} else {
stdin_handle = win32.HANDLE((^File_Impl)(desc.stdin.impl).fd) stdin_handle = win32.HANDLE((^File_Impl)(desc.stdin.impl).fd)
} }
working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator()) or_else nil) if len(desc.working_dir) > 0 else nil working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator) or_else nil) if len(desc.working_dir) > 0 else nil
process_info: win32.PROCESS_INFORMATION process_info: win32.PROCESS_INFORMATION
ok := win32.CreateProcessW( ok := win32.CreateProcessW(
nil, nil,
@@ -583,7 +614,7 @@ _process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path
} }
_get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) { _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
token_handle: win32.HANDLE token_handle: win32.HANDLE
if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) { if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) {
err = _get_platform_error() err = _get_platform_error()
@@ -598,7 +629,7 @@ _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Alloc
} }
err = nil err = nil
} }
token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator()) or_return)) token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator) or_return))
if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) { if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) {
err = _get_platform_error() err = _get_platform_error()
return return
@@ -614,8 +645,8 @@ _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Alloc
err = _get_platform_error() err = _get_platform_error()
return return
} }
username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator()) or_return username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator) or_return
domain := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator()) or_return domain := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator) or_return
return strings.concatenate({domain, "\\", username}, allocator) return strings.concatenate({domain, "\\", username}, allocator)
} }
+4 -4
View File
@@ -73,14 +73,14 @@ last_write_time_by_name :: modification_time_by_path
@(require_results) @(require_results)
modification_time :: proc(f: ^File) -> (time.Time, Error) { modification_time :: proc(f: ^File) -> (time.Time, Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
fi, err := fstat(f, temp_allocator()) fi, err := fstat(f, temp_allocator)
return fi.modification_time, err return fi.modification_time, err
} }
@(require_results) @(require_results)
modification_time_by_path :: proc(path: string) -> (time.Time, Error) { modification_time_by_path :: proc(path: string) -> (time.Time, Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
fi, err := stat(path, temp_allocator()) fi, err := stat(path, temp_allocator)
return fi.modification_time, err return fi.modification_time, err
} }
+4 -4
View File
@@ -47,8 +47,8 @@ _fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fi: File
// NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath // NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath
_stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
fd, errno := linux.open(name_cstr, {}) fd, errno := linux.open(name_cstr, {})
if errno != .NONE { if errno != .NONE {
@@ -59,8 +59,8 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err
} }
_lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
name_cstr := temp_cstring(name) or_return name_cstr := clone_to_cstring(name, temp_allocator) or_return
fd, errno := linux.open(name_cstr, {.PATH, .NOFOLLOW}) fd, errno := linux.open(name_cstr, {.PATH, .NOFOLLOW})
if errno != .NONE { if errno != .NONE {
+9 -8
View File
@@ -69,8 +69,8 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err
return return
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
cname := temp_cstring(name) or_return cname := clone_to_cstring(name, temp_allocator) or_return
fd := posix.open(cname, {}) fd := posix.open(cname, {})
if fd == -1 { if fd == -1 {
@@ -96,33 +96,34 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er
return return
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
// NOTE: can't use realpath or open (+ fcntl F_GETPATH) here because it tries to resolve symlinks. // NOTE: can't use realpath or open (+ fcntl F_GETPATH) here because it tries to resolve symlinks.
// NOTE: This might not be correct when given "/symlink/foo.txt", // NOTE: This might not be correct when given "/symlink/foo.txt",
// you would want that to resolve "/symlink", but not resolve "foo.txt". // you would want that to resolve "/symlink", but not resolve "foo.txt".
fullpath := clean_path(name, temp_allocator()) or_return fullpath := clean_path(name, temp_allocator) or_return
assert(len(fullpath) > 0) assert(len(fullpath) > 0)
switch { switch {
case fullpath[0] == '/': case fullpath[0] == '/':
// nothing. // nothing.
case fullpath == ".": case fullpath == ".":
fullpath = getwd(temp_allocator()) or_return fullpath = getwd(temp_allocator) or_return
case len(fullpath) > 1 && fullpath[0] == '.' && fullpath[1] == '/': case len(fullpath) > 1 && fullpath[0] == '.' && fullpath[1] == '/':
fullpath = fullpath[2:] fullpath = fullpath[2:]
fallthrough fallthrough
case: case:
fullpath = concatenate({ fullpath = concatenate({
getwd(temp_allocator()) or_return, getwd(temp_allocator) or_return,
"/", "/",
fullpath, fullpath,
}, temp_allocator()) or_return }, temp_allocator) or_return
} }
stat: posix.stat_t stat: posix.stat_t
if posix.lstat(temp_cstring(fullpath), &stat) != .OK { c_fullpath := clone_to_cstring(fullpath, temp_allocator) or_return
if posix.lstat(c_fullpath, &stat) != .OK {
err = _get_platform_error() err = _get_platform_error()
return return
} }
+86 -44
View File
@@ -45,15 +45,15 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path
name = "." name = "."
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
p := win32_utf8_to_utf16(name, temp_allocator()) or_return p := win32_utf8_to_utf16(name, temp_allocator) or_return
n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil) n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil)
if n == 0 { if n == 0 {
return "", _get_platform_error() return "", _get_platform_error()
} }
buf := make([]u16, n+1, temp_allocator()) buf := make([]u16, n+1, temp_allocator)
n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil) n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil)
if n == 0 { if n == 0 {
return "", _get_platform_error() return "", _get_platform_error()
@@ -65,9 +65,9 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt
if len(name) == 0 { if len(name) == 0 {
return {}, .Not_Exist return {}, .Not_Exist
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
wname := _fix_long_path(name, temp_allocator()) or_return wname := _fix_long_path(name, temp_allocator) or_return
fa: win32.WIN32_FILE_ATTRIBUTE_DATA fa: win32.WIN32_FILE_ATTRIBUTE_DATA
ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa) ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa)
if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
@@ -137,9 +137,9 @@ _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (strin
return "", _get_platform_error() return "", _get_platform_error()
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buf := make([]u16, max(n, 260)+1, temp_allocator()) buf := make([]u16, max(n, 260)+1, temp_allocator)
n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0) n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
return _cleanpath_from_buf(buf[:n], allocator) return _cleanpath_from_buf(buf[:n], allocator)
} }
@@ -155,9 +155,9 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) {
return nil, _get_platform_error() return nil, _get_platform_error()
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
buf := make([]u16, max(n, 260)+1, temp_allocator()) buf := make([]u16, max(n, 260)+1, temp_allocator)
n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0) n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
return _cleanpath_strip_prefix(buf[:n]), nil return _cleanpath_strip_prefix(buf[:n]), nil
} }
@@ -236,14 +236,30 @@ _file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: wi
return return
} }
// a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC)
time_as_filetime :: #force_inline proc(t: time.Time) -> (ft: win32.LARGE_INTEGER) {
win := u64(t._nsec / 100) + 116444736000000000
return win32.LARGE_INTEGER(win)
}
filetime_as_time_li :: #force_inline proc(ft: win32.LARGE_INTEGER) -> (t: time.Time) {
return {_nsec=(i64(ft) - 116444736000000000) * 100}
}
filetime_as_time_ft :: #force_inline proc(ft: win32.FILETIME) -> (t: time.Time) {
return filetime_as_time_li(win32.LARGE_INTEGER(ft.dwLowDateTime) + win32.LARGE_INTEGER(ft.dwHighDateTime) << 32)
}
filetime_as_time :: proc{filetime_as_time_ft, filetime_as_time_li}
_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { _file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0) type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
fi.type = type fi.type = type
fi.mode |= mode fi.mode |= mode
fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) fi.creation_time = filetime_as_time(d.ftCreationTime)
fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) fi.modification_time = filetime_as_time(d.ftLastWriteTime)
fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) fi.access_time = filetime_as_time(d.ftLastAccessTime)
fi.fullpath, e = full_path_from_name(name, allocator) fi.fullpath, e = full_path_from_name(name, allocator)
fi.name = basename(fi.fullpath) fi.name = basename(fi.fullpath)
return return
@@ -254,9 +270,9 @@ _file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string
type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0) type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
fi.type = type fi.type = type
fi.mode |= mode fi.mode |= mode
fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) fi.creation_time = filetime_as_time(d.ftCreationTime)
fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) fi.modification_time = filetime_as_time(d.ftLastWriteTime)
fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) fi.access_time = filetime_as_time(d.ftLastAccessTime)
fi.fullpath, e = full_path_from_name(name, allocator) fi.fullpath, e = full_path_from_name(name, allocator)
fi.name = basename(fi.fullpath) fi.name = basename(fi.fullpath)
return return
@@ -286,9 +302,9 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA
type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, h, 0) type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, h, 0)
fi.type = type fi.type = type
fi.mode |= mode fi.mode |= mode
fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) fi.creation_time = filetime_as_time(d.ftCreationTime)
fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) fi.modification_time = filetime_as_time(d.ftLastWriteTime)
fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) fi.access_time = filetime_as_time(d.ftLastAccessTime)
return fi, nil return fi, nil
} }
@@ -310,42 +326,68 @@ _is_reserved_name :: proc(path: string) -> bool {
return false return false
} }
_is_UNC :: proc(path: string) -> bool { _volume_name_len :: proc(path: string) -> (length: int) {
return _volume_name_len(path) > 2
}
_volume_name_len :: proc(path: string) -> int {
if len(path) < 2 { if len(path) < 2 {
return 0 return 0
} }
c := path[0]
if path[1] == ':' { if path[1] == ':' {
switch c { switch path[0] {
case 'a'..='z', 'A'..='Z': case 'a'..='z', 'A'..='Z':
return 2 return 2
} }
} }
// URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx /*
if l := len(path); l >= 5 && _is_path_separator(path[0]) && _is_path_separator(path[1]) && See: URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
!_is_path_separator(path[2]) && path[2] != '.' { Further allowed paths can be of the form of:
for n := 3; n < l-1; n += 1 { - \\server\share or \\server\share\more\path
if _is_path_separator(path[n]) { - \\?\C:\...
n += 1 - \\.\PhysicalDriveX
if !_is_path_separator(path[n]) { */
if path[n] == '.' { // Any remaining kind of path has to start with two slashes.
break if !_is_path_separator(path[0]) || !_is_path_separator(path[1]) {
} return 0
} }
for ; n < l; n += 1 {
if _is_path_separator(path[n]) { // Device path. The volume name is the whole string
break if len(path) >= 5 && path[2] == '.' && _is_path_separator(path[3]) {
} return len(path)
} }
return n
// We're a UNC share `\\host\share`, file namespace `\\?\C:` or UNC in file namespace `\\?\\host\share`
prefix := 2
// File namespace.
if len(path) >= 5 && path[2] == '?' && _is_path_separator(path[3]) {
if _is_path_separator(path[4]) {
// `\\?\\` UNC path in file namespace
prefix = 5
}
if len(path) >= 6 && path[5] == ':' {
switch path[4] {
case 'a'..='z', 'A'..='Z':
return 6
case:
return 0
} }
break
} }
} }
return 0
// UNC path, minimum version of the volume is `\\h\s` for host, share.
// Can also contain an IP address in the host position.
slash_count := 0
for i in prefix..<len(path) {
// Host needs to be at least 1 character
if _is_path_separator(path[i]) && i > 0 {
slash_count += 1
if slash_count == 2 {
return i
}
}
}
return len(path)
} }
+11 -9
View File
@@ -15,13 +15,13 @@ MAX_ATTEMPTS :: 1<<13 // Should be enough for everyone, right?
// The caller must `close` the file once finished with. // The caller must `close` the file once finished with.
@(require_results) @(require_results)
create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) { create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({})
dir := dir if dir != "" else temp_directory(temp_allocator()) or_return dir := dir if dir != "" else temp_directory(temp_allocator) or_return
prefix, suffix := _prefix_and_suffix(pattern) or_return prefix, suffix := _prefix_and_suffix(pattern) or_return
prefix = temp_join_path(dir, prefix) or_return prefix = temp_join_path(dir, prefix) or_return
rand_buf: [10]byte rand_buf: [10]byte
name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator()) name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator)
attempts := 0 attempts := 0
for { for {
@@ -47,13 +47,13 @@ mkdir_temp :: make_directory_temp
// If `dir` is an empty tring, `temp_directory()` will be used. // If `dir` is an empty tring, `temp_directory()` will be used.
@(require_results) @(require_results)
make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (temp_path: string, err: Error) { make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (temp_path: string, err: Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
dir := dir if dir != "" else temp_directory(temp_allocator()) or_return dir := dir if dir != "" else temp_directory(temp_allocator) or_return
prefix, suffix := _prefix_and_suffix(pattern) or_return prefix, suffix := _prefix_and_suffix(pattern) or_return
prefix = temp_join_path(dir, prefix) or_return prefix = temp_join_path(dir, prefix) or_return
rand_buf: [10]byte rand_buf: [10]byte
name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator()) name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator)
attempts := 0 attempts := 0
for { for {
@@ -70,7 +70,7 @@ make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator)
return "", err return "", err
} }
if err == .Not_Exist { if err == .Not_Exist {
if _, serr := stat(dir, temp_allocator()); serr == .Not_Exist { if _, serr := stat(dir, temp_allocator); serr == .Not_Exist {
return "", serr return "", serr
} }
} }
@@ -89,9 +89,11 @@ temp_directory :: proc(allocator: runtime.Allocator) -> (string, Error) {
@(private="file") @(private="file")
temp_join_path :: proc(dir, name: string) -> (string, runtime.Allocator_Error) { temp_join_path :: proc(dir, name: string) -> (string, runtime.Allocator_Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({})
if len(dir) > 0 && is_path_separator(dir[len(dir)-1]) { if len(dir) > 0 && is_path_separator(dir[len(dir)-1]) {
return concatenate({dir, name}, temp_allocator(),) return concatenate({dir, name}, temp_allocator,)
} }
return concatenate({dir, Path_Separator_String, name}, temp_allocator()) return concatenate({dir, Path_Separator_String, name}, temp_allocator)
} }
+2 -2
View File
@@ -4,8 +4,8 @@ package os2
import "base:runtime" import "base:runtime"
_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
tmpdir := get_env("TMPDIR", temp_allocator()) tmpdir := get_env("TMPDIR", temp_allocator)
if tmpdir == "" { if tmpdir == "" {
tmpdir = "/tmp" tmpdir = "/tmp"
} }
+2 -2
View File
@@ -9,9 +9,9 @@ _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Er
if n == 0 { if n == 0 {
return "", nil return "", nil
} }
TEMP_ALLOCATOR_GUARD() temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
b := make([]u16, max(win32.MAX_PATH, n), temp_allocator()) b := make([]u16, max(win32.MAX_PATH, n), temp_allocator)
n = win32.GetTempPathW(u32(len(b)), raw_data(b)) n = win32.GetTempPathW(u32(len(b)), raw_data(b))
if n == 3 && b[1] == ':' && b[2] == '\\' { if n == 3 && b[1] == ':' && b[2] == '\\' {
+141 -71
View File
@@ -2,78 +2,148 @@ package os2
import "base:runtime" import "base:runtime"
@(require_results) // ```
user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { // Windows: C:\Users\Alice
TEMP_ALLOCATOR_GUARD() // macOS: /Users/Alice
// Linux: /home/alice
#partial switch ODIN_OS { // ```
case .Windows:
dir = get_env("LocalAppData", temp_allocator())
if dir != "" {
dir = clone_string(dir, allocator) or_return
}
case .Darwin:
dir = get_env("HOME", temp_allocator())
if dir != "" {
dir = concatenate({dir, "/Library/Caches"}, allocator) or_return
}
case: // All other UNIX systems
dir = get_env("XDG_CACHE_HOME", allocator)
if dir == "" {
dir = get_env("HOME", temp_allocator())
if dir == "" {
return
}
dir = concatenate({dir, "/.cache"}, allocator) or_return
}
}
if dir == "" {
err = .Invalid_Path
}
return
}
@(require_results)
user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
TEMP_ALLOCATOR_GUARD()
#partial switch ODIN_OS {
case .Windows:
dir = get_env("AppData", temp_allocator())
if dir != "" {
dir = clone_string(dir, allocator) or_return
}
case .Darwin:
dir = get_env("HOME", temp_allocator())
if dir != "" {
dir = concatenate({dir, "/.config"}, allocator) or_return
}
case: // All other UNIX systems
dir = get_env("XDG_CONFIG_HOME", allocator)
if dir == "" {
dir = get_env("HOME", temp_allocator())
if dir == "" {
return
}
dir = concatenate({dir, "/.config"}, allocator) or_return
}
}
if dir == "" {
err = .Invalid_Path
}
return
}
@(require_results) @(require_results)
user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
env := "HOME" return _user_home_dir(allocator)
#partial switch ODIN_OS {
case .Windows:
env = "USERPROFILE"
}
if v := get_env(env, allocator); v != "" {
return v, nil
}
return "", .Invalid_Path
} }
// Files that applications can regenerate/refetch at a loss of speed, e.g. shader caches
//
// Sometimes deleted for system maintenance
//
// ```
// Windows: C:\Users\Alice\AppData\Local
// macOS: /Users/Alice/Library/Caches
// Linux: /home/alice/.cache
// ```
@(require_results)
user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
return _user_cache_dir(allocator)
}
// User-hidden application data
//
// ```
// Windows: C:\Users\Alice\AppData\Local ("C:\Users\Alice\AppData\Roaming" if `roaming`)
// macOS: /Users/Alice/Library/Application Support
// Linux: /home/alice/.local/share
// ```
//
// NOTE: (Windows only) `roaming` is for syncing across multiple devices within a *domain network*
@(require_results)
user_data_dir :: proc(allocator: runtime.Allocator, roaming := false) -> (dir: string, err: Error) {
return _user_data_dir(allocator, roaming)
}
// Non-essential application data, e.g. history, ui layout state
//
// ```
// Windows: C:\Users\Alice\AppData\Local
// macOS: /Users/Alice/Library/Application Support
// Linux: /home/alice/.local/state
// ```
@(require_results)
user_state_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
return _user_state_dir(allocator)
}
// Application log files
//
// ```
// Windows: C:\Users\Alice\AppData\Local
// macOS: /Users/Alice/Library/Logs
// Linux: /home/alice/.local/state
// ```
@(require_results)
user_log_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
return _user_log_dir(allocator)
}
// Application settings/preferences
//
// ```
// Windows: C:\Users\Alice\AppData\Local ("C:\Users\Alice\AppData\Roaming" if `roaming`)
// macOS: /Users/Alice/Library/Application Support
// Linux: /home/alice/.config
// ```
//
// NOTE: (Windows only) `roaming` is for syncing across multiple devices within a *domain network*
@(require_results)
user_config_dir :: proc(allocator: runtime.Allocator, roaming := false) -> (dir: string, err: Error) {
return _user_config_dir(allocator, roaming)
}
// ```
// Windows: C:\Users\Alice\Music
// macOS: /Users/Alice/Music
// Linux: /home/alice/Music
// ```
@(require_results)
user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
return _user_music_dir(allocator)
}
// ```
// Windows: C:\Users\Alice\Desktop
// macOS: /Users/Alice/Desktop
// Linux: /home/alice/Desktop
// ```
@(require_results)
user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
return _user_desktop_dir(allocator)
}
// ```
// Windows: C:\Users\Alice\Documents
// macOS: /Users/Alice/Documents
// Linux: /home/alice/Documents
// ```
@(require_results)
user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
return _user_documents_dir(allocator)
}
// ```
// Windows: C:\Users\Alice\Downloads
// macOS: /Users/Alice/Downloads
// Linux: /home/alice/Downloads
// ```
@(require_results)
user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
return _user_downloads_dir(allocator)
}
// ```
// Windows: C:\Users\Alice\Pictures
// macOS: /Users/Alice/Pictures
// Linux: /home/alice/Pictures
// ```
@(require_results)
user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
return _user_pictures_dir(allocator)
}
// ```
// Windows: C:\Users\Alice\Public
// macOS: /Users/Alice/Public
// Linux: /home/alice/Public
// ```
@(require_results)
user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
return _user_public_dir(allocator)
}
// ```
// Windows: C:\Users\Alice\Videos
// macOS: /Users/Alice/Movies
// Linux: /home/alice/Videos
// ```
@(require_results)
user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
return _user_videos_dir(allocator)
}
+183
View File
@@ -0,0 +1,183 @@
#+build !windows
package os2
import "base:runtime"
import "core:encoding/ini"
import "core:strings"
import "core:sys/posix"
_user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Library/Caches", allocator)
case: // Unix
return _xdg_lookup("XDG_CACHE_HOME", "/.cache", allocator)
}
}
_user_config_dir :: proc(allocator: runtime.Allocator, _roaming: bool) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Library/Application Support", allocator)
case: // Unix
return _xdg_lookup("XDG_CONFIG_HOME", "/.config", allocator)
}
}
_user_state_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Library/Application Support", allocator)
case: // Unix
return _xdg_lookup("XDG_STATE_HOME", "/.local/state", allocator)
}
}
_user_log_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Library/Logs", allocator)
case: // Unix
return _xdg_lookup("XDG_STATE_HOME", "/.local/state", allocator)
}
}
_user_data_dir :: proc(allocator: runtime.Allocator, _roaming: bool) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Library/Application Support", allocator)
case: // Unix
return _xdg_lookup("XDG_DATA_HOME", "/.local/share", allocator)
}
}
_user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Music", allocator)
case: // Unix
return _xdg_lookup("XDG_MUSIC_DIR", "/Music", allocator)
}
}
_user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Desktop", allocator)
case: // Unix
return _xdg_lookup("XDG_DESKTOP_DIR", "/Desktop", allocator)
}
}
_user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Documents", allocator)
case: // Unix
return _xdg_lookup("XDG_DOCUMENTS_DIR", "/Documents", allocator)
}
}
_user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Downloads", allocator)
case: // Unix
return _xdg_lookup("XDG_DOWNLOAD_DIR", "/Downloads", allocator)
}
}
_user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Pictures", allocator)
case: // Unix
return _xdg_lookup("XDG_PICTURES_DIR", "/Pictures", allocator)
}
}
_user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Public", allocator)
case: // Unix
return _xdg_lookup("XDG_PUBLICSHARE_DIR", "/Public", allocator)
}
}
_user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
#partial switch ODIN_OS {
case .Darwin:
return _xdg_lookup("", "/Movies", allocator)
case: // Unix
return _xdg_lookup("XDG_VIDEOS_DIR", "/Videos", allocator)
}
}
_user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
if v := get_env("HOME", allocator); v != "" {
return v, nil
}
err = .No_HOME_Variable
return
}
_xdg_lookup :: proc(xdg_key: string, fallback_suffix: string, allocator: runtime.Allocator) -> (dir: string, err: Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
if xdg_key == "" { // Darwin doesn't have XDG paths.
dir = get_env("HOME", temp_allocator)
if dir == "" {
err = .No_HOME_Variable
return
}
return concatenate({dir, fallback_suffix}, allocator)
} else {
if strings.ends_with(xdg_key, "_DIR") {
dir = _xdg_user_dirs_lookup(xdg_key, allocator) or_return
} else {
dir = get_env(xdg_key, allocator)
}
if dir == "" {
dir = get_env("HOME", temp_allocator)
if dir == "" {
err = .No_HOME_Variable
return
}
dir = concatenate({dir, fallback_suffix}, allocator) or_return
}
return
}
}
// If `<config-dir>/user-dirs.dirs` doesn't exist, or `xdg_key` can't be found there: returns `""`
_xdg_user_dirs_lookup :: proc(xdg_key: string, allocator: runtime.Allocator) -> (dir: string, err: Error) {
temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
config_dir := user_config_dir(temp_allocator) or_return
user_dirs_path := concatenate({config_dir, "/user-dirs.dirs"}, temp_allocator) or_return
content := read_entire_file(user_dirs_path, temp_allocator) or_return
it := ini.Iterator{
section = "",
_src = string(content),
options = ini.Options{
comment = "#",
key_lower_case = false,
},
}
for k, v in ini.iterate(&it) {
if k == xdg_key {
we: posix.wordexp_t
defer posix.wordfree(&we)
if _err := posix.wordexp(strings.clone_to_cstring(v, temp_allocator), &we, nil); _err != nil || we.we_wordc != 1 {
return "", .Wordexp_Failed
}
return strings.clone_from_cstring(we.we_wordv[0], allocator)
}
}
return
}
+79
View File
@@ -0,0 +1,79 @@
package os2
import "base:runtime"
@(require) import win32 "core:sys/windows"
_local_appdata :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
guid := win32.FOLDERID_LocalAppData
return _get_known_folder_path(&guid, allocator)
}
_local_appdata_or_roaming :: proc(allocator: runtime.Allocator, roaming: bool) -> (dir: string, err: Error) {
guid := win32.FOLDERID_LocalAppData
if roaming {
guid = win32.FOLDERID_RoamingAppData
}
return _get_known_folder_path(&guid, allocator)
}
_user_config_dir :: _local_appdata_or_roaming
_user_data_dir :: _local_appdata_or_roaming
_user_state_dir :: _local_appdata
_user_log_dir :: _local_appdata
_user_cache_dir :: _local_appdata
_user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
guid := win32.FOLDERID_Profile
return _get_known_folder_path(&guid, allocator)
}
_user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
guid := win32.FOLDERID_Music
return _get_known_folder_path(&guid, allocator)
}
_user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
guid := win32.FOLDERID_Desktop
return _get_known_folder_path(&guid, allocator)
}
_user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
guid := win32.FOLDERID_Documents
return _get_known_folder_path(&guid, allocator)
}
_user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
guid := win32.FOLDERID_Downloads
return _get_known_folder_path(&guid, allocator)
}
_user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
guid := win32.FOLDERID_Pictures
return _get_known_folder_path(&guid, allocator)
}
_user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
guid := win32.FOLDERID_Public
return _get_known_folder_path(&guid, allocator)
}
_user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
guid := win32.FOLDERID_Videos
return _get_known_folder_path(&guid, allocator)
}
_get_known_folder_path :: proc(rfid: win32.REFKNOWNFOLDERID, allocator: runtime.Allocator) -> (dir: string, err: Error) {
// https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath
// See also `known_folders.odin` in `core:sys/windows` for the GUIDs.
path_w: win32.LPWSTR
res := win32.SHGetKnownFolderPath(rfid, 0, nil, &path_w)
defer win32.CoTaskMemFree(path_w)
if res != 0 {
return "", .Invalid_Path
}
dir, _ = win32.wstring_to_utf8(path_w, -1, allocator)
return
}
+4
View File
@@ -249,3 +249,7 @@ exit :: proc "contextless" (code: int) -> ! {
current_thread_id :: proc "contextless" () -> int { current_thread_id :: proc "contextless" () -> int {
return 0 return 0
} }
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
return "", false
}
+36
View File
@@ -0,0 +1,36 @@
package filepath
import "base:runtime"
import "core:strings"
SEPARATOR :: '/'
SEPARATOR_STRING :: `/`
LIST_SEPARATOR :: ':'
is_reserved_name :: proc(path: string) -> bool {
return false
}
is_abs :: proc(path: string) -> bool {
return strings.has_prefix(path, "/")
}
abs :: proc(path: string, allocator := context.allocator) -> (string, bool) {
if is_abs(path) {
return strings.clone(string(path), allocator), true
}
return path, false
}
join :: proc(elems: []string, allocator := context.allocator) -> (joined: string, err: runtime.Allocator_Error) #optional_allocator_error {
for e, i in elems {
if e != "" {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
p := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) or_return
return clean(p, allocator)
}
}
return "", nil
}
+211 -10
View File
@@ -21,20 +21,17 @@ package simd
import "base:builtin" import "base:builtin"
import "base:intrinsics" import "base:intrinsics"
import "base:runtime"
/* /*
Check if SIMD is software-emulated on a target platform. Check if SIMD is software-emulated on a target platform.
This value is `false`, when the compile-time target has the hardware support for This value is `true`, when the compile-time target has the hardware support for
at 128-bit (or wider) SIMD. If the compile-time target lacks the hardware support at least 128-bit (or wider) SIMD. If the compile-time target lacks the hardware support
for 128-bit SIMD, this value is `true`, and all SIMD operations will likely be for 128-bit SIMD, this value is `false`, and all SIMD operations will likely be
emulated. emulated.
*/ */
IS_EMULATED :: true when (ODIN_ARCH == .amd64 || ODIN_ARCH == .i386) && !intrinsics.has_target_feature("sse2") else HAS_HARDWARE_SIMD :: runtime.HAS_HARDWARE_SIMD
true when (ODIN_ARCH == .arm64 || ODIN_ARCH == .arm32) && !intrinsics.has_target_feature("neon") else
true when (ODIN_ARCH == .wasm64p32 || ODIN_ARCH == .wasm32) && !intrinsics.has_target_feature("simd128") else
true when (ODIN_ARCH == .riscv64) && !intrinsics.has_target_feature("v") else
false
/* /*
Vector of 16 `u8` lanes (128 bits). Vector of 16 `u8` lanes (128 bits).
@@ -1759,7 +1756,103 @@ Returns:
replace :: intrinsics.simd_replace replace :: intrinsics.simd_replace
/* /*
Reduce a vector to a scalar by adding up all the lanes. Reduce a vector to a scalar by adding up all the lanes in a bisecting fashion.
This procedure returns a scalar that is the sum of all lanes, calculated by
bisecting the vector into two parts, where the first contains lanes [0, N/2)
and the second contains lanes [N/2, N), and adding the two halves element-wise
to produce N/2 values. This is repeated until only a single element remains.
This order may be faster to compute than the ordered sum for floats, as it can
often be better parallelized.
The order of the sum may be important for accounting for precision errors in
floating-point computation, as floating-point addition is not associative, that
is `(a+b)+c` may not be equal to `a+(b+c)`.
Inputs:
- `v`: The vector to reduce.
Result:
- Sum of all lanes, as a scalar.
**Operation**:
for n > 1 {
n = n / 2
for i in 0 ..< n {
a[i] += a[i+n]
}
}
res := a[0]
Graphical representation of the operation for N=4:
+-----------------------+
| v0 | v1 | v2 | v3 |
+-----------------------+
| | | |
[+]<-- | ---' |
| [+]<--------'
| |
`>[+]<'
|
v
+-----+
result: | y0 |
+-----+
*/
reduce_add_bisect :: intrinsics.simd_reduce_add_bisect
/*
Reduce a vector to a scalar by multiplying up all the lanes in a bisecting fashion.
This procedure returns a scalar that is the product of all lanes, calculated by
bisecting the vector into two parts, where the first contains indices [0, N/2)
and the second contains indices [N/2, N), and multiplying the two halves
together element-wise to produce N/2 values. This is repeated until only a
single element remains. This order may be faster to compute than the ordered
product for floats, as it can often be better parallelized.
The order of the product may be important for accounting for precision errors
in floating-point computation, as floating-point multiplication is not
associative, that is `(a*b)*c` may not be equal to `a*(b*c)`.
Inputs:
- `v`: The vector to reduce.
Result:
- Product of all lanes, as a scalar.
**Operation**:
for n > 1 {
n = n / 2
for i in 0 ..< n {
a[i] *= a[i+n]
}
}
res := a[0]
Graphical representation of the operation for N=4:
+-----------------------+
| v0 | v1 | v2 | v3 |
+-----------------------+
| | | |
[x]<-- | ---' |
| [x]<--------'
| |
`>[x]<'
|
v
+-----+
result: | y0 |
+-----+
*/
reduce_mul_bisect :: intrinsics.simd_reduce_mul_bisect
/*
Reduce a vector to a scalar by adding up all the lanes in an ordered fashion.
This procedure returns a scalar that is the ordered sum of all lanes. The This procedure returns a scalar that is the ordered sum of all lanes. The
ordered sum may be important for accounting for precision errors in ordered sum may be important for accounting for precision errors in
@@ -1782,7 +1875,7 @@ Result:
reduce_add_ordered :: intrinsics.simd_reduce_add_ordered reduce_add_ordered :: intrinsics.simd_reduce_add_ordered
/* /*
Reduce a vector to a scalar by multiplying all the lanes. Reduce a vector to a scalar by multiplying all the lanes in an ordered fashion.
This procedure returns a scalar that is the ordered product of all lanes. This procedure returns a scalar that is the ordered product of all lanes.
The ordered product may be important for accounting for precision errors in The ordered product may be important for accounting for precision errors in
@@ -1804,6 +1897,100 @@ Result:
*/ */
reduce_mul_ordered :: intrinsics.simd_reduce_mul_ordered reduce_mul_ordered :: intrinsics.simd_reduce_mul_ordered
/*
Reduce a vector to a scalar by adding up all the lanes in a pairwise fashion.
This procedure returns a scalar that is the sum of all lanes, calculated by
adding each even-indexed element with the following odd-indexed element to
produce N/2 values. This is repeated until only a single element remains. This
order is supported by hardware instructions for some types/architectures (e.g.
i16/i32/f32/f64 on x86 SSE, i8/i16/i32/f32 on ARM NEON).
The order of the sum may be important for accounting for precision errors in
floating-point computation, as floating-point addition is not associative, that
is `(a+b)+c` may not be equal to `a+(b+c)`.
Inputs:
- `v`: The vector to reduce.
Result:
- Sum of all lanes, as a scalar.
**Operation**:
for n > 1 {
n = n / 2
for i in 0 ..< n {
a[i] = a[2*i+0] + a[2*i+1]
}
}
res := a[0]
Graphical representation of the operation for N=4:
+-----------------------+
v: | v0 | v1 | v2 | v3 |
+-----------------------+
| | | |
`>[+]<' `>[+]<'
| |
`--->[+]<--'
|
v
+-----+
result: | y0 |
+-----+
*/
reduce_add_pairs :: intrinsics.simd_reduce_add_pairs
/*
Reduce a vector to a scalar by multiplying all the lanes in a pairwise fashion.
This procedure returns a scalar that is the product of all lanes, calculated by
bisecting the vector into two parts, where the first contains lanes [0, N/2)
and the second contains lanes [N/2, N), and multiplying the two halves together
multiplying each even-indexed element with the following odd-indexed element to
produce N/2 values. This is repeated until only a single element remains. This
order may be faster to compute than the ordered product for floats, as it can
often be better parallelized.
The order of the product may be important for accounting for precision errors
in floating-point computation, as floating-point multiplication is not
associative, that is `(a*b)*c` may not be equal to `a*(b*c)`.
Inputs:
- `v`: The vector to reduce.
Result:
- Product of all lanes, as a scalar.
**Operation**:
for n > 1 {
n = n / 2
for i in 0 ..< n {
a[i] = a[2*i+0] * a[2*i+1]
}
}
res := a[0]
Graphical representation of the operation for N=4:
+-----------------------+
v: | v0 | v1 | v2 | v3 |
+-----------------------+
| | | |
`>[x]<' `>[x]<'
| |
`--->[x]<--'
|
v
+-----+
result: | y0 |
+-----+
*/
reduce_mul_pairs :: intrinsics.simd_reduce_mul_pairs
/* /*
Reduce a vector to a scalar by finding the minimum value between all of the lanes. Reduce a vector to a scalar by finding the minimum value between all of the lanes.
@@ -2510,3 +2697,17 @@ Example:
recip :: #force_inline proc "contextless" (v: $T/#simd[$LANES]$E) -> T where intrinsics.type_is_float(E) { recip :: #force_inline proc "contextless" (v: $T/#simd[$LANES]$E) -> T where intrinsics.type_is_float(E) {
return T(1) / v return T(1) / v
} }
/*
Create a vector where each lane contains the index of that lane.
Inputs:
- `V`: The type of the vector to create.
Result:
- A vector of the given type, where each lane contains the index of that lane.
**Operation**:
for i in 0 ..< N {
res[i] = i
}
*/
indices :: intrinsics.simd_indices
+79
View File
@@ -0,0 +1,79 @@
#+build i386, amd64
package simd_x86
import "base:intrinsics"
@(require_results, enable_target_feature="bmi")
_andn_u32 :: #force_inline proc "c" (a, b: u32) -> u32 {
return a &~ b
}
@(require_results, enable_target_feature="bmi")
_andn_u64 :: #force_inline proc "c" (a, b: u64) -> u64 {
return a &~ b
}
@(require_results, enable_target_feature="bmi")
_bextr_u32 :: #force_inline proc "c" (a, start, len: u32) -> u32 {
return bextr_u32(a, (start & 0xff) | (len << 8))
}
@(require_results, enable_target_feature="bmi")
_bextr_u64 :: #force_inline proc "c" (a: u64, start, len: u32) -> u64 {
return bextr_u64(a, cast(u64)((start & 0xff) | (len << 8)))
}
@(require_results, enable_target_feature="bmi")
_bextr2_u32 :: #force_inline proc "c" (a, control: u32) -> u32 {
return bextr_u32(a, control)
}
@(require_results, enable_target_feature="bmi")
_bextr2_u64 :: #force_inline proc "c" (a, control: u64) -> u64 {
return bextr_u64(a, control)
}
@(require_results, enable_target_feature="bmi")
_blsi_u32 :: #force_inline proc "c" (a: u32) -> u32 {
return a & -a
}
@(require_results, enable_target_feature="bmi")
_blsi_u64 :: #force_inline proc "c" (a: u64) -> u64 {
return a & -a
}
@(require_results, enable_target_feature="bmi")
_blsmsk_u32 :: #force_inline proc "c" (a: u32) -> u32 {
return a ~ (a-1)
}
@(require_results, enable_target_feature="bmi")
_blsmsk_u64 :: #force_inline proc "c" (a: u64) -> u64 {
return a ~ (a-1)
}
@(require_results, enable_target_feature="bmi")
_blsr_u32 :: #force_inline proc "c" (a: u32) -> u32 {
return a & (a-1)
}
@(require_results, enable_target_feature="bmi")
_blsr_u64 :: #force_inline proc "c" (a: u64) -> u64 {
return a & (a-1)
}
@(require_results, enable_target_feature = "bmi")
_tzcnt_u16 :: #force_inline proc "c" (a: u16) -> u16 {
return intrinsics.count_trailing_zeros(a)
}
@(require_results, enable_target_feature = "bmi")
_tzcnt_u32 :: #force_inline proc "c" (a: u32) -> u32 {
return intrinsics.count_trailing_zeros(a)
}
@(require_results, enable_target_feature = "bmi")
_tzcnt_u64 :: #force_inline proc "c" (a: u64) -> u64 {
return intrinsics.count_trailing_zeros(a)
}
@(private, default_calling_convention = "none")
foreign _ {
@(link_name = "llvm.x86.bmi.bextr.32")
bextr_u32 :: proc(a, control: u32) -> u32 ---
@(link_name = "llvm.x86.bmi.bextr.64")
bextr_u64 :: proc(a, control: u64) -> u64 ---
}
+46
View File
@@ -0,0 +1,46 @@
#+build i386, amd64
package simd_x86
@(require_results, enable_target_feature = "bmi2")
_bzhi_u32 :: #force_inline proc "c" (a, index: u32) -> u32 {
return bzhi_u32(a, index)
}
@(require_results, enable_target_feature = "bmi2")
_bzhi_u64 :: #force_inline proc "c" (a, index: u64) -> u64 {
return bzhi_u64(a, index)
}
@(require_results, enable_target_feature = "bmi2")
_pdep_u32 :: #force_inline proc "c" (a, mask: u32) -> u32 {
return pdep_u32(a, mask)
}
@(require_results, enable_target_feature = "bmi2")
_pdep_u64 :: #force_inline proc "c" (a, mask: u64) -> u64 {
return pdep_u64(a, mask)
}
@(require_results, enable_target_feature = "bmi2")
_pext_u32 :: #force_inline proc "c" (a, mask: u32) -> u32 {
return pext_u32(a, mask)
}
@(require_results, enable_target_feature = "bmi2")
_pext_u64 :: #force_inline proc "c" (a, mask: u64) -> u64 {
return pext_u64(a, mask)
}
@(private, default_calling_convention = "none")
foreign _ {
@(link_name = "llvm.x86.bmi.bzhi.32")
bzhi_u32 :: proc(a, index: u32) -> u32 ---
@(link_name = "llvm.x86.bmi.bzhi.64")
bzhi_u64 :: proc(a, index: u64) -> u64 ---
@(link_name = "llvm.x86.bmi.pdep.32")
pdep_u32 :: proc(a, mask: u32) -> u32 ---
@(link_name = "llvm.x86.bmi.pdep.64")
pdep_u64 :: proc(a, mask: u64) -> u64 ---
@(link_name = "llvm.x86.bmi.pext.32")
pext_u32 :: proc(a, mask: u32) -> u32 ---
@(link_name = "llvm.x86.bmi.pext.64")
pext_u64 :: proc(a, mask: u64) -> u64 ---
}
-46
View File
@@ -30,14 +30,6 @@ sort :: proc(it: Interface) {
_quick_sort(it, 0, n, max_depth(n)) _quick_sort(it, 0, n, max_depth(n))
} }
@(deprecated="use slice.sort")
slice :: proc(array: $T/[]$E) where ORD(E) {
_slice.sort(array)
// s := array;
// sort(slice_interface(&s));
}
slice_interface :: proc(s: ^$T/[]$E) -> Interface where ORD(E) { slice_interface :: proc(s: ^$T/[]$E) -> Interface where ORD(E) {
return Interface{ return Interface{
collection = rawptr(s), collection = rawptr(s),
@@ -80,31 +72,6 @@ reverse_sort :: proc(it: Interface) {
sort(reverse_interface(&it)) sort(reverse_interface(&it))
} }
@(deprecated="use slice.reverse")
reverse_slice :: proc(array: $T/[]$E) where ORD(E) {
_slice.reverse(array)
/*
s := array;
sort(Interface{
collection = rawptr(&s),
len = proc(it: Interface) -> int {
s := (^T)(it.collection);
return len(s^);
},
less = proc(it: Interface, i, j: int) -> bool {
s := (^T)(it.collection);
return s[j] < s[i]; // manual set up
},
swap = proc(it: Interface, i, j: int) {
s := (^T)(it.collection);
s[i], s[j] = s[j], s[i];
},
});
*/
}
is_sorted :: proc(it: Interface) -> bool { is_sorted :: proc(it: Interface) -> bool {
n := it->len() n := it->len()
for i := n-1; i > 0; i -= 1 { for i := n-1; i > 0; i -= 1 {
@@ -294,11 +261,6 @@ _insertion_sort :: proc(it: Interface, a, b: int) {
} }
} }
// @(deprecated="use sort.sort or slice.sort_by")
bubble_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) { bubble_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
assert(f != nil) assert(f != nil)
count := len(array) count := len(array)
@@ -327,7 +289,6 @@ bubble_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
} }
} }
// @(deprecated="use sort.sort_slice or slice.sort")
bubble_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) { bubble_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
count := len(array) count := len(array)
@@ -355,7 +316,6 @@ bubble_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
} }
} }
// @(deprecated="use sort.sort or slice.sort_by")
quick_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) { quick_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
assert(f != nil) assert(f != nil)
a := array a := array
@@ -384,7 +344,6 @@ quick_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
quick_sort_proc(a[i:n], f) quick_sort_proc(a[i:n], f)
} }
// @(deprecated="use sort.sort_slice or slice.sort")
quick_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) { quick_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
a := array a := array
n := len(a) n := len(a)
@@ -420,7 +379,6 @@ _log2 :: proc(x: int) -> int {
return res return res
} }
// @(deprecated="use sort.sort or slice.sort_by")
merge_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) { merge_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
merge :: proc(a: A, start, mid, end: int, f: proc(T, T) -> int) { merge :: proc(a: A, start, mid, end: int, f: proc(T, T) -> int) {
s, m := start, mid s, m := start, mid
@@ -462,7 +420,6 @@ merge_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
internal_sort(array, 0, len(array)-1, f) internal_sort(array, 0, len(array)-1, f)
} }
// @(deprecated="use sort.sort_slice or slice.sort")
merge_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) { merge_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
merge :: proc(a: A, start, mid, end: int) { merge :: proc(a: A, start, mid, end: int) {
s, m := start, mid s, m := start, mid
@@ -504,8 +461,6 @@ merge_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
internal_sort(array, 0, len(array)-1) internal_sort(array, 0, len(array)-1)
} }
// @(deprecated="use sort.sort or slice.sort_by")
heap_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) { heap_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
sift_proc :: proc(a: A, pi: int, n: int, f: proc(T, T) -> int) #no_bounds_check { sift_proc :: proc(a: A, pi: int, n: int, f: proc(T, T) -> int) #no_bounds_check {
p := pi p := pi
@@ -540,7 +495,6 @@ heap_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) {
} }
} }
// @(deprecated="use sort.sort_slice or slice.sort")
heap_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) { heap_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) {
sift :: proc(a: A, pi: int, n: int) #no_bounds_check { sift :: proc(a: A, pi: int, n: int) #no_bounds_check {
p := pi p := pi
+38
View File
@@ -0,0 +1,38 @@
package strconv
// (2025-06-05) These procedures are to be removed at a later release.
@(deprecated="Use write_bits instead")
append_bits :: proc(buf: []byte, x: u64, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string {
return write_bits(buf, x, base, is_signed, bit_size, digits, flags)
}
@(deprecated="Use write_bits_128 instead")
append_bits_128 :: proc(buf: []byte, x: u128, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string {
return write_bits_128(buf, x, base, is_signed, bit_size, digits, flags)
}
@(deprecated="Use write_bool instead")
append_bool :: proc(buf: []byte, b: bool) -> string {
return write_bool(buf, b)
}
@(deprecated="Use write_uint instead")
append_uint :: proc(buf: []byte, u: u64, base: int) -> string {
return write_uint(buf, u, base)
}
@(deprecated="Use write_int instead")
append_int :: proc(buf: []byte, i: i64, base: int) -> string {
return write_int(buf, i, base)
}
@(deprecated="Use write_u128 instead")
append_u128 :: proc(buf: []byte, u: u128, base: int) -> string {
return write_u128(buf, u, base)
}
@(deprecated="Use write_float instead")
append_float :: proc(buf: []byte, f: f64, fmt: byte, prec, bit_size: int) -> string {
return write_float(buf, f, fmt, prec, bit_size)
}
+7 -7
View File
@@ -48,7 +48,7 @@ is_integer_negative :: proc(x: u64, is_signed: bool, bit_size: int) -> (u: u64,
return return
} }
/* /*
Appends the string representation of an integer to a buffer with specified base, flags, and digit set. Writes the string representation of an integer to a buffer with specified base, flags, and digit set.
**Inputs** **Inputs**
- buf: The buffer to append the integer representation to - buf: The buffer to append the integer representation to
@@ -62,9 +62,9 @@ Appends the string representation of an integer to a buffer with specified base,
**Returns** **Returns**
- The string containing the integer representation appended to the buffer - The string containing the integer representation appended to the buffer
*/ */
append_bits :: proc(buf: []byte, x: u64, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string { write_bits :: proc(buf: []byte, x: u64, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string {
if base < 2 || base > MAX_BASE { if base < 2 || base > MAX_BASE {
panic("strconv: illegal base passed to append_bits") panic("strconv: illegal base passed to write_bits")
} }
a: [129]byte a: [129]byte
@@ -146,7 +146,7 @@ is_integer_negative_128 :: proc(x: u128, is_signed: bool, bit_size: int) -> (u:
return return
} }
/* /*
Appends the string representation of a 128-bit integer to a buffer with specified base, flags, and digit set. Writes the string representation of a 128-bit integer to a buffer with specified base, flags, and digit set.
**Inputs** **Inputs**
- buf: The buffer to append the integer representation to - buf: The buffer to append the integer representation to
@@ -158,11 +158,11 @@ Appends the string representation of a 128-bit integer to a buffer with specifie
- flags: The Int_Flags bit set to control integer formatting - flags: The Int_Flags bit set to control integer formatting
**Returns** **Returns**
- The string containing the integer representation appended to the buffer - The string containing the integer representation written to the buffer
*/ */
append_bits_128 :: proc(buf: []byte, x: u128, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string { write_bits_128 :: proc(buf: []byte, x: u128, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string {
if base < 2 || base > MAX_BASE { if base < 2 || base > MAX_BASE {
panic("strconv: illegal base passed to append_bits") panic("strconv: illegal base passed to write_bits")
} }
a: [140]byte a: [140]byte
+75 -42
View File
@@ -1095,6 +1095,39 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) {
return transmute(f64)bits, ok return transmute(f64)bits, ok
} }
if len(str) > 2 && str[0] == '0' && str[1] == 'h' {
nr = 2
as_int: u64
digits: int
for r in str[2:] {
if r == '_' {
nr += 1
continue
}
v := u64(_digit_value(r))
if v >= 16 {
break
}
as_int *= 16
as_int += v
digits += 1
}
nr += digits
ok = len(str) == nr
switch digits {
case 4:
value = cast(f64)transmute(f16)cast(u16)as_int
case 8:
value = cast(f64)transmute(f32)cast(u32)as_int
case 16:
value = transmute(f64)as_int
case:
ok = false
}
return
}
if value, nr, ok = check_special(str); ok { if value, nr, ok = check_special(str); ok {
return return
@@ -1418,19 +1451,19 @@ parse_quaternion64 :: proc(str: string, n: ^int = nil) -> (value: quaternion64,
return cast(quaternion64)v, ok return cast(quaternion64)v, ok
} }
/* /*
Appends a boolean value as a string to the given buffer Writes a boolean value as a string to the given buffer
**Inputs** **Inputs**
- buf: The buffer to append the boolean value to - buf: The buffer to write the boolean value to
- b: The boolean value to be appended - b: The boolean value to be written
Example: Example:
import "core:fmt" import "core:fmt"
import "core:strconv" import "core:strconv"
append_bool_example :: proc() { write_bool_example :: proc() {
buf: [6]byte buf: [6]byte
result := strconv.append_bool(buf[:], true) result := strconv.write_bool(buf[:], true)
fmt.println(result, buf) fmt.println(result, buf)
} }
@@ -1439,9 +1472,9 @@ Output:
true [116, 114, 117, 101, 0, 0] true [116, 114, 117, 101, 0, 0]
**Returns** **Returns**
- The resulting string after appending the boolean value - The resulting string after writing the boolean value
*/ */
append_bool :: proc(buf: []byte, b: bool) -> string { write_bool :: proc(buf: []byte, b: bool) -> string {
n := 0 n := 0
if b { if b {
n = copy(buf, "true") n = copy(buf, "true")
@@ -1451,20 +1484,20 @@ append_bool :: proc(buf: []byte, b: bool) -> string {
return string(buf[:n]) return string(buf[:n])
} }
/* /*
Appends an unsigned integer value as a string to the given buffer with the specified base Writes an unsigned integer value as a string to the given buffer with the specified base
**Inputs** **Inputs**
- buf: The buffer to append the unsigned integer value to - buf: The buffer to write the unsigned integer value to
- u: The unsigned integer value to be appended - u: The unsigned integer value to be written
- base: The base to use for converting the integer value - base: The base to use for converting the integer value
Example: Example:
import "core:fmt" import "core:fmt"
import "core:strconv" import "core:strconv"
append_uint_example :: proc() { write_uint_example :: proc() {
buf: [4]byte buf: [4]byte
result := strconv.append_uint(buf[:], 42, 16) result := strconv.write_uint(buf[:], 42, 16)
fmt.println(result, buf) fmt.println(result, buf)
} }
@@ -1473,26 +1506,26 @@ Output:
2a [50, 97, 0, 0] 2a [50, 97, 0, 0]
**Returns** **Returns**
- The resulting string after appending the unsigned integer value - The resulting string after writing the unsigned integer value
*/ */
append_uint :: proc(buf: []byte, u: u64, base: int) -> string { write_uint :: proc(buf: []byte, u: u64, base: int) -> string {
return append_bits(buf, u, base, false, 8*size_of(uint), digits, nil) return write_bits(buf, u, base, false, 8*size_of(uint), digits, nil)
} }
/* /*
Appends a signed integer value as a string to the given buffer with the specified base Writes a signed integer value as a string to the given buffer with the specified base
**Inputs** **Inputs**
- buf: The buffer to append the signed integer value to - buf: The buffer to write the signed integer value to
- i: The signed integer value to be appended - i: The signed integer value to be written
- base: The base to use for converting the integer value - base: The base to use for converting the integer value
Example: Example:
import "core:fmt" import "core:fmt"
import "core:strconv" import "core:strconv"
append_int_example :: proc() { write_int_example :: proc() {
buf: [4]byte buf: [4]byte
result := strconv.append_int(buf[:], -42, 10) result := strconv.write_int(buf[:], -42, 10)
fmt.println(result, buf) fmt.println(result, buf)
} }
@@ -1501,16 +1534,16 @@ Output:
-42 [45, 52, 50, 0] -42 [45, 52, 50, 0]
**Returns** **Returns**
- The resulting string after appending the signed integer value - The resulting string after writing the signed integer value
*/ */
append_int :: proc(buf: []byte, i: i64, base: int) -> string { write_int :: proc(buf: []byte, i: i64, base: int) -> string {
return append_bits(buf, u64(i), base, true, 8*size_of(int), digits, nil) return write_bits(buf, u64(i), base, true, 8*size_of(int), digits, nil)
} }
append_u128 :: proc(buf: []byte, u: u128, base: int) -> string { write_u128 :: proc(buf: []byte, u: u128, base: int) -> string {
return append_bits_128(buf, u, base, false, 8*size_of(uint), digits, nil) return write_bits_128(buf, u, base, false, 8*size_of(uint), digits, nil)
} }
/* /*
@@ -1538,7 +1571,7 @@ Output:
- The resulting string after converting the integer value - The resulting string after converting the integer value
*/ */
itoa :: proc(buf: []byte, i: int) -> string { itoa :: proc(buf: []byte, i: int) -> string {
return append_int(buf, i64(i), 10) return write_int(buf, i64(i), 10)
} }
/* /*
Converts a string to an integer value Converts a string to an integer value
@@ -1590,14 +1623,14 @@ atof :: proc(s: string) -> f64 {
v, _ := parse_f64(s) v, _ := parse_f64(s)
return v return v
} }
// Alias to `append_float` // Alias to `write_float`
ftoa :: append_float ftoa :: write_float
/* /*
Appends a float64 value as a string to the given buffer with the specified format and precision Writes a float64 value as a string to the given buffer with the specified format and precision
**Inputs** **Inputs**
- buf: The buffer to append the float64 value to - buf: The buffer to write the float64 value to
- f: The float64 value to be appended - f: The float64 value to be written
- fmt: The byte specifying the format to use for the conversion - fmt: The byte specifying the format to use for the conversion
- prec: The precision to use for the conversion - prec: The precision to use for the conversion
- bit_size: The size of the float in bits (32 or 64) - bit_size: The size of the float in bits (32 or 64)
@@ -1606,9 +1639,9 @@ Example:
import "core:fmt" import "core:fmt"
import "core:strconv" import "core:strconv"
append_float_example :: proc() { write_float_example :: proc() {
buf: [8]byte buf: [8]byte
result := strconv.append_float(buf[:], 3.14159, 'f', 2, 64) result := strconv.write_float(buf[:], 3.14159, 'f', 2, 64)
fmt.println(result, buf) fmt.println(result, buf)
} }
@@ -1617,16 +1650,16 @@ Output:
+3.14 [43, 51, 46, 49, 52, 0, 0, 0] +3.14 [43, 51, 46, 49, 52, 0, 0, 0]
**Returns** **Returns**
- The resulting string after appending the float - The resulting string after writing the float
*/ */
append_float :: proc(buf: []byte, f: f64, fmt: byte, prec, bit_size: int) -> string { write_float :: proc(buf: []byte, f: f64, fmt: byte, prec, bit_size: int) -> string {
return string(generic_ftoa(buf, f, fmt, prec, bit_size)) return string(generic_ftoa(buf, f, fmt, prec, bit_size))
} }
/* /*
Appends a quoted string representation of the input string to a given byte slice and returns the result as a string Writes a quoted string representation of the input string to a given byte slice and returns the result as a string
**Inputs** **Inputs**
- buf: The byte slice to which the quoted string will be appended - buf: The byte slice to which the quoted string will be written
- str: The input string to be quoted - str: The input string to be quoted
!! ISSUE !! NOT EXPECTED -- "\"hello\"" was expected !! ISSUE !! NOT EXPECTED -- "\"hello\"" was expected
@@ -1646,7 +1679,7 @@ Output:
"'h''e''l''l''o'" [34, 39, 104, 39, 39, 101, 39, 39, 108, 39, 39, 108, 39, 39, 111, 39, 34, 0, 0, 0] "'h''e''l''l''o'" [34, 39, 104, 39, 39, 101, 39, 39, 108, 39, 39, 108, 39, 39, 111, 39, 34, 0, 0, 0]
**Returns** **Returns**
- The resulting string after appending the quoted string representation - The resulting string after writing the quoted string representation
*/ */
quote :: proc(buf: []byte, str: string) -> string { quote :: proc(buf: []byte, str: string) -> string {
write_byte :: proc(buf: []byte, i: ^int, bytes: ..byte) { write_byte :: proc(buf: []byte, i: ^int, bytes: ..byte) {
@@ -1686,10 +1719,10 @@ quote :: proc(buf: []byte, str: string) -> string {
return string(buf[:i]) return string(buf[:i])
} }
/* /*
Appends a quoted rune representation of the input rune to a given byte slice and returns the result as a string Writes a quoted rune representation of the input rune to a given byte slice and returns the result as a string
**Inputs** **Inputs**
- buf: The byte slice to which the quoted rune will be appended - buf: The byte slice to which the quoted rune will be written
- r: The input rune to be quoted - r: The input rune to be quoted
Example: Example:
@@ -1707,7 +1740,7 @@ Output:
'A' [39, 65, 39, 0] 'A' [39, 65, 39, 0]
**Returns** **Returns**
- The resulting string after appending the quoted rune representation - The resulting string after writing the quoted rune representation
*/ */
quote_rune :: proc(buf: []byte, r: rune) -> string { quote_rune :: proc(buf: []byte, r: rune) -> string {
write_byte :: proc(buf: []byte, i: ^int, bytes: ..byte) { write_byte :: proc(buf: []byte, i: ^int, bytes: ..byte) {
@@ -1750,7 +1783,7 @@ quote_rune :: proc(buf: []byte, r: rune) -> string {
if r < 32 { if r < 32 {
write_string(buf, &i, "\\x") write_string(buf, &i, "\\x")
b: [2]byte b: [2]byte
s := append_bits(b[:], u64(r), 16, true, 64, digits, nil) s := write_bits(b[:], u64(r), 16, true, 64, digits, nil)
switch len(s) { switch len(s) {
case 0: write_string(buf, &i, "00") case 0: write_string(buf, &i, "00")
case 1: write_rune(buf, &i, '0') case 1: write_rune(buf, &i, '0')

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