diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ede32f093..3b378b693 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -32,8 +32,8 @@ jobs:
gmake -C vendor/miniaudio/src
./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_amd64
./odin check examples/all -vet -strict-style -disallow-do -target:netbsd_arm64
- ./odin check vendor/sdl3 -vet -strict-style -disallow-do -target:netbsd_amd64 -no-entry-point
- ./odin check vendor/sdl3 -vet -strict-style -disallow-do -target:netbsd_arm64 -no-entry-point
+ ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -target:netbsd_amd64 -no-entry-point
+ ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -target:netbsd_arm64 -no-entry-point
./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
./odin test tests/core/speed.odin -file -all-packages -vet -strict-style -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
@@ -64,7 +64,7 @@ jobs:
gmake -C vendor/cgltf/src
gmake -C vendor/miniaudio/src
./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
- ./odin check vendor/sdl3 -vet -strict-style -disallow-do -target:freebsd_amd64 -no-entry-point
+ ./odin check examples/all/sdl3 -vet -strict-style -disallow-do -target:freebsd_amd64 -no-entry-point
./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
./odin test tests/core/speed.odin -file -all-packages -vet -strict-style -disallow-do -o:speed -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
@@ -75,14 +75,29 @@ jobs:
fail-fast: false
matrix:
# 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 }}
- 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
steps:
+
- uses: actions/checkout@v4
- - name: Download LLVM (Linux)
+ - name: Download LLVM (MacOS Intel)
+ if: matrix.os == 'macos-13'
+ run: |
+ brew update
+ brew install llvm@20 lua@5.4 lld
+ echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH
+
+ - name: Download LLVM (MacOS ARM)
+ if: matrix.os == 'macos-14'
+ run: |
+ brew update
+ 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
@@ -90,18 +105,6 @@ jobs:
sudo ./llvm.sh 20
echo "/usr/lib/llvm-20/bin" >> $GITHUB_PATH
- - name: Download LLVM (MacOS Intel)
- if: matrix.os == 'macos-13'
- run: |
- brew install llvm@18 lua@5.4
- echo "/usr/local/opt/llvm@18/bin" >> $GITHUB_PATH
-
- - name: Download LLVM (MacOS ARM)
- if: matrix.os == 'macos-14'
- run: |
- brew install llvm@18 wasmtime lua@5.4
- echo "/opt/homebrew/opt/llvm@18/bin" >> $GITHUB_PATH
-
- name: Build Odin
run: ./build_odin.sh release
- name: Odin version
@@ -121,56 +124,61 @@ jobs:
run: ./odin run examples/demo -debug
- name: Odin check examples/all
run: ./odin check examples/all -strict-style -vet -disallow-do
- - name: Odin check vendor/sdl3
- run: ./odin check vendor/sdl3 -strict-style -vet -disallow-do -no-entry-point
+ - name: Odin check examples/all/sdl3
+ run: ./odin check examples/all/sdl3 -strict-style -vet -disallow-do -no-entry-point
- name: Normal Core library tests
- run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+ 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
- 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
- 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
- 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
run: |
cd tests/issues
./run.sh
- - name: Check benchmarks
- run: ./odin check tests/benchmark -vet -strict-style -no-entry-point
- - name: Odin check examples/all for Linux i386
- run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_i386
- if: matrix.os == 'ubuntu-latest'
- - name: Odin check examples/all for Linux arm64
- run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_arm64
- if: matrix.os == 'ubuntu-latest'
- - name: Odin check examples/all for FreeBSD amd64
- run: ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
- if: matrix.os == 'ubuntu-latest'
- - name: Odin check examples/all for OpenBSD amd64
- run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64
- if: matrix.os == 'ubuntu-latest'
-
- - name: Odin check vendor/sdl3 for Linux i386
- run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_i386
- if: matrix.os == 'ubuntu-latest'
- - name: Odin check vendor/sdl3 for Linux arm64
- run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_arm64
- if: matrix.os == 'ubuntu-latest'
- - name: Odin check vendor/sdl3 for FreeBSD amd64
- run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:freebsd_amd64
- if: matrix.os == 'ubuntu-latest'
- - name: Odin check vendor/sdl3 for OpenBSD amd64
- run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:openbsd_amd64
- if: matrix.os == 'ubuntu-latest'
-
-
- name: Run demo on WASI WASM32
run: |
./odin build examples/demo -target:wasi_wasm32 -vet -strict-style -disallow-do -out:demo
wasmtime ./demo.wasm
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:
name: Windows Build, Check, and Test
runs-on: windows-2022
@@ -206,32 +214,32 @@ jobs:
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
odin check examples/all -vet -strict-style -disallow-do
- - name: Odin check vendor/sdl3
+ - name: Odin check examples/all/sdl3
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
- odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point
+ odin check examples/all/sdl3 -vet -strict-style -disallow-do -no-entry-point
- name: Core library tests
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
- odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+ 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
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
- odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+ 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
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
copy vendor\lua\5.4\windows\*.dll .
- odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+ 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
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
- odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+ 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
shell: cmd
run: |
@@ -293,8 +301,8 @@ jobs:
- name: Odin check examples/all
run: ./odin check examples/all -target:linux_riscv64 -vet -strict-style -disallow-do
- - name: Odin check vendor/sdl3
- run: ./odin check vendor/sdl3 -target:linux_riscv64 -vet -strict-style -disallow-do -no-entry-point
+ - name: Odin check examples/all/sdl3
+ run: ./odin check examples/all/sdl3 -target:linux_riscv64 -vet -strict-style -disallow-do -no-entry-point
- name: Install riscv64 toolchain and qemu
run: sudo apt-get install -y qemu-user qemu-user-static gcc-12-riscv64-linux-gnu libc6-riscv64-cross
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index 314711efb..eb67eb209 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -49,12 +49,12 @@ jobs:
- uses: actions/checkout@v4
- uses: jirutka/setup-alpine@v1
with:
- branch: v3.20
+ branch: edge
- name: (Linux) Download LLVM
run: |
apk add --no-cache \
- musl-dev llvm18-dev clang18 git mold lz4 \
- libxml2-static llvm18-static zlib-static zstd-static \
+ musl-dev llvm20-dev clang20 git mold lz4 \
+ libxml2-static llvm20-static zlib-static zstd-static \
make
shell: alpine.sh --root {0}
- name: build odin
@@ -93,8 +93,9 @@ jobs:
- uses: actions/checkout@v4
- name: Download LLVM and setup PATH
run: |
- brew install llvm@18 dylibbundler
- echo "/usr/local/opt/llvm@18/bin" >> $GITHUB_PATH
+ brew update
+ brew install llvm@20 dylibbundler lld
+
- name: build odin
# These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to
# not link with libunwind bundled with LLVM but link with libunwind on the system.
@@ -130,8 +131,9 @@ jobs:
- uses: actions/checkout@v4
- name: Download LLVM and setup PATH
run: |
- brew install llvm@18 dylibbundler
- echo "/opt/homebrew/opt/llvm@18/bin" >> $GITHUB_PATH
+ brew update
+ brew install llvm@20 dylibbundler lld
+
- name: build odin
# These -L makes the linker prioritize system libraries over LLVM libraries, this is mainly to
# not link with libunwind bundled with LLVM but link with libunwind on the system.
diff --git a/.gitignore b/.gitignore
index 32e5f5b0f..1187596de 100644
--- a/.gitignore
+++ b/.gitignore
@@ -293,5 +293,6 @@ build.sh
# RAD debugger project file
*.raddbg
-
+*.rdi
+tests/issues/build/*
misc/featuregen/featuregen
diff --git a/README.md b/README.md
index cc40fb1ad..5e48f4168 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@
-
+
diff --git a/base/builtin/builtin.odin b/base/builtin/builtin.odin
index 227ceeb49..0196e2030 100644
--- a/base/builtin/builtin.odin
+++ b/base/builtin/builtin.odin
@@ -7,13 +7,232 @@ nil :: nil
false :: 0!=0
true :: 0==0
-ODIN_OS :: ODIN_OS
-ODIN_ARCH :: ODIN_ARCH
-ODIN_ENDIAN :: ODIN_ENDIAN
-ODIN_VENDOR :: ODIN_VENDOR
-ODIN_VERSION :: ODIN_VERSION
-ODIN_ROOT :: ODIN_ROOT
-ODIN_DEBUG :: ODIN_DEBUG
+// The following constants are added in `checker.cpp`'s `init_universal` procedure.
+
+/*
+ An `enum` value indicating the target's CPU architecture.
+ Possible values are: `.amd64`, `.i386`, `.arm32`, `.arm64`, `.wasm32`, `.wasm64p32`, and `.riscv64`.
+*/
+ODIN_ARCH :: ODIN_ARCH
+
+/*
+ A `string` indicating the target's CPU architecture.
+ Possible values are: "amd64", "i386", "arm32", "arm64", "wasm32", "wasm64p32", "riscv64".
+*/
+ODIN_ARCH_STRING :: ODIN_ARCH_STRING
+
+/*
+ An `enum` value indicating the type of compiled output, chosen using `-build-mode`.
+ Possible values are: `.Executable`, `.Dynamic`, `.Static`, `.Object`, `.Assembly`, and `.LLVM_IR`.
+*/
+ODIN_BUILD_MODE :: ODIN_BUILD_MODE
+
+/*
+ A `string` containing the name of the folder that contains the entry point,
+ e.g. for `%ODIN_ROOT%/examples/demo`, this would contain `demo`.
+*/
+ODIN_BUILD_PROJECT_NAME :: ODIN_BUILD_PROJECT_NAME
+
+/*
+ An `i64` containing the time at which the executable was compiled, in nanoseconds.
+ This is compatible with the `time.Time` type, i.e. `time.Time{_nsec=ODIN_COMPILE_TIMESTAMP}`
+*/
+ODIN_COMPILE_TIMESTAMP :: ODIN_COMPILE_TIMESTAMP
+
+/*
+ `true` if the `-debug` command line switch is passed, which enables debug info generation.
+*/
+ODIN_DEBUG :: ODIN_DEBUG
+
+/*
+ `true` if the `-default-to-nil-allocator` command line switch is passed,
+ which sets the initial `context.allocator` to an allocator that does nothing.
+*/
+ODIN_DEFAULT_TO_NIL_ALLOCATOR :: ODIN_DEFAULT_TO_NIL_ALLOCATOR
+
+/*
+ `true` if the `-default-to-panic-allocator` command line switch is passed,
+ which sets the initial `context.allocator` to an allocator that panics if allocated from.
+*/
+ODIN_DEFAULT_TO_PANIC_ALLOCATOR :: ODIN_DEFAULT_TO_PANIC_ALLOCATOR
+
+/*
+ `true` if the `-disable-assert` command line switch is passed,
+ which removes all calls to `assert` from the program.
+*/
+ODIN_DISABLE_ASSERT :: ODIN_DISABLE_ASSERT
+
+/*
+ An `enum` value indicating the endianness of the target.
+ Possible values are: `.Little` and `.Big`.
+*/
+ODIN_ENDIAN :: ODIN_ENDIAN
+
+/*
+ An `string` indicating the endianness of the target.
+ Possible values are: "little" and "big".
+*/
+ODIN_ENDIAN_STRING :: ODIN_ENDIAN_STRING
+
+/*
+ An `enum` value set using the `-error-pos-style` switch, indicating the source location style used for compile errors and warnings.
+ Possible values are: `.Default` (Odin-style) and `.Unix`.
+*/
+ODIN_ERROR_POS_STYLE :: ODIN_ERROR_POS_STYLE
+
+/*
+ `true` if the `-foreign-error-procedures` command line switch is passed,
+ which inhibits generation of runtime error procedures, so that they can be in a separate compilation unit.
+*/
+ODIN_FOREIGN_ERROR_PROCEDURES :: ODIN_FOREIGN_ERROR_PROCEDURES
+
+/*
+ A `string` describing the microarchitecture used for code generation.
+ If not set using the `-microarch` command line switch, the compiler will pick a default.
+ Possible values include, but are not limited to: "sandybridge", "x86-64-v2".
+*/
+ODIN_MICROARCH_STRING :: ODIN_MICROARCH_STRING
+
+/*
+ An `int` value representing the minimum OS version given to the linker, calculated as `major * 10_000 + minor * 100 + revision`.
+ If not set using the `-minimum-os-version` command line switch, it defaults to `0`, except on Darwin, where it's `11_00_00`.
+*/
+ODIN_MINIMUM_OS_VERSION :: ODIN_MINIMUM_OS_VERSION
+
+/*
+ `true` if the `-no-bounds-check` command line switch is passed, which disables bounds checking at runtime.
+*/
+ODIN_NO_BOUNDS_CHECK :: ODIN_NO_BOUNDS_CHECK
+
+/*
+ `true` if the `-no-crt` command line switch is passed, which inhibits linking with the C Runtime Library, a.k.a. LibC.
+*/
+ODIN_NO_CRT :: ODIN_NO_CRT
+
+/*
+ `true` if the `-no-entry-point` command line switch is passed, which makes the declaration of a `main` procedure optional.
+*/
+ODIN_NO_ENTRY_POINT :: ODIN_NO_ENTRY_POINT
+
+/*
+ `true` if the `-no-rtti` command line switch is passed, which inhibits generation of full Runtime Type Information.
+*/
+ODIN_NO_RTTI :: ODIN_NO_RTTI
+
+/*
+ `true` if the `-no-type-assert` command line switch is passed, which disables type assertion checking program wide.
+*/
+ODIN_NO_TYPE_ASSERT :: ODIN_NO_TYPE_ASSERT
+
+/*
+ An `enum` value indicating the optimization level selected using the `-o` command line switch.
+ Possible values are: `.None`, `.Minimal`, `.Size`, `.Speed`, and `.Aggressive`.
+
+ If `ODIN_OPTIMIZATION_MODE` is anything other than `.None` or `.Minimal`, the compiler will also perform a unity build,
+ and `ODIN_USE_SEPARATE_MODULES` will be set to `false` as a result.
+*/
+ODIN_OPTIMIZATION_MODE :: ODIN_OPTIMIZATION_MODE
+
+/*
+ An `enum` value indicating what the target operating system is.
+*/
+ODIN_OS :: ODIN_OS
+
+/*
+ A `string` indicating what the target operating system is.
+*/
+ODIN_OS_STRING :: ODIN_OS_STRING
+
+/*
+ An `enum` value indicating the platform subtarget, chosen using the `-subtarget` switch.
+ Possible values are: `.Default` `.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
@@ -119,7 +338,8 @@ jmag :: proc(value: Quaternion) -> Float ---
kmag :: proc(value: Quaternion) -> Float ---
conj :: proc(value: Complex_Or_Quaternion) -> Complex_Or_Quaternion ---
-expand_values :: proc(value: Struct_Or_Array) -> (A, B, C, ...) ---
+expand_values :: proc(value: Struct_Or_Array) -> (A, B, C, ...) ---
+compress_values :: proc(values: ...) -> Struct_Or_Array_Like_Type ---
min :: proc(values: ..T) -> T ---
max :: proc(values: ..T) -> T ---
@@ -130,3 +350,6 @@ soa_zip :: proc(slices: ...) -> #soa[]Struct ---
soa_unzip :: proc(value: $S/#soa[]$E) -> (slices: ...) ---
unreachable :: proc() -> ! ---
+
+// Where T is a string, slice, dynamic array, or pointer to an array type
+raw_data :: proc(t: $T) -> rawptr
\ No newline at end of file
diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin
index bec452007..c1d16c5e4 100644
--- a/base/intrinsics/intrinsics.odin
+++ b/base/intrinsics/intrinsics.odin
@@ -169,6 +169,7 @@ type_is_union :: proc($T: typeid) -> bool ---
type_is_enum :: proc($T: typeid) -> bool ---
type_is_proc :: proc($T: typeid) -> bool ---
type_is_bit_set :: proc($T: typeid) -> bool ---
+type_is_bit_field :: proc($T: typeid) -> bool ---
type_is_simd_vector :: proc($T: typeid) -> bool ---
type_is_matrix :: proc($T: typeid) -> bool ---
@@ -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_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) ---
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_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_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_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)---
@@ -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_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_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_selector :: struct{}
objc_class :: struct{}
+objc_ivar :: struct{}
+
objc_id :: ^objc_object
objc_SEL :: ^objc_selector
objc_Class :: ^objc_class
+objc_Ivar :: ^objc_ivar
objc_find_selector :: proc($name: string) -> objc_SEL ---
objc_register_selector :: proc($name: string) -> objc_SEL ---
objc_find_class :: proc($name: string) -> objc_Class ---
objc_register_class :: proc($name: string) -> objc_Class ---
-
+objc_ivar_get :: proc(self: ^$T) -> ^$U ---
valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr ---
diff --git a/base/runtime/core.odin b/base/runtime/core.odin
index c6bb87af4..3308f3ad7 100644
--- a/base/runtime/core.odin
+++ b/base/runtime/core.odin
@@ -125,7 +125,6 @@ Type_Info_Parameters :: struct { // Only used for procedures parameters and resu
types: []^Type_Info,
names: []string,
}
-Type_Info_Tuple :: Type_Info_Parameters // Will be removed eventually
Type_Info_Struct_Flags :: distinct bit_set[Type_Info_Struct_Flag; u8]
Type_Info_Struct_Flag :: enum u8 {
@@ -587,10 +586,14 @@ ALL_ODIN_OS_TYPES :: Odin_OS_Types{
Odin_Platform_Subtarget_Type :: enum int {
Default,
iOS,
+ Android,
}
*/
Odin_Platform_Subtarget_Type :: type_of(ODIN_PLATFORM_SUBTARGET)
+Odin_Platform_Subtarget_Types :: bit_set[Odin_Platform_Subtarget_Type]
+
+
/*
// Defined internally by the compiler
Odin_Sanitizer_Flag :: enum u32 {
diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin
index cf379aacb..bc201b6e1 100644
--- a/base/runtime/core_builtin.odin
+++ b/base/runtime/core_builtin.odin
@@ -648,6 +648,9 @@ append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: i
@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 {
+ when !ODIN_NO_BOUNDS_CHECK {
+ ensure(index >= 0, "Index must be positive.", loc)
+ }
if array == nil {
return
}
@@ -666,6 +669,9 @@ inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcas
@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 {
+ when !ODIN_NO_BOUNDS_CHECK {
+ ensure(index >= 0, "Index must be positive.", loc)
+ }
if array == nil {
return
}
@@ -689,6 +695,9 @@ inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadca
@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 {
+ when !ODIN_NO_BOUNDS_CHECK {
+ ensure(index >= 0, "Index must be positive.", loc)
+ }
if array == nil {
return
}
diff --git a/base/runtime/default_temp_allocator_arena.odin b/base/runtime/default_temp_allocator_arena.odin
index 5f25dac95..74994344a 100644
--- a/base/runtime/default_temp_allocator_arena.odin
+++ b/base/runtime/default_temp_allocator_arena.odin
@@ -1,6 +1,7 @@
package runtime
import "base:intrinsics"
+import "base:sanitizer"
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.capacity = uint(end - uintptr(block.base))
+ sanitizer.address_poison(block.base, block.capacity)
+
// Should be zeroed
assert(block.used == 0)
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) {
if block_to_free != nil {
allocator := block_to_free.allocator
+ sanitizer.address_unpoison(block_to_free.base, block_to_free.capacity)
mem_free(block_to_free, allocator, loc)
}
}
@@ -83,6 +87,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint)
return
}
data = block.base[block.used+alignment_offset:][:min_size]
+ sanitizer.address_unpoison(block.base[block.used:block.used+size])
block.used += size
return
}
@@ -162,6 +167,7 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
if arena.curr_block != nil {
intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used)
arena.curr_block.used = 0
+ sanitizer.address_poison(arena.curr_block.base, arena.curr_block.capacity)
}
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
block.used = uint(new_end)
data = block.base[start:new_end]
+ sanitizer.address_unpoison(data)
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)
amount_to_zero := block.used-temp.used
intrinsics.mem_zero(block.base[temp.used:], amount_to_zero)
+ sanitizer.address_poison(block.base[temp.used:block.capacity])
block.used = temp.used
arena.total_used -= amount_to_zero
}
diff --git a/base/runtime/dynamic_map_internal.odin b/base/runtime/dynamic_map_internal.odin
index 96ae9c73c..7b65a2fa0 100644
--- a/base/runtime/dynamic_map_internal.odin
+++ b/base/runtime/dynamic_map_internal.odin
@@ -1029,3 +1029,32 @@ default_hasher_cstring :: proc "contextless" (data: rawptr, seed: uintptr) -> ui
h &= HASH_MASK
return uintptr(h) | uintptr(uintptr(h) == 0)
}
+
+default_hasher_f64 :: proc "contextless" (f: f64, seed: uintptr) -> uintptr {
+ f := f
+ buf: [size_of(f)]u8
+ if f == 0 {
+ return default_hasher(&buf, seed, size_of(buf))
+ }
+ if f != f {
+ // TODO(bill): What should the logic be for NaNs?
+ return default_hasher(&f, seed, size_of(f))
+ }
+ return default_hasher(&f, seed, size_of(f))
+}
+
+default_hasher_complex128 :: proc "contextless" (x, y: f64, seed: uintptr) -> uintptr {
+ seed := seed
+ seed = default_hasher_f64(x, seed)
+ seed = default_hasher_f64(y, seed)
+ return seed
+}
+
+default_hasher_quaternion256 :: proc "contextless" (x, y, z, w: f64, seed: uintptr) -> uintptr {
+ seed := seed
+ seed = default_hasher_f64(x, seed)
+ seed = default_hasher_f64(y, seed)
+ seed = default_hasher_f64(z, seed)
+ seed = default_hasher_f64(w, seed)
+ return seed
+}
\ No newline at end of file
diff --git a/base/runtime/heap_allocator_windows.odin b/base/runtime/heap_allocator_windows.odin
index e07df7559..04a6f149b 100644
--- a/base/runtime/heap_allocator_windows.odin
+++ b/base/runtime/heap_allocator_windows.odin
@@ -1,5 +1,7 @@
package runtime
+import "../sanitizer"
+
foreign import kernel32 "system:Kernel32.lib"
@(private="file")
@@ -16,7 +18,10 @@ foreign kernel32 {
_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
HEAP_ZERO_MEMORY :: 0x00000008
- return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size))
+ 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 {
if new_size == 0 {
@@ -28,7 +33,10 @@ _heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
}
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) {
if ptr == nil {
diff --git a/base/runtime/internal.odin b/base/runtime/internal.odin
index 59811a525..907b187f1 100644
--- a/base/runtime/internal.odin
+++ b/base/runtime/internal.odin
@@ -16,6 +16,12 @@ RUNTIME_REQUIRE :: false // !ODIN_TILDE
@(private)
__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)
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 x == y: return true
}
- a, b := ([^]byte)(x), ([^]byte)(y)
- length := uint(n)
+ a, b := cast([^]byte)x, cast([^]byte)y
- 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] {
return false
}
}
return true
-
-/*
-
- when size_of(uint) == 8 {
- if word_length := length >> 3; word_length != 0 {
- for _ in 0..> 2; word_length != 0 {
- for _ in 0.. int #no_bounds_check {
+
+memory_compare :: proc "contextless" (x, y: rawptr, n: int) -> int #no_bounds_check {
switch {
- case a == b: return 0
- case a == nil: return -1
- case b == nil: return +1
+ case x == y: return 0
+ case x == nil: return -1
+ case y == nil: return +1
}
+ a, b := cast([^]byte)x, cast([^]byte)y
+
+ n := uint(n)
+ i := uint(0)
+ m := uint(0)
- x := uintptr(a)
- y := uintptr(b)
- n := uintptr(n)
-
- SU :: size_of(uintptr)
- fast := n/SU + 1
- offset := (fast-1)*SU
- curr_block := uintptr(0)
- if n < SU {
- fast = 0
- }
-
- for /**/; curr_block < fast; curr_block += 1 {
- va := (^uintptr)(x + curr_block * size_of(uintptr))^
- vb := (^uintptr)(y + curr_block * size_of(uintptr))^
- if va ~ vb != 0 {
- for pos := curr_block*SU; pos < n; pos += 1 {
- a := (^byte)(x+pos)^
- b := (^byte)(y+pos)^
- if a ~ b != 0 {
- return -1 if (int(a) - int(b)) < 0 else +1
+ 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 -1 if a[i+index_reduce] < b[i+index_reduce] else +1
}
}
}
}
- for /**/; offset < n; offset += 1 {
- a := (^byte)(x+offset)^
- b := (^byte)(y+offset)^
- if a ~ b != 0 {
- return -1 if (int(a) - int(b)) < 0 else +1
+ 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])
+ 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
}
memory_compare_zero :: proc "contextless" (a: rawptr, n: int) -> int #no_bounds_check {
- x := uintptr(a)
- n := uintptr(n)
+ n := uint(n)
+ i := uint(0)
+ m := uint(0)
- SU :: size_of(uintptr)
- fast := n/SU + 1
- offset := (fast-1)*SU
- curr_block := uintptr(0)
- if n < SU {
- fast = 0
+ // Because we're comparing against zero, we never return -1, as that would
+ // indicate the compared value is less than zero.
+ //
+ // Note that a zero return value here means equality.
+
+ 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 {
- va := (^uintptr)(x + curr_block * size_of(uintptr))^
- if va ~ 0 != 0 {
- for pos := curr_block*SU; pos < n; pos += 1 {
- a := (^byte)(x+pos)^
- if a ~ 0 != 0 {
- return -1 if int(a) < 0 else +1
+ for /**/; i < n; i += 1 {
+ if bytes[i] != 0 {
+ return 1
+ }
+ }
+ return 0
+}
+
+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 {
- a := (^byte)(x+offset)^
- if a ~ 0 != 0 {
- return -1 if int(a) < 0 else +1
+ 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])
+ 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 {
@@ -1106,3 +1203,11 @@ __read_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uin
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) ---
+ }
+}
+
diff --git a/base/runtime/procs_darwin.odin b/base/runtime/procs_darwin.odin
index c3fc46af1..0aec57e80 100644
--- a/base/runtime/procs_darwin.odin
+++ b/base/runtime/procs_darwin.odin
@@ -2,21 +2,34 @@
package runtime
@(priority_index=-1e6)
-foreign import "system:Foundation.framework"
+foreign import ObjC "system:objc"
import "base:intrinsics"
-objc_id :: ^intrinsics.objc_object
+objc_id :: ^intrinsics.objc_object
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 ---
- 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_fpret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 ---
objc_msgSend_fp2ret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 ---
objc_msgSend_stret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
+
+ objc_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 ---
}
+
diff --git a/base/sanitizer/address.odin b/base/sanitizer/address.odin
new file mode 100644
index 000000000..edfdfc172
--- /dev/null
+++ b/base/sanitizer/address.odin
@@ -0,0 +1,601 @@
+#+no-instrumentation
+package sanitizer
+
+Address_Death_Callback :: #type proc "c" (pc: rawptr, bp: rawptr, sp: rawptr, addr: rawptr, is_write: i32, access_size: uint)
+
+@(private="file")
+ASAN_ENABLED :: .Address in ODIN_SANITIZER_FLAGS
+
+@(private="file")
+@(default_calling_convention="system")
+foreign {
+ __asan_poison_memory_region :: proc(address: rawptr, size: uint) ---
+ __asan_unpoison_memory_region :: proc(address: rawptr, size: uint) ---
+ __sanitizer_set_death_callback :: proc(callback: Address_Death_Callback) ---
+ __asan_region_is_poisoned :: proc(begin: rawptr, size: uint) -> rawptr ---
+ __asan_address_is_poisoned :: proc(addr: rawptr) -> i32 ---
+ __asan_describe_address :: proc(addr: rawptr) ---
+ __asan_report_present :: proc() -> i32 ---
+ __asan_get_report_pc :: proc() -> rawptr ---
+ __asan_get_report_bp :: proc() -> rawptr ---
+ __asan_get_report_sp :: proc() -> rawptr ---
+ __asan_get_report_address :: proc() -> rawptr ---
+ __asan_get_report_access_type :: proc() -> i32 ---
+ __asan_get_report_access_size :: proc() -> uint ---
+ __asan_get_report_description :: proc() -> cstring ---
+ __asan_locate_address :: proc(addr: rawptr, name: rawptr, name_size: uint, region_address: ^rawptr, region_size: ^uint) -> cstring ---
+ __asan_get_alloc_stack :: proc(addr: rawptr, trace: rawptr, size: uint, thread_id: ^i32) -> uint ---
+ __asan_get_free_stack :: proc(addr: rawptr, trace: rawptr, size: uint, thread_id: ^i32) -> uint ---
+ __asan_get_shadow_mapping :: proc(shadow_scale: ^uint, shadow_offset: ^uint) ---
+ __asan_print_accumulated_stats :: proc() ---
+ __asan_get_current_fake_stack :: proc() -> rawptr ---
+ __asan_addr_is_in_fake_stack :: proc(fake_stack: rawptr, addr: rawptr, beg: ^rawptr, end: ^rawptr) -> rawptr ---
+ __asan_handle_no_return :: proc() ---
+ __asan_update_allocation_context :: proc(addr: rawptr) -> i32 ---
+}
+
+Address_Access_Type :: enum {
+ none,
+ read,
+ write,
+}
+
+Address_Located_Address :: struct {
+ category: string,
+ name: string,
+ region: []byte,
+}
+
+Address_Shadow_Mapping :: struct {
+ scale: 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) {
+ when ASAN_ENABLED {
+ __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) {
+ when ASAN_ENABLED {
+ __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) {
+ when ASAN_ENABLED {
+ __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) {
+ when ASAN_ENABLED {
+ __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) {
+ when ASAN_ENABLED {
+ assert_contextless(len >= 0)
+ __asan_poison_memory_region(ptr, uint(len))
+ }
+}
+
+/*
+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) {
+ when ASAN_ENABLED {
+ assert_contextless(len >= 0)
+ __asan_unpoison_memory_region(ptr, uint(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_uint :: proc "contextless" (ptr: rawptr, len: uint) {
+ when ASAN_ENABLED {
+ __asan_unpoison_memory_region(ptr, len)
+ }
+}
+
+address_poison :: proc {
+ address_poison_slice,
+ address_poison_ptr,
+ address_poison_rawptr,
+ address_poison_rawptr_uint,
+}
+
+address_unpoison :: proc {
+ address_unpoison_slice,
+ address_unpoison_ptr,
+ 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) {
+ when ASAN_ENABLED {
+ __sanitizer_set_death_callback(callback)
+ }
+}
+
+/*
+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 {
+ return __asan_region_is_poisoned(raw_data(region), size_of(E) * len(region))
+ } else {
+ return nil
+ }
+}
+
+/*
+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 {
+ when ASAN_ENABLED {
+ return __asan_region_is_poisoned(ptr, size_of(T))
+ } else {
+ return nil
+ }
+}
+
+/*
+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 {
+ when ASAN_ENABLED {
+ assert_contextless(len >= 0)
+ return __asan_region_is_poisoned(region, uint(len))
+ } else {
+ return nil
+ }
+}
+
+/*
+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_slice,
+ address_region_is_poisoned_ptr,
+ address_region_is_poisoned_rawptr,
+ address_region_is_poisoned_rawptr_uint,
+}
+
+/*
+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 {
+ return __asan_address_is_poisoned(address) != 0
+ } else {
+ return false
+ }
+}
+
+/*
+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) {
+ when ASAN_ENABLED {
+ __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 {
+ when ASAN_ENABLED {
+ return __asan_report_present() != 0
+ } else {
+ return false
+ }
+}
+
+/*
+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 {
+ when ASAN_ENABLED {
+ return __asan_get_report_pc()
+ } else {
+ return nil
+ }
+}
+
+/*
+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 {
+ when ASAN_ENABLED {
+ return __asan_get_report_bp()
+ } else {
+ return nil
+ }
+}
+
+/*
+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 {
+ when ASAN_ENABLED {
+ return __asan_get_report_sp()
+ } else {
+ return nil
+ }
+}
+
+/*
+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 {
+ when ASAN_ENABLED {
+ return __asan_get_report_address()
+ } else {
+ return nil
+ }
+}
+
+/*
+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 {
+ when ASAN_ENABLED {
+ if ! address_report_present() {
+ return .none
+ }
+ return __asan_get_report_access_type() == 0 ? .read : .write
+ } else {
+ 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 {
+ when ASAN_ENABLED {
+ return __asan_get_report_access_size()
+ } else {
+ return 0
+ }
+}
+
+/*
+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 {
+ when ASAN_ENABLED {
+ return string(__asan_get_report_description())
+ } else {
+ return ""
+ }
+}
+
+/*
+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 {
+ out_addr: rawptr
+ out_size: uint
+ 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] },
+ } else {
+ 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) {
+ when ASAN_ENABLED {
+ out_thread: i32
+ __asan_get_alloc_stack(addr, raw_data(data), len(data), &out_thread)
+ return data, int(out_thread)
+ } else {
+ return {}, 0
+ }
+}
+
+/*
+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) {
+ when ASAN_ENABLED {
+ out_thread: i32
+ __asan_get_free_stack(addr, raw_data(data), len(data), &out_thread)
+ return data, int(out_thread)
+ } else {
+ return {}, 0
+ }
+}
+
+/*
+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 {
+ when ASAN_ENABLED {
+ result: Address_Shadow_Mapping
+ __asan_get_shadow_mapping(&result.scale, &result.offset)
+ return result
+ } else {
+ return {}
+ }
+}
+
+/*
+Prints asan statistics to `stderr`
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
+address_print_accumulated_stats :: proc "contextless" () {
+ when ASAN_ENABLED {
+ __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 {
+ when ASAN_ENABLED {
+ return __asan_get_current_fake_stack()
+ } else {
+ return nil
+ }
+}
+
+/*
+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) {
+ when ASAN_ENABLED {
+ begin: rawptr
+ end: rawptr
+ if __asan_addr_is_in_fake_stack(fake_stack, addr, &begin, &end) == nil {
+ return {}, false
+ }
+ return ((cast([^]byte)begin)[:uintptr(end)-uintptr(begin)]), true
+ } else {
+ return {}, false
+ }
+}
+
+/*
+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" () {
+ when ASAN_ENABLED {
+ __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 {
+ when ASAN_ENABLED {
+ return __asan_update_allocation_context(addr) != 0
+ } else {
+ return false
+ }
+}
+
diff --git a/base/sanitizer/doc.odin b/base/sanitizer/doc.odin
new file mode 100644
index 000000000..707f41ce0
--- /dev/null
+++ b/base/sanitizer/doc.odin
@@ -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
+
diff --git a/base/sanitizer/memory.odin b/base/sanitizer/memory.odin
new file mode 100644
index 000000000..b16309a49
--- /dev/null
+++ b/base/sanitizer/memory.odin
@@ -0,0 +1,74 @@
+#+no-instrumentation
+package sanitizer
+
+@(private="file")
+MSAN_ENABLED :: .Memory in ODIN_SANITIZER_FLAGS
+
+@(private="file")
+@(default_calling_convention="system")
+foreign {
+ __msan_unpoison :: proc(addr: rawptr, size: uint) ---
+}
+
+/*
+Marks a slice as fully initialized.
+
+Code instrumented with `-sanitize:memory` will be permitted to access any
+address within the slice as if it had already been initialized.
+
+When msan is not enabled this procedure does nothing.
+*/
+memory_unpoison_slice :: proc "contextless" (region: $T/[]$E) {
+ when MSAN_ENABLED {
+ __msan_unpoison(raw_data(region), size_of(E) * len(region))
+ }
+}
+
+/*
+Marks a pointer as fully initialized.
+
+Code instrumented with `-sanitize:memory` will be permitted to access memory
+within the region the pointer points to as if it had already been initialized.
+
+When msan is not enabled this procedure does nothing.
+*/
+memory_unpoison_ptr :: proc "contextless" (ptr: ^$T) {
+ when MSAN_ENABLED {
+ __msan_unpoison(ptr, size_of(T))
+ }
+}
+
+/*
+Marks the region covering `[ptr, ptr+len)` as fully initialized.
+
+Code instrumented with `-sanitize:memory` will be permitted to access memory
+within this range as if it had already been initialized.
+
+When msan is not enabled this procedure does nothing.
+*/
+memory_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
+ when MSAN_ENABLED {
+ __msan_unpoison(ptr, uint(len))
+ }
+}
+
+/*
+Marks the region covering `[ptr, ptr+len)` as fully initialized.
+
+Code instrumented with `-sanitize:memory` will be permitted to access memory
+within this range as if it had already been initialized.
+
+When msan is not enabled this procedure does nothing.
+*/
+memory_unpoison_rawptr_uint :: proc "contextless" (ptr: rawptr, len: uint) {
+ when MSAN_ENABLED {
+ __msan_unpoison(ptr, len)
+ }
+}
+
+memory_unpoison :: proc {
+ memory_unpoison_slice,
+ memory_unpoison_ptr,
+ memory_unpoison_rawptr,
+ memory_unpoison_rawptr_uint,
+}
diff --git a/bin/llvm/windows/clang_rt.asan-x86_64.lib b/bin/llvm/windows/clang_rt.asan-x86_64.lib
index d7dfcbb1c..0b209dc8d 100644
Binary files a/bin/llvm/windows/clang_rt.asan-x86_64.lib and b/bin/llvm/windows/clang_rt.asan-x86_64.lib differ
diff --git a/build.bat b/build.bat
index 4c015e133..b1ff9b173 100644
--- a/build.bat
+++ b/build.bat
@@ -19,16 +19,27 @@ if "%VSCMD_ARG_TGT_ARCH%" neq "x64" (
)
)
+where /Q git.exe || goto skip_git_hash
+if not exist .git\ goto skip_git_hash
+for /f "tokens=1,2" %%i IN ('git show "--pretty=%%cd %%h" "--date=format:%%Y-%%m-%%d" --no-patch --no-notes HEAD') do (
+ set CURR_DATE_TIME=%%i
+ set GIT_SHA=%%j
+)
+if %ERRORLEVEL% equ 0 (
+ goto have_git_hash_and_date
+)
+:skip_git_hash
pushd misc
cl /nologo get-date.c
-popd
-
-for /f %%i in ('misc\get-date') do (
+for /f %%i in ('get-date') do (
set CURR_DATE_TIME=%%i
+ rem Don't set GIT_SHA
)
+popd
+:have_git_hash_and_date
set curr_year=%CURR_DATE_TIME:~0,4%
-set curr_month=%CURR_DATE_TIME:~4,2%
-set curr_day=%CURR_DATE_TIME:~6,2%
+set curr_month=%CURR_DATE_TIME:~5,2%
+set curr_day=%CURR_DATE_TIME:~8,2%
:: Make sure this is a decent name and not generic
set exe_name=odin.exe
@@ -61,31 +72,14 @@ if %release_mode% equ 0 (
set V4=0
set odin_version_full="%V1%.%V2%.%V3%.%V4%"
set odin_version_raw="dev-%V1%-%V2%"
-
set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -EHsc- -GR- -GF
rem Parse source code as utf-8 even on shift-jis and other codepages
rem See https://learn.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170
set compiler_flags= %compiler_flags% /utf-8
-set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\"
+set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\" -DGIT_SHA=\"%GIT_SHA%\"
rem fileversion is defined as {Major,Minor,Build,Private: u16} so a bit limited
-set rc_flags=-nologo ^
--DV1=%V1% -DV2=%V2% -DV3=%V3% -DV4=%V4% ^
--DVF=%odin_version_full% -DNIGHTLY=%nightly%
-
-where /Q git.exe || goto skip_git_hash
-if not exist .git\ goto skip_git_hash
-for /f "tokens=1,2" %%i IN ('git show "--pretty=%%cd %%h" "--date=format:%%Y-%%m" --no-patch --no-notes HEAD') do (
- set odin_version_raw=dev-%%i
- set GIT_SHA=%%j
-)
-if %ERRORLEVEL% equ 0 (
- set compiler_defines=%compiler_defines% -DGIT_SHA=\"%GIT_SHA%\"
- set rc_flags=%rc_flags% -DGIT_SHA=%GIT_SHA% -DVP=%odin_version_raw%:%GIT_SHA%
-) else (
- set rc_flags=%rc_flags% -DVP=%odin_version_raw%
-)
-:skip_git_hash
+set rc_flags="-DGIT_SHA=%GIT_SHA% -DVP=dev-%V1%-%V2%:%GIT_SHA% nologo -DV1=%V1% -DV2=%V2% -DV3=%V3% -DV4=%V4% -DVF=%odin_version_full% -DNIGHTLY=%nightly%"
if %nightly% equ 1 set compiler_defines=%compiler_defines% -DNIGHTLY
@@ -138,6 +132,7 @@ del *.ilk > NUL 2> NUL
rc %rc_flags% %odin_rc%
cl %compiler_settings% "src\main.cpp" "src\libtommath.cpp" /link %linker_settings% -OUT:%exe_name%
+if %errorlevel% neq 0 goto end_of_build
mt -nologo -inputresource:%exe_name%;#1 -manifest misc\odin.manifest -outputresource:%exe_name%;#1 -validate_manifest -identity:"odin, processorArchitecture=amd64, version=%odin_version_full%, type=win32"
if %errorlevel% neq 0 goto end_of_build
@@ -152,4 +147,4 @@ if %release_mode% EQU 0 echo: & echo Debug compiler built. Note: run "build.bat
del *.obj > NUL 2> NUL
-:end_of_build
+:end_of_build
\ No newline at end of file
diff --git a/build_odin.sh b/build_odin.sh
index 773958d5f..0d7e8a26e 100755
--- a/build_odin.sh
+++ b/build_odin.sh
@@ -6,7 +6,6 @@ set -eu
: ${LDFLAGS=}
: ${LLVM_CONFIG=}
-CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$(date +"%Y-%m")\""
CXXFLAGS="$CXXFLAGS -std=c++14"
DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value"
LDFLAGS="$LDFLAGS -pthread -lm"
@@ -15,8 +14,12 @@ OS_NAME="$(uname -s)"
if [ -d ".git" ] && [ -n "$(command -v git)" ]; then
GIT_SHA=$(git show --pretty='%h' --no-patch --no-notes HEAD)
+ GIT_DATE=$(git show "--pretty=%cd" "--date=format:%Y-%m" --no-patch --no-notes HEAD)
CPPFLAGS="$CPPFLAGS -DGIT_SHA=\"$GIT_SHA\""
+else
+ GIT_DATE=$(date +"%Y-%m")
fi
+CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$GIT_DATE\""
error() {
printf "ERROR: %s\n" "$1"
@@ -25,7 +28,7 @@ error() {
# Brew advises people not to add llvm to their $PATH, so try and use brew to find it.
if [ -z "$LLVM_CONFIG" ] && [ -n "$(command -v brew)" ]; then
- if [ -n "$(command -v $(brew --prefix llvm)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm)/bin/llvm-config"
+ if [ -n "$(command -v $(brew --prefix llvm@20)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@20)/bin/llvm-config"
elif [ -n "$(command -v $(brew --prefix llvm@19)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@19)/bin/llvm-config"
elif [ -n "$(command -v $(brew --prefix llvm@18)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@18)/bin/llvm-config"
elif [ -n "$(command -v $(brew --prefix llvm@17)/bin/llvm-config)" ]; then LLVM_CONFIG="$(brew --prefix llvm@17)/bin/llvm-config"
diff --git a/ci/build_linux_static.sh b/ci/build_linux_static.sh
index f821cbb59..2eb99116e 100755
--- a/ci/build_linux_static.sh
+++ b/ci/build_linux_static.sh
@@ -1,8 +1,8 @@
#!/usr/bin/env sh
# Intended for use in Alpine containers, see the "nightly" Github action for a list of dependencies
-CXX="clang++-18"
-LLVM_CONFIG="llvm-config-18"
+CXX="clang++-20"
+LLVM_CONFIG="llvm-config-20"
DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value"
diff --git a/core/bufio/reader.odin b/core/bufio/reader.odin
index a875c732d..b78cac6e1 100644
--- a/core/bufio/reader.odin
+++ b/core/bufio/reader.odin
@@ -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 &&
!utf8.full_rune(b.buf[b.r:b.w]) &&
b.err == nil &&
- b.w-b.w < len(b.buf) {
+ b.w-b.r < len(b.buf) {
_reader_read_new_chunk(b) or_return
}
diff --git a/core/bytes/bytes.odin b/core/bytes/bytes.odin
index c0d25bcce..71b6ef70c 100644
--- a/core/bytes/bytes.odin
+++ b/core/bytes/bytes.odin
@@ -350,7 +350,7 @@ index_byte :: proc "contextless" (s: []byte, c: byte) -> (index: int) #no_bounds
}
c_vec: simd.u8x16 = c
- when !simd.IS_EMULATED {
+ when simd.HAS_HARDWARE_SIMD {
// Note: While this is something that could also logically take
// advantage of AVX512, the various downclocking and power
// consumption related woes make premature to have a dedicated
@@ -485,7 +485,7 @@ last_index_byte :: proc "contextless" (s: []byte, c: byte) -> int #no_bounds_che
}
c_vec: simd.u8x16 = c
- when !simd.IS_EMULATED {
+ when simd.HAS_HARDWARE_SIMD {
// Note: While this is something that could also logically take
// advantage of AVX512, the various downclocking and power
// consumption related woes make premature to have a dedicated
diff --git a/core/compress/common.odin b/core/compress/common.odin
index 242538e78..f4429b667 100644
--- a/core/compress/common.odin
+++ b/core/compress/common.odin
@@ -139,9 +139,6 @@ Context_Memory_Input :: struct #packed {
}
when size_of(rawptr) == 8 {
#assert(size_of(Context_Memory_Input) == 64)
-} else {
- // e.g. `-target:windows_i386`
- #assert(size_of(Context_Memory_Input) == 52)
}
Context_Stream_Input :: struct #packed {
diff --git a/core/container/intrusive/list/intrusive_list.odin b/core/container/intrusive/list/intrusive_list.odin
index 5b29efb22..1e116ef18 100644
--- a/core/container/intrusive/list/intrusive_list.odin
+++ b/core/container/intrusive/list/intrusive_list.odin
@@ -278,19 +278,19 @@ Example:
iterate_next_example :: proc() {
l: list.List
- one := My_Struct{value=1}
- two := My_Struct{value=2}
+ one := My_Next_Struct{value=1}
+ two := My_Next_Struct{value=2}
list.push_back(&l, &one.node)
list.push_back(&l, &two.node)
- it := list.iterator_head(l, My_Struct, "node")
+ it := list.iterator_head(l, My_Next_Struct, "node")
for num in list.iterate_next(&it) {
fmt.println(num.value)
}
}
- My_Struct :: struct {
+ My_Next_Struct :: struct {
node : list.Node,
value: int,
}
@@ -325,22 +325,22 @@ Example:
import "core:fmt"
import "core:container/intrusive/list"
- iterate_next_example :: proc() {
+ iterate_prev_example :: proc() {
l: list.List
- one := My_Struct{value=1}
- two := My_Struct{value=2}
+ one := My_Prev_Struct{value=1}
+ two := My_Prev_Struct{value=2}
list.push_back(&l, &one.node)
list.push_back(&l, &two.node)
- it := list.iterator_tail(l, My_Struct, "node")
+ it := list.iterator_tail(l, My_Prev_Struct, "node")
for num in list.iterate_prev(&it) {
fmt.println(num.value)
}
}
- My_Struct :: struct {
+ My_Prev_Struct :: struct {
node : list.Node,
value: int,
}
diff --git a/core/container/lru/lru_cache.odin b/core/container/lru/lru_cache.odin
index f8aa55dc2..d95e8e1d2 100644
--- a/core/container/lru/lru_cache.odin
+++ b/core/container/lru/lru_cache.odin
@@ -129,7 +129,7 @@ remove :: proc(c: ^$C/Cache($Key, $Value), key: Key) -> bool {
return false
}
_remove_node(c, e)
- free(node, c.node_allocator)
+ free(e, c.node_allocator)
c.count -= 1
return true
}
diff --git a/core/container/priority_queue/priority_queue.odin b/core/container/priority_queue/priority_queue.odin
index 8a6d77288..c62a821f4 100644
--- a/core/container/priority_queue/priority_queue.odin
+++ b/core/container/priority_queue/priority_queue.odin
@@ -1,6 +1,7 @@
package container_priority_queue
import "base:builtin"
+import "base:runtime"
Priority_Queue :: struct($T: typeid) {
queue: [dynamic]T,
@@ -17,13 +18,14 @@ default_swap_proc :: proc($T: typeid) -> proc(q: []T, i, j: int) {
}
}
-init :: proc(pq: ^$Q/Priority_Queue($T), less: proc(a, b: T) -> bool, swap: proc(q: []T, i, j: int), capacity := DEFAULT_CAPACITY, allocator := context.allocator) {
+init :: proc(pq: ^$Q/Priority_Queue($T), less: proc(a, b: T) -> bool, swap: proc(q: []T, i, j: int), capacity := DEFAULT_CAPACITY, allocator := context.allocator) -> (err: runtime.Allocator_Error) {
if pq.queue.allocator.procedure == nil {
pq.queue.allocator = allocator
}
- reserve(pq, capacity)
+ reserve(pq, capacity) or_return
pq.less = less
pq.swap = swap
+ return .None
}
init_from_dynamic_array :: proc(pq: ^$Q/Priority_Queue($T), queue: [dynamic]T, less: proc(a, b: T) -> bool, swap: proc(q: []T, i, j: int)) {
@@ -41,8 +43,8 @@ destroy :: proc(pq: ^$Q/Priority_Queue($T)) {
delete(pq.queue)
}
-reserve :: proc(pq: ^$Q/Priority_Queue($T), capacity: int) {
- builtin.reserve(&pq.queue, capacity)
+reserve :: proc(pq: ^$Q/Priority_Queue($T), capacity: int) -> (err: runtime.Allocator_Error) {
+ return builtin.reserve(&pq.queue, capacity)
}
clear :: proc(pq: ^$Q/Priority_Queue($T)) {
builtin.clear(&pq.queue)
@@ -103,9 +105,10 @@ fix :: proc(pq: ^$Q/Priority_Queue($T), i: int) {
}
}
-push :: proc(pq: ^$Q/Priority_Queue($T), value: T) {
- append(&pq.queue, value)
+push :: proc(pq: ^$Q/Priority_Queue($T), value: T) -> (err: runtime.Allocator_Error) {
+ append(&pq.queue, value) or_return
_shift_up(pq, builtin.len(pq.queue)-1)
+ return .None
}
pop :: proc(pq: ^$Q/Priority_Queue($T), loc := #caller_location) -> (value: T) {
@@ -130,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) {
n := builtin.len(pq.queue)
if 0 <= i && i < n {
- if n != i {
- pq.swap(pq.queue[:], i, n)
- _shift_down(pq, i, n)
- _shift_up(pq, i)
- }
- value, ok = builtin.pop_safe(&pq.queue)
+ pq.swap(pq.queue[:], i, n-1)
+ _shift_down(pq, i, n-1)
+ _shift_up(pq, i)
+ value, ok = builtin.pop(&pq.queue), true
}
return
}
diff --git a/core/container/small_array/doc.odin b/core/container/small_array/doc.odin
new file mode 100644
index 000000000..f3e9acd57
--- /dev/null
+++ b/core/container/small_array/doc.odin
@@ -0,0 +1,55 @@
+/*
+Package small_array implements a dynamic array like
+interface on a stack-allocated, fixed-size array.
+
+The Small_Array type is optimal for scenarios where you need
+a container for a fixed number of elements of a specific type,
+with the total number known at compile time but the exact
+number to be used determined at runtime.
+
+Example:
+ import "core:fmt"
+ import "core:container/small_array"
+
+ create :: proc() -> (result: small_array.Small_Array(10, rune)) {
+ // appending single elements
+ small_array.push(&result, 'e')
+ // pushing a bunch of elements at once
+ small_array.push(&result, 'l', 'i', 'x', '-', 'e')
+ // pre-pending
+ small_array.push_front(&result, 'H')
+ // removing elements
+ small_array.ordered_remove(&result, 4)
+ // resizing to the desired length (the capacity will stay unchanged)
+ small_array.resize(&result, 7)
+ // inserting elements
+ small_array.inject_at(&result, 'p', 5)
+ // updating elements
+ small_array.set(&result, 3, 'l')
+ // getting pointers to elements
+ o := small_array.get_ptr(&result, 4)
+ o^ = 'o'
+ // and much more ....
+ return
+ }
+
+ // the Small_Array can be an ordinary parameter 'generic' over
+ // the actual length to be usable with different sizes
+ print_elements :: proc(arr: ^small_array.Small_Array($N, rune)) {
+ for r in small_array.slice(arr) {
+ fmt.print(r)
+ }
+ }
+
+ main :: proc() {
+ arr := create()
+ // ...
+ print_elements(&arr)
+ }
+
+Output:
+
+ Hellope
+
+*/
+package container_small_array
diff --git a/core/container/small_array/small_array.odin b/core/container/small_array/small_array.odin
index 77bb21cbc..49d441079 100644
--- a/core/container/small_array/small_array.odin
+++ b/core/container/small_array/small_array.odin
@@ -4,36 +4,171 @@ import "base:builtin"
import "base:runtime"
_ :: runtime
+/*
+A fixed-size stack-allocated array operated on in a dynamic fashion.
+
+Fields:
+- `data`: The underlying array
+- `len`: Amount of items that the `Small_Array` currently holds
+
+Example:
+
+ import "core:container/small_array"
+
+ example :: proc() {
+ a: small_array.Small_Array(100, int)
+ small_array.push_back(&a, 10)
+ }
+*/
Small_Array :: struct($N: int, $T: typeid) where N >= 0 {
data: [N]T,
len: int,
}
+/*
+Returns the amount of items in the small-array.
+**Inputs**
+- `a`: The small-array
+
+**Returns**
+- the amount of items in the array
+*/
len :: proc "contextless" (a: $A/Small_Array) -> int {
return a.len
}
+/*
+Returns the capacity of the small-array.
+
+**Inputs**
+- `a`: The small-array
+
+**Returns** the capacity
+*/
cap :: proc "contextless" (a: $A/Small_Array) -> int {
return builtin.len(a.data)
}
+/*
+Returns how many more items the small-array could fit.
+
+**Inputs**
+- `a`: The small-array
+
+**Returns**
+- the number of unused slots
+*/
space :: proc "contextless" (a: $A/Small_Array) -> int {
return builtin.len(a.data) - a.len
}
+/*
+Returns a slice of the data.
+
+**Inputs**
+- `a`: The pointer to the small-array
+
+**Returns**
+- the slice
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ slice_example :: proc() {
+ print :: proc(a: ^small_array.Small_Array($N, int)) {
+ for item in small_array.slice(a) {
+ fmt.println(item)
+ }
+ }
+
+ a: small_array.Small_Array(5, int)
+ small_array.push_back(&a, 1)
+ small_array.push_back(&a, 2)
+ print(&a)
+ }
+
+Output:
+
+ 1
+ 2
+*/
slice :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> []T {
return a.data[:a.len]
}
+/*
+Get a copy of the item at the specified position.
+This operation assumes that the small-array is large enough.
+This will result in:
+ - the value if 0 <= index < len
+ - the zero value of the type if len < index < capacity
+ - 'crash' if capacity < index or index < 0
+
+**Inputs**
+- `a`: The small-array
+- `index`: The position of the item to get
+
+**Returns**
+- the element at the specified position
+*/
get :: proc "contextless" (a: $A/Small_Array($N, $T), index: int) -> T {
return a.data[index]
}
+
+/*
+Get a pointer to the item at the specified position.
+This operation assumes that the small-array is large enough.
+
+This will result in:
+ - the pointer if 0 <= index < len
+ - the pointer to the zero value if len < index < capacity
+ - 'crash' if capacity < index or index < 0
+
+**Inputs**
+- `a`: A pointer to the small-array
+- `index`: The position of the item to get
+
+**Returns**
+- the pointer to the element at the specified position
+*/
get_ptr :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int) -> ^T {
return &a.data[index]
}
+/*
+Attempt to get a copy of the item at the specified position.
+
+**Inputs**
+- `a`: The small-array
+- `index`: The position of the item to get
+
+**Returns**
+- the element at the specified position
+- true if element exists, false otherwise
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ get_safe_example :: proc() {
+ a: small_array.Small_Array(5, rune)
+ small_array.push_back(&a, 'A')
+
+ fmt.println(small_array.get_safe(a, 0) or_else 'x')
+ fmt.println(small_array.get_safe(a, 1) or_else 'x')
+ }
+
+Output:
+
+ A
+ x
+
+*/
get_safe :: proc(a: $A/Small_Array($N, $T), index: int) -> (T, bool) #no_bounds_check {
if index < 0 || index >= a.len {
return {}, false
@@ -41,6 +176,17 @@ get_safe :: proc(a: $A/Small_Array($N, $T), index: int) -> (T, bool) #no_bounds_
return a.data[index], true
}
+/*
+Get a pointer to the item at the specified position.
+
+**Inputs**
+- `a`: A pointer to the small-array
+- `index`: The position of the item to get
+
+**Returns**
+- the pointer to the element at the specified position
+- true if element exists, false otherwise
+*/
get_ptr_safe :: proc(a: ^$A/Small_Array($N, $T), index: int) -> (^T, bool) #no_bounds_check {
if index < 0 || index >= a.len {
return {}, false
@@ -48,15 +194,128 @@ get_ptr_safe :: proc(a: ^$A/Small_Array($N, $T), index: int) -> (^T, bool) #no_b
return &a.data[index], true
}
+/*
+Set the element at the specified position to the given value.
+This operation assumes that the small-array is large enough.
+
+This will result in:
+ - the value being set if 0 <= index < capacity
+ - 'crash' otherwise
+
+**Inputs**
+- `a`: A pointer to the small-array
+- `index`: The position of the item to set
+- `value`: The value to set the element to
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ set_example :: proc() {
+ a: small_array.Small_Array(5, rune)
+ small_array.push_back(&a, 'A')
+ small_array.push_back(&a, 'B')
+ fmt.println(small_array.slice(&a))
+
+ // updates index 0
+ small_array.set(&a, 0, 'Z')
+ fmt.println(small_array.slice(&a))
+
+ // updates to a position x, where
+ // len <= x < cap are not visible since
+ // the length of the small-array remains unchanged
+ small_array.set(&a, 2, 'X')
+ small_array.set(&a, 3, 'Y')
+ small_array.set(&a, 4, 'Z')
+ fmt.println(small_array.slice(&a))
+
+ // resizing makes the change visible
+ small_array.resize(&a, 100)
+ fmt.println(small_array.slice(&a))
+ }
+
+Output:
+
+ [A, B]
+ [Z, B]
+ [Z, B]
+ [Z, B, X, Y, Z]
+
+*/
set :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, item: T) {
a.data[index] = item
}
+/*
+Tries to resize the small-array to the specified length.
+
+The new length will be:
+ - `length` if `length` <= capacity
+ - capacity if length > capacity
+
+**Inputs**
+- `a`: A pointer to the small-array
+- `length`: The new desired length
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ resize_example :: proc() {
+ a: small_array.Small_Array(5, int)
+
+ small_array.push_back(&a, 1)
+ small_array.push_back(&a, 2)
+ fmt.println(small_array.slice(&a))
+
+ small_array.resize(&a, 1)
+ fmt.println(small_array.slice(&a))
+
+ small_array.resize(&a, 100)
+ fmt.println(small_array.slice(&a))
+ }
+
+Output:
+
+ [1, 2]
+ [1]
+ [1, 2, 0, 0, 0]
+*/
resize :: proc "contextless" (a: ^$A/Small_Array, length: int) {
a.len = min(length, builtin.len(a.data))
}
+/*
+Attempts to add the given element to the end.
+**Inputs**
+- `a`: A pointer to the small-array
+- `item`: The item to append
+
+**Returns**
+- true if there was enough space to fit the element, false otherwise
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ push_back_example :: proc() {
+ a: small_array.Small_Array(2, int)
+
+ assert(small_array.push_back(&a, 1), "this should fit")
+ assert(small_array.push_back(&a, 2), "this should fit")
+ assert(!small_array.push_back(&a, 3), "this should not fit")
+
+ fmt.println(small_array.slice(&a))
+ }
+
+Output:
+
+ [1, 2]
+*/
push_back :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool {
if a.len < cap(a^) {
a.data[a.len] = item
@@ -66,6 +325,39 @@ push_back :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool {
return false
}
+/*
+Attempts to add the given element at the beginning.
+This operation assumes that the small-array is not empty.
+
+Note: Performing this operation will cause pointers obtained
+through get_ptr(_save) to reference incorrect elements.
+
+**Inputs**
+- `a`: A pointer to the small-array
+- `item`: The item to append
+
+**Returns**
+- true if there was enough space to fit the element, false otherwise
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ push_front_example :: proc() {
+ a: small_array.Small_Array(2, int)
+
+ assert(small_array.push_front(&a, 2), "this should fit")
+ assert(small_array.push_front(&a, 1), "this should fit")
+ assert(!small_array.push_back(&a, 0), "this should not fit")
+
+ fmt.println(small_array.slice(&a))
+ }
+
+Output:
+
+ [1, 2]
+*/
push_front :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool {
if a.len < cap(a^) {
a.len += 1
@@ -77,6 +369,35 @@ push_front :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool {
return false
}
+/*
+Removes and returns the last element of the small-array.
+This operation assumes that the small-array is not empty.
+
+**Inputs**
+- `a`: A pointer to the small-array
+
+**Returns**
+- a copy of the element removed from the end of the small-array
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ pop_back_example :: proc() {
+ a: small_array.Small_Array(5, int)
+ small_array.push(&a, 0, 1, 2)
+
+ fmt.println("BEFORE:", small_array.slice(&a))
+ small_array.pop_back(&a)
+ fmt.println("AFTER: ", small_array.slice(&a))
+ }
+
+Output:
+
+ BEFORE: [0, 1, 2]
+ AFTER: [0, 1]
+*/
pop_back :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) -> T {
assert(condition=(N > 0 && a.len > 0), loc=loc)
item := a.data[a.len-1]
@@ -84,6 +405,38 @@ pop_back :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) ->
return item
}
+/*
+Removes and returns the first element of the small-array.
+This operation assumes that the small-array is not empty.
+
+Note: Performing this operation will cause pointers obtained
+through get_ptr(_save) to reference incorrect elements.
+
+**Inputs**
+- `a`: A pointer to the small-array
+
+**Returns**
+- a copy of the element removed from the beginning of the small-array
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ pop_front_example :: proc() {
+ a: small_array.Small_Array(5, int)
+ small_array.push(&a, 0, 1, 2)
+
+ fmt.println("BEFORE:", small_array.slice(&a))
+ small_array.pop_front(&a)
+ fmt.println("AFTER: ", small_array.slice(&a))
+ }
+
+Output:
+
+ BEFORE: [0, 1, 2]
+ AFTER: [1, 2]
+*/
pop_front :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) -> T {
assert(condition=(N > 0 && a.len > 0), loc=loc)
item := a.data[0]
@@ -93,6 +446,32 @@ pop_front :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) -
return item
}
+/*
+Attempts to remove and return the last element of the small array.
+Unlike `pop_back`, it does not assume that the array is non-empty.
+
+**Inputs**
+- `a`: A pointer to the small-array
+
+**Returns**
+- a copy of the element removed from the end of the small-array
+- true if the small-array was not empty, false otherwise
+
+Example:
+
+ import "core:container/small_array"
+
+ pop_back_safe_example :: proc() {
+ a: small_array.Small_Array(3, int)
+ small_array.push(&a, 1)
+
+ el, ok := small_array.pop_back_safe(&a)
+ assert(ok, "there was an element in the array")
+
+ el, ok = small_array.pop_back_safe(&a)
+ assert(!ok, "there was NO element in the array")
+ }
+*/
pop_back_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) {
if N > 0 && a.len > 0 {
item = a.data[a.len-1]
@@ -102,6 +481,35 @@ pop_back_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, ok
return
}
+/*
+Attempts to remove and return the first element of the small array.
+Unlike `pop_front`, it does not assume that the array is non-empty.
+
+Note: Performing this operation will cause pointers obtained
+through get_ptr(_save) to reference incorrect elements.
+
+**Inputs**
+- `a`: A pointer to the small-array
+
+**Returns**
+- a copy of the element removed from the beginning of the small-array
+- true if the small-array was not empty, false otherwise
+
+Example:
+
+ import "core:container/small_array"
+
+ pop_front_safe_example :: proc() {
+ a: small_array.Small_Array(3, int)
+ small_array.push(&a, 1)
+
+ el, ok := small_array.pop_front_safe(&a)
+ assert(ok, "there was an element in the array")
+
+ el, ok = small_array.pop_front_(&a)
+ assert(!ok, "there was NO element in the array")
+ }
+*/
pop_front_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) {
if N > 0 && a.len > 0 {
item = a.data[0]
@@ -113,11 +521,70 @@ pop_front_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, o
return
}
+/*
+Decreases the length of the small-array by the given amount.
+The elements are therefore not really removed and can be
+recovered by calling `resize`.
+
+Note: This procedure assumes that the array has a sufficient length.
+
+**Inputs**
+- `a`: A pointer to the small-array
+- `count`: The amount the length should be reduced by
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ consume_example :: proc() {
+ a: small_array.Small_Array(3, int)
+ small_array.push(&a, 0, 1, 2)
+
+ fmt.println("BEFORE:", small_array.slice(&a))
+ small_array.consume(&a, 2)
+ fmt.println("AFTER :", small_array.slice(&a))
+ }
+
+Output:
+
+ BEFORE: [0, 1, 2]
+ AFTER : [0]
+*/
consume :: proc "odin" (a: ^$A/Small_Array($N, $T), count: int, loc := #caller_location) {
assert(condition=a.len >= count, loc=loc)
a.len -= count
}
+/*
+Removes the element at the specified index while retaining order.
+
+Note: Performing this operation will cause pointers obtained
+through get_ptr(_save) to reference incorrect elements.
+
+**Inputs**
+- `a`: A pointer to the small-array
+- `index`: The position of the element to remove
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ ordered_remove_example :: proc() {
+ a: small_array.Small_Array(4, int)
+ small_array.push(&a, 0, 1, 2, 3)
+
+ fmt.println("BEFORE:", small_array.slice(&a))
+ small_array.ordered_remove(&a, 1)
+ fmt.println("AFTER :", small_array.slice(&a))
+ }
+
+Output:
+
+ BEFORE: [0, 1, 2, 3]
+ AFTER : [0, 2, 3]
+*/
ordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check {
runtime.bounds_check_error_loc(loc, index, a.len)
if index+1 < a.len {
@@ -126,6 +593,32 @@ ordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, lo
a.len -= 1
}
+/*
+Removes the element at the specified index without retaining order.
+
+**Inputs**
+- `a`: A pointer to the small-array
+- `index`: The position of the element to remove
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ unordered_remove_example :: proc() {
+ a: small_array.Small_Array(4, int)
+ small_array.push(&a, 0, 1, 2, 3)
+
+ fmt.println("BEFORE:", small_array.slice(&a))
+ small_array.unordered_remove(&a, 1)
+ fmt.println("AFTER :", small_array.slice(&a))
+ }
+
+Output:
+
+ BEFORE: [0, 1, 2, 3]
+ AFTER : [0, 3, 2]
+*/
unordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check {
runtime.bounds_check_error_loc(loc, index, a.len)
n := a.len-1
@@ -135,10 +628,63 @@ unordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int,
a.len -= 1
}
+/*
+Sets the length of the small-array to 0.
+
+**Inputs**
+- `a`: A pointer to the small-array
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ clear_example :: proc() {
+ a: small_array.Small_Array(4, int)
+ small_array.push(&a, 0, 1, 2, 3)
+
+ fmt.println("BEFORE:", small_array.slice(&a))
+ small_array.clear(&a)
+ fmt.println("AFTER :", small_array.slice(&a))
+ }
+
+Output:
+
+ BEFORE: [0, 1, 2, 3]
+ AFTER : []
+
+*/
clear :: proc "contextless" (a: ^$A/Small_Array($N, $T)) {
resize(a, 0)
}
+/*
+Attempts to append all elements to the small-array returning
+false if there is not enough space to fit all of them.
+
+**Inputs**
+- `a`: A pointer to the small-array
+- `item`: The item to append
+- ..:
+
+**Returns**
+- true if there was enough space to fit the element, false otherwise
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ push_back_elems_example :: proc() {
+ a: small_array.Small_Array(100, int)
+ small_array.push_back_elems(&a, 0, 1, 2, 3, 4)
+ fmt.println(small_array.slice(&a))
+ }
+
+Output:
+
+ [0, 1, 2, 3, 4]
+*/
push_back_elems :: proc "contextless" (a: ^$A/Small_Array($N, $T), items: ..T) -> bool {
if a.len + builtin.len(items) <= cap(a^) {
n := copy(a.data[a.len:], items[:])
@@ -148,6 +694,36 @@ push_back_elems :: proc "contextless" (a: ^$A/Small_Array($N, $T), items: ..T) -
return false
}
+/*
+Tries to insert an element at the specified position.
+
+Note: Performing this operation will cause pointers obtained
+through get_ptr(_save) to reference incorrect elements.
+
+**Inputs**
+- `a`: A pointer to the small-array
+- `item`: The item to insert
+- `index`: The index to insert the item at
+
+**Returns**
+- true if there was enough space to fit the element, false otherwise
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ inject_at_example :: proc() {
+ arr: small_array.Small_Array(100, rune)
+ small_array.push(&arr, 'A', 'C', 'D')
+ small_array.inject_at(&arr, 'B', 1)
+ fmt.println(small_array.slice(&arr))
+ }
+
+Output:
+
+ [A, B, C, D]
+*/
inject_at :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T, index: int) -> bool #no_bounds_check {
if a.len < cap(a^) && index >= 0 && index <= len(a^) {
a.len += 1
@@ -160,7 +736,38 @@ inject_at :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T, index: int
return false
}
+// Alias for `push_back`
append_elem :: push_back
+// Alias for `push_back_elems`
append_elems :: push_back_elems
+
+/*
+Tries to append the element(s) to the small-array.
+
+**Inputs**
+- `a`: A pointer to the small-array
+- `item`: The item to append
+- ..:
+
+**Returns**
+- true if there was enough space to fit the element, false otherwise
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ push_example :: proc() {
+ a: small_array.Small_Array(100, int)
+ small_array.push(&a, 0)
+ small_array.push(&a, 1, 2, 3, 4)
+ fmt.println(small_array.slice(&a))
+ }
+
+Output:
+
+ [0, 1, 2, 3, 4]
+*/
push :: proc{push_back, push_back_elems}
+// Alias for `push`
append :: proc{push_back, push_back_elems}
diff --git a/core/crypto/_aes/hw_intel/api.odin b/core/crypto/_aes/hw_intel/api.odin
index 52669cb35..502d56213 100644
--- a/core/crypto/_aes/hw_intel/api.odin
+++ b/core/crypto/_aes/hw_intel/api.odin
@@ -6,7 +6,7 @@ import "core:sys/info"
// is_supported returns true iff hardware accelerated AES
// is supported.
is_supported :: proc "contextless" () -> bool {
- features, ok := info.cpu_features.?
+ features, ok := info.cpu.features.?
if !ok {
return false
}
diff --git a/core/crypto/_chacha20/simd128/chacha20_simd128.odin b/core/crypto/_chacha20/simd128/chacha20_simd128.odin
index cf78541d1..4bf40e240 100644
--- a/core/crypto/_chacha20/simd128/chacha20_simd128.odin
+++ b/core/crypto/_chacha20/simd128/chacha20_simd128.odin
@@ -39,7 +39,7 @@ when ODIN_ARCH == .arm64 || ODIN_ARCH == .arm32 {
// Some targets lack runtime feature detection, and will flat out refuse
// 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.
//
// See:
@@ -227,7 +227,7 @@ is_performant :: proc "contextless" () -> bool {
req_features :: info.CPU_Features{.V}
}
- features, ok := info.cpu_features.?
+ features, ok := info.cpu.features.?
if !ok {
return false
}
diff --git a/core/crypto/_chacha20/simd256/chacha20_simd256.odin b/core/crypto/_chacha20/simd256/chacha20_simd256.odin
index ccb02a947..12fffeb2f 100644
--- a/core/crypto/_chacha20/simd256/chacha20_simd256.odin
+++ b/core/crypto/_chacha20/simd256/chacha20_simd256.odin
@@ -41,7 +41,7 @@ _VEC_TWO: simd.u64x4 : {2, 0, 2, 0}
is_performant :: proc "contextless" () -> bool {
req_features :: info.CPU_Features{.avx, .avx2}
- features, ok := info.cpu_features.?
+ features, ok := info.cpu.features.?
if !ok {
return false
}
diff --git a/core/crypto/sha2/sha2_impl_hw_intel.odin b/core/crypto/sha2/sha2_impl_hw_intel.odin
index f16f353df..cb29a3a20 100644
--- a/core/crypto/sha2/sha2_impl_hw_intel.odin
+++ b/core/crypto/sha2/sha2_impl_hw_intel.odin
@@ -52,7 +52,7 @@ K_15 :: simd.u64x2{0xa4506ceb90befffa, 0xc67178f2bef9a3f7}
// is_hardware_accelerated_256 returns true iff hardware accelerated
// SHA-224/SHA-256 is supported.
is_hardware_accelerated_256 :: proc "contextless" () -> bool {
- features, ok := info.cpu_features.?
+ features, ok := info.cpu.features.?
if !ok {
return false
}
diff --git a/core/debug/trace/trace_windows.odin b/core/debug/trace/trace_windows.odin
index c9868e338..96507714c 100644
--- a/core/debug/trace/trace_windows.odin
+++ b/core/debug/trace/trace_windows.odin
@@ -49,7 +49,9 @@ _resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> (
data: [size_of(win32.SYMBOL_INFOW) + size_of([256]win32.WCHAR)]byte
symbol := (^win32.SYMBOL_INFOW)(&data[0])
- symbol.SizeOfStruct = size_of(symbol)
+ // The value of SizeOfStruct must be the size of the whole struct,
+ // not just the size of the pointer
+ symbol.SizeOfStruct = size_of(symbol^)
symbol.MaxNameLen = 255
if win32.SymFromAddrW(ctx.impl.hProcess, win32.DWORD64(frame), &{}, symbol) {
fl.procedure, _ = win32.wstring_to_utf8(&symbol.Name[0], -1, allocator)
diff --git a/core/encoding/cbor/cbor.odin b/core/encoding/cbor/cbor.odin
index 8eb829ed3..1fb7c34ab 100644
--- a/core/encoding/cbor/cbor.odin
+++ b/core/encoding/cbor/cbor.odin
@@ -385,17 +385,17 @@ to_diagnostic_format_writer :: proc(w: io.Writer, val: Value, padding := 0) -> i
// which we want for the diagnostic format.
case f16:
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:] }
io.write_string(w, str) or_return
case f32:
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:] }
io.write_string(w, str) or_return
case f64:
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:] }
io.write_string(w, str) or_return
diff --git a/core/encoding/cbor/marshal.odin b/core/encoding/cbor/marshal.odin
index 6e1948719..446df1fbd 100644
--- a/core/encoding/cbor/marshal.odin
+++ b/core/encoding/cbor/marshal.odin
@@ -612,6 +612,42 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er
case:
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..marshal(e, any{rawptr(data), info.elem.id}) or_return
+ }
+ return
+ }
+
+ elem_ti := runtime.type_info_core(type_info_of(info.elem.id))
+ for i in 0..marshal(e, any{rawptr(data), info.elem.id}) or_return
+ }
+ return
+ }
+
+ elem_ti := runtime.type_info_core(type_info_of(info.elem.id))
+ for i in 0.. (err: Unmarshal_Error) {
@@ -51,6 +52,11 @@ unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, all
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) {
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
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
@@ -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) }
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)
}
}
diff --git a/core/encoding/csv/doc.odin b/core/encoding/csv/doc.odin
index bfeadafd6..7abe2be49 100644
--- a/core/encoding/csv/doc.odin
+++ b/core/encoding/csv/doc.odin
@@ -63,8 +63,6 @@ Example:
read_csv_from_string :: proc(filename: string) {
r: csv.Reader
r.trim_leading_space = true
- r.reuse_record = true // Without it you have to delete(record)
- r.reuse_record_buffer = true // Without it you have to each of the fields within it
defer csv.reader_destroy(&r)
csv_data, ok := os.read_entire_file(filename)
diff --git a/core/encoding/csv/reader.odin b/core/encoding/csv/reader.odin
index 5348624d5..577ef219d 100644
--- a/core/encoding/csv/reader.odin
+++ b/core/encoding/csv/reader.odin
@@ -130,7 +130,7 @@ reader_destroy :: proc(r: ^Reader) {
for record, row_idx in csv.iterator_next(&r) { ... }
TIP: If you process the results within the loop and don't need to own the results,
- you can set the Reader's `reuse_record` and `reuse_record_reuse_record_buffer` to true;
+ you can set the Reader's `reuse_record` and `reuse_record_buffer` to true;
you won't need to delete the record or its fields.
*/
iterator_next :: proc(r: ^Reader) -> (record: []string, idx: int, err: Error, more: bool) {
diff --git a/core/encoding/entity/entity.odin b/core/encoding/entity/entity.odin
index d2f1d46b2..cb8fa8611 100644
--- a/core/encoding/entity/entity.odin
+++ b/core/encoding/entity/entity.odin
@@ -108,7 +108,7 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
it couldn't have been part of an XML tag body to be decoded here.
Keep in mind that we could already *be* inside a CDATA tag.
- If so, write `>` as a literal and continue.
+ If so, write `<` as a literal and continue.
*/
if in_data {
write_rune(&builder, '<')
@@ -119,11 +119,9 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
case ']':
// If we're unboxing _and_ decoding CDATA, we'll have to check for the end tag.
if in_data {
- if t.read_offset + len(CDATA_END) < len(t.src) {
- if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
- in_data = false
- t.read_offset += len(CDATA_END) - 1
- }
+ if strings.has_prefix(t.src[t.offset:], CDATA_END) {
+ in_data = false
+ t.read_offset += len(CDATA_END) - 1
}
continue
} else {
@@ -297,40 +295,40 @@ _handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: X
assert(t != nil && t.r == '<')
if t.read_offset + len(CDATA_START) >= len(t.src) { return false, .None }
- if string(t.src[t.offset:][:len(CDATA_START)]) == CDATA_START {
- t.read_offset += len(CDATA_START) - 1
-
+ s := string(t.src[t.offset:])
+ if strings.has_prefix(s, CDATA_START) {
if .Unbox_CDATA in options && .Decode_CDATA in options {
// We're unboxing _and_ decoding CDATA
+ t.read_offset += len(CDATA_START) - 1
return true, .None
}
- // CDATA is passed through.
- offset := t.offset
-
- // Scan until end of CDATA.
+ // CDATA is passed through. Scan until end of CDATA.
+ start_offset := t.offset
+ t.read_offset += len(CDATA_START)
for {
- advance(t) or_return
- if t.r < 0 { return true, .CDATA_Not_Terminated }
+ advance(t)
+ if t.r < 0 {
+ // error(t, offset, "[scan_string] CDATA was not terminated\n")
+ return true, .CDATA_Not_Terminated
+ }
- if t.read_offset + len(CDATA_END) < len(t.src) {
- if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
- t.read_offset += len(CDATA_END) - 1
+ // Scan until the end of a CDATA tag.
+ if s = string(t.src[t.read_offset:]); strings.has_prefix(s, CDATA_END) {
+ t.read_offset += len(CDATA_END)
+ cdata := string(t.src[start_offset:t.read_offset])
- cdata := string(t.src[offset : t.read_offset])
-
- if .Unbox_CDATA in options {
- cdata = cdata[len(CDATA_START):]
- cdata = cdata[:len(cdata) - len(CDATA_END)]
- }
-
- write_string(builder, cdata)
- return false, .None
+ if .Unbox_CDATA in options {
+ cdata = cdata[len(CDATA_START):]
+ cdata = cdata[:len(cdata) - len(CDATA_END)]
}
+ write_string(builder, cdata)
+ return false, .None
}
}
- } else if string(t.src[t.offset:][:len(COMMENT_START)]) == COMMENT_START {
+
+ } else if strings.has_prefix(s, COMMENT_START) {
t.read_offset += len(COMMENT_START)
// Comment is passed through by default.
offset := t.offset
diff --git a/core/encoding/hxa/read.odin b/core/encoding/hxa/read.odin
index a679946f8..6dde16848 100644
--- a/core/encoding/hxa/read.odin
+++ b/core/encoding/hxa/read.odin
@@ -79,7 +79,6 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato
read_meta :: proc(r: ^Reader, capacity: u32le, allocator := context.allocator, loc := #caller_location) -> (meta_data: []Meta, err: Read_Error) {
meta_data = make([]Meta, int(capacity), allocator=allocator)
count := 0
- defer meta_data = meta_data[:count]
for &m in meta_data {
m.name = read_name(r) or_return
@@ -105,6 +104,7 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato
count += 1
}
+ meta_data = meta_data[:count]
return
}
@@ -112,7 +112,6 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato
stack_count := read_value(r, u32le) or_return
layer_count := 0
layers = make(Layer_Stack, stack_count, allocator=allocator, loc=loc)
- defer layers = layers[:layer_count]
for &layer in layers {
layer.name = read_name(r) or_return
layer.components = read_value(r, u8) or_return
@@ -136,6 +135,7 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato
layer_count += 1
}
+ layers = layers[:layer_count]
return
}
diff --git a/core/encoding/json/marshal.odin b/core/encoding/json/marshal.odin
index c786ea10a..9d6d6a8d2 100644
--- a/core/encoding/json/marshal.odin
+++ b/core/encoding/json/marshal.odin
@@ -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) {
switch i in a {
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:
- 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 {
- 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
@@ -286,7 +286,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
case ^runtime.Type_Info_Integer:
buf: [40]byte
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
case: return .Unsupported_Type
diff --git a/core/encoding/json/tokenizer.odin b/core/encoding/json/tokenizer.odin
index e46d879a7..ad928b7d9 100644
--- a/core/encoding/json/tokenizer.odin
+++ b/core/encoding/json/tokenizer.odin
@@ -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 {
case '"', '\'', '\\', '/', 'b', 'n', 'r', 't', 'f':
next_rune(t)
@@ -310,7 +310,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
break
}
if r == '\\' {
- scan_espace(t)
+ scan_escape(t)
}
}
diff --git a/core/encoding/json/unmarshal.odin b/core/encoding/json/unmarshal.odin
index 6351c412c..2b4c1d2c1 100644
--- a/core/encoding/json/unmarshal.odin
+++ b/core/encoding/json/unmarshal.odin
@@ -406,6 +406,9 @@ unmarshal_expect_token :: proc(p: ^Parser, kind: Token_Kind, loc := #caller_loca
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)
json_name_from_tag_value :: proc(value: string) -> (json_name, extra: string) {
json_name = value
@@ -441,12 +444,6 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
defer delete(key, p.allocator)
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 := intrinsics.alloca(field_used_bytes + 1, 1) // + 1 to not overflow on size_of 0 types.
@@ -465,7 +462,9 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
if use_field_idx < 0 {
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
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
type = field.type
found = true
@@ -508,6 +509,11 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
}
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) {
return .Multiple_Use_Field
}
diff --git a/core/encoding/uuid/generation.odin b/core/encoding/uuid/generation.odin
index 7c9d4b80c..b210f6a52 100644
--- a/core/encoding/uuid/generation.odin
+++ b/core/encoding/uuid/generation.odin
@@ -240,7 +240,7 @@ Example:
import "core:encoding/uuid"
import "core:fmt"
- main :: proc() {
+ generate_v8_hash_bytes_example :: proc() {
my_uuid := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.odin-lang.org", .SHA256)
my_uuid_string := uuid.to_string(my_uuid, context.temp_allocator)
fmt.println(my_uuid_string)
@@ -306,7 +306,7 @@ Example:
import "core:encoding/uuid"
import "core:fmt"
- main :: proc() {
+ generate_v8_hash_string_example :: proc() {
my_uuid := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.odin-lang.org", .SHA256)
my_uuid_string := uuid.to_string(my_uuid, context.temp_allocator)
fmt.println(my_uuid_string)
diff --git a/core/encoding/xml/tokenizer.odin b/core/encoding/xml/tokenizer.odin
index a2bbaf28e..3ef9a6388 100644
--- a/core/encoding/xml/tokenizer.odin
+++ b/core/encoding/xml/tokenizer.odin
@@ -16,6 +16,7 @@ package encoding_xml
import "core:fmt"
import "core:unicode"
import "core:unicode/utf8"
+import "core:strings"
Error_Handler :: #type proc(pos: Pos, fmt: string, args: ..any)
@@ -121,7 +122,7 @@ default_error_handler :: proc(pos: Pos, msg: string, args: ..any) {
error :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) {
pos := offset_to_pos(t, offset)
if t.err != nil {
- t.err(pos, msg, ..args)
+ t.err(pos=pos, fmt=msg, args=args)
}
t.error_count += 1
}
@@ -268,32 +269,27 @@ scan_comment :: proc(t: ^Tokenizer) -> (comment: string, err: Error) {
// Skip CDATA
skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) {
- if t.read_offset + len(CDATA_START) >= len(t.src) {
- // Can't be the start of a CDATA tag.
+ if s := string(t.src[t.offset:]); !strings.has_prefix(s, CDATA_START) {
return .None
}
- if string(t.src[t.offset:][:len(CDATA_START)]) == CDATA_START {
- t.read_offset += len(CDATA_START)
- offset := t.offset
+ t.read_offset += len(CDATA_START)
+ offset := t.offset
- cdata_scan: for {
- advance_rune(t)
- if t.ch < 0 {
- error(t, offset, "[scan_string] CDATA was not terminated\n")
- return .Premature_EOF
- }
+ cdata_scan: for {
+ advance_rune(t)
+ if t.ch < 0 {
+ error(t, offset, "[scan_string] CDATA was not terminated\n")
+ return .Premature_EOF
+ }
- // Scan until the end of a CDATA tag.
- if t.read_offset + len(CDATA_END) < len(t.src) {
- if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
- t.read_offset += len(CDATA_END)
- break cdata_scan
- }
- }
+ // Scan until the end of a CDATA tag.
+ if s := string(t.src[t.read_offset:]); strings.has_prefix(s, CDATA_END) {
+ t.read_offset += len(CDATA_END)
+ break cdata_scan
}
}
- return
+ return .None
}
@(optimization_mode="favor_size")
@@ -393,6 +389,8 @@ scan :: proc(t: ^Tokenizer, multiline_string := false) -> Token {
case '/': kind = .Slash
case '-': kind = .Dash
case ':': kind = .Colon
+ case '[': kind = .Open_Bracket
+ case ']': kind = .Close_Bracket
case '"', '\'':
kind = .Invalid
diff --git a/core/encoding/xml/xml_reader.odin b/core/encoding/xml/xml_reader.odin
index b8c8b13a4..707d2b3f3 100644
--- a/core/encoding/xml/xml_reader.odin
+++ b/core/encoding/xml/xml_reader.odin
@@ -56,7 +56,7 @@ Option_Flag :: enum {
Option_Flags :: bit_set[Option_Flag; u16]
Document :: struct {
- elements: [dynamic]Element,
+ elements: [dynamic]Element `fmt:"v,element_count"`,
element_count: Element_ID,
prologue: Attributes,
@@ -70,15 +70,15 @@ Document :: struct {
// If we encounter comments before the root node, and the option to intern comments is given, this is where they'll live.
// Otherwise they'll be in the element tree.
- comments: [dynamic]string,
+ comments: [dynamic]string `fmt:"-"`,
// Internal
- tokenizer: ^Tokenizer,
- allocator: mem.Allocator,
+ tokenizer: ^Tokenizer `fmt:"-"`,
+ allocator: mem.Allocator `fmt:"-"`,
// Input. Either the original buffer, or a copy if `.Input_May_Be_Modified` isn't specified.
- input: []u8,
- strings_to_free: [dynamic]string,
+ input: []u8 `fmt:"-"`,
+ strings_to_free: [dynamic]string `fmt:"-"`,
}
Element :: struct {
@@ -175,7 +175,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
data = bytes.clone(data)
}
- t := &Tokenizer{}
+ t := new(Tokenizer)
init(t, string(data), path, error_handler)
doc = new(Document)
@@ -195,7 +195,6 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
loop: for {
skip_whitespace(t)
- // NOTE(Jeroen): This is faster as a switch.
switch t.ch {
case '<':
// Consume peeked `<`
@@ -306,9 +305,17 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
}
}
+ case .Open_Bracket:
+ // This could be a CDATA tag part of a tag's body. Unread the ` (attr: Attribute, offset: int, err: E
t := doc.tokenizer
key := expect(t, .Ident) or_return
- offset = t.offset - len(key.text)
-
_ = expect(t, .Eq) or_return
value := expect(t, .String, multiline_string=true) or_return
@@ -591,6 +566,47 @@ parse_doctype :: proc(doc: ^Document) -> (err: Error) {
return .None
}
+parse_body :: proc(doc: ^Document, element: Element_ID, opts: Options) -> (err: Error) {
+ assert(doc != nil)
+ context.allocator = doc.allocator
+ t := doc.tokenizer
+
+ body_text := scan_string(t, t.offset) or_return
+ needs_processing := .Unbox_CDATA in opts.flags
+ needs_processing |= .Decode_SGML_Entities in opts.flags
+
+ if !needs_processing {
+ append(&doc.elements[element].value, body_text)
+ return
+ }
+
+ decode_opts := entity.XML_Decode_Options{}
+ if .Keep_Tag_Body_Comments not_in opts.flags {
+ decode_opts += { .Comment_Strip }
+ }
+
+ if .Decode_SGML_Entities not_in opts.flags {
+ decode_opts += { .No_Entity_Decode }
+ }
+
+ if .Unbox_CDATA in opts.flags {
+ decode_opts += { .Unbox_CDATA }
+ if .Decode_SGML_Entities in opts.flags {
+ decode_opts += { .Decode_CDATA }
+ }
+ }
+
+ decoded, decode_err := entity.decode_xml(body_text, decode_opts)
+ if decode_err == .None {
+ append(&doc.elements[element].value, decoded)
+ append(&doc.strings_to_free, decoded)
+ } else {
+ append(&doc.elements[element].value, body_text)
+ }
+
+ return
+}
+
Element_ID :: u32
new_element :: proc(doc: ^Document) -> (id: Element_ID) {
@@ -609,4 +625,4 @@ new_element :: proc(doc: ^Document) -> (id: Element_ID) {
cur := doc.element_count
doc.element_count += 1
return cur
-}
+}
\ No newline at end of file
diff --git a/core/flags/constants.odin b/core/flags/constants.odin
index 6f5281928..dc2663e2a 100644
--- a/core/flags/constants.odin
+++ b/core/flags/constants.odin
@@ -19,7 +19,7 @@ SUBTAG_NAME :: "name"
SUBTAG_POS :: "pos"
SUBTAG_REQUIRED :: "required"
SUBTAG_HIDDEN :: "hidden"
-SUBTAG_VARIADIC :: "variadic"
+SUBTAG_MANIFOLD :: "manifold"
SUBTAG_FILE :: "file"
SUBTAG_PERMS :: "perms"
SUBTAG_INDISTINCT :: "indistinct"
@@ -28,7 +28,7 @@ TAG_USAGE :: "usage"
UNDOCUMENTED_FLAG :: ""
-INTERNAL_VARIADIC_FLAG :: "varg"
+INTERNAL_OVERFLOW_FLAG :: #config(ODIN_CORE_FLAGS_OVERFLOW_FLAG, "overflow")
RESERVED_HELP_FLAG :: "help"
RESERVED_HELP_FLAG_SHORT :: "h"
diff --git a/core/flags/doc.odin b/core/flags/doc.odin
index b97547806..440acd52c 100644
--- a/core/flags/doc.odin
+++ b/core/flags/doc.odin
@@ -20,6 +20,17 @@ The format is similar to the Odin binary's way of handling compiler flags.
-